From 40ee4f2ab2986a80ad4e4f90a9fca2274b27bd38 Mon Sep 17 00:00:00 2001 From: Juan Felipe Duarte <70235683+DuarteJFelipe@users.noreply.github.com> Date: Wed, 10 May 2023 08:43:40 -0500 Subject: [PATCH] fix --- android/app/build.gradle | 1 + android/app/src/main/AndroidManifest.xml | 9 + lib/main.dart | 4 +- lib/src/components/drawer_menu.dart | 38 +++- lib/src/components/drawer_professional.dart | 26 ++- lib/src/models/event_model.dart | 7 +- lib/src/models/scores_model.dart | 6 +- lib/src/models/setting_model.dart | 47 ++++- lib/src/models/user_model.dart | 22 +-- lib/src/screens/about.dart | 83 +++++++++ lib/src/screens/calendar.dart | 52 ++++-- lib/src/screens/chat.dart | 8 +- lib/src/screens/cita.dart | 157 +++++++++++++++- lib/src/screens/configuracion.dart | 36 ++++ lib/src/screens/my_services.dart | 65 +++++-- lib/src/screens/my_services_pro.dart | 80 ++++++--- lib/src/screens/professional.dart | 26 +-- lib/src/screens/professional_info.dart | 2 +- lib/src/screens/reputacion_pro.dart | 107 +++++++++++ lib/src/screens/reputation.dart | 5 +- lib/src/screens/score.dart | 168 ++++++++++++++++++ lib/src/screens/service.dart | 56 +++--- lib/src/screens/solicitudes.dart | 34 +++- lib/src/screens/support.dart | 145 +++++++++++++++ lib/src/screens/web_view.dart | 47 +++++ macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 80 ++++++++- pubspec.yaml | 5 +- .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 30 files changed, 1175 insertions(+), 147 deletions(-) create mode 100644 lib/src/screens/about.dart create mode 100644 lib/src/screens/configuracion.dart create mode 100644 lib/src/screens/reputacion_pro.dart create mode 100644 lib/src/screens/score.dart create mode 100644 lib/src/screens/support.dart create mode 100644 lib/src/screens/web_view.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index f900c60..f7f642f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -70,4 +70,5 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation platform('com.google.firebase:firebase-bom:31.3.0') + implementation 'com.google.firebase:firebase-analytics-ktx' } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index cc84a01..9187f5c 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -2,6 +2,12 @@ package="com.example.prosappco"> + + + + + + + + + diff --git a/lib/main.dart b/lib/main.dart index 0db13c9..45af13b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; @@ -19,6 +20,7 @@ import 'package:prosappco/src/screens/profile.dart'; import 'package:prosappco/src/screens/profile_pro.dart'; import 'package:prosappco/src/screens/register.dart'; import 'package:prosappco/src/screens/request_sent.dart'; +import 'package:prosappco/src/screens/score.dart'; import 'package:prosappco/src/screens/service.dart'; import 'package:prosappco/src/screens/solicitudes.dart'; import 'firebase_options.dart'; @@ -64,7 +66,7 @@ class MyApp extends StatelessWidget { '/profilePro': (context) => const ProfilePro(), '/calendar': (context) => const CalendarScreen(), '/solicitud': (context) => SolicitudScreen(), - '/misserviciospro': (context) => MyServicesProScreen(), + '/misserviciospro': (context) => const MyServicesProScreen(), '/misservicios': (context) => MyServicesScreen(), }, onGenerateRoute: (settings) { diff --git a/lib/src/components/drawer_menu.dart b/lib/src/components/drawer_menu.dart index 1ac0f8a..1addff2 100644 --- a/lib/src/components/drawer_menu.dart +++ b/lib/src/components/drawer_menu.dart @@ -6,8 +6,11 @@ import 'package:prosappco/src/authentication/authentication_repository.dart'; import 'package:prosappco/src/components/photo_view.dart'; import 'package:prosappco/src/models/scores_model.dart'; import 'package:prosappco/src/models/user_model.dart'; +import 'package:prosappco/src/screens/configuracion.dart'; import 'package:prosappco/src/screens/messages_user.dart'; import 'package:prosappco/src/screens/profile.dart'; +import 'package:prosappco/src/screens/reputation.dart'; +import 'package:prosappco/src/screens/support.dart'; class DrawerMenu extends StatefulWidget { @override @@ -144,7 +147,16 @@ class _DrawerMenuState extends State { ), ), ListTile( - onTap: () {}, + onTap: () { + Navigator.push( + context, + CupertinoPageRoute( + builder: (BuildContext context) { + return const ConfiguracionScreen(); + }, + ), + ); + }, leading: const Icon( Icons.construction_outlined, color: Colors.black, @@ -155,7 +167,16 @@ class _DrawerMenuState extends State { ), ), ListTile( - onTap: () {}, + onTap: () { + Navigator.push( + context, + CupertinoPageRoute( + builder: (BuildContext context) { + return const SupportScreen(); + }, + ), + ); + }, leading: const Icon( Icons.question_mark_rounded, color: Colors.black, @@ -227,7 +248,16 @@ class _DrawerMenuState extends State { color: const Color(0xFFD6F4FF), child: ListTile( tileColor: const Color(0xFFD6F4FF), - onTap: () {}, + onTap: () { + Navigator.push( + context, + CupertinoPageRoute( + builder: (BuildContext context) { + return const ReputationScreen(); + }, + ), + ); + }, trailing: const Icon(Icons.keyboard_arrow_right, color: Colors.black), title: const Text( @@ -255,7 +285,7 @@ class _DrawerMenuState extends State { ), const SizedBox(width: 5), Text( - '(${scoresModel?.total.toString()}) ${scoresModel?.average.toString()}'), + '(${scoresModel?.total.toString()}) ${scoresModel?.average.toStringAsFixed(1)}'), ], ), ), diff --git a/lib/src/components/drawer_professional.dart b/lib/src/components/drawer_professional.dart index d8a119c..9d48560 100644 --- a/lib/src/components/drawer_professional.dart +++ b/lib/src/components/drawer_professional.dart @@ -14,6 +14,8 @@ import 'package:prosappco/src/screens/profile_pro.dart'; import 'package:prosappco/src/screens/reputation.dart'; import '../models/scores_model.dart'; +import '../screens/configuracion.dart'; +import '../screens/support.dart'; class DrawerProfessional extends StatefulWidget { @override @@ -153,7 +155,16 @@ class _DrawerProfessionalState extends State { ), ), ListTile( - onTap: () {}, + onTap: () { + Navigator.push( + context, + CupertinoPageRoute( + builder: (BuildContext context) { + return const ConfiguracionScreen(); + }, + ), + ); + }, leading: const Icon( Icons.construction_outlined, color: Colors.black, @@ -164,7 +175,16 @@ class _DrawerProfessionalState extends State { ), ), ListTile( - onTap: () {}, + onTap: () { + Navigator.push( + context, + CupertinoPageRoute( + builder: (BuildContext context) { + return const SupportScreen(); + }, + ), + ); + }, leading: const Icon( Icons.question_mark_rounded, color: Colors.black, @@ -296,7 +316,7 @@ class _DrawerProfessionalState extends State { ), const SizedBox(width: 5), Text( - '(${scoresModel?.total.toString()}) ${scoresModel?.average.toString()}'), + '(${scoresModel?.total.toString()}) ${scoresModel?.average.toStringAsFixed(1)}'), ], ), ), diff --git a/lib/src/models/event_model.dart b/lib/src/models/event_model.dart index e51a1a1..f31a9a3 100644 --- a/lib/src/models/event_model.dart +++ b/lib/src/models/event_model.dart @@ -17,6 +17,7 @@ class EventoService { double latitude, double longitude, String status, + int? tarifa, ) async { try { DateTime ahora = DateTime.now(); @@ -35,7 +36,8 @@ class EventoService { 'latitude': latitude, 'longitude': longitude, 'status': status, - 'Timestamp': ahora + 'Timestamp': ahora, + 'tarifa': tarifa ?? 0, }).then((value) { FirebaseFirestore.instance.collection('users').doc(uid).update({ 'services': FieldValue.arrayUnion([value.id]) @@ -135,6 +137,7 @@ class Event { double? latitud; String status; ScoresModel? scoresModel; + int? tarifa; Event({ this.id, @@ -150,6 +153,7 @@ class Event { this.longitud, this.latitud, this.status = 'pendiente', + this.tarifa, }); factory Event.fromJson(Map json) { @@ -167,6 +171,7 @@ class Event { longitud: json['longitude'] ?? 0, latitud: json['latitude'] ?? 0, status: json['status'], + tarifa: json['tarifa'] ?? 0, ); } diff --git a/lib/src/models/scores_model.dart b/lib/src/models/scores_model.dart index f7b8c99..3c685a1 100644 --- a/lib/src/models/scores_model.dart +++ b/lib/src/models/scores_model.dart @@ -30,7 +30,7 @@ class ScoresModel { String userReceived, bool isFromClient, bool userInfo) async { final receivedScoresQuery = FirebaseFirestore.instance .collection('scores') - .where('is_from_client', isEqualTo: isFromClient) + .where('is_from_professional', isEqualTo: isFromClient) .where('to_user', isEqualTo: userReceived); final receivedScoresSnapshot = await receivedScoresQuery.get(); @@ -48,7 +48,7 @@ class ScoresModel { String userGiven, bool isFromClient, bool userInfo) async { final givenScoresQuery = FirebaseFirestore.instance .collection('scores') - .where('is_from_client', isEqualTo: isFromClient) + .where('is_from_professional', isEqualTo: isFromClient) .where('from_user', isEqualTo: userGiven); final givenScoresSnapshot = await givenScoresQuery.get(); @@ -101,7 +101,7 @@ class ScoreDetailModel { fromUser: data['from_user'], toUser: data['to_user'], comment: data['comment'], - isFromClient: data['is_from_client'], + isFromClient: data['is_from_professional'], name: user?.name ?? "...", avatar: user?.photo); } catch (e) { diff --git a/lib/src/models/setting_model.dart b/lib/src/models/setting_model.dart index 823f843..f3e743f 100644 --- a/lib/src/models/setting_model.dart +++ b/lib/src/models/setting_model.dart @@ -3,17 +3,51 @@ import 'package:cloud_firestore/cloud_firestore.dart'; class SettingModel { final bool domicilios; final bool tarifas; + final String titulo; + final String parrafo; + final String numero; + final String email; + final String dias; + final String horas; + final String version; + final String proliticasPrivacidad; + final String terminosCondiciones; - SettingModel(this.domicilios, this.tarifas); + SettingModel( + this.domicilios, + this.tarifas, + this.titulo, + this.parrafo, + this.numero, + this.email, + this.dias, + this.horas, + this.version, + this.proliticasPrivacidad, + this.terminosCondiciones, + ); static Future fromJson(Map json) async { try { - if (json == null) return SettingModel(false, false); + if (json == null) + return SettingModel(false, false, '', '', '', '', '', '', '', '', ''); - return SettingModel(json['domicilio'], json['tarifa']); + return SettingModel( + json['domicilio'], + json['tarifa'], + json['titulo_soporte'], + json['parrafo_soporte'], + json['numero_soporte'], + json['email_soporte'], + json['dias_soporte'], + json['horas_soporte'], + json['version'], + json['politicas_privacidad'], + json['terminos_condiciones'], + ); } catch (e) { print('Error settings: $e'); - return SettingModel(false, false); + return SettingModel(false, false, '', '', '', '', '', '', '', '', ''); } } @@ -24,11 +58,14 @@ class SettingModel { .collection('settings') .doc('global') .get(); + final Map? data = snapshot.data(); + + print('data $data'); return fromJson(data!); } catch (e) { print('Error getting settings: $e'); - return SettingModel(false, false); + return SettingModel(false, false, '', '', '', '', '', '', '', '', ''); } } } diff --git a/lib/src/models/user_model.dart b/lib/src/models/user_model.dart index 3c4d750..b02aa02 100644 --- a/lib/src/models/user_model.dart +++ b/lib/src/models/user_model.dart @@ -8,25 +8,27 @@ class UserModel { final String? profession; final String? state; final Reference? photo; + final int? tarifa; - UserModel(this.name, this.city, this.profession, this.state, this.photo); + UserModel(this.name, this.city, this.profession, this.state, this.photo, + this.tarifa); static Future fromJson( Map? json, String uid) async { try { - if (json == null) return UserModel('', '', '', null, null); + if (json == null) return UserModel('', '', '', null, null, 0); String? avatar = json['photo']; return UserModel( - json['name'], - json['city'], - json['profesion'], - json['estado'], - avatar != null ? storage.ref().child(avatar) : null, - ); + json['name'], + json['city'], + json['profesion'], + json['estado'], + avatar != null ? storage.ref().child(avatar) : null, + json['tarifa'] ?? 0); } catch (e) { print('XD user $e'); - return UserModel('', '', '', null, null); + return UserModel('', '', '', null, null, 0); } } @@ -38,7 +40,7 @@ class UserModel { return fromJson(data, uid); } catch (e) { print('Error getting user: $e'); - return UserModel('', '', '', null, null); + return UserModel('', '', '', null, null, 0); } } } diff --git a/lib/src/screens/about.dart b/lib/src/screens/about.dart new file mode 100644 index 0000000..4fbb094 --- /dev/null +++ b/lib/src/screens/about.dart @@ -0,0 +1,83 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:prosappco/src/components/pop_appbar.dart'; +import 'package:prosappco/src/models/setting_model.dart'; +import 'package:prosappco/src/screens/web_view.dart'; + +class AboutScreen extends StatefulWidget { + const AboutScreen({super.key}); + + @override + State createState() => _AboutScreenState(); +} + +class _AboutScreenState extends State { + SettingModel? settings; + + @override + void initState() { + super.initState(); + if (settings == null) { + SettingModel.getSettings().then( + (SettingModel value) => setState(() { + settings = value; + }), + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: PopAppbar( + onPressed: () { + Navigator.pop(context); + }, + label: 'Acerca de la aplicación'), + body: ListView( + children: [ + ListTile( + onTap: () { + Navigator.push( + context, + CupertinoPageRoute( + builder: (BuildContext context) { + return WebViewScreen( + label: 'Políticas de privacidad', + link: settings?.proliticasPrivacidad ?? '', + ); + }, + ), + ); + }, + title: const Text('Políticas de privacidad'), + trailing: + const Icon(Icons.keyboard_arrow_right, color: Colors.black), + ), + ListTile( + onTap: () { + Navigator.push( + context, + CupertinoPageRoute( + builder: (BuildContext context) { + return WebViewScreen( + label: 'Términos y condiciones', + link: settings?.terminosCondiciones ?? '', + ); + }, + ), + ); + }, + title: const Text('Términos y condiciones'), + trailing: + const Icon(Icons.keyboard_arrow_right, color: Colors.black), + ), + ListTile( + title: const Text('Versión de la aplicación'), + subtitle: Text(settings?.version ?? ''), + ), + ], + ), + ); + } +} diff --git a/lib/src/screens/calendar.dart b/lib/src/screens/calendar.dart index 38b480e..efb525e 100644 --- a/lib/src/screens/calendar.dart +++ b/lib/src/screens/calendar.dart @@ -5,7 +5,6 @@ import 'package:prosappco/src/authentication/authentication_repository.dart'; import 'package:prosappco/src/components/pop_appbar.dart'; import 'package:prosappco/src/components/primary_btn.dart'; import 'package:prosappco/src/models/event_model.dart'; -import 'package:prosappco/src/models/scores_model.dart'; import 'package:prosappco/src/screens/cita.dart'; import 'package:table_calendar/table_calendar.dart'; import 'package:intl/intl.dart'; @@ -26,7 +25,10 @@ class _CalendarScreenState extends State { EventoService eventoService = EventoService(); CalendarFormat _calendarFormat = CalendarFormat.month; + DateTime today = DateTime.now(); + DateTime now = DateTime.now(); + TimeOfDay? _selectedTime1; TimeOfDay? _selectedTime2; @@ -283,19 +285,36 @@ class _CalendarScreenState extends State { ), PrimaryButtom( onPressed: () async { + final DateTime combinedDate1 = DateTime( + today.year, + today.month, + today.day, + _selectedTime1!.hour, + _selectedTime1!.minute, + ); + final DateTime combinedDate2 = DateTime( + today.year, + today.month, + today.day, + _selectedTime2!.hour, + _selectedTime2!.minute, + ); + await eventoService .createEvent( - _titleController.text, - _descriptionController.text, - today.toString(), - _selectedTime1!.format(context).toString(), - _selectedTime2!.format(context).toString(), - uid.toString(), - '', - '', - 0, - 0, - 'aprobado') + _titleController.text, + _descriptionController.text, + DateFormat('yyyy-MM-dd HH:mm:ss.SSS').format(today), + '$combinedDate1', + '$combinedDate2', + uid.toString(), + '', + '', + 0, + 0, + 'aprobado', + 0, + ) .then((value) { Navigator.pop(context); _titleController.text = ''; @@ -323,8 +342,7 @@ class _CalendarScreenState extends State { Widget _eventList() { return FutureBuilder( - future: - getByProId('${DateFormat("yyyy-MM-dd 00:00:00.000").format(today)}Z'), + future: getByProId(DateFormat("yyyy-MM-dd 00:00:00.000").format(today)), builder: (BuildContext context, AsyncSnapshot> snapshot) { if (!snapshot.hasData) { return const Text('No tiene citas'); @@ -355,7 +373,9 @@ class _CalendarScreenState extends State { ), ); }, - leading: Text(e.range1Hour1), + leading: Text( + TimeOfDay.fromDateTime(DateTime.parse(e.range1Hour1)) + .format(context)), title: RichText( text: TextSpan( children: [ @@ -402,7 +422,7 @@ class _CalendarScreenState extends State { ), const SizedBox(width: 5), Text( - '(${e.scoresModel?.total.toString()}) ${e.scoresModel?.average.toString()}'), + '(${e.scoresModel?.total.toString()}) ${e.scoresModel?.average.toStringAsFixed(1)}'), ], ), Text( diff --git a/lib/src/screens/chat.dart b/lib/src/screens/chat.dart index f777799..4ec96d9 100644 --- a/lib/src/screens/chat.dart +++ b/lib/src/screens/chat.dart @@ -37,12 +37,6 @@ class _ChatScreenState extends State { } } }); - - // if (scoresModel == null) { - // ScoresModel.scoreTo(uid.toString(), false, false).then( - // (ScoresModel s) => setState(() => scoresModel = s), - // ); - // } } @override @@ -62,7 +56,7 @@ class _ChatScreenState extends State { color: Colors.grey.withOpacity(0.3), spreadRadius: 2, blurRadius: 3, - offset: const Offset(0, 2), // changes position of shadow + offset: const Offset(0, 2), ), ], ), diff --git a/lib/src/screens/cita.dart b/lib/src/screens/cita.dart index 544653d..ad3fda7 100644 --- a/lib/src/screens/cita.dart +++ b/lib/src/screens/cita.dart @@ -3,6 +3,7 @@ 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:get/get.dart'; import 'package:intl/intl.dart'; import 'package:prosappco/src/authentication/authentication_repository.dart'; import 'package:prosappco/src/components/photo_view.dart'; @@ -11,6 +12,7 @@ import 'package:prosappco/src/models/event_model.dart'; import 'package:prosappco/src/models/scores_model.dart'; import 'package:prosappco/src/models/user_model.dart'; import 'package:prosappco/src/screens/chat.dart'; +import 'package:prosappco/src/screens/score.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:community_material_icon/community_material_icon.dart'; @@ -25,13 +27,20 @@ class CitaScreen extends StatefulWidget { class _CitaScreenState extends State { final uid = AuthenticationRepository.instance.getCurrentUserUid(); UserModel? user; - @override + DateTime today = DateTime.now(); String nombre = ''; + int tarifa = 0; Reference? ref_photo; ScoresModel? scoresModel; final phoneNumber = '+573022548060'; + String formatCurrency(int number) { + final formatter = + NumberFormat.currency(locale: 'es_CO', decimalDigits: 0, symbol: ''); + return '\$${formatter.format(number)}'; + } + Future _openMap(double lat, double lng) async { final Uri _url = Uri.parse('https://www.google.com/maps/search/?api=1&query=$lat,$lng'); @@ -73,17 +82,25 @@ class _CitaScreenState extends State { @override Widget build(BuildContext context) { + today.difference(DateTime.parse(widget.evento.range1Hour1)); + // DateTime todays = DateFormat('hh:mm a').parse(today.timeZoneName); + final eventDate = DateFormat('yyyy-MM-dd').parse(widget.evento.day); + if (nombre == '') { if (uid != widget.evento.userId) { UserModel.getUser(widget.evento.userId).then((value) { - setState(() { - nombre = value.name; - ref_photo = value.photo; + UserModel.getUser(uid.toString()).then((me) { + setState(() { + tarifa = me.tarifa ?? 0; + nombre = value.name; + ref_photo = value.photo; + }); }); }); } else { UserModel.getUser(widget.evento.professionalId).then((value) { setState(() { + tarifa = value.tarifa ?? 0; nombre = value.name; ref_photo = value.photo; }); @@ -122,7 +139,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))} ${TimeOfDay.fromDateTime(DateTime.parse(widget.evento.range1Hour1)).format(context)}', style: const TextStyle( color: Colors.grey, fontSize: 16, @@ -151,7 +168,7 @@ class _CitaScreenState extends State { ), const SizedBox(width: 5), Text( - '(${scoresModel?.total.toString()}) ${scoresModel?.average.toString()}'), + '(${scoresModel?.total.toString()}) ${scoresModel?.average.toStringAsFixed(1)}'), ], ), ), @@ -194,6 +211,18 @@ class _CitaScreenState extends State { ], ), ), + Column( + children: [ + Text( + formatCurrency(tarifa), + style: const TextStyle( + fontWeight: FontWeight.w600, fontSize: 25), + ), + Text('Tarifa consulta ${widget.evento.tarifa}', + style: const TextStyle(fontSize: 15)), + ], + ), + const SizedBox(height: 15), Text( textAlign: TextAlign.center, '" ${widget.evento.description} "', @@ -367,6 +396,80 @@ class _CitaScreenState extends State { ], ), ), + widget.evento.status == 'aprobado' + ? eventDate.year == today.year && + eventDate.month == today.month && + eventDate.day == today.day + ? (DateTime.now() + .difference( + DateTime.parse(widget.evento.range1Hour1)) + .abs() <= + const Duration(minutes: 30)) + ? Padding( + padding: const EdgeInsets.only(bottom: 30), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 20), + child: ElevatedButton( + onPressed: () { + FirebaseFirestore.instance + .collection("services") + .doc('${widget.evento.id}') + .update({"status": "iniciado"}); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF2BA4EC), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50), + ), + elevation: 0, + minimumSize: const Size(230, 60), + ), + child: const Text( + 'Iniciar servicio', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 18, + ), + ), + ), + ), + ElevatedButton( + onPressed: () { + FirebaseFirestore.instance + .collection("services") + .doc('${widget.evento.id}') + .update({"status": "denegado"}).then( + (value) { + Navigator.pushReplacementNamed( + context, '/solicitud'); + }); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFEC2B2B), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50), + ), + elevation: 0, + minimumSize: const Size(230, 60), + ), + child: const Text( + 'Cancelar servicio', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 18, + ), + ), + ), + ], + ), + ) + : const SizedBox() + : const SizedBox() + : const SizedBox(), widget.evento.status == 'pendiente' ? Padding( padding: const EdgeInsets.only(bottom: 30), @@ -433,6 +536,48 @@ class _CitaScreenState extends State { ), ) : const SizedBox(), + widget.evento.status == 'iniciado' + ? Padding( + padding: const EdgeInsets.only(bottom: 30), + child: Column( + children: [ + ElevatedButton( + onPressed: () { + FirebaseFirestore.instance + .collection("services") + .doc('${widget.evento.id}') + .update({"status": "terminado"}).then((value) { + Navigator.push( + context, + CupertinoPageRoute( + builder: (BuildContext context) { + return ScoreScreen(evento: widget.evento); + }, + ), + ); + }); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF2BA4EC), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50), + ), + elevation: 0, + minimumSize: const Size(230, 60), + ), + child: const Text( + 'Terminar servicio', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 18, + ), + ), + ), + ], + ), + ) + : const SizedBox(), ], ), ); diff --git a/lib/src/screens/configuracion.dart b/lib/src/screens/configuracion.dart new file mode 100644 index 0000000..a3e92f9 --- /dev/null +++ b/lib/src/screens/configuracion.dart @@ -0,0 +1,36 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:prosappco/src/components/pop_appbar.dart'; +import 'package:prosappco/src/screens/about.dart'; + +class ConfiguracionScreen extends StatelessWidget { + const ConfiguracionScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: PopAppbar( + onPressed: () { + Navigator.pop(context); + }, + label: 'Configuración'), + body: ListTile( + onTap: () { + Navigator.push( + context, + CupertinoPageRoute( + builder: (BuildContext context) { + return const AboutScreen(); + }, + ), + ); + }, + title: const Text('Acerca de la aplicación'), + trailing: const Icon( + Icons.keyboard_arrow_right, + color: Colors.black, + ), + ), + ); + } +} diff --git a/lib/src/screens/my_services.dart b/lib/src/screens/my_services.dart index 36954dc..f09a59b 100644 --- a/lib/src/screens/my_services.dart +++ b/lib/src/screens/my_services.dart @@ -1,10 +1,13 @@ +import 'package:cloud_firestore/cloud_firestore.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/drawer_professional.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/screens/cita.dart'; class MyServicesScreen extends StatefulWidget { @@ -16,6 +19,7 @@ class MyServicesScreen extends StatefulWidget { class _MyServicesScreenState extends State { DateTime today = DateTime.now(); + final uid = AuthenticationRepository.instance.getCurrentUserUid(); @override void initState() { @@ -44,10 +48,32 @@ class _MyServicesScreenState extends State { } Widget _eventList() { - return FutureBuilder( - future: getByUserIdAll('aprobado', 'denegado'), + return StreamBuilder>( + stream: FirebaseFirestore.instance + .collection('services') + .where('user_id', isEqualTo: uid) + .where('status', + whereIn: ['aprobado', 'denegado', 'iniciado', 'terminado']) + .snapshots() + .asyncMap((snapshot) async { + try { + List 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 getByProId $e'); + return []; + } + }), builder: (BuildContext context, AsyncSnapshot> snapshot) { - if (!snapshot.hasData) { + if (snapshot.connectionState == ConnectionState.waiting) { return const Center( child: CircularProgressIndicator(), ); @@ -67,13 +93,14 @@ class _MyServicesScreenState extends State { child: Center(child: Text('No tienes citas')), ); } + return Column( children: [ ...eventos.map( (event) => ListTile( - tileColor: event.status == 'aprobado' - ? Colors.blue[100] - : Colors.red[100], + tileColor: event.status == 'denegado' + ? Colors.red[100] + : Colors.blue[100], onTap: () { Navigator.push( context, @@ -90,11 +117,23 @@ class _MyServicesScreenState extends State { color: Colors.blue, size: 40, ) - : const Icon( - Icons.close, - color: Colors.red, - size: 40, - ), + : event.status == 'iniciado' + ? const Icon( + Icons.access_time, + color: Colors.blue, + size: 40, + ) + : event.status == 'terminado' + ? const Icon( + Icons.rocket_launch, + color: Colors.blue, + size: 40, + ) + : const Icon( + Icons.close, + color: Colors.red, + size: 40, + ), title: RichText( text: TextSpan( children: [ @@ -108,7 +147,7 @@ class _MyServicesScreenState extends State { ), TextSpan( text: - '${DateFormat('dd MMM', 'es').format(DateTime.parse(event.day))} - ${event.range1Hour1}', + '${DateFormat('dd MMM', 'es').format(DateTime.parse(event.day))} - ${TimeOfDay.fromDateTime(DateTime.parse(event.range1Hour1)).format(context)}', style: TextStyle( color: Colors.grey[600], fontSize: 16, @@ -141,7 +180,7 @@ class _MyServicesScreenState extends State { ), const SizedBox(width: 5), Text( - '(${event.scoresModel?.total.toString()}) ${event.scoresModel?.average.toString()}'), + '(${event.scoresModel?.total.toString()}) ${event.scoresModel?.average.toStringAsFixed(1)}'), ], ), Text( diff --git a/lib/src/screens/my_services_pro.dart b/lib/src/screens/my_services_pro.dart index d23b821..0488ea9 100644 --- a/lib/src/screens/my_services_pro.dart +++ b/lib/src/screens/my_services_pro.dart @@ -1,13 +1,17 @@ +import 'package:cloud_firestore/cloud_firestore.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/drawer_professional.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/screens/cita.dart'; class MyServicesProScreen extends StatefulWidget { - MyServicesProScreen({super.key}); + const MyServicesProScreen({super.key}); @override State createState() => _MyServicesProScreenState(); @@ -15,6 +19,7 @@ class MyServicesProScreen extends StatefulWidget { class _MyServicesProScreenState extends State { DateTime today = DateTime.now(); + final uid = AuthenticationRepository.instance.getCurrentUserUid(); @override void initState() { @@ -43,10 +48,32 @@ class _MyServicesProScreenState extends State { } Widget _eventList() { - return FutureBuilder( - future: getByProIdAll('aprobado', 'denegado'), + return StreamBuilder>( + stream: FirebaseFirestore.instance + .collection('services') + .where('professional_id', isEqualTo: uid) + .where('status', + whereIn: ['aprobado', 'denegado', 'iniciado', 'terminado']) + .snapshots() + .asyncMap((snapshot) async { + try { + List 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 getByProId $e'); + return []; + } + }), builder: (BuildContext context, AsyncSnapshot> snapshot) { - if (!snapshot.hasData) { + if (snapshot.connectionState == ConnectionState.waiting) { return const Center( child: CircularProgressIndicator(), ); @@ -66,24 +93,23 @@ class _MyServicesProScreenState extends State { child: Center(child: Text('No tienes citas')), ); } + return Column( children: [ ...eventos.map( (event) => ListTile( - tileColor: event.status == 'aprobado' - ? Colors.blue[100] - : Colors.red[100], + tileColor: event.status == 'denegado' + ? Colors.red[100] + : Colors.blue[100], onTap: () { - print(event.id); - - // Navigator.push( - // context, - // CupertinoPageRoute( - // builder: (BuildContext context) { - // return CitaScreen(evento: event); - // }, - // ), - // ); + Navigator.push( + context, + CupertinoPageRoute( + builder: (BuildContext context) { + return CitaScreen(evento: event); + }, + ), + ); }, leading: event.status == 'aprobado' ? const Icon( @@ -91,11 +117,17 @@ class _MyServicesProScreenState extends State { color: Colors.blue, size: 40, ) - : const Icon( - Icons.close, - color: Colors.red, - size: 40, - ), + : event.status == 'iniciado' + ? const Icon( + Icons.access_time, + color: Colors.blue, + size: 40, + ) + : const Icon( + Icons.close, + color: Colors.red, + size: 40, + ), title: RichText( text: TextSpan( children: [ @@ -109,7 +141,7 @@ class _MyServicesProScreenState extends State { ), TextSpan( text: - '${DateFormat('dd MMM', 'es').format(DateTime.parse(event.day))} - ${event.range1Hour1}', + '${DateFormat('dd MMM', 'es').format(DateTime.parse(event.day))} - ${TimeOfDay.fromDateTime(DateTime.parse(event.range1Hour1)).format(context)}', style: TextStyle( color: Colors.grey[600], fontSize: 16, @@ -142,7 +174,7 @@ class _MyServicesProScreenState extends State { ), const SizedBox(width: 5), Text( - '(${event.scoresModel?.total.toString()}) ${event.scoresModel?.average.toString()}'), + '(${event.scoresModel?.total.toString()}) ${event.scoresModel?.average.toStringAsFixed(1)}'), ], ), Text( diff --git a/lib/src/screens/professional.dart b/lib/src/screens/professional.dart index cff9935..402d134 100644 --- a/lib/src/screens/professional.dart +++ b/lib/src/screens/professional.dart @@ -36,6 +36,7 @@ class Professional { final double longitude; final List professionalEspecializado; final ScoresModel scores; + final int? tarifa; Professional({ required this.id, @@ -49,6 +50,7 @@ class Professional { required this.realAddress, required this.latitude, required this.longitude, + this.tarifa, }); String getEspecializaciones() { @@ -90,18 +92,18 @@ class Professional { .toList(); Professional professional = Professional( - id: user.id, - name: data['name'], - professionName: data['profesion'], - cityName: data['city'], - professionalRef: storage.ref().child(photo), - professionalEspecializado: especializaciones, - ubicacion: data['ubicacion'] ?? '', - realAddress: data['address'] ?? '', - latitude: data['latitude'] ?? 0, - longitude: data['longitude'] ?? 0, - scores: await ScoresModel.scoreFrom(uid, true, true), - ); + id: user.id, + name: data['name'], + professionName: data['profesion'], + cityName: data['city'], + professionalRef: storage.ref().child(photo), + professionalEspecializado: especializaciones, + ubicacion: data['ubicacion'] ?? '', + realAddress: data['address'] ?? '', + latitude: data['latitude'] ?? 0, + longitude: data['longitude'] ?? 0, + scores: await ScoresModel.scoreFrom(uid, true, true), + tarifa: data['tarifa'] ?? 0); return professional; } } catch (e) { diff --git a/lib/src/screens/professional_info.dart b/lib/src/screens/professional_info.dart index 835666b..abd78de 100644 --- a/lib/src/screens/professional_info.dart +++ b/lib/src/screens/professional_info.dart @@ -170,7 +170,7 @@ class _ProfessionalInfoScreenState extends State { ), const SizedBox(width: 5), Text( - '(${widget.professional.scores.total.toString()}) ${widget.professional.scores.average.toString()}'), + '(${widget.professional.scores.total.toString()}) ${widget.professional.scores.average.toStringAsFixed(1)}'), ], ), ), diff --git a/lib/src/screens/reputacion_pro.dart b/lib/src/screens/reputacion_pro.dart new file mode 100644 index 0000000..0f8b2f4 --- /dev/null +++ b/lib/src/screens/reputacion_pro.dart @@ -0,0 +1,107 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.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/scores_model.dart'; + +class ReputationProScreen extends StatefulWidget { + const ReputationProScreen({ + super.key, + }); + + @override + State createState() => _ReputationProScreenState(); +} + +class _ReputationProScreenState extends State { + ScoresModel? scoresModel; + + @override + void initState() { + super.initState(); + final uid = AuthenticationRepository.instance.getCurrentUserUid(); + + if (scoresModel == null) { + ScoresModel.scoreFrom(uid.toString(), false, true).then( + (ScoresModel s) => setState(() => scoresModel = s), + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: PopAppbar( + onPressed: () { + Navigator.pop(context); + }, + label: 'Reputación'), + body: SingleChildScrollView( + child: Column( + children: [ + ..._scoresList(scoresModel?.details ?? []), + ], + )), + ); + } + + List _scoresList(List list) { + return list.map((e) => _scoreItem(e)).toList(); + } + + Widget _scoreItem(ScoreDetailModel scoreDetails) { + return ListTile( + onTap: () {}, + leading: ReferencePhoto( + ref: scoreDetails.avatar, + size: 50, + sizeCircle: 50, + sizeIcon: 35, + ), + title: Row( + children: [ + RatingBar.builder( + initialRating: scoreDetails.score, + minRating: 1, + direction: Axis.horizontal, + allowHalfRating: true, + itemCount: 5, + itemSize: 22, + maxRating: 5, + itemPadding: const EdgeInsets.symmetric(horizontal: 0), + itemBuilder: (context, _) => const Icon( + Icons.star, + color: Color(0xFF2BA4EC), + ), + onRatingUpdate: (rating) {}, + ignoreGestures: true, + ), + Text( + ' (${scoreDetails.score})', + style: const TextStyle(color: Colors.black54, fontSize: 13), + ) + ], + ), + subtitle: Row( + children: [ + Expanded( + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: '${scoreDetails.name}, ', + style: const TextStyle(fontSize: 15, color: Colors.black), + ), + TextSpan( + text: '"${scoreDetails.comment}"', + style: const TextStyle(fontSize: 15, color: Colors.grey), + ), + ], + ), + ), + ), + ], + )); + } +} diff --git a/lib/src/screens/reputation.dart b/lib/src/screens/reputation.dart index 1aa327c..4b3fd7a 100644 --- a/lib/src/screens/reputation.dart +++ b/lib/src/screens/reputation.dart @@ -4,10 +4,9 @@ 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/scores_model.dart'; -import 'package:prosappco/src/models/user_model.dart'; class ReputationScreen extends StatefulWidget { - ReputationScreen({ + const ReputationScreen({ super.key, }); @@ -24,7 +23,7 @@ class _ReputationScreenState extends State { final uid = AuthenticationRepository.instance.getCurrentUserUid(); if (scoresModel == null) { - ScoresModel.scoreTo(uid.toString(), true, true).then( + ScoresModel.scoreTo(uid.toString(), false, true).then( (ScoresModel s) => setState(() => scoresModel = s), ); } diff --git a/lib/src/screens/score.dart b/lib/src/screens/score.dart new file mode 100644 index 0000000..3bf14f4 --- /dev/null +++ b/lib/src/screens/score.dart @@ -0,0 +1,168 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_storage/firebase_storage.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/user_model.dart'; + +class ScoreScreen extends StatefulWidget { + final Event evento; + + const ScoreScreen({super.key, required this.evento}); + + @override + State createState() => ScoreScreenState(); +} + +class ScoreScreenState extends State { + TextEditingController commentController = TextEditingController(); + Reference? ref_photo; + String nombre = ''; + double _rating = 1.0; + + @override + Widget build(BuildContext context) { + if (nombre == '') { + if (uid != widget.evento.userId) { + UserModel.getUser(widget.evento.userId).then((value) { + UserModel.getUser(uid.toString()).then((me) { + 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( + appBar: PopAppbar(onPressed: () {}, label: 'Puntuación'), + body: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: ListTile( + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 5), + child: ReferencePhoto( + ref: ref_photo, + size: 50, + sizeCircle: 50, + sizeIcon: 35, + ), + ), + title: Text( + nombre, + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.w600, + fontSize: 16), + ), + subtitle: Text( + '${DateFormat('dd MMM', 'es').format(DateTime.parse(widget.evento.day))} ${TimeOfDay.fromDateTime(DateTime.parse(widget.evento.range1Hour1)).format(context)}'), + ), + ), + RatingBar.builder( + initialRating: _rating, + minRating: 1, + direction: Axis.horizontal, + allowHalfRating: true, + itemCount: 5, + itemSize: 40, + glow: false, + maxRating: 5, + itemPadding: const EdgeInsets.symmetric(horizontal: 5), + itemBuilder: (context, _) => const Icon( + Icons.star, + color: Color(0xFF2BA4EC), + ), + onRatingUpdate: (rating) { + setState(() { + _rating = rating; + }); + }, + ignoreGestures: false, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 40), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Comentario', + style: TextStyle(fontSize: 18), + ), + TextFormField( + maxLines: null, + keyboardType: TextInputType + .multiline, // establece el tipo de teclado como "multilinea" + controller: commentController, + ), + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 30), + child: Column( + children: [ + ElevatedButton( + onPressed: () { + if (widget.evento.professionalId == uid) { + FirebaseFirestore.instance.collection("scores").add({ + "comment": commentController.text, + "from_user": uid, + "is_from_professional": false, + "score": _rating, + "to_user": widget.evento.userId, + }).then((value) { + // Navigator.pushReplacementNamed(context, '/score'); + }); + } else { + FirebaseFirestore.instance.collection("scores").add({ + "comment": commentController.text, + "from_user": uid, + "is_from_professional": true, + "score": _rating, + "to_user": widget.evento.professionalId, + }).then((value) { + // Navigator.pushReplacementNamed(context, '/score'); + }); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF2BA4EC), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50), + ), + elevation: 0, + minimumSize: const Size(230, 60), + ), + child: const Text( + 'Enviar', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 18, + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/screens/service.dart b/lib/src/screens/service.dart index 64d3c04..5f6c290 100644 --- a/lib/src/screens/service.dart +++ b/lib/src/screens/service.dart @@ -1,5 +1,5 @@ import 'package:cloud_firestore/cloud_firestore.dart'; -import 'package:community_material_icon/community_material_icon.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; @@ -35,6 +35,7 @@ class _ServiceScreenState extends State { final _serviceTypeController = TextEditingController(); final _observacionController = TextEditingController(); String professionalId = ''; + int professionalTarifa = 0; String professionalAddress = ''; String professionalUbicacion = ''; double? professionalLatitude; @@ -130,6 +131,7 @@ class _ServiceScreenState extends State { final FirebaseStorage storage = FirebaseStorage.instance; final uid = AuthenticationRepository.instance.getCurrentUserUid(); + final fcmToken = FirebaseMessaging.instance.getToken(); BitmapDescriptor? _markerIcon; @@ -411,6 +413,9 @@ class _ServiceScreenState extends State { professionalId = profesional.id; + professionalTarifa = + profesional.tarifa ?? 0; + professionalUbicacion = profesional.ubicacion; @@ -549,35 +554,36 @@ class _ServiceScreenState extends State { const SizedBox(height: 20), ElevatedButton( onPressed: () { - int tiempo2 = ((_selectedTime!.hour * 3600 + - _selectedTime!.minute * 60) + - 7200) % - 86400; + final DateTime combinedDate = DateTime( + _selectedDate!.year, + _selectedDate!.month, + _selectedDate!.day, + _selectedTime!.hour, + _selectedTime!.minute); - int hora2 = tiempo2 ~/ 3600; - int minutos2 = (tiempo2 % 3600) ~/ 60; - - TimeOfDay time2 = - TimeOfDay(hour: hora2, minute: minutos2); + DateTime time2 = + combinedDate.add(const Duration(hours: 2)); UserModel.getUser(uid.toString()).then((value) { 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, - 'pendiente') + value.name, + _observacionController.text, + '$_selectedDate', + '$combinedDate', + '$time2', + professionalId, + ubicacion, + _locationController.text, + ubicacion != 'sitio' + ? coordinates.latitude + : professionalLatitude, + ubicacion != 'sitio' + ? coordinates.longitude + : professionalLongitude, + 'pendiente', + professionalTarifa, + ) .then((value) { Navigator.pushReplacement( context, diff --git a/lib/src/screens/solicitudes.dart b/lib/src/screens/solicitudes.dart index dc5fab4..1e97e5c 100644 --- a/lib/src/screens/solicitudes.dart +++ b/lib/src/screens/solicitudes.dart @@ -1,9 +1,11 @@ +import 'package:cloud_firestore/cloud_firestore.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/components/drawer_professional.dart'; import 'package:prosappco/src/models/event_model.dart'; +import 'package:prosappco/src/models/scores_model.dart'; import 'package:prosappco/src/screens/cita.dart'; class SolicitudScreen extends StatefulWidget { @@ -50,8 +52,29 @@ class _SolicitudScreenState extends State { } Widget _eventList() { - return FutureBuilder( - future: getByProIdAll('pendiente', ''), + return StreamBuilder>( + stream: FirebaseFirestore.instance + .collection('services') + .where('professional_id', isEqualTo: uid) + .where('status', whereIn: ['pendiente', '']) + .snapshots() + .asyncMap((snapshot) async { + try { + List 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 getByProId $e'); + return []; + } + }), builder: (BuildContext context, AsyncSnapshot> snapshot) { if (!snapshot.hasData) { return const Center( @@ -87,7 +110,10 @@ class _SolicitudScreenState extends State { ), ); }, - leading: Text(event.range1Hour1), + leading: Text( + TimeOfDay.fromDateTime(DateTime.parse(event.range1Hour1)) + .format(context), + ), title: RichText( text: TextSpan( children: [ @@ -134,7 +160,7 @@ class _SolicitudScreenState extends State { ), const SizedBox(width: 5), Text( - '(${event.scoresModel?.total.toString()}) ${event.scoresModel?.average.toString()}'), + '(${event.scoresModel?.total.toString()}) ${event.scoresModel?.average.toStringAsFixed(1)}'), ], ), Text( diff --git a/lib/src/screens/support.dart b/lib/src/screens/support.dart new file mode 100644 index 0000000..fd3922c --- /dev/null +++ b/lib/src/screens/support.dart @@ -0,0 +1,145 @@ +import 'package:community_material_icon/community_material_icon.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_email_sender/flutter_email_sender.dart'; +import 'package:prosappco/src/components/pop_appbar.dart'; +import 'package:prosappco/src/models/setting_model.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class SupportScreen extends StatefulWidget { + const SupportScreen({super.key}); + + @override + State createState() => SsupportStateScreen(); +} + +class SsupportStateScreen extends State { + SettingModel? settings; + + Future _sendWhatsapp(String? number) async { + final _whatsappUrl = + 'https://wa.me/$number?text=${Uri.parse('Hola! soy usuario de Prosapp y quisiera conocer mas sobre esta app')}'; + + if (!await launch(_whatsappUrl)) { + throw Exception('Could not launch $_whatsappUrl'); + } + } + + Future _sendEmail(String recipients) async { + final Email email = Email( + body: '', + subject: 'Soporte Prosapp', + recipients: [recipients], + isHTML: false, + ); + + await FlutterEmailSender.send(email); + } + + @override + void initState() { + super.initState(); + if (settings == null) { + SettingModel.getSettings().then( + (SettingModel value) => setState(() { + settings = value; + }), + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: PopAppbar( + onPressed: () { + Navigator.pop(context); + }, + label: 'Soporte'), + body: Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 30, left: 35, right: 35), + child: Text( + '${settings?.titulo}', + style: const TextStyle(fontWeight: FontWeight.w600), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 35), + child: Text('${settings?.parrafo}'), + ), + Row( + children: [ + const Expanded(child: SizedBox()), + ElevatedButton( + onPressed: () { + _sendWhatsapp(settings?.numero); + }, + style: ElevatedButton.styleFrom( + foregroundColor: Colors.white, + backgroundColor: const Color(0xFF2BA4EC), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50), + side: const BorderSide( + color: Color(0xFF2BA4EC), + width: 2, + ), + ), + ), + child: const Padding( + padding: EdgeInsets.symmetric(vertical: 18, horizontal: 0), + child: Icon( + CommunityMaterialIcons.whatsapp, + size: 30, + color: Colors.white, + ), + ), + ), + const SizedBox(width: 20), + ElevatedButton( + onPressed: () { + _sendEmail(settings?.email ?? ''); + }, + style: ElevatedButton.styleFrom( + foregroundColor: Colors.white, + backgroundColor: const Color(0xFF2BA4EC), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50), + side: const BorderSide( + color: Color(0xFF2BA4EC), + width: 2, + ), + ), + ), + child: const Padding( + padding: EdgeInsets.symmetric(vertical: 18, horizontal: 0), + child: Icon( + Icons.email_outlined, + size: 30, + color: Colors.white, + ), + ), + ), + const Expanded(child: SizedBox()), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 50), + child: Column( + children: [ + const Text( + 'Horario de atención:', + style: TextStyle(fontWeight: FontWeight.w600), + ), + const SizedBox(height: 20), + Text('${settings?.dias}'), + const SizedBox(height: 5), + Text('${settings?.horas}'), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/screens/web_view.dart b/lib/src/screens/web_view.dart new file mode 100644 index 0000000..6fc390e --- /dev/null +++ b/lib/src/screens/web_view.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:prosappco/src/components/pop_appbar.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +class WebViewScreen extends StatefulWidget { + String label; + String link; + WebViewScreen({super.key, required this.label, required this.link}); + + @override + State createState() => _WebViewScreenState(); +} + +class _WebViewScreenState extends State { + @override + Widget build(BuildContext context) { + final controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setBackgroundColor(const Color(0x00000000)) + ..setNavigationDelegate( + NavigationDelegate( + onProgress: (int progress) { + // Update loading bar. + }, + onPageStarted: (String url) {}, + onPageFinished: (String url) {}, + onWebResourceError: (WebResourceError error) {}, + onNavigationRequest: (NavigationRequest request) { + if (request.url.startsWith('https://www.youtube.com/')) { + return NavigationDecision.prevent; + } + return NavigationDecision.navigate; + }, + ), + ) + ..loadRequest(Uri.parse('${widget.link}')); + return Scaffold( + appBar: PopAppbar( + onPressed: () { + Navigator.pop(context); + }, + label: widget.label, + ), + body: WebViewWidget(controller: controller), + ); + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 9c4e7a1..2ef86b8 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,6 +8,7 @@ import Foundation import cloud_firestore import firebase_auth import firebase_core +import firebase_messaging import firebase_storage import geolocator_apple import url_launcher_macos @@ -16,6 +17,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin")) FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) + FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) FLTFirebaseStoragePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseStoragePlugin")) GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 25ddfc5..379a90f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: f175bc1414e4edf8c5b83372c98eeabecf8353f39c9da423c2cfdf1f1f508788 + sha256: "867b77e2367bc502dcd4d5a66302615409f04eb20ed82ba1c0ba073f9107e018" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" async: dependency: transitive description: @@ -141,26 +141,50 @@ packages: dependency: "direct main" description: name: firebase_core - sha256: ed611fb8e67e43ecc7956f242cecca383d87cf71aace27287aa5dd4bdba4ac07 + sha256: dcf54c170c5371ad0e79229d0fb372c58262ae0968b7de222bf28e51dd236be0 url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.11.0" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - sha256: "0df0a064ab0cad7f8836291ca6f3272edd7b83ad5b3540478ee46a0849d8022b" + sha256: ae79f335f6c7f2dadb00c98c429da2ca905d265e0225fb5e7dfa62ac3accad48 url: "https://pub.dev" source: hosted - version: "4.6.0" + version: "4.7.0" firebase_core_web: dependency: transitive description: name: firebase_core_web - sha256: "347351a8f0518f3343d79a9a0690fa67ad232fc32e2ea270677791949eac792b" + sha256: e57ef862257a0d977c1308d02e2fbb9b68525e6d85711b08f3df8cec836fb444 url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.4.0" + firebase_messaging: + dependency: "direct main" + description: + name: firebase_messaging + sha256: "88a4bf569899344d863db26a73092681b590274f570f0a431051b92c4dd9bee3" + url: "https://pub.dev" + source: hosted + version: "14.5.0" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + sha256: "482fa6998f6cb10b30324ca8bfa0e2fcc0defed9484ccccce118b1b347e39a24" + url: "https://pub.dev" + source: hosted + version: "4.4.0" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + sha256: "61f7d323085704fbcc7b056a49d75211b2eb698610b878fec6cda18dd04d754f" + url: "https://pub.dev" + source: hosted + version: "3.4.0" firebase_storage: dependency: "direct main" description: @@ -198,6 +222,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.1+1" + flutter_email_sender: + dependency: "direct main" + description: + name: flutter_email_sender + sha256: "9e253c69617f43d4cb5de672e93a7a19c12a21fb6a75e66c6ce7626336c4c1bc" + url: "https://pub.dev" + source: hosted + version: "5.2.0" flutter_lints: dependency: "direct dev" description: @@ -725,6 +757,38 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + webview_flutter: + dependency: "direct main" + description: + name: webview_flutter + sha256: "1a37bdbaaf5fbe09ad8579ab09ecfd473284ce482f900b5aea27cf834386a567" + url: "https://pub.dev" + source: hosted + version: "4.2.0" + webview_flutter_android: + dependency: transitive + description: + name: webview_flutter_android + sha256: "1acea8def62592123e2fbbca164ed8681a98a890bdcbb88f916d5b4a22687759" + url: "https://pub.dev" + source: hosted + version: "3.7.0" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: "78715dc442b7849dbde74e92bb67de1cecf5addf95531c5fb474e72f5fe9a507" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: "61f33512810bf1ee9ac89761a4b02663ff64e8227b7dc80654642acd660fd49d" + url: "https://pub.dev" + source: hosted + version: "3.4.2" sdks: dart: ">=2.19.3 <3.0.0" flutter: ">=3.3.0" diff --git a/pubspec.yaml b/pubspec.yaml index a5ec4e1..0682bed 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -56,8 +56,11 @@ dependencies: google_maps_flutter: ^2.2.5 geolocator: ^9.0.2 geocoding: ^2.1.0 - url_launcher: ^6.0.3 + url_launcher: ^6.1.10 community_material_icon: ^5.9.55 + flutter_email_sender: ^5.2.0 + webview_flutter: ^4.2.0 + firebase_messaging: ^14.5.0 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 94586cc..16039c1 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,10 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + FirebaseCorePluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); GeolocatorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("GeolocatorWindows")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index f0bcafd..e30096f 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + firebase_core geolocator_windows url_launcher_windows )