diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 9187f5c..7a9f68e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -16,6 +16,8 @@ Injector.appInstance.get(), ), BlocProvider( - create: (context) => Injector.appInstance.get(), + create: (context) => + Injector.appInstance.get(), + ), + BlocProvider( + create: (context) => Injector.appInstance.get(), ) ], child: BlocBuilder( diff --git a/lib/blocs/notification_bloc/notification_bloc.dart b/lib/blocs/notification_bloc/notification_bloc.dart new file mode 100644 index 0000000..9a48d6a --- /dev/null +++ b/lib/blocs/notification_bloc/notification_bloc.dart @@ -0,0 +1,115 @@ +import 'dart:developer'; + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:prosappco/firebase_options.dart'; +import 'package:prosappco/local_notifications/local_notifications.dart'; +import 'package:user_repository/user_repository.dart'; + +part 'notification_event.dart'; +part 'notification_state.dart'; + +Future firebaseMessagingBackgroundHandler(RemoteMessage message) async { + await Firebase.initializeApp(); +} + +class NotificationBloc extends Bloc { + FirebaseMessaging messaging = FirebaseMessaging.instance; + + final Future Function() requestLocalNotificationPermission; + final void Function({ + required int id, + String? title, + String? body, + String? data, + }) showLocalNotification; + + NotificationBloc( + {required this.requestLocalNotificationPermission, + required this.showLocalNotification}) + : super(const NotificationState()) { + on(_notificationStatusChanged); + + _initialStatusCheck(); + + _onForegroundMessage(); + } + + static Future initializeFCM() async { + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); + } + + void _notificationStatusChanged( + NotificationStatusChanged event, Emitter emit) { + emit(state.copyWith(status: event.status)); + + _getFCMToken(); + } + + void _initialStatusCheck() async { + final settings = await messaging.getNotificationSettings(); + add(NotificationStatusChanged(settings.authorizationStatus)); + _getFCMToken(); + } + + Future _saveFCMTokenToFirestore(String token) async { + try { + CollectionReference users = FirebaseFirestore.instance.collection('users'); + + await users.doc(FirebaseAuth.instance.currentUser!.uid).update({ + 'token': token, + }); + } catch (e) { + print('tokenFCM Error saving FCM token to Firestore: $e'); + } + } + + void _getFCMToken() async { + if (state.status != AuthorizationStatus.authorized) return; + + final fcmToken = await messaging.getToken(); + log('tokenFCM ${fcmToken.toString()}'); + + if (fcmToken == null) return; + + _saveFCMTokenToFirestore(fcmToken); + } + + void _handleRemoteMessage(RemoteMessage message) { + if (message.notification == null) return; + + showLocalNotification( + id: 1, + title: message.notification!.title, + body: message.notification!.body, + ); + } + + void _onForegroundMessage() { + FirebaseMessaging.onMessage.listen(_handleRemoteMessage); + } + + void requestPermission() async { + NotificationSettings settings = await messaging.requestPermission( + alert: true, + announcement: false, + badge: true, + carPlay: false, + criticalAlert: false, + provisional: false, + sound: true, + ); + +// Solicitar permiso a las local notificaciones + + await requestLocalNotificationPermission(); + + add(NotificationStatusChanged(settings.authorizationStatus)); + } +} diff --git a/lib/blocs/notification_bloc/notification_event.dart b/lib/blocs/notification_bloc/notification_event.dart new file mode 100644 index 0000000..03a5f8c --- /dev/null +++ b/lib/blocs/notification_bloc/notification_event.dart @@ -0,0 +1,14 @@ +part of 'notification_bloc.dart'; + +abstract class NotificationEvent extends Equatable { + const NotificationEvent(); + + @override + List get props => []; +} + +class NotificationStatusChanged extends NotificationEvent { + final AuthorizationStatus status; + + const NotificationStatusChanged(this.status); +} diff --git a/lib/blocs/notification_bloc/notification_state.dart b/lib/blocs/notification_bloc/notification_state.dart new file mode 100644 index 0000000..af67485 --- /dev/null +++ b/lib/blocs/notification_bloc/notification_state.dart @@ -0,0 +1,21 @@ +part of 'notification_bloc.dart'; + +class NotificationState extends Equatable { + final AuthorizationStatus status; + + const NotificationState({ + this.status = AuthorizationStatus.notDetermined, + }); + + NotificationState copyWith({ + AuthorizationStatus? status, + }) => + NotificationState( + status: status ?? this.status, + ); + + @override + List get props => [status]; +} + +class NotificationInitial extends NotificationState {} diff --git a/lib/dependency/app_di.dart b/lib/dependency/app_di.dart index 5577dd2..664d5a6 100644 --- a/lib/dependency/app_di.dart +++ b/lib/dependency/app_di.dart @@ -7,6 +7,7 @@ import 'package:prosappco/blocs/auth_bloc/auth_bloc.dart'; import 'package:prosappco/blocs/authentication_bloc/authentication_bloc.dart'; import 'package:prosappco/blocs/chat_bloc/chat_bloc.dart'; import 'package:prosappco/blocs/my_user_bloc/my_user_bloc.dart'; +import 'package:prosappco/blocs/notification_bloc/notification_bloc.dart'; import 'package:prosappco/blocs/professional_bloc/professional_bloc.dart'; import 'package:prosappco/blocs/professional_list_bloc/professional_list_bloc.dart'; import 'package:prosappco/blocs/professional_profile_bloc/professional_profile_bloc.dart'; @@ -16,6 +17,7 @@ import 'package:prosappco/blocs/service_bloc/service_bloc.dart'; import 'package:prosappco/blocs/setting_bloc/setting_bloc.dart'; import 'package:prosappco/blocs/sign_up_bloc/sign_up_bloc.dart'; import 'package:prosappco/blocs/sing_in_bloc/sign_in_bloc.dart'; +import 'package:prosappco/local_notifications/local_notifications.dart'; import 'package:score_repository/score_repository.dart'; import 'package:service_repository/service_repository.dart'; import 'package:user_repository/user_repository.dart'; @@ -56,6 +58,12 @@ class AppDI { professionalRepository: injector.get(), userRepository: injector.get()), ); + injector.registerSingleton( + () => NotificationBloc( + requestLocalNotificationPermission: LocalNotifications.requestPermissionLocalNotifications, + showLocalNotification: LocalNotifications.showLocalNotification, + ), + ); injector.registerDependency( () => SignInBloc(userRepository: injector.get())); diff --git a/lib/local_notifications/local_notifications.dart b/lib/local_notifications/local_notifications.dart new file mode 100644 index 0000000..fee4f34 --- /dev/null +++ b/lib/local_notifications/local_notifications.dart @@ -0,0 +1,92 @@ +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; + +class LocalNotifications { + static Future requestPermissionLocalNotifications() async { + final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + + await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.requestPermission(); + } + + static Future initializeLocalNotifications() async { + final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + + const initializationSettingsAndroid = + AndroidInitializationSettings('app_icon'); + + const initializationSettings = InitializationSettings( + android: initializationSettingsAndroid, + ); + + await flutterLocalNotificationsPlugin.initialize( + initializationSettings, + ); + } + + static void showLocalNotification({ + required int id, + String? title, + String? body, + String? data, + }) { + const androidDetails = AndroidNotificationDetails( + 'channel_id', + 'channel_name', + importance: Importance.max, + priority: Priority.high, + playSound: true, + sound: RawResourceAndroidNotificationSound('notification'), + ); + + const notificationDetails = NotificationDetails( + android: androidDetails, + ); + + final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + + flutterLocalNotificationsPlugin.show( + id, + title, + body, + notificationDetails, + payload: data, + ); + } + + static Future sendPushNotification( + String token, String title, String body) async { + try { + http.Response response = await http.post( + Uri.parse('https://fcm.googleapis.com/fcm/send'), + headers: { + 'Content-Type': 'application/json; charset=UTF-8', + 'Authorization': + 'key=AAAAORdR-xU:APA91bF_wblg86jHAC-uexrXPHavYRlk5wge1Gf46m56V4J2D2L37Cp_hf46JZUzpvsWPSpqc5ewHelKI9LTifUG_s2mciMI6e5VLKo7E1R8btbNo7iaM9do2ctoyHKUm1atlZBdKaN2', + }, + body: jsonEncode( + { + 'notification': { + 'body': body, + 'title': title, + + }, + 'priority': 'high', + 'data': { + 'click_action': 'FLUTTER_NOTIFICATION_CLICK', + 'id': '1', + 'status': 'done', + }, + 'to': token, + }, + ), + ); + response; + } catch (e) { + rethrow; + } + } +} diff --git a/lib/main.dart b/lib/main.dart index 0634ee4..bed080a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,181 +1,62 @@ -// import 'package:flutter/material.dart'; -// import 'package:flutter/services.dart'; -// import 'package:get/get.dart'; -// import 'package:intl/date_symbol_data_local.dart'; -// import 'package:prosappco/src/authentication/authentication_repository.dart'; -// import 'package:prosappco/src/presentation/screens/service_web.dart'; -// import 'package:prosappco/src/providers/user_provider.dart'; -// import 'package:prosappco/src/presentation/screens/calendar.dart'; -// import 'package:prosappco/src/presentation/screens/city.dart'; -// import 'package:prosappco/src/presentation/screens/login/login.dart'; -// import 'package:firebase_core/firebase_core.dart'; -// import 'package:prosappco/src/presentation/screens/login/login_email.dart'; -// import 'package:prosappco/src/presentation/screens/my_services.dart'; -// import 'package:prosappco/src/presentation/screens/my_services_pro.dart'; -// import 'package:prosappco/src/presentation/screens/new_number.dart'; -// import 'package:prosappco/src/presentation/screens/new_password.dart'; -// import 'package:prosappco/src/presentation/screens/profession.dart'; -// import 'package:prosappco/src/presentation/screens/professional_profile.dart'; -// import 'package:prosappco/src/presentation/screens/professional_revision.dart'; -// import 'package:prosappco/src/presentation/screens/profile/profile.dart'; -// import 'package:prosappco/src/presentation/screens/profile/profile_pro.dart'; -// import 'package:prosappco/src/presentation/screens/register/register.dart'; -// import 'package:prosappco/src/presentation/screens/request_sent.dart'; -// import 'package:prosappco/src/presentation/screens/reset_password/reset_password.dart'; -// import 'package:prosappco/src/presentation/screens/map/service.dart'; -// import 'package:prosappco/src/presentation/screens/solicitudes.dart'; -// import 'package:prosappco/src/services/local_notifications.dart'; -// import 'firebase_options.dart'; -// import 'package:firebase_messaging/firebase_messaging.dart'; -// import 'package:flutter/foundation.dart' show kIsWeb; -// import 'package:provider/provider.dart'; -// import 'package:flutter_localizations/flutter_localizations.dart'; - -// Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { -// await Firebase.initializeApp(); - -// print('Handling a backdround message: ${message.messageId}'); -// } - -// void main() async { -// WidgetsFlutterBinding.ensureInitialized(); -// await Firebase.initializeApp( -// options: DefaultFirebaseOptions.currentPlatform, -// ).then((value) => Get.put(AuthenticationRepository())); - -// // Evitar la solicitud de permiso en la versión web -// if (!kIsWeb) { -// FirebaseMessaging messaging = FirebaseMessaging.instance; - -// await messaging.requestPermission( -// alert: true, -// announcement: false, -// badge: true, -// carPlay: false, -// criticalAlert: false, -// provisional: false, -// sound: true, -// ); - -// FirebaseMessaging.onBackgroundMessage( -// (_firebaseMessagingBackgroundHandler)); - -// FirebaseMessaging.onMessage.listen((RemoteMessage message) {}); -// } - -// await initNotifications(); - -// await initializeDateFormatting('es_MX', null); - -// SystemChrome.setPreferredOrientations([ -// DeviceOrientation.portraitUp, -// DeviceOrientation.portraitDown, -// ]); - -// runApp(const MyApp()); -// } - -// class MyApp extends StatelessWidget { -// const MyApp({super.key}); - -// @override -// Widget build(BuildContext context) { -// return MultiProvider( -// providers: [ -// ChangeNotifierProvider( -// create: (_) => UserProvider(), -// ), -// ], -// child: GetMaterialApp( -// theme: ThemeData(fontFamily: 'Poppins'), -// debugShowCheckedModeBanner: false, -// localizationsDelegates: const [ -// GlobalMaterialLocalizations.delegate, -// GlobalWidgetsLocalizations.delegate, -// GlobalCupertinoLocalizations.delegate, -// ], -// supportedLocales: const [ -// Locale('es', 'US'), -// ], -// title: 'ProsApp', -// initialRoute: '/', -// routes: { -// '/': (context) => const LoginScreen(), -// '/splash': (context) => const SplashScreen(), -// '/login': (context) => const LoginEmailScreen(), -// '/profile': (context) => const ProfileScreen(), -// '/register': (context) => const RegisterScreen(), -// '/city': (context) => const CityScreen(), -// '/profession': (context) => const ProfessionScreen(), -// '/newNumber': (context) => const NewNumberScreen(), -// '/profesionalRevision': (context) => -// const ProfessionalRevisionScreen(), -// '/profesionalProfile': (context) => const ProfessionalProfileScreen(), -// '/solicitudEnviada': (context) => const RequestSentScreen(), -// '/nuevaPassword': (context) => NewPasswordScreen(), -// '/servicio': (context) => const ServiceScreen(), -// '/servicioWeb': (context) => const ServiceWebScreen(), -// '/profilePro': (context) => const ProfileProScreen(), -// '/calendar': (context) => const CalendarScreen(), -// '/solicitud': (context) => SolicitudScreen(), -// '/misserviciospro': (context) => MyServicesProScreen(), -// '/misservicios': (context) => MyServicesScreen(), -// '/resetpassword': (context) => const ResetPasswordScreen(), -// }, -// onGenerateRoute: (settings) { -// throw Exception('Ruta desconocida: ${settings.name}'); -// }, -// ), -// ); -// } -// } - -// class SplashScreen extends StatefulWidget { -// const SplashScreen({super.key}); - -// @override -// State createState() => _SplashScreenState(); -// } - -// class _SplashScreenState extends State { -// @override -// void initState() { -// super.initState(); -// if (kIsWeb) { -// // Espera 3 segundos y luego redirige a LoginScreen -// Future.delayed(const Duration(seconds: 3), () { -// Navigator.pushReplacement( -// context, -// MaterialPageRoute(builder: (context) => const LoginScreen()), -// ); -// }); -// } -// } - -// @override -// Widget build(BuildContext context) { -// return Scaffold( -// backgroundColor: Colors.blue, -// body: Center( -// child: kIsWeb ? Image.asset('/images/splash.png') : const LoginScreen(), -// ), -// ); -// } -// } - +import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:injector/injector.dart'; +import 'package:prosappco/app_view.dart'; +import 'package:prosappco/blocs/authentication_bloc/authentication_bloc.dart'; +import 'package:prosappco/blocs/my_user_bloc/my_user_bloc.dart'; +import 'package:prosappco/blocs/notification_bloc/notification_bloc.dart'; +import 'package:prosappco/blocs/professional_bloc/professional_bloc.dart'; +import 'package:prosappco/blocs/professional_profile_bloc/professional_profile_bloc.dart'; +import 'package:prosappco/blocs/profile_bloc/profile_bloc.dart'; import 'package:prosappco/dependency/app_di.dart'; -import 'app.dart'; +import 'package:prosappco/local_notifications/local_notifications.dart'; import 'simple_bloc_observer.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - await Firebase.initializeApp(); + FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler); + + // await Firebase.initializeApp(); + await NotificationBloc.initializeFCM(); + await LocalNotifications.initializeLocalNotifications(); + Bloc.observer = SimpleBlocObserver(); SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); AppDI().register(); - runApp(const MainApp()); + + runApp( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => Injector.appInstance.get(), + ), + BlocProvider( + create: (context) => Injector.appInstance.get(), + ), + BlocProvider( + create: (context) => Injector.appInstance.get(), + ), + BlocProvider( + create: (context) => Injector.appInstance.get(), + ), + BlocProvider( + create: (context) => + Injector.appInstance.get(), + ), + BlocProvider( + create: (context) => Injector.appInstance.get(), + ) + ], + child: BlocBuilder( + builder: (context, state) { + return const SafeArea(child: MyAppView()); + }, + ), + ), + ); + + // runApp(const MainApp()); } diff --git a/lib/screens/chat/chat_screen.dart b/lib/screens/chat/chat_screen.dart index eab5431..842c75e 100644 --- a/lib/screens/chat/chat_screen.dart +++ b/lib/screens/chat/chat_screen.dart @@ -8,6 +8,7 @@ import 'package:injector/injector.dart'; import 'package:intl/intl.dart'; import 'package:prosappco/blocs/chat_bloc/chat_bloc.dart'; import 'package:prosappco/components/general_reputation.dart'; +import 'package:prosappco/local_notifications/local_notifications.dart'; import 'package:service_repository/service_repository.dart'; import 'package:user_repository/user_repository.dart'; @@ -26,6 +27,7 @@ class _ChatScreenState extends State { final FocusNode _messageFocusNode = FocusNode(); List? _userInfo; + DateTime? _lastNotificationTime; @override void initState() { @@ -177,126 +179,8 @@ class _ChatScreenState extends State { ), ); } else { - // Indicador de carga opcional si se desea mostrar mientras se carga la información por primera vez. return const Center(child: CircularProgressIndicator()); } - - // if (snapshot.connectionState == ConnectionState.waiting) { - // return const Center(child: CircularProgressIndicator()); - // } else { - // if (snapshot.hasError) { - // return Center( - // child: Text('Error inesperado: ${snapshot.error}'), - // ); - // } else { - // final userInfo = snapshot.data![0] as MyUser; - - // return Container( - // padding: const EdgeInsets.symmetric(vertical: 8), - // color: const Color(0xFFD6F4FF), - // alignment: Alignment.topCenter, - // child: ListTile( - // leading: Container( - // width: 60, - // height: 60, - // decoration: BoxDecoration( - // color: Colors.grey.shade300, - // shape: BoxShape.circle, - // image: userInfo.picture == null - // ? null - // : DecorationImage( - // image: NetworkImage( - // userInfo.picture ?? ''), - // fit: BoxFit.contain, - // ), - // ), - // child: userInfo.picture == null - // ? Icon( - // CupertinoIcons.person, - // color: Colors.grey.shade400, - // size: 40, - // ) - // : null, - // ), - // title: Text( - // userInfo.name ?? '', - // overflow: TextOverflow.ellipsis, - // style: const TextStyle( - // fontSize: 15, - // fontWeight: FontWeight.w600, - // ), - // ), - // subtitle: widget.service.userId == - // FirebaseAuth.instance.currentUser!.uid - // ? GeneralReputation( - // userId: widget.service.professionalId, - // builder: (context, reputation) { - // final average = reputation.averagePro; - // final total = reputation.totalPro; - - // return Row( - // children: [ - // RatingBar.builder( - // initialRating: - // calculoRating(average), - // minRating: 1, - // direction: Axis.horizontal, - // allowHalfRating: true, - // itemCount: 5, - // itemSize: 25, - // maxRating: 5, - // itemBuilder: (context, _) => - // const Icon( - // Icons.star, - // color: Color(0xFF2BA4EC), - // ), - // onRatingUpdate: (rating) {}, - // ignoreGestures: true, - // ), - // const SizedBox(width: 5), - // Text( - // '${average.toStringAsFixed(1)} (${total.toString()})', - // ), - // ], - // ); - // }) - // : GeneralReputation( - // userId: widget.service.userId, - // builder: (context, reputation) { - // final average = reputation.average; - // final total = reputation.total; - - // return Row( - // children: [ - // RatingBar.builder( - // initialRating: - // calculoRating(average), - // minRating: 1, - // direction: Axis.horizontal, - // allowHalfRating: true, - // itemCount: 5, - // itemSize: 25, - // maxRating: 5, - // itemBuilder: (context, _) => - // const Icon( - // Icons.star, - // color: Color(0xFF2BA4EC), - // ), - // onRatingUpdate: (rating) {}, - // ignoreGestures: true, - // ), - // const SizedBox(width: 5), - // Text( - // '${average.toStringAsFixed(1)} (${total.toString()})', - // ), - // ], - // ); - // }, - // ), - // ), - // ); - // } - // } }, ), Expanded( @@ -353,6 +237,26 @@ class _ChatScreenState extends State { serviceId: widget.service.id!, message: message, )); + + if (_userInfo != null) { + final userInfo = _userInfo![0] as MyUser; + + if (userInfo.token == null) return; + + if (_lastNotificationTime == null || + DateTime.now().difference( + _lastNotificationTime!) > + const Duration(minutes: 5)) { + LocalNotifications.sendPushNotification( + userInfo.token!, + 'Nuevo mensaje', + 'Tienes un nuevo mensaje de ${userInfo.name}', + ); + + _lastNotificationTime = DateTime.now(); + } + } + _messageController.clear(); _messageFocusNode.requestFocus(); }, @@ -375,6 +279,26 @@ class _ChatScreenState extends State { serviceId: widget.service.id!, message: message, )); + + if (_userInfo != null) { + final userInfo = _userInfo![0] as MyUser; + + if (userInfo.token == null) return; + + if (_lastNotificationTime == null || + DateTime.now().difference( + _lastNotificationTime!) > + const Duration(minutes: 5)) { + LocalNotifications.sendPushNotification( + userInfo.token!, + 'Nuevo mensaje', + 'Tienes un nuevo mensaje de ${userInfo.name}', + ); + + _lastNotificationTime = DateTime.now(); + } + } + _messageController.clear(); _messageFocusNode.requestFocus(); }, diff --git a/lib/screens/user/user_calendar_screen.dart b/lib/screens/user/user_calendar_screen.dart index d890538..018aa4f 100644 --- a/lib/screens/user/user_calendar_screen.dart +++ b/lib/screens/user/user_calendar_screen.dart @@ -180,7 +180,7 @@ class UserCalendarScreenState extends State { schedule.range2Hour2!, ); - return rangesItemList(ranges, _services); + return rangesItemList(ranges, _services, today); } List ranges1 = TimeOfDayUtils.genRanges( @@ -193,26 +193,29 @@ class UserCalendarScreenState extends State { ); return [ - ...rangesItemList(ranges1, _services), - ...rangesItemList(ranges2, _services), + ...rangesItemList(ranges1, _services, today), + ...rangesItemList(ranges2, _services, today), ]; } - bool _isHora1Ocupada(TimeOfDay hora1, List? events) { + bool _isHora1Ocupada( + TimeOfDay hora1, List? events, DateTime selectedDay) { if (events != null) { for (ServiceEntity event in events) { - if (hora1 == event.range1Hour1) { - return true; + if (selectedDay.toString() == event.day) { + if (hora1 == event.range1Hour1) { + return true; + } } } } return false; } - List rangesItemList( - List ranges, List? events) { + List rangesItemList(List ranges, + List? events, DateTime selectedDay) { return ranges.map((time) { - if (_isHora1Ocupada(time, events)) { + if (_isHora1Ocupada(time, events, selectedDay)) { return Card( elevation: 4, margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), diff --git a/lib/screens/user/user_map_screen.dart b/lib/screens/user/user_map_screen.dart index 580e38b..b960444 100644 --- a/lib/screens/user/user_map_screen.dart +++ b/lib/screens/user/user_map_screen.dart @@ -13,9 +13,11 @@ import 'package:injector/injector.dart'; import 'package:intl/intl.dart'; import 'package:professional_repository/professional_repository.dart'; import 'package:prosappco/blocs/my_user_bloc/my_user_bloc.dart'; +import 'package:prosappco/blocs/notification_bloc/notification_bloc.dart'; import 'package:prosappco/blocs/professional_list_bloc/professional_list_bloc.dart'; import 'package:prosappco/blocs/service_bloc/service_bloc.dart'; import 'package:prosappco/constansts.dart'; +import 'package:prosappco/local_notifications/local_notifications.dart'; import 'package:prosappco/screens/lists/professional_list_screen.dart'; import 'package:prosappco/screens/user/user_service_screen.dart'; import 'package:prosappco/utils/time_of_day_extension.dart'; @@ -86,6 +88,10 @@ class _UserMapScreenState extends State { } } + // void requestNotificationPermission() async { + // context.read().requestPermission(); + // } + void _loadSettings() { settingRepository.getSettings().then( (value) => setState(() { @@ -172,10 +178,13 @@ class _UserMapScreenState extends State { elevation: 3, minimumSize: const Size(50, 50), ), - child: const Icon( + child: Icon( Icons.menu, color: Colors.black, - size: 35, + size: context.select( + (NotificationBloc bloc) => + bloc.state.status.index > 0 ? 35 : 35, + ), ), onPressed: () { Scaffold.of(context).openDrawer(); @@ -318,6 +327,9 @@ class _UserMapScreenState extends State { decoration: const InputDecoration( prefixIcon: Icon(Icons.assignment_ind_rounded), suffixIcon: Icon(Icons.arrow_drop_down), + // hintText: context.select( + // (NotificationBloc bloc) => '${bloc.state.status}', + // ), hintText: 'Selecciona un profesional', ), ), @@ -386,7 +398,7 @@ class _UserMapScreenState extends State { onPressed: isLoading ? null : () { - // tODO: Crear servicio + if (profesionalSeleccionado == null) return; if (serviceLocationPreference == ServiceLocationPreferences.office) { if (settings?.tarifas == true) { @@ -487,7 +499,14 @@ class _UserMapScreenState extends State { // TODO: error inesperado } - // clear inputs + if (profesionalSeleccionado!.myUser.token != null) { + LocalNotifications.sendPushNotification( + profesionalSeleccionado!.myUser.token!, + 'Nuevo servicio', + 'Tienes una nueva solicitud de servicio pendiente', + ); + } + fechaSeleccionada = null; horaSeleccionada = null; profesionalSeleccionado = null; @@ -538,7 +557,6 @@ class _UserMapScreenState extends State { borderRadius: BorderRadius.circular(10), ), ), - //x child: const Icon( CupertinoIcons.xmark, color: Colors.white, diff --git a/packages/user_repository/lib/src/entities/my_user_entity.dart b/packages/user_repository/lib/src/entities/my_user_entity.dart index 2ad4504..05a1d97 100644 --- a/packages/user_repository/lib/src/entities/my_user_entity.dart +++ b/packages/user_repository/lib/src/entities/my_user_entity.dart @@ -12,6 +12,7 @@ class MyUserEntity extends Equatable { final String? birthday; final String? gender; final ProState proState; + final String? token; const MyUserEntity({ required this.id, @@ -24,6 +25,7 @@ class MyUserEntity extends Equatable { required this.birthday, required this.gender, required this.proState, + required this.token, }); Map toDocument() { @@ -38,6 +40,7 @@ class MyUserEntity extends Equatable { 'birthday': birthday, 'gender': gender, 'professional_state': enumToInt(proState), + 'token': token }; } @@ -53,12 +56,24 @@ class MyUserEntity extends Equatable { birthday: doc['birthday'] as String?, gender: doc['gender'] as String?, proState: intToEnum(doc['professional_state'] as int), + token: doc['token'] as String?, ); } @override - List get props => - [id, email, phone, name, nickname, city, picture, birthday, gender]; + List get props => [ + id, + email, + phone, + name, + nickname, + city, + picture, + birthday, + gender, + proState, + token + ]; @override String toString() { @@ -72,7 +87,8 @@ class MyUserEntity extends Equatable { picture: $picture birthday: $birthday gender: $gender, - proState: ${proState.name} + proState: ${proState.name}, + token: $token }'''; } } diff --git a/packages/user_repository/lib/src/models/my_user.dart b/packages/user_repository/lib/src/models/my_user.dart index d85dce9..e1aeba8 100644 --- a/packages/user_repository/lib/src/models/my_user.dart +++ b/packages/user_repository/lib/src/models/my_user.dart @@ -12,6 +12,7 @@ class MyUser extends Equatable { final String? birthday; final String? gender; final ProState proState; + final String? token; const MyUser({ required this.id, @@ -24,6 +25,7 @@ class MyUser extends Equatable { this.birthday, this.gender, required this.proState, + this.token, }); get drawerLabel => email != null && email!.isNotEmpty @@ -44,6 +46,7 @@ class MyUser extends Equatable { birthday: '', gender: '', proState: ProState.inactive, + token: '', ); /// Modify MyUser parameters @@ -58,6 +61,7 @@ class MyUser extends Equatable { String? birthday, String? gender, ProState? proState, + String? token, }) { return MyUser( id: id ?? this.id, @@ -70,6 +74,7 @@ class MyUser extends Equatable { birthday: birthday ?? this.birthday, gender: gender ?? this.gender, proState: proState ?? this.proState, + token: token ?? this.token, ); } @@ -91,6 +96,7 @@ class MyUser extends Equatable { birthday: birthday, gender: gender, proState: proState, + token: token, ); } @@ -106,6 +112,7 @@ class MyUser extends Equatable { birthday: entity.birthday, gender: entity.gender, proState: entity.proState, + token: entity.token, ); } @@ -121,5 +128,6 @@ class MyUser extends Equatable { birthday, gender, proState, + token, ]; }