This commit is contained in:
Juan Felipe Duarte
2023-05-04 16:07:32 -05:00
parent 1a0e33328e
commit bc1357b7a6
9 changed files with 505 additions and 114 deletions
+18 -7
View File
@@ -6,6 +6,8 @@ import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/photo_view.dart';
import 'package:prosappco/src/models/user_model.dart';
import 'package:prosappco/src/screens/calendar.dart';
import 'package:prosappco/src/screens/messages.dart';
import 'package:prosappco/src/screens/my_services_pro.dart';
import 'package:prosappco/src/screens/professional.dart';
import 'package:prosappco/src/screens/profile.dart';
import 'package:prosappco/src/screens/profile_pro.dart';
@@ -113,12 +115,13 @@ class _DrawerProfessionalState extends State<DrawerProfessional> {
children: [
ListTile(
onTap: () {
if (ModalRoute.of(context)?.settings.name !=
'/misserviciospro') {
Navigator.pushNamed(context, '/misserviciospro');
} else {
Scaffold.of(context).openEndDrawer();
}
Navigator.of(context).push(
CupertinoPageRoute(
builder: (BuildContext context) {
return MyServicesProScreen();
},
),
);
},
leading: const Icon(
Icons.history,
@@ -172,7 +175,15 @@ class _DrawerProfessionalState extends State<DrawerProfessional> {
),
),
ListTile(
onTap: () {},
onTap: () {
Navigator.of(context).push(
CupertinoPageRoute(
builder: (BuildContext context) {
return MessagesScreen();
},
),
);
},
leading: const Icon(
Icons.messenger_outline,
color: Colors.black,
+78 -5
View File
@@ -1,12 +1,24 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:prosappco/src/models/user_model.dart';
class ChatModel {
List<MessageModel> messages;
String professional_id;
String user_id;
UserModel? user;
UserModel? professional;
ChatModel({required this.messages});
ChatModel({
required this.messages,
required this.professional_id,
required this.user_id,
this.user,
this.professional,
});
static ChatModel fromDocumentSnapshot(
DocumentSnapshot<Map<String, dynamic>> snapshot) {
static Future<ChatModel> fromDocumentSnapshot2(
DocumentSnapshot<Map<String, dynamic>> snapshot,
bool fillUserModel) async {
try {
List<MessageModel> messages = [];
List<dynamic> messagesData = snapshot.get('message') ?? [];
@@ -19,12 +31,73 @@ class ChatModel {
));
}
return ChatModel(messages: messages);
return ChatModel(
messages: messages,
professional_id: snapshot.get('professional_id'),
user_id: snapshot.get('user_id'),
user: fillUserModel
? await UserModel.getUser(snapshot.get('user_id'))
: null,
);
} catch (e) {
print('error $e');
return ChatModel(messages: []);
return ChatModel(messages: [], professional_id: '', user_id: '');
}
}
static ChatModel fromDocumentSnapshot(
DocumentSnapshot<Map<String, dynamic>> snapshot,
) {
try {
List<MessageModel> messages = [];
List<dynamic> messagesData = snapshot.get('message') ?? [];
for (var data in messagesData) {
messages.add(MessageModel(
user: data['user'] ?? '',
content: data['content'] ?? '',
timestamp: (data['timestamp'] as Timestamp).toDate(),
));
}
return ChatModel(
messages: messages,
professional_id: snapshot.get('professional_id'),
user_id: snapshot.get('user_id'),
);
} catch (e) {
print('error $e');
return ChatModel(messages: [], professional_id: '', user_id: '');
}
}
static Future<List<ChatModel>> getChatsByProId(String userReceived) async {
final receivedScoresQuery = FirebaseFirestore.instance
.collection('chats')
.where('professional_id', isEqualTo: userReceived);
final receivedScoresSnapshot = await receivedScoresQuery.get();
final receivedScores = await Future.wait(receivedScoresSnapshot.docs
.map((doc) async => await fromDocumentSnapshot2(doc, true))
.toList());
return receivedScores;
}
static Future<List<ChatModel>> getChatsByUserId(String userReceived) async {
final receivedScoresQuery = FirebaseFirestore.instance
.collection('chats')
.where('user_id', isEqualTo: userReceived);
final receivedScoresSnapshot = await receivedScoresQuery.get();
final receivedScores = await Future.wait(receivedScoresSnapshot.docs
.map((doc) async => await fromDocumentSnapshot2(doc, true))
.toList());
return receivedScores;
}
}
class MessageModel {
+33 -3
View File
@@ -96,6 +96,28 @@ Future<List<Event>> getByProIdAll(String state1, String state2) async {
}
}
Future<List<Event>> getByUserIdAll(String state1, String state2) async {
try {
var snapshot = await FirebaseFirestore.instance
.collection('services')
.where('user_id', isEqualTo: uid)
.where('status', whereIn: [state1, state2]).get();
List<Event> eventos = [];
for (var element in snapshot.docs) {
final event = Event.fromJson(element.data());
event.scoresModel = await ScoresModel.scoreTo(event.userId, false, false);
event.id = element.id;
eventos.add(event);
}
print('eventos $eventos');
return eventos;
} catch (e) {
print('Error getByUserId $e');
return [];
}
}
class Event {
String? id;
String title;
@@ -151,9 +173,17 @@ class Event {
final snapshot = await FirebaseFirestore.instance
.collection('services')
.doc(uid)
.get();
final Map<String, dynamic>? data = snapshot.data();
return Event.fromJson(data!);
.get()
.then((docSnapshot) {
if (docSnapshot.exists) {
print('info ${docSnapshot.data()}');
} else {
print('No se encontró ningún documento con la ruta especificada');
}
;
});
final Map<String, dynamic> data = snapshot.data();
return Event.fromJson(data);
} catch (e) {
print('Error getting user: $e');
return Event(
+6 -4
View File
@@ -5,26 +5,28 @@ import 'package:prosappco/src/screens/professional.dart';
class UserModel {
final String name;
final String city;
final String? profession;
final String? state;
final Reference? photo;
UserModel(this.name, this.city, this.state, this.photo);
UserModel(this.name, this.city, this.profession, this.state, this.photo);
static Future<UserModel> fromJson(
Map<String, dynamic>? json, String uid) async {
try {
if (json == null) return UserModel('', '', null, null);
if (json == null) return UserModel('', '', '', null, null);
String? avatar = json['photo'];
return UserModel(
json['name'],
json['city'],
json['profesion'],
json['estado'],
avatar != null ? storage.ref().child(avatar) : null,
);
} catch (e) {
print('XD user $e');
return UserModel('', '', null, null);
return UserModel('', '', '', null, null);
}
}
@@ -36,7 +38,7 @@ class UserModel {
return fromJson(data, uid);
} catch (e) {
print('Error getting user: $e');
return UserModel('', '', null, null);
return UserModel('', '', '', null, null);
}
}
}
+236 -73
View File
@@ -1,12 +1,16 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/photo_view.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/models/chat_model.dart';
import 'package:prosappco/src/models/event_model.dart';
import 'package:prosappco/src/models/user_model.dart';
class ChatScreen extends StatefulWidget {
final String eventoId;
const ChatScreen({super.key, required this.eventoId});
final Event evento;
const ChatScreen({super.key, required this.evento});
@override
State<ChatScreen> createState() => _ChatScreenState();
@@ -15,6 +19,31 @@ class ChatScreen extends StatefulWidget {
class _ChatScreenState extends State<ChatScreen> {
final _textController = TextEditingController();
final uid = AuthenticationRepository.instance.getCurrentUserUid();
UserModel? user;
// ScoresModel? scoresModel;
@override
void initState() {
super.initState();
if (user == null) {
if (uid != widget.evento.userId) {
UserModel.getUser(widget.evento.userId).then(
(UserModel s) => setState(() => user = s),
);
} else {
UserModel.getUser(widget.evento.professionalId).then(
(UserModel s) => setState(() => user = s),
);
}
}
// if (scoresModel == null) {
// ScoresModel.scoreTo(uid.toString(), false, false).then(
// (ScoresModel s) => setState(() => scoresModel = s),
// );
// }
}
@override
Widget build(BuildContext context) {
@@ -24,86 +53,220 @@ class _ChatScreenState extends State<ChatScreen> {
onPressed: () {
Navigator.pop(context);
},
label: 'Mensajes'),
label: 'Chat'),
body: Column(
children: [
StreamBuilder(
stream: FirebaseFirestore.instance
.collection('chats')
.doc(widget.eventoId)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final data = snapshot.data!;
final chat = ChatModel.fromDocumentSnapshot(data);
print('messages ${chat.messages}');
return Column(children: [
...chat.messages.map(
(e) => ListTile(
title: RichText(
text: TextSpan(
children: [
TextSpan(
text: '${e.content}, ',
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
// TextSpan(
// text: DateFormat('dd MMM', 'es')
// .format(DateTime.parse(e.day)),
// style: const TextStyle(
// color: Colors.grey,
// fontSize: 16,
// ),
// ),
],
Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.3),
spreadRadius: 2,
blurRadius: 3,
offset: const Offset(0, 2), // changes position of shadow
),
],
),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 8),
color: const Color(0xFFD6F4FF),
alignment: Alignment.topCenter,
child: ListTile(
leading: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: ReferencePhoto(
ref: user?.photo,
size: 55,
sizeCircle: 60,
),
),
title: Text(
'${user?.name}',
style: const TextStyle(
color: Colors.black, fontWeight: FontWeight.w600),
),
subtitle: Text('${user?.profession}'),
trailing: const Icon(Icons.keyboard_arrow_right),
),
),
),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.only(top: 10),
reverse: true,
child: streamB(uid!),
),
),
Container(
alignment: Alignment.bottomCenter,
width: MediaQuery.of(context).size.width,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 18),
width: MediaQuery.of(context).size.width,
child: Row(
children: [
Expanded(
child: TextFormField(
controller: _textController,
style: const TextStyle(color: Colors.black),
decoration: InputDecoration(
hintText: 'Mensaje',
hintStyle:
TextStyle(color: Colors.grey[600], fontSize: 16),
border: OutlineInputBorder(
borderSide: const BorderSide(
color: Colors.grey, width: 1.0),
borderRadius: BorderRadius.circular(50)),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(
color: Colors.grey, width: 1.0),
borderRadius: BorderRadius.circular(50)),
contentPadding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 15),
filled: true,
fillColor: Colors.grey[200],
),
),
),
)
]);
const SizedBox(width: 12),
GestureDetector(
onTap: () async {
final nuevoMensaje = MessageModel(
user: uid!,
content: _textController.text,
timestamp: DateTime.now());
// ListTile(
// title: Text('${}'),
// );
},
final nuevoMensajeMap = {
'user': nuevoMensaje.user,
'content': nuevoMensaje.content,
'timestamp': nuevoMensaje.timestamp,
};
FirebaseFirestore.instance
.collection('chats')
.doc(widget.evento.id)
.update({
'message': FieldValue.arrayUnion([nuevoMensajeMap])
});
_textController.clear();
},
child: Container(
height: 50,
width: 50,
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(30),
),
child: const Center(
child: Icon(
Icons.send,
color: Colors.white,
),
),
),
)
],
),
),
),
TextFormField(
controller: _textController,
),
IconButton(
onPressed: () async {
final nuevoMensaje = MessageModel(
user: uid!,
content: _textController.text,
timestamp: DateTime.now());
final nuevoMensajeMap = {
'user': nuevoMensaje.user,
'content': nuevoMensaje.content,
'timestamp': nuevoMensaje.timestamp,
};
FirebaseFirestore.instance
.collection('chats')
.doc(widget.eventoId)
.update({
'message': FieldValue.arrayUnion([nuevoMensajeMap])
});
_textController.clear();
},
icon: const Icon(Icons.send))
],
),
);
}
StreamBuilder<DocumentSnapshot<Map<String, dynamic>>> streamB(String uid) {
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection('chats')
.doc(widget.evento.id)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final data = snapshot.data!;
final chat = ChatModel.fromDocumentSnapshot(data);
return Column(children: [
...chat.messages.map(
(e) => uid != e.user
? ListTile(
title: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(right: 60),
padding: const EdgeInsets.symmetric(
vertical: 10, horizontal: 16),
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: const BorderRadius.only(
topRight: Radius.circular(20),
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20),
),
),
child: Text(
e.content,
style: const TextStyle(fontSize: 16),
),
),
const SizedBox(width: 5),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Text(
DateFormat('h:mm a').format(e.timestamp),
style: const TextStyle(
color: Colors.grey, fontSize: 12),
),
),
],
),
)
: ListTile(
title: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Container(
margin: const EdgeInsets.only(left: 60),
padding: const EdgeInsets.symmetric(
vertical: 10, horizontal: 16),
decoration: const BoxDecoration(
color: Color(0xFFD5EFFF),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20),
),
),
child: Text(
e.content,
style: const TextStyle(fontSize: 16),
),
),
const SizedBox(width: 5),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Text(
DateFormat('h:mm a').format(e.timestamp),
style: const TextStyle(
color: Colors.grey, fontSize: 12),
),
),
],
),
),
)
]);
// ListTile(
// title: Text('${}'),
// );
},
);
}
}
+35 -13
View File
@@ -1,10 +1,10 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:intl/intl.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/photo_view.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/models/event_model.dart';
@@ -23,6 +23,7 @@ class CitaScreen extends StatefulWidget {
}
class _CitaScreenState extends State<CitaScreen> {
final uid = AuthenticationRepository.instance.getCurrentUserUid();
UserModel? user;
@override
String nombre = '';
@@ -54,23 +55,40 @@ class _CitaScreenState extends State<CitaScreen> {
super.initState();
if (scoresModel == null) {
ScoresModel.scoreTo(widget.evento.userId.toString(), false, false).then(
(ScoresModel s) => setState(() {
scoresModel = s;
}),
);
if (uid != widget.evento.userId) {
ScoresModel.scoreTo(widget.evento.userId, false, false).then(
(ScoresModel s) => setState(() {
scoresModel = s;
}),
);
} else {
ScoresModel.scoreTo(widget.evento.professionalId, true, false).then(
(ScoresModel s) => setState(() {
scoresModel = s;
}),
);
}
}
}
@override
Widget build(BuildContext context) {
if (nombre == '') {
UserModel.getUser(widget.evento.userId).then((value) {
setState(() {
nombre = value.name;
ref_photo = value.photo;
if (uid != widget.evento.userId) {
UserModel.getUser(widget.evento.userId).then((value) {
setState(() {
nombre = value.name;
ref_photo = value.photo;
});
});
});
} else {
UserModel.getUser(widget.evento.professionalId).then((value) {
setState(() {
nombre = value.name;
ref_photo = value.photo;
});
});
}
}
return Scaffold(
@@ -221,13 +239,17 @@ class _CitaScreenState extends State<CitaScreen> {
await FirebaseFirestore.instance
.collection('chats')
.doc(widget.evento.id)
.set({});
.set({
'professional_id': widget.evento.professionalId,
'user_id': widget.evento.userId
}, SetOptions(merge: true)).catchError((error) =>
print('Error al crear el documento: $error'));
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return ChatScreen(eventoId: widget.evento.id!);
return ChatScreen(evento: widget.evento);
},
),
);
+80
View File
@@ -0,0 +1,80 @@
import 'package:community_material_icon/community_material_icon.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/models/chat_model.dart';
import 'package:prosappco/src/models/event_model.dart';
import 'package:prosappco/src/models/user_model.dart';
import 'package:prosappco/src/screens/chat.dart';
class MessagesScreen extends StatefulWidget {
const MessagesScreen({super.key});
@override
State<MessagesScreen> createState() => _MessagesScreenState();
}
class _MessagesScreenState extends State<MessagesScreen> {
final uid = AuthenticationRepository.instance.getCurrentUserUid();
List<ChatModel> list = [];
@override
void initState() {
super.initState();
ChatModel.getChatsByProId(uid!).then(
(List<ChatModel> s) => setState(() {
list = s;
}),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PopAppbar(
onPressed: () {
Navigator.pop(context);
},
label: 'Mensajes'),
body: Column(
children: [
const Padding(
padding: EdgeInsets.all(10),
child: TextField(
// controller: searchController,
decoration: InputDecoration(
hintText: 'Escriba un nombre',
prefixIcon: Icon(CommunityMaterialIcons.stethoscope),
),
),
),
Expanded(
child: ListView.builder(
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(
list[index].user?.name ?? '',
),
onTap: () {
Event.getEventById('scOUCJUs6iW7zVo4g2m8').then((value) {
Navigator.push(
context,
CupertinoPageRoute(
builder: (BuildContext context) {
return ChatScreen(evento: value);
},
),
);
});
},
);
},
),
),
],
),
);
}
}
+17 -7
View File
@@ -45,7 +45,7 @@ class _MyServicesScreenState extends State<MyServicesScreen> {
Widget _eventList() {
return FutureBuilder(
future: getByProIdAll('aprobado', 'denegado'),
future: getByUserIdAll('aprobado', 'denegado'),
builder: (BuildContext context, AsyncSnapshot<List<Event>> snapshot) {
if (!snapshot.hasData) {
return const Center(
@@ -72,7 +72,7 @@ class _MyServicesScreenState extends State<MyServicesScreen> {
...eventos.map(
(event) => ListTile(
tileColor: event.status == 'aprobado'
? Colors.green[100]
? Colors.blue[100]
: Colors.red[100],
onTap: () {
Navigator.push(
@@ -84,7 +84,17 @@ class _MyServicesScreenState extends State<MyServicesScreen> {
),
);
},
leading: Text(event.range1Hour1),
leading: event.status == 'aprobado'
? const Icon(
Icons.check,
color: Colors.blue,
size: 40,
)
: const Icon(
Icons.close,
color: Colors.red,
size: 40,
),
title: RichText(
text: TextSpan(
children: [
@@ -97,10 +107,10 @@ class _MyServicesScreenState extends State<MyServicesScreen> {
),
),
TextSpan(
text: DateFormat('dd MMM', 'es')
.format(DateTime.parse(event.day)),
style: const TextStyle(
color: Colors.grey,
text:
'${DateFormat('dd MMM', 'es').format(DateTime.parse(event.day))} - ${event.range1Hour1}',
style: TextStyle(
color: Colors.grey[600],
fontSize: 16,
),
),
+2 -2
View File
@@ -109,8 +109,8 @@ class _MyServicesProScreenState extends State<MyServicesProScreen> {
TextSpan(
text:
'${DateFormat('dd MMM', 'es').format(DateTime.parse(event.day))} - ${event.range1Hour1}',
style: const TextStyle(
color: Colors.grey,
style: TextStyle(
color: Colors.grey[600],
fontSize: 16,
),
),