From b3bd1b4adf9ff77982563a7e280579b7b4ad1bf5 Mon Sep 17 00:00:00 2001 From: NewtonMutugi Date: Fri, 21 Jun 2024 08:56:37 +0300 Subject: [PATCH 01/38] Added Firebase dependencies and configuration files --- android/app/build.gradle | 3 ++ android/app/google-services.json | 68 +++++++++++++++++++++++++++++ android/build.gradle | 3 ++ firebase.json | 1 + lib/firebase_options.dart | 75 ++++++++++++++++++++++++++++++++ lib/main.dart | 8 +++- pubspec.yaml | 1 + 7 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 android/app/google-services.json create mode 100644 firebase.json create mode 100644 lib/firebase_options.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index b2a95895..f65daee6 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,5 +1,8 @@ plugins { id "com.android.application" + // START: FlutterFire Configuration + id 'com.google.gms.google-services' + // END: FlutterFire Configuration id "kotlin-android" id "dev.flutter.flutter-gradle-plugin" } diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 00000000..78f85918 --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,68 @@ +{ + "project_info": { + "project_number": "266828776832", + "project_id": "nishauri-164c4", + "storage_bucket": "nishauri-164c4.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:266828776832:android:cc3f88904ad3ee168e6d65", + "android_client_info": { + "package_name": "com.mhealth.nishauri" + } + }, + "oauth_client": [ + { + "client_id": "266828776832-hl0fh10inbjk3r9mqpll34a8rkhvltue.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyBHXy40KGPvk6SalJI_qnPVTGXTtIdualU" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "266828776832-hl0fh10inbjk3r9mqpll34a8rkhvltue.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:266828776832:android:4f451e4500d5e9098e6d65", + "android_client_info": { + "package_name": "org.kenyahmis.nishauri.nishauri" + } + }, + "oauth_client": [ + { + "client_id": "266828776832-hl0fh10inbjk3r9mqpll34a8rkhvltue.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyBHXy40KGPvk6SalJI_qnPVTGXTtIdualU" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "266828776832-hl0fh10inbjk3r9mqpll34a8rkhvltue.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index dcd263b4..133a7bac 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -6,6 +6,9 @@ buildscript { } dependencies { + // START: FlutterFire Configuration + classpath 'com.google.gms:google-services:4.3.15' + // END: FlutterFire Configuration classpath 'com.android.tools.build:gradle:7.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } diff --git a/firebase.json b/firebase.json new file mode 100644 index 00000000..5107f1c0 --- /dev/null +++ b/firebase.json @@ -0,0 +1 @@ +{"flutter":{"platforms":{"android":{"default":{"projectId":"nishauri-164c4","appId":"1:266828776832:android:4f451e4500d5e9098e6d65","fileOutput":"android/app/google-services.json"}},"dart":{"lib/firebase_options.dart":{"projectId":"nishauri-164c4","configurations":{"android":"1:266828776832:android:4f451e4500d5e9098e6d65","ios":"1:266828776832:ios:0c9838f44fc4c08c8e6d65","web":"1:266828776832:web:ec77c6c1a5af8c868e6d65"}}}}}} \ No newline at end of file diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart new file mode 100644 index 00000000..07b2dc95 --- /dev/null +++ b/lib/firebase_options.dart @@ -0,0 +1,75 @@ +// File generated by FlutterFire CLI. +// ignore_for_file: type=lint +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; +import 'package:flutter/foundation.dart' + show defaultTargetPlatform, kIsWeb, TargetPlatform; + +/// Default [FirebaseOptions] for use with your Firebase apps. +/// +/// Example: +/// ```dart +/// import 'firebase_options.dart'; +/// // ... +/// await Firebase.initializeApp( +/// options: DefaultFirebaseOptions.currentPlatform, +/// ); +/// ``` +class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + return web; + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + return ios; + case TargetPlatform.macOS: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for macos - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.windows: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for windows - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.linux: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for linux - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + default: + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + } + } + + static const FirebaseOptions web = FirebaseOptions( + apiKey: 'AIzaSyB80-6Ywc27sqKziks2vn08TeLzF6BO2E0', + appId: '1:266828776832:web:ec77c6c1a5af8c868e6d65', + messagingSenderId: '266828776832', + projectId: 'nishauri-164c4', + authDomain: 'nishauri-164c4.firebaseapp.com', + storageBucket: 'nishauri-164c4.appspot.com', + measurementId: 'G-RZ4897SPCB', + ); + + static const FirebaseOptions android = FirebaseOptions( + apiKey: 'AIzaSyBHXy40KGPvk6SalJI_qnPVTGXTtIdualU', + appId: '1:266828776832:android:4f451e4500d5e9098e6d65', + messagingSenderId: '266828776832', + projectId: 'nishauri-164c4', + storageBucket: 'nishauri-164c4.appspot.com', + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: 'AIzaSyD_viqw29i469Bl_iWuGjC18TRKCMgh1AI', + appId: '1:266828776832:ios:0c9838f44fc4c08c8e6d65', + messagingSenderId: '266828776832', + projectId: 'nishauri-164c4', + storageBucket: 'nishauri-164c4.appspot.com', + iosBundleId: 'org.kenyahmis.nishauri.nishauri', + ); +} diff --git a/lib/main.dart b/lib/main.dart index 072ee2d8..122cf152 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,14 @@ +import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:nishauri/firebase_options.dart'; import 'package:nishauri/src/app/app.dart'; -void main() { +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); runApp( const ProviderScope(child: NishauriApp()), ); diff --git a/pubspec.yaml b/pubspec.yaml index 9f72690c..4023be45 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -71,6 +71,7 @@ dependencies: flutter_image_compress: ^2.2.0 image_cropper: ^5.0.1 flutter_launcher_icons: ^0.13.1 + firebase_core: ^3.1.0 dev_dependencies: flutter_test: From aa35cc338c8434a5cdac2304f8a2189becf17fe4 Mon Sep 17 00:00:00 2001 From: NewtonMutugi Date: Mon, 24 Jun 2024 01:43:53 +0300 Subject: [PATCH 02/38] Added basic push notification functionality --- android/app/src/main/AndroidManifest.xml | 5 ++ lib/main.dart | 8 +++ .../interfaces/notification_service.dart | 62 +++++++++++++++++++ pubspec.yaml | 2 + 4 files changed, 77 insertions(+) create mode 100644 lib/src/shared/interfaces/notification_service.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index c42b3b50..61b84b85 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,10 @@ + + + + + main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); + + // Initialize firebase messaging + await PushNotifications.initializeFirebaseMessaging(); + + // Initialize local notifications + await PushNotifications.initializeLocalNotifications(); + runApp( const ProviderScope(child: NishauriApp()), ); diff --git a/lib/src/shared/interfaces/notification_service.dart b/lib/src/shared/interfaces/notification_service.dart new file mode 100644 index 00000000..373fedea --- /dev/null +++ b/lib/src/shared/interfaces/notification_service.dart @@ -0,0 +1,62 @@ +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; + +abstract class PushNotifications { + static final _firebaseMessaging = FirebaseMessaging.instance; + static final FlutterLocalNotificationsPlugin + _flutterLocalNotificationsplugin = FlutterLocalNotificationsPlugin(); + + // Request for permissions + static Future initializeFirebaseMessaging() async { + NotificationSettings settings = await _firebaseMessaging.requestPermission( + alert: true, + badge: true, + provisional: false, + sound: true, + ); + debugPrint('User granted permission: ${settings.authorizationStatus}'); + + // Get device FCM token + final token = await _firebaseMessaging.getToken(); + debugPrint('FCM Token: $token'); + } + + // Initialize local notifications + static Future initializeLocalNotifications() async { + const AndroidInitializationSettings initializationSettingsAndroid = + AndroidInitializationSettings('@mipmap/ic_launcher'); + const DarwinInitializationSettings initializationSettingsDarwin = + DarwinInitializationSettings(); + const InitializationSettings initializationSettings = + InitializationSettings( + android: initializationSettingsAndroid, + iOS: initializationSettingsDarwin, + ); + await _flutterLocalNotificationsplugin.initialize(initializationSettings); + + // Request notifications for Android 13 and above + _flutterLocalNotificationsplugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.createNotificationChannel( + const AndroidNotificationChannel( + 'high_importance_channel', // id + 'High Importance Notifications', // title + importance: Importance.high, + playSound: true, + ), + ); + _flutterLocalNotificationsplugin.initialize( + initializationSettings, + onDidReceiveNotificationResponse: onTapNotification, + onDidReceiveBackgroundNotificationResponse: onTapNotification, + ); + } + + // Handle notification tap + static Future onTapNotification(NotificationResponse? response) async { + // TODO: Handle notification tap + throw UnimplementedError(); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 4023be45..e401f018 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -72,6 +72,8 @@ dependencies: image_cropper: ^5.0.1 flutter_launcher_icons: ^0.13.1 firebase_core: ^3.1.0 + flutter_local_notifications: ^17.1.2 + firebase_messaging: ^15.0.1 dev_dependencies: flutter_test: From 2e75afd9606a4b9237768f67c1a45b6e1ffb4006 Mon Sep 17 00:00:00 2001 From: NewtonMutugi Date: Mon, 24 Jun 2024 18:07:24 +0300 Subject: [PATCH 03/38] Update notification service initialization in main.dart and app.dart --- android/app/src/main/AndroidManifest.xml | 6 + android/app/src/main/res/values/styles.xml | 1 + lib/main.dart | 4 +- lib/src/app/app.dart | 31 +++--- .../interfaces/notification_service.dart | 104 +++++++++++++----- 5 files changed, 103 insertions(+), 43 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 61b84b85..f04722e0 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -25,6 +25,12 @@ android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/NormalTheme" /> + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index cb1ef880..37c69e0b 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -15,4 +15,5 @@ + high_importance_channel diff --git a/lib/main.dart b/lib/main.dart index 89c5c220..0a0f2c40 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,10 +12,10 @@ Future main() async { ); // Initialize firebase messaging - await PushNotifications.initializeFirebaseMessaging(); + await NotificationService.initializeFirebaseMessaging(); // Initialize local notifications - await PushNotifications.initializeLocalNotifications(); + await NotificationService.initializeLocalNotifications(); runApp( const ProviderScope(child: NishauriApp()), diff --git a/lib/src/app/app.dart b/lib/src/app/app.dart index 83ced09c..9bb02e74 100644 --- a/lib/src/app/app.dart +++ b/lib/src/app/app.dart @@ -1,26 +1,29 @@ +import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:nishauri/src/app/app_theme.dart'; import 'package:nishauri/src/app/navigation/app_router.dart'; +import 'package:nishauri/src/shared/interfaces/notification_service.dart'; class NishauriApp extends StatelessWidget { const NishauriApp({super.key}); @override Widget build(BuildContext context) { - return Consumer( - builder: (context, ref, child) { - final router = ref.watch(routesProvider); - return MaterialApp.router( - title: "Nishauri", - // routeInformationParser: router.routeInformationParser, - // routerDelegate: router.routerDelegate, - // routeInformationProvider: router.routeInformationProvider, - routerConfig: router, - theme: ref.watch(mainTheme), - debugShowCheckedModeBanner: false, - ); - } - ); + NotificationService.handleOpenNotification(context); + return Consumer(builder: (context, ref, child) { + final router = ref.watch(routesProvider); + + return MaterialApp.router( + title: "Nishauri", + // routeInformationParser: router.routeInformationParser, + // routerDelegate: router.routerDelegate, + // routeInformationProvider: router.routeInformationProvider, + routerConfig: router, + theme: ref.watch(mainTheme), + debugShowCheckedModeBanner: false, + ); + }); } } diff --git a/lib/src/shared/interfaces/notification_service.dart b/lib/src/shared/interfaces/notification_service.dart index 373fedea..f0c75276 100644 --- a/lib/src/shared/interfaces/notification_service.dart +++ b/lib/src/shared/interfaces/notification_service.dart @@ -1,15 +1,23 @@ +import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; -abstract class PushNotifications { - static final _firebaseMessaging = FirebaseMessaging.instance; - static final FlutterLocalNotificationsPlugin - _flutterLocalNotificationsplugin = FlutterLocalNotificationsPlugin(); +abstract class NotificationService { + static final firebaseMessaging = FirebaseMessaging.instance; + static final FlutterLocalNotificationsPlugin flutterLocalNotificationsplugin = + FlutterLocalNotificationsPlugin(); + static const AndroidNotificationChannel channel = AndroidNotificationChannel( + 'high_importance_channel', // id + 'High Importance Notifications', // title + description: 'This channel is used for important notifications.', + importance: Importance.high, + playSound: true, + ); // Request for permissions static Future initializeFirebaseMessaging() async { - NotificationSettings settings = await _firebaseMessaging.requestPermission( + NotificationSettings settings = await firebaseMessaging.requestPermission( alert: true, badge: true, provisional: false, @@ -18,8 +26,64 @@ abstract class PushNotifications { debugPrint('User granted permission: ${settings.authorizationStatus}'); // Get device FCM token - final token = await _firebaseMessaging.getToken(); + final token = await firebaseMessaging.getToken(); debugPrint('FCM Token: $token'); + + // Set foreground notification presentation options + await FirebaseMessaging.instance + .setForegroundNotificationPresentationOptions( + alert: true, + badge: true, + sound: true, + ); + + FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler); + + FirebaseMessaging.onMessage.listen((message) { + RemoteNotification? notification = message.notification; + AndroidNotification? android = message.notification?.android; + if (notification != null && android != null) { + NotificationService.flutterLocalNotificationsplugin.show( + notification.hashCode, + notification.title, + notification.body, + NotificationDetails( + android: AndroidNotificationDetails( + NotificationService.channel.id, + NotificationService.channel.name, + channelDescription: NotificationService.channel.description, + importance: Importance.max, + priority: Priority.high, + showWhen: false, + playSound: true, + icon: '@mipmap/launcher_icon', + ), + ), + ); + } + }); + } + + static void handleOpenNotification(context) { + FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { + RemoteNotification? notification = message.notification; + AndroidNotification? android = message.notification?.android; + + if (notification != null && android != null) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(notification.title!), + content: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [Text(notification.body!)], + ), + ), + ), + ); + } + }); } // Initialize local notifications @@ -33,30 +97,16 @@ abstract class PushNotifications { android: initializationSettingsAndroid, iOS: initializationSettingsDarwin, ); - await _flutterLocalNotificationsplugin.initialize(initializationSettings); - - // Request notifications for Android 13 and above - _flutterLocalNotificationsplugin + await flutterLocalNotificationsplugin.initialize(initializationSettings); + await flutterLocalNotificationsplugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() - ?.createNotificationChannel( - const AndroidNotificationChannel( - 'high_importance_channel', // id - 'High Importance Notifications', // title - importance: Importance.high, - playSound: true, - ), - ); - _flutterLocalNotificationsplugin.initialize( - initializationSettings, - onDidReceiveNotificationResponse: onTapNotification, - onDidReceiveBackgroundNotificationResponse: onTapNotification, - ); + ?.createNotificationChannel(channel); } - // Handle notification tap - static Future onTapNotification(NotificationResponse? response) async { - // TODO: Handle notification tap - throw UnimplementedError(); + static Future firebaseMessagingBackgroundHandler( + RemoteMessage message) async { + await Firebase.initializeApp(); + debugPrint('Handling a background message: ${message.messageId}'); } } From 7bdc2b60229d7fef62a42d5ff1e6762886275971 Mon Sep 17 00:00:00 2001 From: NewtonMutugi Date: Fri, 28 Jun 2024 17:53:11 +0300 Subject: [PATCH 04/38] Added appointment notification subscription based on appointment date --- .../presentation/widgets/Appointments.dart | 24 +++++++----- .../interfaces/notification_service.dart | 39 +++++++++++++++++++ 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/lib/src/features/common/presentation/widgets/Appointments.dart b/lib/src/features/common/presentation/widgets/Appointments.dart index 51006a7d..c0ff9ebf 100644 --- a/lib/src/features/common/presentation/widgets/Appointments.dart +++ b/lib/src/features/common/presentation/widgets/Appointments.dart @@ -3,9 +3,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:nishauri/src/features/appointments/data/models/appointment.dart'; import 'package:nishauri/src/features/appointments/data/providers/appointment_provider.dart'; import 'package:nishauri/src/features/appointments/presentation/pages/AppointmentRescheduleScreen.dart'; import 'package:nishauri/src/features/common/presentation/widgets/AppointmentCard.dart'; +import 'package:nishauri/src/shared/interfaces/notification_service.dart'; import 'package:nishauri/src/utils/helpers.dart'; import 'package:nishauri/src/utils/routes.dart'; import '../../../../utils/constants.dart'; @@ -31,6 +33,9 @@ class Appointments extends HookConsumerWidget { data: (data) { final activeProgramAppointments = data.where((element) => element.program_status.toString() == "1"); + // Subscribes to the appointments topic + NotificationService.subscribeToTopic( + activeProgramAppointments.toList(), SubscriptionType.appointments); return Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, @@ -103,16 +108,17 @@ class Appointments extends HookConsumerWidget { child: SizedBox( width: size.width * 0.99, child: AppointmentCard( - rescheduleButtonText: pendingOrders.isNotEmpty ? "Has active order" : (artAppointment - .reschedule_status - .toString() == - "0" - ? "Pending approval" - : artAppointment.reschedule_status + rescheduleButtonText: pendingOrders.isNotEmpty + ? "Has active order" + : (artAppointment.reschedule_status .toString() == - "1" - ? "Approved" - : null), + "0" + ? "Pending approval" + : artAppointment.reschedule_status + .toString() == + "1" + ? "Approved" + : null), appointmentType: artAppointment.appointment_type ?? "Unknown type", diff --git a/lib/src/shared/interfaces/notification_service.dart b/lib/src/shared/interfaces/notification_service.dart index f0c75276..320a39cc 100644 --- a/lib/src/shared/interfaces/notification_service.dart +++ b/lib/src/shared/interfaces/notification_service.dart @@ -2,6 +2,12 @@ import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:nishauri/src/features/appointments/data/models/appointment.dart'; + +enum SubscriptionType { + appointments, + drugDeliveryDispatched, +} abstract class NotificationService { static final firebaseMessaging = FirebaseMessaging.instance; @@ -109,4 +115,37 @@ abstract class NotificationService { await Firebase.initializeApp(); debugPrint('Handling a background message: ${message.messageId}'); } + + // Method to subscribe to topics based on appointment date + static Future subscribeToTopic( + List dataList, SubscriptionType type) async { + await Firebase.initializeApp(); + for (var data in dataList) { + switch (type) { + case SubscriptionType.appointments: + final appointment = data as Appointment; + final appointmentDate = DateTime.parse(appointment.appointment_date); + final now = DateTime.now(); + final difference = appointmentDate.difference(now).inDays; + + if (difference == 7 || difference == 1) { + const topic = "AppointmentReminder"; + await firebaseMessaging.subscribeToTopic(topic); + debugPrint( + "Subscribed to topic: $topic for appointment on $appointmentDate"); + } + break; + // case SubscriptionType.drugDeliveryDispatched: + // final delivery = data + // as DrugDelivery; // Assuming DrugDelivery is a class you have for deliveries + // if (delivery.status == "dispatched") { + // const topic = "DrugDeliveryDispatched"; + // await firebaseMessaging.subscribeToTopic(topic); + // debugPrint("Subscribed to topic: $topic for dispatched delivery"); + case SubscriptionType.drugDeliveryDispatched: + // TODO: Handle this case. + } + break; + } + } } From 4b8b34682381ddc920aeca512890ac9a4243e932 Mon Sep 17 00:00:00 2001 From: NewtonMutugi Date: Fri, 28 Jun 2024 18:01:52 +0300 Subject: [PATCH 05/38] Remove commented code for drug delivery dispatched subscription --- android/app/build.gradle | 5 +- .../interfaces/notification_service.dart | 7 -- pubspec.lock | 100 +++++++++++++++++- 3 files changed, 102 insertions(+), 10 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index f65daee6..2fb77184 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -36,6 +36,7 @@ android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 + coreLibraryDesugaringEnabled true } kotlinOptions { @@ -74,4 +75,6 @@ flutter { source '../..' } -dependencies {} +dependencies { + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.2' +} diff --git a/lib/src/shared/interfaces/notification_service.dart b/lib/src/shared/interfaces/notification_service.dart index 320a39cc..c77a2ff2 100644 --- a/lib/src/shared/interfaces/notification_service.dart +++ b/lib/src/shared/interfaces/notification_service.dart @@ -135,13 +135,6 @@ abstract class NotificationService { "Subscribed to topic: $topic for appointment on $appointmentDate"); } break; - // case SubscriptionType.drugDeliveryDispatched: - // final delivery = data - // as DrugDelivery; // Assuming DrugDelivery is a class you have for deliveries - // if (delivery.status == "dispatched") { - // const topic = "DrugDeliveryDispatched"; - // await firebaseMessaging.subscribeToTopic(topic); - // debugPrint("Subscribed to topic: $topic for dispatched delivery"); case SubscriptionType.drugDeliveryDispatched: // TODO: Handle this case. } diff --git a/pubspec.lock b/pubspec.lock index fb625aa6..0bab1e9d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "64.0.0" + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: a315d1c444402c3fa468de626d33a1c666041c87e9e195e8fb355b7084aefcc1 + url: "https://pub.dev" + source: hosted + version: "1.3.38" analyzer: dependency: transitive description: @@ -393,6 +401,54 @@ packages: url: "https://pub.dev" source: hosted version: "0.9.3+1" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "1e06b0538ab3108a61d895ee16951670b491c4a94fce8f2d30e5de7a5eca4b28" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: "1003a5a03a61fc9a22ef49f37cbcb9e46c86313a7b2e7029b9390cf8c6fc32cb" + url: "https://pub.dev" + source: hosted + version: "5.1.0" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: "6643fe3dbd021e6ccfb751f7882b39df355708afbdeb4130fc50f9305a9d1a3d" + url: "https://pub.dev" + source: hosted + version: "2.17.2" + firebase_messaging: + dependency: "direct main" + description: + name: firebase_messaging + sha256: a1eb38242e072118650139f8485a78d8f12e6d9b6ae563808ca0fa406bdebaad + url: "https://pub.dev" + source: hosted + version: "15.0.2" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + sha256: "98faf00cbe125bba136787e1678e7bf213f5e694e8f2615b94ad3d4bdcb0bdc2" + url: "https://pub.dev" + source: hosted + version: "4.5.40" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + sha256: a38e9ccdd5dc4d7dc9eef0097b6a5a3c24842772035e1be103dc1b81d8d09f7c + url: "https://pub.dev" + source: hosted + version: "3.8.10" fixnum: dependency: transitive description: @@ -510,6 +566,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: ca682770957ca40a1c2dd3ada500254337248b99d8027a8ad7dce9a4064bb18f + url: "https://pub.dev" + source: hosted + version: "17.2.0" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: "33f741ef47b5f63cc7f78fe75eeeac7e19f171ff3c3df054d84c1e38bedb6a03" + url: "https://pub.dev" + source: hosted + version: "4.0.0+1" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "85f8d07fe708c1bdcf45037f2c0109753b26ae077e9d9e899d55971711a4ea66" + url: "https://pub.dev" + source: hosted + version: "7.2.0" flutter_localizations: dependency: transitive description: flutter @@ -1546,6 +1626,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.1" + timezone: + dependency: transitive + description: + name: timezone + sha256: a6ccda4a69a442098b602c44e61a1e2b4bf6f5516e875bbf0f427d5df14745d5 + url: "https://pub.dev" + source: hosted + version: "0.9.3" timing: dependency: transitive description: @@ -1746,6 +1834,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" web_socket_channel: dependency: transitive description: @@ -1827,5 +1923,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0-0 <4.0.0" - flutter: ">=3.13.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.16.0" From ef288640a77c66626888c5d9d0db58c65b2c6c8c Mon Sep 17 00:00:00 2001 From: NewtonMutugi Date: Wed, 3 Jul 2024 09:52:54 +0300 Subject: [PATCH 06/38] Subscribe to drug delivery dispatched topic for dispatched orders --- .../presentation/pages/dispatched_drugs.dart | 43 +++++++++++++------ .../interfaces/notification_service.dart | 9 +++- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/lib/src/features/dawa_drop/presentation/pages/dispatched_drugs.dart b/lib/src/features/dawa_drop/presentation/pages/dispatched_drugs.dart index eff76a52..dcd09037 100644 --- a/lib/src/features/dawa_drop/presentation/pages/dispatched_drugs.dart +++ b/lib/src/features/dawa_drop/presentation/pages/dispatched_drugs.dart @@ -6,6 +6,7 @@ import 'package:nishauri/src/features/dawa_drop/data/models/order_request/drug_o import 'package:nishauri/src/features/dawa_drop/data/providers/drug_order_provider.dart'; import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; import 'package:nishauri/src/shared/display/background_image_widget.dart'; +import 'package:nishauri/src/shared/interfaces/notification_service.dart'; import 'package:nishauri/src/utils/constants.dart'; import 'package:nishauri/src/utils/routes.dart'; @@ -21,8 +22,10 @@ class DispatchedDrugs extends ConsumerWidget { data: (data) { List allOrders = data; List dispatchedOrders = - allOrders.where((order) => order.status == 'Dispatched').toList(); - + allOrders.where((order) => order.status == 'Dispatched').toList(); + // Subscribe to dispatched orders + NotificationService.subscribeToTopic( + dispatchedOrders, SubscriptionType.drugDeliveryDispatched); if (dispatchedOrders.isEmpty) { return Scaffold( body: BackgroundImageWidget( @@ -54,47 +57,58 @@ class DispatchedDrugs extends ConsumerWidget { ListTile( title: Card( child: Padding( - padding: const EdgeInsets.all(Constants.SPACING), + padding: + const EdgeInsets.all(Constants.SPACING), child: Row( children: [ Expanded( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.calendar_month_outlined, - color: Constants.dawaDropColor.withOpacity(0.5), + color: Constants.dawaDropColor + .withOpacity(0.5), ), - const SizedBox(width: Constants.SPACING), + const SizedBox( + width: Constants.SPACING), Text( 'Appointment: ${DateFormat("dd MMM yyy").format(DateTime.parse(order.appointment?.appointment_date ?? ''))}', ), ], ), - const SizedBox(height: Constants.SPACING), + const SizedBox( + height: Constants.SPACING), Row( children: [ Icon( Icons.calendar_month_outlined, - color: Constants.dawaDropColor.withOpacity(0.5), + color: Constants.dawaDropColor + .withOpacity(0.5), ), - const SizedBox(width: Constants.SPACING), + const SizedBox( + width: Constants.SPACING), Text( 'Dispatched: ${DateFormat("dd MMM yyy").format(DateTime.parse(order.dispatched_date ?? ''))}', ), ], ), - const SizedBox(height: Constants.SPACING), + const SizedBox( + height: Constants.SPACING), Row( children: [ Icon( Icons.rotate_left_outlined, - color: Constants.dawaDropColor.withOpacity(0.5), + color: Constants.dawaDropColor + .withOpacity(0.5), ), - const SizedBox(width: Constants.SPACING), - Text('Order Status: ${order.status ?? ''}'), + const SizedBox( + width: Constants.SPACING), + Text( + 'Order Status: ${order.status ?? ''}'), ], ), ], @@ -102,7 +116,8 @@ class DispatchedDrugs extends ConsumerWidget { ), Container( decoration: BoxDecoration( - color: theme.primaryColor.withOpacity(0.5), + color: + theme.primaryColor.withOpacity(0.5), shape: BoxShape.rectangle, borderRadius: BorderRadius.circular(10), ), diff --git a/lib/src/shared/interfaces/notification_service.dart b/lib/src/shared/interfaces/notification_service.dart index c77a2ff2..27b0816a 100644 --- a/lib/src/shared/interfaces/notification_service.dart +++ b/lib/src/shared/interfaces/notification_service.dart @@ -3,6 +3,7 @@ import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:nishauri/src/features/appointments/data/models/appointment.dart'; +import 'package:nishauri/src/features/dawa_drop/data/models/order_request/drug_order.dart'; enum SubscriptionType { appointments, @@ -136,7 +137,13 @@ abstract class NotificationService { } break; case SubscriptionType.drugDeliveryDispatched: - // TODO: Handle this case. + final order = data as DrugOrder; + if (order.status == 'Dispatched') { + const topic = "DrugDeliveryDispatched"; + await firebaseMessaging.subscribeToTopic(topic); + debugPrint("Subscribed to topic: $topic for dispatched order"); + } + break; } break; } From 51511afa56bb982378aee063102023913702878d Mon Sep 17 00:00:00 2001 From: NewtonMutugi Date: Wed, 3 Jul 2024 19:15:39 +0300 Subject: [PATCH 07/38] Save and subscribe to dispatched orders for drug delivery from device cache --- .../presentation/pages/dispatched_drugs.dart | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/src/features/dawa_drop/presentation/pages/dispatched_drugs.dart b/lib/src/features/dawa_drop/presentation/pages/dispatched_drugs.dart index dcd09037..991d34b5 100644 --- a/lib/src/features/dawa_drop/presentation/pages/dispatched_drugs.dart +++ b/lib/src/features/dawa_drop/presentation/pages/dispatched_drugs.dart @@ -1,9 +1,12 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; import 'package:nishauri/src/features/dawa_drop/data/models/order_request/drug_order.dart'; import 'package:nishauri/src/features/dawa_drop/data/providers/drug_order_provider.dart'; +import 'package:nishauri/src/local_storage/LocalStorage.dart'; import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; import 'package:nishauri/src/shared/display/background_image_widget.dart'; import 'package:nishauri/src/shared/interfaces/notification_service.dart'; @@ -23,6 +26,8 @@ class DispatchedDrugs extends ConsumerWidget { List allOrders = data; List dispatchedOrders = allOrders.where((order) => order.status == 'Dispatched').toList(); + _saveAndSubscribeToDispatchedOrders(dispatchedOrders); + // Subscribe to dispatched orders NotificationService.subscribeToTopic( dispatchedOrders, SubscriptionType.drugDeliveryDispatched); @@ -175,4 +180,23 @@ class DispatchedDrugs extends ConsumerWidget { ), ); } + + Future _saveAndSubscribeToDispatchedOrders( + List dispatchedOrders) async { + String dispatchedOrdersJson = + jsonEncode(dispatchedOrders.map((order) => order.toJson()).toList()); + await LocalStorage.save('dispatched_orders', dispatchedOrdersJson); + + // Retrieve dispatched orders from local storage + String? cachedDispatchedOrdersJson = + await LocalStorage.get('dispatched_orders'); + + List cachedDispatchedOrders = + (jsonDecode(cachedDispatchedOrdersJson) as List) + .map((orderJson) => DrugOrder.fromJson(orderJson)) + .toList(); + + NotificationService.subscribeToTopic( + cachedDispatchedOrders, SubscriptionType.drugDeliveryDispatched); + } } From 07a41f33a0ee2589330eb9fd000a25a9e07e8672 Mon Sep 17 00:00:00 2001 From: NewtonMutugi Date: Wed, 3 Jul 2024 21:09:38 +0300 Subject: [PATCH 08/38] Save and subscribe to active program appointments for appointments from device cache --- .../appointments/data/models/appointment.dart | 2 +- .../presentation/widgets/Appointments.dart | 26 +++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/lib/src/features/appointments/data/models/appointment.dart b/lib/src/features/appointments/data/models/appointment.dart index 16c181a8..b3045bd3 100644 --- a/lib/src/features/appointments/data/models/appointment.dart +++ b/lib/src/features/appointments/data/models/appointment.dart @@ -23,4 +23,4 @@ class Appointment with _$Appointment { }) = _Appointment; factory Appointment.fromJson(Map json)=> _$AppointmentFromJson(json); -} \ No newline at end of file +} diff --git a/lib/src/features/common/presentation/widgets/Appointments.dart b/lib/src/features/common/presentation/widgets/Appointments.dart index c0ff9ebf..71298f23 100644 --- a/lib/src/features/common/presentation/widgets/Appointments.dart +++ b/lib/src/features/common/presentation/widgets/Appointments.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:carousel_slider/carousel_slider.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; @@ -7,6 +9,8 @@ import 'package:nishauri/src/features/appointments/data/models/appointment.dart' import 'package:nishauri/src/features/appointments/data/providers/appointment_provider.dart'; import 'package:nishauri/src/features/appointments/presentation/pages/AppointmentRescheduleScreen.dart'; import 'package:nishauri/src/features/common/presentation/widgets/AppointmentCard.dart'; +import 'package:nishauri/src/features/dawa_drop/data/models/order_request/drug_order.dart'; +import 'package:nishauri/src/local_storage/LocalStorage.dart'; import 'package:nishauri/src/shared/interfaces/notification_service.dart'; import 'package:nishauri/src/utils/helpers.dart'; import 'package:nishauri/src/utils/routes.dart'; @@ -34,8 +38,7 @@ class Appointments extends HookConsumerWidget { final activeProgramAppointments = data.where((element) => element.program_status.toString() == "1"); // Subscribes to the appointments topic - NotificationService.subscribeToTopic( - activeProgramAppointments.toList(), SubscriptionType.appointments); + _saveAndSubscribeToProgramAppointments(activeProgramAppointments); return Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, @@ -190,4 +193,23 @@ class Appointments extends HookConsumerWidget { ), ); } + + Future _saveAndSubscribeToProgramAppointments(var appointments) async { + String activeProgramAppointments = jsonEncode( + appointments.map((appointment) => appointment.toJson()).toList()); + await LocalStorage.save( + 'active_program_appointments', activeProgramAppointments); + + // Retrieve dispatched orders from local storage + String? cachedProgramAppointmentsJson = + await LocalStorage.get('active_program_appointments'); + + List cachedActiveProgramAppointments = + (jsonDecode(cachedProgramAppointmentsJson) as List) + .map((orderJson) => DrugOrder.fromJson(orderJson)) + .toList(); + + NotificationService.subscribeToTopic(cachedActiveProgramAppointments, + SubscriptionType.drugDeliveryDispatched); + } } From 75d9b0d812a69b93d702f7fb1972718ee5069c18 Mon Sep 17 00:00:00 2001 From: NewtonMutugi Date: Wed, 3 Jul 2024 21:11:19 +0300 Subject: [PATCH 09/38] Update Firebase configuration for new project ID --- android/app/google-services.json | 55 +++++--------------------------- firebase.json | 2 +- lib/firebase_options.dart | 37 ++++++++++----------- pubspec.yaml | 2 ++ 4 files changed, 30 insertions(+), 66 deletions(-) diff --git a/android/app/google-services.json b/android/app/google-services.json index 78f85918..a39b6693 100644 --- a/android/app/google-services.json +++ b/android/app/google-services.json @@ -1,68 +1,29 @@ { "project_info": { - "project_number": "266828776832", - "project_id": "nishauri-164c4", - "storage_bucket": "nishauri-164c4.appspot.com" + "project_number": "267862187023", + "project_id": "nishauri-new", + "storage_bucket": "nishauri-new.appspot.com" }, "client": [ { "client_info": { - "mobilesdk_app_id": "1:266828776832:android:cc3f88904ad3ee168e6d65", + "mobilesdk_app_id": "1:267862187023:android:9630eda52e0b0fa5c1977a", "android_client_info": { "package_name": "com.mhealth.nishauri" } }, - "oauth_client": [ - { - "client_id": "266828776832-hl0fh10inbjk3r9mqpll34a8rkhvltue.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyBHXy40KGPvk6SalJI_qnPVTGXTtIdualU" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [ - { - "client_id": "266828776832-hl0fh10inbjk3r9mqpll34a8rkhvltue.apps.googleusercontent.com", - "client_type": 3 - } - ] - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:266828776832:android:4f451e4500d5e9098e6d65", - "android_client_info": { - "package_name": "org.kenyahmis.nishauri.nishauri" - } - }, - "oauth_client": [ - { - "client_id": "266828776832-hl0fh10inbjk3r9mqpll34a8rkhvltue.apps.googleusercontent.com", - "client_type": 3 - } - ], + "oauth_client": [], "api_key": [ { - "current_key": "AIzaSyBHXy40KGPvk6SalJI_qnPVTGXTtIdualU" + "current_key": "AIzaSyBKDyCG0apB4uUJogcl-3vpSuIOlyrqRWE" } ], "services": { "appinvite_service": { - "other_platform_oauth_client": [ - { - "client_id": "266828776832-hl0fh10inbjk3r9mqpll34a8rkhvltue.apps.googleusercontent.com", - "client_type": 3 - } - ] + "other_platform_oauth_client": [] } } } ], "configuration_version": "1" -} \ No newline at end of file +} diff --git a/firebase.json b/firebase.json index 5107f1c0..d5fe4981 100644 --- a/firebase.json +++ b/firebase.json @@ -1 +1 @@ -{"flutter":{"platforms":{"android":{"default":{"projectId":"nishauri-164c4","appId":"1:266828776832:android:4f451e4500d5e9098e6d65","fileOutput":"android/app/google-services.json"}},"dart":{"lib/firebase_options.dart":{"projectId":"nishauri-164c4","configurations":{"android":"1:266828776832:android:4f451e4500d5e9098e6d65","ios":"1:266828776832:ios:0c9838f44fc4c08c8e6d65","web":"1:266828776832:web:ec77c6c1a5af8c868e6d65"}}}}}} \ No newline at end of file +{"flutter":{"platforms":{"android":{"default":{"projectId":"nishauri-new","appId":"1:267862187023:android:9630eda52e0b0fa5c1977a","fileOutput":"android/app/google-services.json"}},"dart":{"lib/firebase_options.dart":{"projectId":"nishauri-new","configurations":{"android":"1:267862187023:android:9630eda52e0b0fa5c1977a","ios":"1:267862187023:ios:f292a3afa35839afc1977a","web":"1:267862187023:web:6b51693fe50d4f0bc1977a"}}}}}} diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart index 07b2dc95..be58ffbb 100644 --- a/lib/firebase_options.dart +++ b/lib/firebase_options.dart @@ -47,29 +47,30 @@ class DefaultFirebaseOptions { } static const FirebaseOptions web = FirebaseOptions( - apiKey: 'AIzaSyB80-6Ywc27sqKziks2vn08TeLzF6BO2E0', - appId: '1:266828776832:web:ec77c6c1a5af8c868e6d65', - messagingSenderId: '266828776832', - projectId: 'nishauri-164c4', - authDomain: 'nishauri-164c4.firebaseapp.com', - storageBucket: 'nishauri-164c4.appspot.com', - measurementId: 'G-RZ4897SPCB', + apiKey: 'AIzaSyBLVyv1jjsYc3KVRg22mgzLitYTAccYgNE', + appId: '1:267862187023:web:6b51693fe50d4f0bc1977a', + messagingSenderId: '267862187023', + projectId: 'nishauri-new', + authDomain: 'nishauri-new.firebaseapp.com', + storageBucket: 'nishauri-new.appspot.com', + measurementId: 'G-EYY0YEJG62', ); static const FirebaseOptions android = FirebaseOptions( - apiKey: 'AIzaSyBHXy40KGPvk6SalJI_qnPVTGXTtIdualU', - appId: '1:266828776832:android:4f451e4500d5e9098e6d65', - messagingSenderId: '266828776832', - projectId: 'nishauri-164c4', - storageBucket: 'nishauri-164c4.appspot.com', + apiKey: 'AIzaSyBKDyCG0apB4uUJogcl-3vpSuIOlyrqRWE', + appId: '1:267862187023:android:9630eda52e0b0fa5c1977a', + messagingSenderId: '267862187023', + projectId: 'nishauri-new', + storageBucket: 'nishauri-new.appspot.com', ); static const FirebaseOptions ios = FirebaseOptions( - apiKey: 'AIzaSyD_viqw29i469Bl_iWuGjC18TRKCMgh1AI', - appId: '1:266828776832:ios:0c9838f44fc4c08c8e6d65', - messagingSenderId: '266828776832', - projectId: 'nishauri-164c4', - storageBucket: 'nishauri-164c4.appspot.com', + apiKey: 'AIzaSyDqMTJ-UYVEsFZBV72Woprz4G2sHCqEKsQ', + appId: '1:267862187023:ios:f292a3afa35839afc1977a', + messagingSenderId: '267862187023', + projectId: 'nishauri-new', + storageBucket: 'nishauri-new.appspot.com', iosBundleId: 'org.kenyahmis.nishauri.nishauri', ); -} + +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index e401f018..134ec710 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -74,6 +74,8 @@ dependencies: firebase_core: ^3.1.0 flutter_local_notifications: ^17.1.2 firebase_messaging: ^15.0.1 + firebase_analytics: ^11.1.0 + firebase_in_app_messaging: ^0.8.0+2 dev_dependencies: flutter_test: From a3c4b60d718d004a45c70edea40d5b4806f0087c Mon Sep 17 00:00:00 2001 From: NewtonMutugi Date: Wed, 3 Jul 2024 21:14:32 +0300 Subject: [PATCH 10/38] Update notification subscription --- .../common/presentation/widgets/Appointments.dart | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/src/features/common/presentation/widgets/Appointments.dart b/lib/src/features/common/presentation/widgets/Appointments.dart index 71298f23..ed445c93 100644 --- a/lib/src/features/common/presentation/widgets/Appointments.dart +++ b/lib/src/features/common/presentation/widgets/Appointments.dart @@ -9,7 +9,6 @@ import 'package:nishauri/src/features/appointments/data/models/appointment.dart' import 'package:nishauri/src/features/appointments/data/providers/appointment_provider.dart'; import 'package:nishauri/src/features/appointments/presentation/pages/AppointmentRescheduleScreen.dart'; import 'package:nishauri/src/features/common/presentation/widgets/AppointmentCard.dart'; -import 'package:nishauri/src/features/dawa_drop/data/models/order_request/drug_order.dart'; import 'package:nishauri/src/local_storage/LocalStorage.dart'; import 'package:nishauri/src/shared/interfaces/notification_service.dart'; import 'package:nishauri/src/utils/helpers.dart'; @@ -204,12 +203,12 @@ class Appointments extends HookConsumerWidget { String? cachedProgramAppointmentsJson = await LocalStorage.get('active_program_appointments'); - List cachedActiveProgramAppointments = + List cachedActiveProgramAppointments = (jsonDecode(cachedProgramAppointmentsJson) as List) - .map((orderJson) => DrugOrder.fromJson(orderJson)) + .map((appointmentJson) => Appointment.fromJson(appointmentJson)) .toList(); - NotificationService.subscribeToTopic(cachedActiveProgramAppointments, - SubscriptionType.drugDeliveryDispatched); + NotificationService.subscribeToTopic( + cachedActiveProgramAppointments, SubscriptionType.appointments); } } From cb39785793b3486e7b50e6cabe6926dbb0f65af8 Mon Sep 17 00:00:00 2001 From: NewtonMutugi Date: Thu, 4 Jul 2024 14:18:33 +0300 Subject: [PATCH 11/38] Update gradle.properties and MainActivity.kt, and update gradle and Firebase configurations --- .metadata | 2 +- android/app/build.gradle | 42 +------------------ android/app/google-services.json | 31 +++++++++++--- android/app/src/debug/AndroidManifest.xml | 2 +- android/app/src/main/AndroidManifest.xml | 20 +++++---- .../com/example/nishauri_new/MainActivity.kt | 5 +++ .../nishauri/nishauri/MainActivity.kt | 7 ---- android/app/src/profile/AndroidManifest.xml | 2 +- android/build.gradle | 16 ------- android/gradle.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- android/settings.gradle | 17 ++++++-- firebase.json | 2 +- lib/firebase_options.dart | 34 +++++++-------- pubspec.lock | 40 ++++++++++++++++++ 15 files changed, 120 insertions(+), 104 deletions(-) create mode 100644 android/app/src/main/kotlin/com/example/nishauri_new/MainActivity.kt delete mode 100644 android/app/src/main/kotlin/org/kenyahmis/nishauri/nishauri/MainActivity.kt diff --git a/.metadata b/.metadata index 001ed4bb..ef08f7a2 100644 --- a/.metadata +++ b/.metadata @@ -15,7 +15,7 @@ migration: - platform: root create_revision: 300451adae589accbece3490f4396f10bdf15e6e base_revision: 300451adae589accbece3490f4396f10bdf15e6e - - platform: linux + - platform: android create_revision: 300451adae589accbece3490f4396f10bdf15e6e base_revision: 300451adae589accbece3490f4396f10bdf15e6e diff --git a/android/app/build.gradle b/android/app/build.gradle index 8f063041..654ae570 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -8,19 +8,6 @@ plugins { } def localProperties = new Properties() - -//def keystorePropertiesFile = rootProject.file("key.properties") -//def keystoreProperties = new Properties() -// -//if (keystorePropertiesFile.exists()) { -// keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) -//} else { -// throw new GradleException("Cannot find 'key.properties' file: " + keystorePropertiesFile) -//} - - - - def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> @@ -39,33 +26,13 @@ if (flutterVersionName == null) { } android { - // namespace "org.kenyahmis.nishauri.nishauri" - // Modified the namespace to match the package name in the AndroidManifest.xml namespace "com.mhealth.nishauri" -// compileSdkVersion flutter.compileSdkVersion - compileSdkVersion 34 + compileSdk flutter.compileSdkVersion ndkVersion flutter.ndkVersion - -// signingConfigs { -// release { -// keyAlias keystoreProperties['keyAlias'] -// keyPassword keystoreProperties['keyPassword'] -// storeFile file("C:/Users/This/new_UIrecent/android/app/key.jks") -// storePassword keystoreProperties['storePassword'] -// } -// } - - - - - - - compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 - coreLibraryDesugaringEnabled true } kotlinOptions { @@ -78,9 +45,6 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - // applicationId "org.kenyahmis.nishauri.nishauri" - // applicationId "org.kenyahmis.nishauri.nishauri" - // Modified applicationId to mmatch the package name in the AndroidManifest.xml applicationId "com.mhealth.nishauri" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. @@ -104,6 +68,4 @@ flutter { source '../..' } -dependencies { - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.2' -} +dependencies {} diff --git a/android/app/google-services.json b/android/app/google-services.json index a39b6693..80d3a68a 100644 --- a/android/app/google-services.json +++ b/android/app/google-services.json @@ -1,13 +1,13 @@ { "project_info": { - "project_number": "267862187023", - "project_id": "nishauri-new", - "storage_bucket": "nishauri-new.appspot.com" + "project_number": "921170637462", + "project_id": "nishauri-new-2", + "storage_bucket": "nishauri-new-2.appspot.com" }, "client": [ { "client_info": { - "mobilesdk_app_id": "1:267862187023:android:9630eda52e0b0fa5c1977a", + "mobilesdk_app_id": "1:921170637462:android:5e50f392a12562c59b48b7", "android_client_info": { "package_name": "com.mhealth.nishauri" } @@ -15,7 +15,26 @@ "oauth_client": [], "api_key": [ { - "current_key": "AIzaSyBKDyCG0apB4uUJogcl-3vpSuIOlyrqRWE" + "current_key": "AIzaSyDDhK2nErRy_-SajzArNonosAo5nWaF9Us" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:921170637462:android:69b9ba4fc66399099b48b7", + "android_client_info": { + "package_name": "org.kenyahmis.nishauri.nishauri" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyDDhK2nErRy_-SajzArNonosAo5nWaF9Us" } ], "services": { @@ -26,4 +45,4 @@ } ], "configuration_version": "1" -} +} \ No newline at end of file diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index 47dc37e6..f12c2d87 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -1,4 +1,4 @@ - + - - - - - + - + + + + + + + diff --git a/android/app/src/main/kotlin/com/example/nishauri_new/MainActivity.kt b/android/app/src/main/kotlin/com/example/nishauri_new/MainActivity.kt new file mode 100644 index 00000000..6d38d513 --- /dev/null +++ b/android/app/src/main/kotlin/com/example/nishauri_new/MainActivity.kt @@ -0,0 +1,5 @@ +package com.mhealth.nishauri + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/android/app/src/main/kotlin/org/kenyahmis/nishauri/nishauri/MainActivity.kt b/android/app/src/main/kotlin/org/kenyahmis/nishauri/nishauri/MainActivity.kt deleted file mode 100644 index 82bf4318..00000000 --- a/android/app/src/main/kotlin/org/kenyahmis/nishauri/nishauri/MainActivity.kt +++ /dev/null @@ -1,7 +0,0 @@ -// package org.kenyahmis.nishauri.nishauri -package com.mhealth.nishauri - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity: FlutterActivity() { -} diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml index 99d94528..5f20e79c 100644 --- a/android/app/src/profile/AndroidManifest.xml +++ b/android/app/src/profile/AndroidManifest.xml @@ -1,4 +1,4 @@ - + register ${e.toString()}'); rethrow; diff --git a/lib/src/features/chatbot/presentations/ChatScreen.dart b/lib/src/features/chatbot/presentations/ChatScreen.dart index 28990479..7f0f2e21 100644 --- a/lib/src/features/chatbot/presentations/ChatScreen.dart +++ b/lib/src/features/chatbot/presentations/ChatScreen.dart @@ -4,11 +4,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:go_router/go_router.dart'; import 'package:nishauri/custom_icons.dart'; -import 'package:nishauri/src/features/auth/data/respositories/auth_repository.dart'; -import 'package:nishauri/src/features/auth/data/services/AuthApiService.dart'; import 'package:nishauri/src/features/auth/presentation/widget/Terms.dart'; import 'package:nishauri/src/features/chatbot/data/models/message.dart'; -import 'package:nishauri/src/features/chatbot/data/models/personal_info.dart'; import 'package:nishauri/src/features/chatbot/data/repository/ChatbotRepository.dart'; import 'package:nishauri/src/features/chatbot/data/services/ChatbotService.dart'; import 'package:nishauri/src/features/consent/data/models/consent.dart'; @@ -42,12 +39,12 @@ class _TypingAnimationState extends State curve: Curves.easeInOut, ), )..addStatusListener((status) { - if (status == AnimationStatus.completed) { - _controller.reverse(); - } else if (status == AnimationStatus.dismissed) { - _controller.forward(); - } - }); + if (status == AnimationStatus.completed) { + _controller.reverse(); + } else if (status == AnimationStatus.dismissed) { + _controller.forward(); + } + }); _controller.forward(); } @@ -100,7 +97,7 @@ class _ChatScreenState extends ConsumerState { Color iconColor = message.isSentByUser ? Colors.blue : Colors.grey; return Align( alignment: - message.isSentByUser ? Alignment.centerRight : Alignment.centerLeft, + message.isSentByUser ? Alignment.centerRight : Alignment.centerLeft, child: Row( mainAxisAlignment: message.isSentByUser ? MainAxisAlignment.end @@ -109,13 +106,13 @@ class _ChatScreenState extends ConsumerState { message.isSentByUser ? const SizedBox() : Padding( - padding: - const EdgeInsets.symmetric(horizontal: Constants.SPACING), - child: Icon( - userIcon, - color: iconColor, - ), - ), + padding: + const EdgeInsets.symmetric(horizontal: Constants.SPACING), + child: Icon( + userIcon, + color: iconColor, + ), + ), Flexible( child: Container( margin: const EdgeInsets.symmetric(vertical: 4.0), @@ -146,13 +143,13 @@ class _ChatScreenState extends ConsumerState { ), message.isSentByUser ? Padding( - padding: - const EdgeInsets.symmetric(horizontal: Constants.SPACING), - child: Icon( - userIcon, - color: iconColor, - ), - ) + padding: + const EdgeInsets.symmetric(horizontal: Constants.SPACING), + child: Icon( + userIcon, + color: iconColor, + ), + ) : const SizedBox(), ], ), @@ -160,23 +157,9 @@ class _ChatScreenState extends ConsumerState { } void _handleSubmit(String text) async { - final AuthRepository authRepository = AuthRepository(AuthApiService()); - final repository = await ref.read(consentProvider); - final consentData = await repository.getConsent(); - final gender = await authRepository.getGender(); - final age = await authRepository.getAge(); - final appointment = await authRepository.getAppointmentDate(); - final regimen = await authRepository.getRegimen(); - final vl = await authRepository.getVL(); - final vlDate = await authRepository.getVlDate(); - var userConsent = - consentData.isNotEmpty ? consentData.first.chatbot_consent : null; - final consent = userConsent == "1" ? "Yes" : "No"; if (text.isEmpty) return; _textController.clear(); - PersonalInfo personalInfo = PersonalInfo(gender: gender, age: int.parse(age), regimen: regimen, - appointment_datetime: appointment, viral_load: vl, viral_load_datetime: vlDate); - Message message = Message(question: text, isSentByUser: true,personal_info: personalInfo, consent: consent); + Message message = Message(question: text, isSentByUser: true); setState(() { _messages.add(message); _isBotTyping = true; // Bot starts typing when user sends a message @@ -197,7 +180,7 @@ class _ChatScreenState extends ConsumerState { question: 'Failed to receive response from the server', isSentByUser: false)); _isBotTyping = - false; // Bot stops typing on failure to receive response + false; // Bot stops typing on failure to receive response }); } Future.delayed(const Duration(milliseconds: 100), () { @@ -232,7 +215,7 @@ class _ChatScreenState extends ConsumerState { _messages = [ Message( question: - "Hi $currentUser 👋 \nWelcome to Nuru \nHow can I assist you today?", + "Hi $currentUser 👋 \nWelcome to Nuru \nHow can I assist you today?", isSentByUser: false, ), ]; @@ -266,18 +249,18 @@ class _ChatScreenState extends ConsumerState { : 'Are you sure you want to revoke your consent for personalized response by Nuru?'), type == ConsentType.accept ? GestureDetector( - onTap: () => showTermsDialog(context), - // Show terms dialog on tap - child: const Text( - "Terms and Conditions", - style: TextStyle( - color: Colors.blue, - // Change color to indicate it's a link - decoration: TextDecoration - .underline, // Add underline to indicate it's a link - ), - ), - ) + onTap: () => showTermsDialog(context), + // Show terms dialog on tap + child: const Text( + "Terms and Conditions", + style: TextStyle( + color: Colors.blue, + // Change color to indicate it's a link + decoration: TextDecoration + .underline, // Add underline to indicate it's a link + ), + ), + ) : const SizedBox(), ], ), @@ -322,7 +305,7 @@ class _ChatScreenState extends ConsumerState { debugPrint("Fetched consent data: $consentData"); // Update UI based on fetched consent data var remoteConsent = - consentData.isNotEmpty ? consentData.first.chatbot_consent : null; + consentData.isNotEmpty ? consentData.first.chatbot_consent : null; setState(() { _consent = remoteConsent == "1" ? true : false; }); @@ -389,19 +372,6 @@ class _ChatScreenState extends ConsumerState { Text('Chat with Nuru 🤖', style: theme.textTheme.headlineLarge), const SizedBox(width: Constants.SPACING), - _consent - ? TextButton( - onPressed: () { - _showConsentDialog( - context, ref, ConsentType.revoke); - }, - child: const Text("Revoke consent", style: TextStyle(color: Colors.red),)) - : TextButton( - onPressed: () { - _showConsentDialog( - context, ref, ConsentType.accept); - }, - child: const Text("Give consent")) ], ), ), @@ -411,17 +381,17 @@ class _ChatScreenState extends ConsumerState { children: [ _consent ? TextButton( - onPressed: () { - _showConsentDialog( - context, ref, ConsentType.revoke); - }, - child: const Text("Revoke consent", style: TextStyle(color: Colors.red))) + onPressed: () { + _showConsentDialog( + context, ref, ConsentType.revoke); + }, + child: const Text("Revoke consent", style: TextStyle(color: Colors.red),)) : TextButton( - onPressed: () { - _showConsentDialog( - context, ref, ConsentType.accept); - }, - child: const Text("Give consent")) + onPressed: () { + _showConsentDialog( + context, ref, ConsentType.accept); + }, + child: const Text("Give consent", style: TextStyle(color: Colors.green),)) ], ), const Divider(), @@ -437,9 +407,9 @@ class _ChatScreenState extends ConsumerState { ), _isBotTyping ? Padding( - padding: const EdgeInsets.all(Constants.SPACING), - child: TypingAnimation(), // Bot typing indicator - ) + padding: const EdgeInsets.all(Constants.SPACING), + child: TypingAnimation(), // Bot typing indicator + ) : _buildComposer(), ], ), @@ -484,4 +454,4 @@ class _ChatScreenState extends ConsumerState { ), ); } -} +} \ No newline at end of file From a124d0c566264c3f7acf76f91c73ca99d05f4e8a Mon Sep 17 00:00:00 2001 From: Ogollah Date: Tue, 16 Jul 2024 10:03:17 +0300 Subject: [PATCH 37/38] fix first time user --- .../features/chatbot/presentations/ChatScreen.dart | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/src/features/chatbot/presentations/ChatScreen.dart b/lib/src/features/chatbot/presentations/ChatScreen.dart index 7f0f2e21..1782725b 100644 --- a/lib/src/features/chatbot/presentations/ChatScreen.dart +++ b/lib/src/features/chatbot/presentations/ChatScreen.dart @@ -280,13 +280,13 @@ class _ChatScreenState extends ConsumerState { ), TextButton( onPressed: () { - ref - .read(settingsNotifierProvider.notifier) - .updateSettings(firstNuruAccess: false); - setState(() { - type == ConsentType.accept ? _consent = false : _consent; - }); - _updateConsent(type == ConsentType.accept ? true : false); + // ref + // .read(settingsNotifierProvider.notifier) + // .updateSettings(firstNuruAccess: false); + // setState(() { + // type == ConsentType.accept ? _consent = false : _consent; + // }); + // _updateConsent(type == ConsentType.accept ? true : false); Navigator.of(context).pop(); }, child: const Text('No'), From 2031f0cdf186be8867caed443fdf3c2f1f16825f Mon Sep 17 00:00:00 2001 From: Ogollah Date: Tue, 16 Jul 2024 13:52:59 +0300 Subject: [PATCH 38/38] Update profile builder --- lib/src/features/auth/data/services/AuthApiService.dart | 4 ++-- .../features/user/presentation/forms/PersonalInformation.dart | 4 ++++ .../user/presentation/pages/ProfileWizardFormScreen.dart | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/src/features/auth/data/services/AuthApiService.dart b/lib/src/features/auth/data/services/AuthApiService.dart index a4c71763..a8a7e9c2 100644 --- a/lib/src/features/auth/data/services/AuthApiService.dart +++ b/lib/src/features/auth/data/services/AuthApiService.dart @@ -23,7 +23,7 @@ class AuthApiService extends HTTPService { accessToken: data["data"]?["token"] ?? '', refreshToken: data["data"]?["refreshToken"] ?? '', accountVerified: data["data"]?["account_verified"] == "1"!, - profileUpdated: data["data"]?["account_verified"] == "1"!, + profileUpdated: data["data"]?["profile_complete"] == "1"!, userId: data["data"]?["user_id"]!, message: data["msg"]!, phoneNumber: data["data"]?["phone_no"]! @@ -55,7 +55,7 @@ class AuthApiService extends HTTPService { accessToken: data["data"]?["token"]?? ''!, refreshToken: data["data"]?["refreshToken"]?? ''!, accountVerified: data["data"]?["account_verified"] == "1"!, - profileUpdated: data["data"]?["account_verified"] == "1"!, + profileUpdated: data["data"]?["profile_complete"] == "1"!, userId: data["data"]?["user_id"]!, message: data["msg"]!, phoneNumber: data["data"]?["phone_no"]! diff --git a/lib/src/features/user/presentation/forms/PersonalInformation.dart b/lib/src/features/user/presentation/forms/PersonalInformation.dart index ae86beb1..2b693b94 100644 --- a/lib/src/features/user/presentation/forms/PersonalInformation.dart +++ b/lib/src/features/user/presentation/forms/PersonalInformation.dart @@ -105,6 +105,10 @@ class _PersonalInformationState extends State { FormBuilderValidators.required(), ]), items: const [ + DropdownMenuItem( + value: "Female", + child: Text("Select gender"), + ), DropdownMenuItem( value: "Male", child: Text("Male"), diff --git a/lib/src/features/user/presentation/pages/ProfileWizardFormScreen.dart b/lib/src/features/user/presentation/pages/ProfileWizardFormScreen.dart index 071710aa..95b66fb7 100644 --- a/lib/src/features/user/presentation/pages/ProfileWizardFormScreen.dart +++ b/lib/src/features/user/presentation/pages/ProfileWizardFormScreen.dart @@ -51,7 +51,7 @@ class ProfileWizardFormScreen extends HookConsumerWidget { isActive: currentStep.value == 0, ), Step( - title: const Text("Personal Information"), + title: const Text("Personal Information **"), subtitle: const Text( "Provide basic personal details for a comprehensive profile.", ),