From 5baf01919819235b3935e240b951af8b04d4a427 Mon Sep 17 00:00:00 2001 From: Juan Felipe Duarte <70235683+DuarteJFelipe@users.noreply.github.com> Date: Sat, 29 Apr 2023 17:56:29 -0500 Subject: [PATCH] vista --- lib/src/models/event_model.dart | 85 +++++++++++--- lib/src/screens/calendar.dart | 45 +++----- lib/src/screens/cita.dart | 110 +++++++++++++++++- lib/src/screens/service.dart | 142 +++++++++++++++-------- lib/src/screens/service_after.dart | 173 ++++++++++++++++++++++++++++- lib/src/screens/solicitudes.dart | 4 +- 6 files changed, 460 insertions(+), 99 deletions(-) diff --git a/lib/src/models/event_model.dart b/lib/src/models/event_model.dart index 9f51dde..c2ced35 100644 --- a/lib/src/models/event_model.dart +++ b/lib/src/models/event_model.dart @@ -5,25 +5,48 @@ import 'package:prosappco/src/models/scores_model.dart'; final uid = AuthenticationRepository.instance.getCurrentUserUid(); class EventoService { - Future createEvent(String title, String description, String day, - String range1Hour1, String range1Hour2, String professionalId) async { + Future createEvent( + String title, + String description, + String day, + String range1Hour1, + String range1Hour2, + String professionalId, + String ubicacion, + String address, + double latitude, + double longitude, + ) async { try { - await FirebaseFirestore.instance.collection('services').add({ + DateTime ahora = DateTime.now(); + + final eventId = + await FirebaseFirestore.instance.collection('services').add({ 'user_id': uid, 'title': title, 'description': description, 'day': day, 'range1Hour1': range1Hour1, 'range1Hour2': range1Hour2, - 'professional_id': professionalId + 'professional_id': professionalId, + 'ubicacion': ubicacion, + 'address': address, + 'latitude': latitude, + 'longitude': longitude, + 'Timestamp': ahora }).then((value) { FirebaseFirestore.instance.collection('users').doc(uid).update({ 'services': FieldValue.arrayUnion([value.id]) }); + + return value.id; }); + + return eventId; } catch (e) { print('Evento $e'); } + return null; } } @@ -37,7 +60,9 @@ Future> getByProId(String day) async { List eventos = []; for (var element in snapshot.docs) { - eventos.add(Event.fromJson(element.data())); + final event = Event.fromJson(element.data()); + event.scoresModel = await ScoresModel.scoreTo(event.userId, false, false); + eventos.add(event); } print('eventos $eventos'); return eventos; @@ -57,8 +82,7 @@ Future> getByProIdAll() async { List eventos = []; for (var element in snapshot.docs) { final event = Event.fromJson(element.data()); - event.scoresModel = - await ScoresModel.scoreTo(event.userId ?? "", false, false); + event.scoresModel = await ScoresModel.scoreTo(event.userId, false, false); eventos.add(event); } print('eventos $eventos'); @@ -70,23 +94,31 @@ Future> getByProIdAll() async { } class Event { - String? title; + String title; String? description; - String? day; - String? range1Hour1; + String day; + String range1Hour1; String? range1Hour2; - String? userId; - String? professionalId; + String userId; + String professionalId; + String? ubicacion; + String? address; + double? longitud; + double? latitud; ScoresModel? scoresModel; Event({ - this.title, + required this.title, this.description, - this.day, - this.range1Hour1, + required this.day, + required this.range1Hour1, this.range1Hour2, - this.userId, - this.professionalId, + required this.userId, + required this.professionalId, + this.ubicacion, + this.address, + this.longitud, + this.latitud, }); factory Event.fromJson(Map json) { @@ -98,9 +130,28 @@ class Event { range1Hour2: json['range1Hour2'], userId: json['user_id'], professionalId: json['professional_id'], + ubicacion: json['ubicacion'] ?? '', + address: json['address'] ?? '', + longitud: json['longitude'] ?? 0, + latitud: json['latitude'] ?? 0, ); } + static Future getEventById(String uid) async { + try { + final snapshot = await FirebaseFirestore.instance + .collection('services') + .doc(uid) + .get(); + final Map? data = snapshot.data(); + return Event.fromJson(data!); + } catch (e) { + print('Error getting user: $e'); + return Event( + title: '', day: '', range1Hour1: '', userId: '', professionalId: ''); + } + } + static Future> getEventsAllById(String uid) async { try { var snapshot = await FirebaseFirestore.instance diff --git a/lib/src/screens/calendar.dart b/lib/src/screens/calendar.dart index 93c2e53..093b450 100644 --- a/lib/src/screens/calendar.dart +++ b/lib/src/screens/calendar.dart @@ -24,8 +24,6 @@ class _CalendarScreenState extends State { final _titleController = TextEditingController(); final _descriptionController = TextEditingController(); - ScoresModel? scoresModel; - EventoService eventoService = EventoService(); CalendarFormat _calendarFormat = CalendarFormat.month; DateTime today = DateTime.now(); @@ -67,12 +65,6 @@ class _CalendarScreenState extends State { _events = value; })); } - - if (scoresModel == null) { - ScoresModel.scoreFrom(uid.toString(), true, false).then( - (ScoresModel s) => setState(() => scoresModel = s), - ); - } } void _onDaySelected(DateTime day, DateTime focusedDay) { @@ -124,7 +116,7 @@ class _CalendarScreenState extends State { eventLoader: (date) { return events .where((element) { - DateTime day = DateTime.parse(element.day ?? ""); + DateTime day = DateTime.parse(element.day); return (date.year == day.year && date.month == day.month && date.day == day.day); @@ -292,13 +284,16 @@ class _CalendarScreenState extends State { onPressed: () async { await eventoService .createEvent( - _titleController.text, - _descriptionController.text, - today.toString(), - _selectedTime1!.format(context).toString(), - _selectedTime2!.format(context).toString(), - uid.toString(), - ) + _titleController.text, + _descriptionController.text, + today.toString(), + _selectedTime1!.format(context).toString(), + _selectedTime2!.format(context).toString(), + uid.toString(), + '', + '', + 0, + 0) .then((value) { Navigator.pop(context); _titleController.text = ''; @@ -348,24 +343,16 @@ class _CalendarScreenState extends State { ...eventos.map( (e) => ListTile( onTap: () { - final evento = Event( - day: e.day, - description: e.description, - userId: e.userId, - range1Hour1: e.range1Hour1, - range1Hour2: e.range1Hour2, - title: e.title, - ); Navigator.push( context, CupertinoPageRoute( builder: (BuildContext context) { - return CitaScreen(evento: evento); + return CitaScreen(evento: e); }, ), ); }, - leading: Text('${e.range1Hour1}'), + leading: Text(e.range1Hour1), title: RichText( text: TextSpan( children: [ @@ -379,7 +366,7 @@ class _CalendarScreenState extends State { ), TextSpan( text: DateFormat('dd MMM', 'es') - .format(DateTime.parse(e.day!)), + .format(DateTime.parse(e.day)), style: const TextStyle( color: Colors.grey, fontSize: 16, @@ -394,7 +381,7 @@ class _CalendarScreenState extends State { Row( children: [ RatingBar.builder( - initialRating: scoresModel?.average ?? 0, + initialRating: e.scoresModel?.average ?? 0, minRating: 1, direction: Axis.horizontal, allowHalfRating: true, @@ -412,7 +399,7 @@ class _CalendarScreenState extends State { ), const SizedBox(width: 5), Text( - '(${scoresModel?.total.toString()}) ${scoresModel?.average.toString()}'), + '(${e.scoresModel?.total.toString()}) ${e.scoresModel?.average.toString()}'), ], ), Text( diff --git a/lib/src/screens/cita.dart b/lib/src/screens/cita.dart index 0f1cf06..2921590 100644 --- a/lib/src/screens/cita.dart +++ b/lib/src/screens/cita.dart @@ -39,7 +39,7 @@ class _CitaScreenState extends State { Widget build(BuildContext context) { if (nombre == '') { - UserModel.getUser(widget.evento.userId!).then((value) { + UserModel.getUser(widget.evento.userId).then((value) { setState(() { nombre = value.name; ref_photo = value.photo; @@ -78,7 +78,7 @@ class _CitaScreenState extends State { ), TextSpan( text: - '${DateFormat('dd MMM', 'es').format(DateTime.parse(widget.evento.day!))} ${widget.evento.range1Hour1}', + '${DateFormat('dd MMM', 'es').format(DateTime.parse(widget.evento.day))} ${widget.evento.range1Hour1}', style: const TextStyle( color: Colors.grey, fontSize: 16, @@ -111,11 +111,117 @@ class _CitaScreenState extends State { ], ), ), + Container( + margin: const EdgeInsets.only( + left: 40, right: 40, top: 20, bottom: 20), + padding: + const EdgeInsets.symmetric(horizontal: 20, vertical: 15), + decoration: BoxDecoration( + color: const Color(0xFFD6F4FF), + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 1, + blurRadius: 5, + offset: const Offset(1, 3), + ), + ], + ), + child: Row( + children: [ + const Icon( + Icons.error_outline, + size: 27, + color: Colors.black54, + ), + const SizedBox(width: 15), + widget.evento.ubicacion != 'sitio' + ? const Text( + 'Servicio a domicilio.', + style: + TextStyle(color: Colors.black, fontSize: 14), + ) + : const Text( + 'Servicio en su sitio / consultorio', + style: + TextStyle(color: Colors.black, fontSize: 14), + ), + ], + ), + ), Text( textAlign: TextAlign.center, '" ${widget.evento.description} "', style: const TextStyle( color: Colors.grey, fontStyle: FontStyle.italic), + ), + const Padding( + padding: EdgeInsets.symmetric(vertical: 20), + child: Text( + 'Medios de comunicación con el usuario.', + style: TextStyle(color: Color(0xFF2BA4EC)), + ), + ), + Row( + children: [ + const Expanded(child: SizedBox()), + Container( + decoration: BoxDecoration( + color: Colors.white, + border: Border.all( + color: const Color(0xFF2BA4EC), width: 2), + borderRadius: BorderRadius.circular(50), + ), + child: FloatingActionButton( + onPressed: () {}, + backgroundColor: Colors.white, + elevation: 0, + child: const Icon(Icons.phone_android, + size: 30, color: Color(0xFF2BA4EC)), + ), + ), + const SizedBox(width: 20), + Container( + decoration: BoxDecoration( + color: Colors.white, + border: Border.all( + color: const Color(0xFF2BA4EC), width: 2), + borderRadius: BorderRadius.circular(50), + ), + child: FloatingActionButton( + onPressed: () {}, + backgroundColor: Colors.white, + elevation: 0, + child: const Icon(Icons.message, + size: 30, color: Color(0xFF2BA4EC)), + ), + ), + const Expanded(child: SizedBox()), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 20), + child: Row( + children: [ + Container( + decoration: BoxDecoration( + color: const Color(0xFF2BA4EC), + border: Border.all( + color: const Color(0xFF2BA4EC), width: 2), + borderRadius: BorderRadius.circular(50), + ), + child: FloatingActionButton( + onPressed: () {}, + backgroundColor: const Color(0xFF2BA4EC), + elevation: 0, + child: const Icon(Icons.near_me, + size: 30, color: Colors.white), + ), + ), + Text('${widget.evento.address}') + ], + ), ) ], ), diff --git a/lib/src/screens/service.dart b/lib/src/screens/service.dart index eeda4c3..969095f 100644 --- a/lib/src/screens/service.dart +++ b/lib/src/screens/service.dart @@ -1,3 +1,4 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; @@ -27,7 +28,8 @@ class _ServiceScreenState extends State { EventoService eventoService = EventoService(); final TextEditingController _locationController = TextEditingController(); - final String _locationPosition = ''; + String _locationPosition = ''; + String ubicacion = ''; final _profesionalController = TextEditingController(); final _serviceTypeController = TextEditingController(); final _observacionController = TextEditingController(); @@ -115,12 +117,44 @@ class _ServiceScreenState extends State { } } + Future>>> + getUsersWithActiveStatus() async { + var querySnapshot = await FirebaseFirestore.instance + .collection('users') + .where('estado', isEqualTo: 'activo') + .get(); + + return querySnapshot.docs; + } + final FirebaseStorage storage = FirebaseStorage.instance; final uid = AuthenticationRepository.instance.getCurrentUserUid(); @override void initState() { super.initState(); + + try { + getUsersWithActiveStatus().then((value) { + for (var doc in value) { + final element = doc.data()!; + + if (element['latitude'] != null && element['longitude'] != null) { + markers.add( + Marker( + markerId: MarkerId(doc.id), + position: LatLng( + element['latitude'], + element['longitude'], + ), + ), + ); + } + } + }); + } catch (e) { + print('error coordenadas $e'); + } } late String lat; @@ -191,14 +225,14 @@ class _ServiceScreenState extends State { ), ), const Positioned( - bottom: 10, + bottom: 25, right: 0, left: 0, top: 0, child: Icon( Icons.location_on, size: 40, - color: Colors.red, + color: Color(0xFFFF0000), ), ), Positioned( @@ -328,13 +362,17 @@ class _ServiceScreenState extends State { child: Form( child: Column( children: [ - TextFormField( - readOnly: true, - controller: _locationController, - decoration: const InputDecoration( - prefixIcon: Icon(Icons.near_me), - hintText: 'Dirección', - ), + Column( + children: [ + TextFormField( + readOnly: true, + controller: _locationController, + decoration: const InputDecoration( + prefixIcon: Icon(Icons.near_me), + hintText: 'Dirección', + ), + ), + ], ), const SizedBox(height: 15), TextFormField( @@ -353,49 +391,45 @@ class _ServiceScreenState extends State { ); if (datos != null) { Professional? profesional = datos[0]; + ubicacion = datos[1]; if (profesional != null) { setState(() { _profesionalController.text = profesional.name.toString(); - professionalAddress = - profesional.realAddress; - professionalUbicacion = profesional.ubicacion; - _locationController.text = - profesional.realAddress; - professionalLatitude = profesional.latitude; professionalLongitude = profesional.longitude; - if (professionalLatitude != null && - professionalLatitude != 0 && - professionalLongitude != null && - professionalLongitude != 0) { - markers.clear(); + if (ubicacion == 'sitio') { + professionalAddress = + profesional.realAddress; - markers.add(Marker( - markerId: - const MarkerId('professional'), - position: LatLng( - professionalLatitude!, - professionalLongitude!))); - googleMapController.animateCamera( - CameraUpdate.newCameraPosition( - CameraPosition( - target: LatLng( - professionalLatitude!, - professionalLongitude!, - ), - zoom: 17), - ), - ); + _locationController.text = + profesional.realAddress; + + if (professionalLatitude != null && + professionalLatitude != 0 && + professionalLongitude != null && + professionalLongitude != 0) { + googleMapController.animateCamera( + CameraUpdate.newCameraPosition( + CameraPosition( + target: LatLng( + professionalLatitude!, + professionalLongitude!, + ), + zoom: 17), + ), + ); + } } + professionalId = profesional.id; }); } @@ -472,24 +506,36 @@ class _ServiceScreenState extends State { TimeOfDay(hour: hora2, minute: minutos2); UserModel.getUser(uid.toString()).then((value) { - eventoService.createEvent( + eventoService + .createEvent( value.name, _observacionController.text, '${_selectedDate}Z', _selectedTime!.format(context), time2.format(context), professionalId, - ); + ubicacion, + _locationController.text, + ubicacion != 'sitio' + ? coordinates.latitude + : professionalLatitude, + ubicacion != 'sitio' + ? coordinates.longitude + : professionalLongitude, + ) + .then((value) { + Navigator.push( + context, + CupertinoPageRoute( + builder: (BuildContext context) { + return ServiceAfterScreen( + eventoId: value, + ); + }, + ), + ); + }); }); - - // Navigator.push( - // context, - // CupertinoPageRoute( - // builder: (BuildContext context) { - // return const ServiceAfterScreen(); - // }, - // ), - // ); }, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF2BA4EC), diff --git a/lib/src/screens/service_after.dart b/lib/src/screens/service_after.dart index 82cdc22..35d2097 100644 --- a/lib/src/screens/service_after.dart +++ b/lib/src/screens/service_after.dart @@ -1,19 +1,190 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; +import 'package:intl/intl.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'; +import 'package:prosappco/src/models/scores_model.dart'; +import 'package:prosappco/src/models/user_model.dart'; class ServiceAfterScreen extends StatefulWidget { - const ServiceAfterScreen({super.key}); + var eventoId; + ServiceAfterScreen({super.key, this.eventoId}); @override State createState() => _ServiceAfterScreenState(); } class _ServiceAfterScreenState extends State { + Event? evento; + UserModel? user; + ScoresModel? scoresModel; + + @override + void initState() { + super.initState(); + + Event.getEventById(widget.eventoId).then((value) { + setState(() { + evento = value; + }); + if (scoresModel == null) { + ScoresModel.scoreTo(value.professionalId, true, false).then( + (ScoresModel s) => setState(() { + scoresModel = s; + }), + ); + } + UserModel.getUser(value.professionalId).then((s) { + setState( + () => user = s, + ); + }); + }); + } + @override Widget build(BuildContext context) { return Scaffold( appBar: PopAppbar(onPressed: () => Navigator.pop(context), label: 'Servicio'), + body: Column( + children: [ + ListTile( + leading: ReferencePhoto( + ref: user?.photo, + size: 55, + sizeCircle: 60, + ), + title: RichText( + text: TextSpan( + children: [ + TextSpan( + text: '${user?.name}, ', + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.w500, + fontSize: 16, + ), + ), + TextSpan( + text: DateFormat('dd MMM', 'es').format(DateTime.parse( + evento?.day ?? '2023-01-01 00:00:00.000Z')), + style: const TextStyle( + color: Colors.grey, + fontSize: 16, + ), + ), + TextSpan( + text: ' ${evento?.range1Hour1}', + style: const TextStyle( + color: Colors.grey, + fontSize: 16, + ), + ), + ], + ), + ), + subtitle: Column( + children: [ + Row( + children: [ + RatingBar.builder( + initialRating: scoresModel?.average ?? 0, + minRating: 1, + direction: Axis.horizontal, + allowHalfRating: true, + itemCount: 5, + itemSize: 25, + maxRating: 5, + itemPadding: const EdgeInsets.symmetric(horizontal: 0), + itemBuilder: (context, _) => const Icon( + Icons.star, + color: Color(0xFF2BA4EC), + ), + onRatingUpdate: (rating) {}, + ignoreGestures: true, + ), + const SizedBox(width: 5), + Text( + '(${scoresModel?.total.toString()}) ${scoresModel?.average.toString()}'), + ], + ), + ], + ), + ), + Container( + margin: + const EdgeInsets.only(left: 40, right: 40, top: 20, bottom: 20), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), + decoration: BoxDecoration( + color: const Color(0xFFD6F4FF), + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 1, + blurRadius: 5, + offset: const Offset(1, 3), + ), + ], + ), + child: Row( + children: [ + const Icon( + Icons.error_outline, + size: 27, + color: Colors.black54, + ), + const SizedBox(width: 15), + evento?.ubicacion != 'sitio' + ? const Text( + 'Servicio a su domicilio.', + style: TextStyle(color: Colors.black, fontSize: 14), + ) + : const Text( + 'Servicio en sitio / consultorio', + style: TextStyle(color: Colors.black, fontSize: 14), + ), + ], + ), + ), + ListTile( + leading: const Icon(Icons.near_me), + title: Text( + '${evento?.address}', + style: TextStyle(fontSize: 15, color: Colors.grey[600]), + ), + ), + Text( + '" ${evento?.description} "', + style: + TextStyle(color: Colors.grey[600], fontStyle: FontStyle.italic), + ), + Center( + child: Column( + children: const [ + Padding( + padding: EdgeInsets.symmetric(vertical: 20), + child: Icon( + Icons.check_circle_outline_rounded, + color: Color(0xFF35A8ED), + size: 70, + ), + ), + Text( + 'Servicio solicitado exitosamente', + style: TextStyle( + color: Color(0xFF35A8ED), + fontSize: 17, + fontWeight: FontWeight.w600), + ), + ], + ), + ), + ], + ), ); } } diff --git a/lib/src/screens/solicitudes.dart b/lib/src/screens/solicitudes.dart index 9cbe2c3..909dddf 100644 --- a/lib/src/screens/solicitudes.dart +++ b/lib/src/screens/solicitudes.dart @@ -88,7 +88,7 @@ class _SolicitudScreenState extends State { ), ); }, - leading: Text('${event.range1Hour1}'), + leading: Text(event.range1Hour1), title: RichText( text: TextSpan( children: [ @@ -102,7 +102,7 @@ class _SolicitudScreenState extends State { ), TextSpan( text: DateFormat('dd MMM', 'es') - .format(DateTime.parse(event.day!)), + .format(DateTime.parse(event.day)), style: const TextStyle( color: Colors.grey, fontSize: 16,