diff --git a/app/android/app/build.gradle b/app/android/app/build.gradle index fccf08fe..a5f910d1 100644 --- a/app/android/app/build.gradle +++ b/app/android/app/build.gradle @@ -26,7 +26,7 @@ if (flutterVersionName == null) { android { namespace "com.canopas.yourspace" - compileSdkVersion 34 + compileSdkVersion 35 ndkVersion "25.1.8937393" compileOptions { diff --git a/app/lib/domain/fcm/awesome_notification_handler.dart b/app/lib/domain/fcm/awesome_notification_handler.dart new file mode 100644 index 00000000..feecb550 --- /dev/null +++ b/app/lib/domain/fcm/awesome_notification_handler.dart @@ -0,0 +1,209 @@ +import 'dart:io'; + +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:data/log/logger.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:yourspace_flutter/main.dart'; +import 'package:yourspace_flutter/ui/app_route.dart'; + +const NOTIFICATION_CHANNEL_KEY = "notification_channel_your_space_regional"; +const NOTIFICATION_CHANNEL_NAME = "YourSpace Notification"; + +const NOTIFICATION_ID = 101; + +const KEY_NOTIFICATION_TYPE = "type"; + +class NotificationChatConst { + static const NOTIFICATION_TYPE_CHAT = "chat"; + static const KEY_PROFILE_URL = "senderProfileUrl"; + static const KEY_IS_GROUP = "isGroup"; + static const KEY_GROUP_NAME = "groupName"; + static const KEY_SENDER_ID = "senderId"; + static const KEY_THREAD_ID = "threadId"; +} + +class NotificationPlaceConst { + static const NOTIFICATION_TYPE_NEW_PLACE_ADDED = "new_place_added"; + static const KEY_SPACE_ID = "spaceId"; +} + +class NotificationGeofenceConst { + static const NOTIFICATION_TYPE_GEOFENCE = "geofence"; +} + +class NotificationNetworkStatusConst { + static const NOTIFICATION_TYPE_NETWORK_STATUS = "network_status"; + static const KEY_USER_ID = "userId"; +} + +class NotificationUpdateStateConst { + static const NOTIFICATION_TYPE_UPDATE_STATE = "updateState"; + static const KEY_USER_ID = "userId"; +} + +final awesomeNotificationHandlerProvider = + StateProvider.autoDispose((ref) => AwesomeNotificationHandler()); + +class AwesomeNotificationHandler { + final AwesomeNotifications awesomeNotifications = AwesomeNotifications(); + + AwesomeNotificationHandler(); + + Future init(BuildContext context) async { + _initFcm(context); + _initLocalNotifications(context); + } + + void _initFcm(BuildContext context) { + FirebaseMessaging.instance.getInitialMessage().then((message) { + if (message != null && context.mounted) { + _onNotificationTap(context, message.data); + } + }); + + FirebaseMessaging.onMessageOpenedApp.listen((event) { + if (context.mounted) { + _onNotificationTap(context, event.data); + } + }); + + if (Platform.isAndroid) { + FirebaseMessaging.onMessage.listen((event) { + _showFirebaseNotification(event); + }); + } + } + + void _initLocalNotifications(BuildContext context) { + _configuration(); + awesomeNotifications.setListeners( + onActionReceivedMethod: onActionReceivedMethod, + ); + } + + Future _configuration() async { + print("configuration check with this"); + await awesomeNotifications.initialize( + 'resource://drawable/app_icon', + [ + NotificationChannel( + channelKey: NOTIFICATION_CHANNEL_KEY, + channelName: NOTIFICATION_CHANNEL_NAME, + channelDescription: 'Notifications for YourSpace', + defaultColor: const Color(0xFF1679AB), + ledColor: const Color(0xFF1679AB), + importance: NotificationImportance.Low, + ), + ], + debug: true, + ); + } + + void _showFirebaseNotification(RemoteMessage event) async { + final notification = event.notification; + final data = event.data; + final title = notification?.title; + final body = notification?.body; + + if (title != null && body != null) { + await awesomeNotifications.createNotification( + content: NotificationContent( + id: DateTime.now().microsecondsSinceEpoch ~/ 1000000, + channelKey: NOTIFICATION_CHANNEL_KEY, + title: title, + body: body, + payload: data.map((key, value) => MapEntry(key, value?.toString())), + notificationLayout: NotificationLayout.Default, + ), + ); + } + } + + void showLocalNotification( + int id, String title, String body, String payload) async { + await awesomeNotifications.createNotification( + content: NotificationContent( + id: id, + channelKey: NOTIFICATION_CHANNEL_KEY, + title: title, + body: body, + payload: {'type': payload}, + notificationLayout: NotificationLayout.Default, + ), + ); + } + + void _onNotificationTap(BuildContext context, Map data) { + print('Notification tapped with data: $data'); + + final type = data[KEY_NOTIFICATION_TYPE]; + + switch (type) { + case NotificationChatConst.NOTIFICATION_TYPE_CHAT: + _handleChatMessage(context, data); + break; + case NotificationPlaceConst.NOTIFICATION_TYPE_NEW_PLACE_ADDED: + _handlePlaceAdded(context, data); + break; + case NotificationGeofenceConst.NOTIFICATION_TYPE_GEOFENCE: + _handleGeoFenceNotificationTap(context); + break; + default: + logger.e("Unhandled notification type: $type"); + } + } + + void _handlePlaceAdded(BuildContext context, Map data) { + final spaceId = data[NotificationPlaceConst.KEY_SPACE_ID]; + if (spaceId != null) { + AppRoute.placesList(spaceId).push(context); + } else { + logger.e("Space ID is null for place notification"); + } + } + + void _handleGeoFenceNotificationTap(BuildContext context) { + AppRoute.home.go(context); + } + + void _handleChatMessage(BuildContext context, Map data) { + final threadId = data[NotificationChatConst.KEY_THREAD_ID]; + if (threadId != null) { + AppRoute.chat(threadId: threadId).push(context); + } else { + logger.e("Thread ID is null for chat notification"); + } + } +} + +@pragma("vm:entry-point") +Future onActionReceivedMethod(ReceivedAction receivedAction) async { +// For background actions, you must hold the execution until the end + print('Message sent via notification input: "${receivedAction}"'); + final payload = receivedAction.payload; + print('XXX payload data: "${payload}"'); + print('XXX payload "${payload?.values}, ${payload?.entries}"'); + if (payload != null) { + final type = payload[KEY_NOTIFICATION_TYPE]; + switch (type) { + case NotificationChatConst.NOTIFICATION_TYPE_CHAT: + final threadId = payload[NotificationChatConst.KEY_THREAD_ID]; + navigatorKey.currentState?.pushNamed( + "/message", // Replace with your actual route + arguments: {'threadId': threadId}, + ); + break; + case NotificationPlaceConst.NOTIFICATION_TYPE_NEW_PLACE_ADDED: + + break; + case NotificationGeofenceConst.NOTIFICATION_TYPE_GEOFENCE: + + break; + default: + logger.e("Unhandled notification type: $type"); + } + } +} + diff --git a/app/lib/domain/fcm/local_notification_handler.dart b/app/lib/domain/fcm/local_notification_handler.dart new file mode 100644 index 00000000..22064901 --- /dev/null +++ b/app/lib/domain/fcm/local_notification_handler.dart @@ -0,0 +1,68 @@ +// import 'dart:convert'; +// +// import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +// import 'package:flutter_riverpod/flutter_riverpod.dart'; +// +// const _androidChannel = AndroidNotificationChannel( +// 'notification_channel_your_space_regional', +// 'YourSpace Notification', +// importance: Importance.max, +// ); +// +// final localNotificationHandler = +// StateProvider.autoDispose((ref) => LocalNotificationHandler()); +// +// class LocalNotificationHandler { +// final _localNotificationPlugin = FlutterLocalNotificationsPlugin(); +// +// LocalNotificationHandler() { +// _localNotificationPlugin +// .resolvePlatformSpecificImplementation< +// AndroidFlutterLocalNotificationsPlugin>() +// ?.createNotificationChannel(_androidChannel); +// } +// +// Future init({ +// required void Function(String?) onTap, +// required void Function(NotificationResponse) onBackgroundTap, +// }) async { +// await _localNotificationPlugin.initialize( +// const InitializationSettings( +// android: AndroidInitializationSettings("app_notification_icon"), +// iOS: DarwinInitializationSettings( +// requestAlertPermission: false, +// requestBadgePermission: false, +// requestSoundPermission: false, +// ), +// ), +// onDidReceiveNotificationResponse: (response) { +// onTap(response.payload); +// }, +// onDidReceiveBackgroundNotificationResponse: onBackgroundTap, +// ); +// } +// +// Future showNotification({ +// required int id, +// required String title, +// required String body, +// String? payload = "", +// bool onGoing = true, +// }) async { +// _localNotificationPlugin.show( +// id, //DateTime.now().microsecondsSinceEpoch ~/ 1000000, +// title, +// body, +// NotificationDetails( +// android: AndroidNotificationDetails( +// _androidChannel.id, +// _androidChannel.name, +// importance: Importance.max, +// priority: Priority.high, +// ongoing: onGoing, +// ), +// ), +// payload: payload, +// ); +// } +// } diff --git a/app/lib/domain/fcm/notification_handler.dart b/app/lib/domain/fcm/notification_handler.dart index a64cb8db..3c3636e4 100644 --- a/app/lib/domain/fcm/notification_handler.dart +++ b/app/lib/domain/fcm/notification_handler.dart @@ -1,180 +1,207 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:data/api/auth/api_user_service.dart'; -import 'package:data/log/logger.dart'; -import 'package:firebase_messaging/firebase_messaging.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:yourspace_flutter/ui/app_route.dart'; - -const YOUR_SPACE_CHANNEL_MESSAGE = "your_space_notification_channel_messages"; -const YOUR_SPACE_CHANNEL_PLACES = "your_space_notification_channel_places"; -const YOUR_SPACE_CHANNEL_GEOFENCE = "your_space_notification_channel_geofence"; - -const _androidChannel = AndroidNotificationChannel( - 'notification_channel_your_space_regional', - 'YourSpace Notification', - importance: Importance.max, -); - -const NOTIFICATION_ID = 101; - -const KEY_NOTIFICATION_TYPE = "type"; - -class NotificationChatConst { - static const NOTIFICATION_TYPE_CHAT = "chat"; - static const KEY_PROFILE_URL = "senderProfileUrl"; - static const KEY_IS_GROUP = "isGroup"; - static const KEY_GROUP_NAME = "groupName"; - static const KEY_SENDER_ID = "senderId"; - static const KEY_THREAD_ID = "threadId"; -} - -class NotificationPlaceConst { - static const NOTIFICATION_TYPE_NEW_PLACE_ADDED = "new_place_added"; - static const KEY_SPACE_ID = "spaceId"; -} - -class NotificationGeofenceConst { - static const NOTIFICATION_TYPE_GEOFENCE = "geofence"; -} - -class NotificationNetworkStatusConst { - static const NOTIFICATION_TYPE_NETWORK_STATUS = "network_status"; - static const KEY_USER_ID = "userId"; -} - -class NotificationUpdateStateConst { - static const NOTIFICATION_TYPE_UPDATE_STATE = "updateState"; - static const KEY_USER_ID = "userId"; -} - -final notificationHandlerProvider = StateProvider.autoDispose( - (ref) => NotificationHandler(ref.read(apiUserServiceProvider))); - -class NotificationHandler { - final _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); - final ApiUserService userService; - - NotificationHandler(this.userService) { - _flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin>() - ?.createNotificationChannel(_androidChannel); - } - - Future init(BuildContext context) async { - _initFcm(context); - if (context.mounted) await _initLocalNotifications(context); - } - - void _initFcm(BuildContext context) { - FirebaseMessaging.instance.getInitialMessage().then((message) { - if (message != null && context.mounted) { - _onNotificationTap(context, message.data); - } - }); - - FirebaseMessaging.onMessageOpenedApp.listen((event) { - if (context.mounted) { - _onNotificationTap(context, event.data); - } - }); - - if (Platform.isAndroid) { - FirebaseMessaging.onMessage.listen((event) { - showLocalNotification(event); - }); - } - } - - Future _initLocalNotifications(BuildContext context) async { - _flutterLocalNotificationsPlugin.initialize( - const InitializationSettings( - android: AndroidInitializationSettings('app_logo'), - iOS: DarwinInitializationSettings( - requestAlertPermission: false, - requestBadgePermission: false, - requestSoundPermission: false, - ), - ), - onDidReceiveNotificationResponse: (response) { - if (response.payload != null && context.mounted) { - _onNotificationTap(context, jsonDecode(response.payload!)); - } - }, - ); - } - - void showLocalNotification(RemoteMessage event) { - final notification = event.notification; - final data = event.data; - final title = notification?.title; - final body = notification?.body; - - if (title != null && body != null) { - _flutterLocalNotificationsPlugin.show( - DateTime.now().microsecondsSinceEpoch ~/ 1000000, - title, - body, - NotificationDetails( - android: AndroidNotificationDetails( - _androidChannel.id, - _androidChannel.name, - importance: Importance.low, - ), - ), - payload: jsonEncode(data), - ); - } - } -} - -extension on NotificationHandler { - void _onNotificationTap( - BuildContext context, Map data) async { - logger.d("Notification handler - _onNotificationTap with data $data"); - - final type = data[KEY_NOTIFICATION_TYPE]; - - switch (type) { - case NotificationChatConst.NOTIFICATION_TYPE_CHAT: - _handleChatMessage(context, data); - break; - case NotificationPlaceConst.NOTIFICATION_TYPE_NEW_PLACE_ADDED: - _handlePlaceAdded(context, data); - break; - case NotificationGeofenceConst.NOTIFICATION_TYPE_GEOFENCE: - _handleGeoFenceNotificationTap(context); - break; - case NotificationNetworkStatusConst.NOTIFICATION_TYPE_NETWORK_STATUS: - break; - } - } - - void _handlePlaceAdded(BuildContext context, Map data) { - if (!context.mounted) return; - final String? spaceId = data[NotificationPlaceConst.KEY_SPACE_ID]; - if (spaceId != null) { - AppRoute.placesList(spaceId).push(context); - } else { - logger.e("Space ID is null for place added notification"); - } - } - - void _handleGeoFenceNotificationTap(BuildContext context) { - if (!context.mounted) return; - AppRoute.home.go(context); - } - - void _handleChatMessage(BuildContext context, Map data) { - final String? threadId = data[NotificationChatConst.KEY_THREAD_ID]; - if (threadId != null) { - AppRoute.chat(threadId: threadId).push(context); - } else { - logger.e("Thread ID is null for chat notification"); - } - } -} +// import 'dart:convert'; +// import 'dart:io'; +// +// import 'package:data/log/logger.dart'; +// import 'package:firebase_messaging/firebase_messaging.dart'; +// import 'package:flutter/cupertino.dart'; +// import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +// import 'package:flutter_riverpod/flutter_riverpod.dart'; +// import 'package:yourspace_flutter/ui/app_route.dart'; +// +// const YOUR_SPACE_CHANNEL_MESSAGE = "your_space_notification_channel_messages"; +// const YOUR_SPACE_CHANNEL_PLACES = "your_space_notification_channel_places"; +// const YOUR_SPACE_CHANNEL_GEOFENCE = "your_space_notification_channel_geofence"; +// +// const _androidChannel = AndroidNotificationChannel( +// 'notification_channel_your_space_regional', +// 'YourSpace Notification', +// importance: Importance.max, +// ); +// +// const NOTIFICATION_ID = 101; +// +// const KEY_NOTIFICATION_TYPE = "type"; +// +// class NotificationChatConst { +// static const NOTIFICATION_TYPE_CHAT = "chat"; +// static const KEY_PROFILE_URL = "senderProfileUrl"; +// static const KEY_IS_GROUP = "isGroup"; +// static const KEY_GROUP_NAME = "groupName"; +// static const KEY_SENDER_ID = "senderId"; +// static const KEY_THREAD_ID = "threadId"; +// } +// +// class NotificationPlaceConst { +// static const NOTIFICATION_TYPE_NEW_PLACE_ADDED = "new_place_added"; +// static const KEY_SPACE_ID = "spaceId"; +// } +// +// class NotificationGeofenceConst { +// static const NOTIFICATION_TYPE_GEOFENCE = "geofence"; +// } +// +// class NotificationNetworkStatusConst { +// static const NOTIFICATION_TYPE_NETWORK_STATUS = "network_status"; +// static const KEY_USER_ID = "userId"; +// } +// +// class NotificationUpdateStateConst { +// static const NOTIFICATION_TYPE_UPDATE_STATE = "updateState"; +// static const KEY_USER_ID = "userId"; +// } +// +// final notificationHandlerProvider = +// StateProvider.autoDispose((ref) => NotificationHandler()); +// +// class NotificationHandler { +// final _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); +// +// NotificationHandler() { +// _flutterLocalNotificationsPlugin +// .resolvePlatformSpecificImplementation< +// AndroidFlutterLocalNotificationsPlugin>() +// ?.createNotificationChannel(_androidChannel); +// } +// +// Future init(BuildContext context) async { +// _initFcm(context); +// if (context.mounted) await _initLocalNotifications(context); +// } +// +// void _initFcm(BuildContext context) { +// FirebaseMessaging.instance.getInitialMessage().then((message) { +// if (message != null && context.mounted) { +// _onNotificationTap(context, message.data); +// } +// }); +// +// FirebaseMessaging.onMessageOpenedApp.listen((event) { +// if (context.mounted) { +// _onNotificationTap(context, event.data); +// } +// }); +// +// if (Platform.isAndroid) { +// FirebaseMessaging.onMessage.listen((event) { +// showLocalNotification(event); +// }); +// } +// } +// +// Future _initLocalNotifications(BuildContext context) async { +// _flutterLocalNotificationsPlugin.initialize( +// const InitializationSettings( +// android: AndroidInitializationSettings('app_logo'), +// iOS: DarwinInitializationSettings( +// requestAlertPermission: false, +// requestBadgePermission: false, +// requestSoundPermission: false, +// ), +// ), +// onDidReceiveNotificationResponse: (response) { +// if (response.payload != null && context.mounted) { +// _onNotificationTap(context, jsonDecode(response.payload!)); +// } +// }, +// ); +// } +// +// void showNotification({ +// required int id, +// required String title, +// required String body, +// String? payload = "", +// bool onGoing = true, +// }) { +// _flutterLocalNotificationsPlugin.show( +// id, +// title, +// body, +// NotificationDetails( +// android: AndroidNotificationDetails( +// _androidChannel.id, +// _androidChannel.name, +// importance: Importance.low, +// ), +// ), +// payload: jsonEncode({KEY_NOTIFICATION_TYPE: payload}), +// ); +// } +// +// void showLocalNotification(RemoteMessage event) { +// final notification = event.notification; +// final data = event.data; +// final title = notification?.title; +// final body = notification?.body; +// +// if (title != null && body != null) { +// _flutterLocalNotificationsPlugin.show( +// DateTime.now().microsecondsSinceEpoch ~/ 1000000, +// title, +// body, +// NotificationDetails( +// android: AndroidNotificationDetails( +// _androidChannel.id, +// _androidChannel.name, +// importance: Importance.low, +// ), +// ), +// payload: jsonEncode(data), +// ); +// } +// } +// } +// +// extension on NotificationHandler { +// void _onNotificationTap( +// BuildContext context, Map data) async { +// logger.d("XXX Notification handler - _onNotificationTap with data $data"); +// +// final type = data[KEY_NOTIFICATION_TYPE]; +// +// switch (type) { +// case NotificationChatConst.NOTIFICATION_TYPE_CHAT: +// _handleChatMessage(context, data); +// break; +// case NotificationPlaceConst.NOTIFICATION_TYPE_NEW_PLACE_ADDED: +// _handlePlaceAdded(context, data); +// break; +// case NotificationGeofenceConst.NOTIFICATION_TYPE_GEOFENCE: +// _handleGeoFenceNotificationTap(context); +// break; +// case NotificationNetworkStatusConst.NOTIFICATION_TYPE_NETWORK_STATUS: +// break; +// case "location": +// _handleLocationTap(); +// break; +// } +// } +// +// void _handlePlaceAdded(BuildContext context, Map data) { +// if (!context.mounted) return; +// final String? spaceId = data[NotificationPlaceConst.KEY_SPACE_ID]; +// if (spaceId != null) { +// AppRoute.placesList(spaceId).push(context); +// } else {} +// } +// +// void _handleGeoFenceNotificationTap(BuildContext context) { +// if (!context.mounted) return; +// AppRoute.home.go(context); +// } +// +// void _handleChatMessage(BuildContext context, Map data) { +// final String? threadId = data[NotificationChatConst.KEY_THREAD_ID]; +// if (threadId != null) { +// AppRoute.chat(threadId: threadId).push(context); +// } else { +// logger.e("Thread ID is null for chat notification"); +// } +// } +// +// void _handleLocationTap() { +// logger.e("XXX on tap of location tracking notification"); +// } +// } +// +// diff --git a/app/lib/main.dart b/app/lib/main.dart index 6f2be80a..995218de 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/api/location/location.dart'; +import 'package:data/log/logger.dart'; import 'package:data/service/location_manager.dart'; import 'package:data/service/network_service.dart'; import 'package:data/storage/preferences_provider.dart'; @@ -10,20 +11,21 @@ import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_background_service/flutter_background_service.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:yourspace_flutter/domain/fcm/awesome_notification_handler.dart'; import 'package:yourspace_flutter/firebase_options.dart'; import 'package:yourspace_flutter/ui/app.dart'; import 'domain/fcm/notification_handler.dart'; +final GlobalKey navigatorKey = GlobalKey(); + const NOTIFICATION_ID = 112233; const NOTIFICATION_CHANNEL_ID = "notification_channel_your_space_regional"; @@ -40,8 +42,8 @@ void main() async { if (Platform.isAndroid) _configureService(); - if (await Permission.location.isGranted) { - await locationMethodChannel.invokeMethod('startTracking'); + if (await Permission.location.isGranted && Platform.isIOS) { + await locationMethodChannel.invokeMethod('startTracking'); } locationMethodChannel.setMethodCallHandler(_handleLocationUpdates); @@ -84,10 +86,10 @@ void updateCurrentUserState( Future _initContainer() async { await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); - if (!kDebugMode) { - await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true); - await FirebaseCrashlytics.instance.setCustomKey("app_type", "flutter"); - } + // if (!kDebugMode) { + // await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true); + // await FirebaseCrashlytics.instance.setCustomKey("app_type", "flutter"); + // } final prefs = await SharedPreferences.getInstance(); @@ -137,25 +139,30 @@ void _configureService() async { Future onStart(ServiceInstance service) async { WidgetsFlutterBinding.ensureInitialized(); if (!await Permission.location.isGranted) return; - - final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + final AwesomeNotificationHandler notificationHandler = AwesomeNotificationHandler(); if (service is AndroidServiceInstance) { if (await service.isForegroundService()) { - flutterLocalNotificationsPlugin.show( + notificationHandler.showLocalNotification( NOTIFICATION_ID, 'Your space Location', 'Location is being tracked', - const NotificationDetails( - android: AndroidNotificationDetails( - NOTIFICATION_CHANNEL_ID, - 'MY FOREGROUND SERVICE', - icon: "app_notification_icon", - ongoing: true, - color: Color(0xFF1679AB), - ), - ), + 'location', ); + // flutterLocalNotificationsPlugin.show( + // NOTIFICATION_ID, + // 'Your space Location', + // 'Location is being tracked', + // const NotificationDetails( + // android: AndroidNotificationDetails( + // NOTIFICATION_CHANNEL_ID, + // 'MY FOREGROUND SERVICE', + // icon: "app_notification_icon", + // ongoing: true, + // color: Color(0xFF1679AB), + // ), + // ), + // ); } } @@ -167,3 +174,21 @@ Future onStart(ServiceInstance service) async { service.stopSelf(); }); } + +void _onLocalNotificationTap(String? payload) { + print("XXX on tap notification: $payload"); + if (payload != null) { + print("XXX get data on tap notification: $payload"); + logger.d("XXX logger get data on tap notification tapped: $payload"); + } +} + +// @pragma('vm:entry-point') +// void _onBackgroundNotificationTap(NotificationResponse response) { +// print("XXX on tap Background notification: $response"); +// if (response.payload != null) { +// print("XXX get Background on tap notification: ${response.payload}"); +// logger.d("XXX logger Background notification tapped: ${response.payload}"); +// // Handle specific cases if required +// } +// } diff --git a/app/lib/ui/app.dart b/app/lib/ui/app.dart index 12ec5c6a..ad8da853 100644 --- a/app/lib/ui/app.dart +++ b/app/lib/ui/app.dart @@ -13,6 +13,7 @@ import 'package:style/extenstions/context_extenstions.dart'; import 'package:style/theme/colors.dart'; import 'package:style/theme/theme.dart'; import 'package:yourspace_flutter/domain/extenstions/context_extenstions.dart'; +import 'package:yourspace_flutter/main.dart'; import '../domain/extenstions/widget_extensions.dart'; import 'app_route.dart'; @@ -41,8 +42,11 @@ class _AppState extends ConsumerState { initialRoute = AppRoute.home; } - _router = - GoRouter(routes: AppRoute.routes, initialLocation: initialRoute.path); + _router = GoRouter( + routes: AppRoute.routes, + initialLocation: initialRoute.path, + navigatorKey: navigatorKey, + ); } @override diff --git a/app/lib/ui/flow/home/home_screen.dart b/app/lib/ui/flow/home/home_screen.dart index 1e1e33b9..4819135a 100644 --- a/app/lib/ui/flow/home/home_screen.dart +++ b/app/lib/ui/flow/home/home_screen.dart @@ -9,6 +9,7 @@ import 'package:permission_handler/permission_handler.dart'; import 'package:style/extenstions/context_extenstions.dart'; import 'package:yourspace_flutter/domain/extenstions/context_extenstions.dart'; import 'package:yourspace_flutter/domain/extenstions/widget_extensions.dart'; +import 'package:yourspace_flutter/domain/fcm/awesome_notification_handler.dart'; import 'package:yourspace_flutter/ui/app_route.dart'; import 'package:yourspace_flutter/ui/components/app_page.dart'; import 'package:yourspace_flutter/ui/components/error_snakebar.dart'; @@ -16,7 +17,6 @@ import 'package:yourspace_flutter/ui/components/resume_detector.dart'; import 'package:yourspace_flutter/ui/flow/home/home_screen_viewmodel.dart'; import 'package:yourspace_flutter/ui/flow/home/map/map_view_model.dart'; -import '../../../domain/fcm/notification_handler.dart'; import '../../components/alert.dart'; import '../../components/no_internet_screen.dart'; import '../../components/permission_dialog.dart'; @@ -33,15 +33,15 @@ class HomeScreen extends ConsumerStatefulWidget { class _HomeScreenState extends ConsumerState { late HomeViewNotifier notifier; late MapViewNotifier mapNotifier; - late NotificationHandler notificationHandler; + late AwesomeNotificationHandler awesomeNotificationHandler; late GeofenceRepository geofenceRepository; @override void initState() { super.initState(); runPostFrame(() { - notificationHandler = ref.read(notificationHandlerProvider); - notificationHandler.init(context); + awesomeNotificationHandler = ref.read(awesomeNotificationHandlerProvider); + awesomeNotificationHandler.init(context); notifier = ref.watch(homeViewStateProvider.notifier); diff --git a/app/pubspec.lock b/app/pubspec.lock index b83638ca..67304423 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -57,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + awesome_notifications: + dependency: "direct main" + description: + name: awesome_notifications + sha256: d051ffb694a53da216ff13d02c8ec645d75320048262f7e6b3c1d95a4f54c902 + url: "https://pub.dev" + source: hosted + version: "0.10.0" battery_plus: dependency: "direct main" description: @@ -661,30 +669,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" - flutter_local_notifications: - dependency: "direct main" - description: - name: flutter_local_notifications - sha256: ef41ae901e7529e52934feba19ed82827b11baa67336829564aeab3129460610 - url: "https://pub.dev" - source: hosted - version: "18.0.1" - flutter_local_notifications_linux: - dependency: transitive - description: - name: flutter_local_notifications_linux - sha256: "8f685642876742c941b29c32030f6f4f6dacd0e4eaecb3efbb187d6a3812ca01" - url: "https://pub.dev" - source: hosted - version: "5.0.0" - flutter_local_notifications_platform_interface: - dependency: transitive - description: - name: flutter_local_notifications_platform_interface - sha256: "6c5b83c86bf819cdb177a9247a3722067dd8cc6313827ce7c77a4b238a26fd52" - url: "https://pub.dev" - source: hosted - version: "8.0.0" flutter_localizations: dependency: "direct main" description: flutter @@ -1225,18 +1209,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.12.0" mime: dependency: transitive description: @@ -1713,14 +1697,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - timezone: - dependency: transitive - description: - name: timezone - sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d" - url: "https://pub.dev" - source: hosted - version: "0.9.4" timing: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 8ca8e4f8..e4e27849 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -76,7 +76,7 @@ dependencies: firebase_core: ^3.8.0 firebase_storage: ^12.3.6 firebase_messaging: ^15.1.5 - flutter_local_notifications: ^18.0.1 + cloud_firestore: ^5.5.0 google_sign_in: ^6.1.6 logger: ^2.3.0 @@ -84,6 +84,7 @@ dependencies: firebase_crashlytics: ^4.1.5 connectivity_plus: ^6.0.5 + awesome_notifications: ^0.10.0 dev_dependencies: flutter_test: diff --git a/data/lib/api/location/journey/journey.freezed.dart b/data/lib/api/location/journey/journey.freezed.dart index e40d3d7a..daf77fda 100644 --- a/data/lib/api/location/journey/journey.freezed.dart +++ b/data/lib/api/location/journey/journey.freezed.dart @@ -33,12 +33,8 @@ mixin _$ApiLocationJourney { int? get update_at => throw _privateConstructorUsedError; String? get type => throw _privateConstructorUsedError; - /// Serializes this ApiLocationJourney to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of ApiLocationJourney - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $ApiLocationJourneyCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -74,8 +70,6 @@ class _$ApiLocationJourneyCopyWithImpl<$Res, $Val extends ApiLocationJourney> // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of ApiLocationJourney - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -176,8 +170,6 @@ class __$$LocationJourneyImplCopyWithImpl<$Res> _$LocationJourneyImpl _value, $Res Function(_$LocationJourneyImpl) _then) : super(_value, _then); - /// Create a copy of ApiLocationJourney - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -333,7 +325,7 @@ class _$LocationJourneyImpl extends _LocationJourney { (identical(other.type, type) || other.type == type)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash( runtimeType, @@ -350,9 +342,7 @@ class _$LocationJourneyImpl extends _LocationJourney { update_at, type); - /// Create a copy of ApiLocationJourney - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$LocationJourneyImplCopyWith<_$LocationJourneyImpl> get copyWith => @@ -410,11 +400,8 @@ abstract class _LocationJourney extends ApiLocationJourney { int? get update_at; @override String? get type; - - /// Create a copy of ApiLocationJourney - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$LocationJourneyImplCopyWith<_$LocationJourneyImpl> get copyWith => throw _privateConstructorUsedError; } @@ -428,12 +415,8 @@ mixin _$JourneyRoute { double get latitude => throw _privateConstructorUsedError; double get longitude => throw _privateConstructorUsedError; - /// Serializes this JourneyRoute to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of JourneyRoute - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $JourneyRouteCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -457,8 +440,6 @@ class _$JourneyRouteCopyWithImpl<$Res, $Val extends JourneyRoute> // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of JourneyRoute - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -497,8 +478,6 @@ class __$$JourneyRouteImplCopyWithImpl<$Res> _$JourneyRouteImpl _value, $Res Function(_$JourneyRouteImpl) _then) : super(_value, _then); - /// Create a copy of JourneyRoute - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -547,13 +526,11 @@ class _$JourneyRouteImpl implements _JourneyRoute { other.longitude == longitude)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash(runtimeType, latitude, longitude); - /// Create a copy of JourneyRoute - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$JourneyRouteImplCopyWith<_$JourneyRouteImpl> get copyWith => @@ -579,11 +556,8 @@ abstract class _JourneyRoute implements JourneyRoute { double get latitude; @override double get longitude; - - /// Create a copy of JourneyRoute - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$JourneyRouteImplCopyWith<_$JourneyRouteImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/data/lib/api/place/api_place.freezed.dart b/data/lib/api/place/api_place.freezed.dart index 149e2b73..a492dca8 100644 --- a/data/lib/api/place/api_place.freezed.dart +++ b/data/lib/api/place/api_place.freezed.dart @@ -31,12 +31,8 @@ mixin _$ApiPlace { DateTime? get created_at => throw _privateConstructorUsedError; List get space_member_ids => throw _privateConstructorUsedError; - /// Serializes this ApiPlace to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of ApiPlace - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $ApiPlaceCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -68,8 +64,6 @@ class _$ApiPlaceCopyWithImpl<$Res, $Val extends ApiPlace> // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of ApiPlace - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -152,8 +146,6 @@ class __$$ApiPlaceImplCopyWithImpl<$Res> _$ApiPlaceImpl _value, $Res Function(_$ApiPlaceImpl) _then) : super(_value, _then); - /// Create a copy of ApiPlace - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -282,7 +274,7 @@ class _$ApiPlaceImpl extends _ApiPlace { .equals(other._space_member_ids, _space_member_ids)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash( runtimeType, @@ -296,9 +288,7 @@ class _$ApiPlaceImpl extends _ApiPlace { created_at, const DeepCollectionEquality().hash(_space_member_ids)); - /// Create a copy of ApiPlace - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$ApiPlaceImplCopyWith<_$ApiPlaceImpl> get copyWith => @@ -347,11 +337,8 @@ abstract class _ApiPlace extends ApiPlace { DateTime? get created_at; @override List get space_member_ids; - - /// Create a copy of ApiPlace - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$ApiPlaceImplCopyWith<_$ApiPlaceImpl> get copyWith => throw _privateConstructorUsedError; } @@ -369,12 +356,8 @@ mixin _$ApiPlaceMemberSetting { List get arrival_alert_for => throw _privateConstructorUsedError; List get leave_alert_for => throw _privateConstructorUsedError; - /// Serializes this ApiPlaceMemberSetting to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of ApiPlaceMemberSetting - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $ApiPlaceMemberSettingCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -404,8 +387,6 @@ class _$ApiPlaceMemberSettingCopyWithImpl<$Res, // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of ApiPlaceMemberSetting - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -466,8 +447,6 @@ class __$$ApiPlaceMemberSettingImplCopyWithImpl<$Res> $Res Function(_$ApiPlaceMemberSettingImpl) _then) : super(_value, _then); - /// Create a copy of ApiPlaceMemberSetting - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -565,7 +544,7 @@ class _$ApiPlaceMemberSettingImpl extends _ApiPlaceMemberSetting { .equals(other._leave_alert_for, _leave_alert_for)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash( runtimeType, @@ -575,9 +554,7 @@ class _$ApiPlaceMemberSettingImpl extends _ApiPlaceMemberSetting { const DeepCollectionEquality().hash(_arrival_alert_for), const DeepCollectionEquality().hash(_leave_alert_for)); - /// Create a copy of ApiPlaceMemberSetting - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$ApiPlaceMemberSettingImplCopyWith<_$ApiPlaceMemberSettingImpl> @@ -614,11 +591,8 @@ abstract class _ApiPlaceMemberSetting extends ApiPlaceMemberSetting { List get arrival_alert_for; @override List get leave_alert_for; - - /// Create a copy of ApiPlaceMemberSetting - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$ApiPlaceMemberSettingImplCopyWith<_$ApiPlaceMemberSettingImpl> get copyWith => throw _privateConstructorUsedError; } @@ -635,12 +609,8 @@ mixin _$ApiNearbyPlace { double get lat => throw _privateConstructorUsedError; double get lng => throw _privateConstructorUsedError; - /// Serializes this ApiNearbyPlace to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of ApiNearbyPlace - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $ApiNearbyPlaceCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -669,8 +639,6 @@ class _$ApiNearbyPlaceCopyWithImpl<$Res, $Val extends ApiNearbyPlace> // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of ApiNearbyPlace - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -729,8 +697,6 @@ class __$$ApiNearbyPlaceImplCopyWithImpl<$Res> _$ApiNearbyPlaceImpl _value, $Res Function(_$ApiNearbyPlaceImpl) _then) : super(_value, _then); - /// Create a copy of ApiNearbyPlace - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -808,14 +774,12 @@ class _$ApiNearbyPlaceImpl extends _ApiNearbyPlace { (identical(other.lng, lng) || other.lng == lng)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash(runtimeType, id, name, formatted_address, lat, lng); - /// Create a copy of ApiNearbyPlace - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$ApiNearbyPlaceImplCopyWith<_$ApiNearbyPlaceImpl> get copyWith => @@ -852,11 +816,8 @@ abstract class _ApiNearbyPlace extends ApiNearbyPlace { double get lat; @override double get lng; - - /// Create a copy of ApiNearbyPlace - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$ApiNearbyPlaceImplCopyWith<_$ApiNearbyPlaceImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/data/lib/api/subscription/subscription_models.freezed.dart b/data/lib/api/subscription/subscription_models.freezed.dart index ea42d9bb..91042844 100644 --- a/data/lib/api/subscription/subscription_models.freezed.dart +++ b/data/lib/api/subscription/subscription_models.freezed.dart @@ -21,9 +21,7 @@ mixin _$SubscriptionPlan { String get planDetail => throw _privateConstructorUsedError; String get planInfo => throw _privateConstructorUsedError; - /// Create a copy of SubscriptionPlan - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $SubscriptionPlanCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -47,8 +45,6 @@ class _$SubscriptionPlanCopyWithImpl<$Res, $Val extends SubscriptionPlan> // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of SubscriptionPlan - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -97,8 +93,6 @@ class __$$SubscriptionPlanImplCopyWithImpl<$Res> $Res Function(_$SubscriptionPlanImpl) _then) : super(_value, _then); - /// Create a copy of SubscriptionPlan - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -167,9 +161,7 @@ class _$SubscriptionPlanImpl implements _SubscriptionPlan { @override int get hashCode => Object.hash(runtimeType, id, name, planDetail, planInfo); - /// Create a copy of SubscriptionPlan - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$SubscriptionPlanImplCopyWith<_$SubscriptionPlanImpl> get copyWith => @@ -192,11 +184,8 @@ abstract class _SubscriptionPlan implements SubscriptionPlan { String get planDetail; @override String get planInfo; - - /// Create a copy of SubscriptionPlan - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$SubscriptionPlanImplCopyWith<_$SubscriptionPlanImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/data/lib/service/NotificationService.dart b/data/lib/service/NotificationService.dart new file mode 100644 index 00000000..24619e7b --- /dev/null +++ b/data/lib/service/NotificationService.dart @@ -0,0 +1,88 @@ +// import 'dart:async'; +// +// import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +// +// const _androidChannel = AndroidNotificationChannel( +// 'notification_channel_your_space_regional', +// 'YourSpace Notification', +// importance: Importance.max, +// ); +// +// class NotificationService { +// final FlutterLocalNotificationsPlugin _notificationsPlugin = +// FlutterLocalNotificationsPlugin(); +// +// NotificationService() { +// _notificationsPlugin +// .resolvePlatformSpecificImplementation< +// AndroidFlutterLocalNotificationsPlugin>() +// ?.createNotificationChannel(_androidChannel); +// } +// +// final StreamController onNotificationClick = +// StreamController(); +// +// /// Initialize Notification Service +// Future initializeService() async { +// const AndroidInitializationSettings initializationSettingsAndroid = +// AndroidInitializationSettings("app_icon"); +// +// const InitializationSettings settings = InitializationSettings( +// android: initializationSettingsAndroid, +// ); +// +// await _notificationsPlugin.initialize( +// settings, +// onDidReceiveNotificationResponse: (NotificationResponse response) { +// print("XXX call on did response onclick 1:$response"); +// }, +// onDidReceiveBackgroundNotificationResponse: notificationTapBackground, +// ); +// } +// +// /// Handle Notification Response (Foreground or Background) +// void _handleNotificationResponse(NotificationResponse response) { +// final payload = response.payload ?? ""; +// print('Notification Clicked: ${response.actionId}'); +// if (payload.isNotEmpty) { +// onNotificationClick.add(payload); +// } +// } +// +// /// Show Notification with Actions +// Future showNotification({ +// required int id, +// required String title, +// required String body, +// String? payload = "", +// }) async { +// _notificationsPlugin.show( +// DateTime.now().microsecondsSinceEpoch ~/ 1000000, +// title, +// body, +// NotificationDetails( +// android: AndroidNotificationDetails( +// _androidChannel.id, +// _androidChannel.name, +// importance: Importance.max, +// priority: Priority.high, +// actions:[AndroidNotificationAction('id', 'Action Name')], +// ), +// ), +// payload: 'sample_payload', +// ); +// } +// } +// +// @pragma('vm:entry-point') +// void notificationTapBackground(NotificationResponse notificationResponse) { +// // ignore: avoid_print +// print('XXX notification(${notificationResponse.id}) action tapped: ' +// '${notificationResponse.actionId} with' +// ' payload: ${notificationResponse.payload}'); +// if (notificationResponse.input?.isNotEmpty ?? false) { +// // ignore: avoid_print +// print( +// 'XXX notification action tapped with input: ${notificationResponse.input}'); +// } +// } diff --git a/data/lib/service/location_manager.dart b/data/lib/service/location_manager.dart index 5af659aa..e8f3e310 100644 --- a/data/lib/service/location_manager.dart +++ b/data/lib/service/location_manager.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:battery_plus/battery_plus.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/repository/journey_repository.dart'; import 'package:data/service/location_service.dart'; @@ -47,6 +48,13 @@ class LocationManager { bool isMoving = false; int movingDistance = STEADY_DISTANCE; + final battery = Battery(); + bool isBatterySaveMode = false; + final StreamController _batterySaveModeController = + StreamController(); + + Stream get batterySaveModeStream => _batterySaveModeController.stream; + Future isServiceRunning() async { return await bgService.isRunning(); } @@ -60,14 +68,19 @@ class LocationManager { } void startService() async { - await locationMethodChannel.invokeMethod('startTracking'); + if (Platform.isIOS) { + await locationMethodChannel.invokeMethod('startTracking'); + } await bgService.startService(); } void stopTrackingService() async { - await locationMethodChannel.invokeMethod('stopTracking'); + if (Platform.isIOS) { + await locationMethodChannel.invokeMethod('stopTracking'); + } positionSubscription?.cancel(); positionSubscription = null; + _batterySaveModeController.close(); bgService.invoke("stopService"); } @@ -84,6 +97,8 @@ class LocationManager { final userId = await _getUserIdFromPreferences(); if (userId == null) return; + // startBatterySaveModeMonitoring(); + // listenToBatterySaveMode(); positionSubscription = Geolocator.getPositionStream( locationSettings: LocationSettings( @@ -178,4 +193,49 @@ class LocationManager { movingDistance = isMoving ? MOVING_DISTANCE : STEADY_DISTANCE; startTracking(); } + + // void startBatterySaveModeMonitoring() async { + // Timer.periodic(const Duration(seconds: 10), (_) async { + // final isInBatterySaveMode = await battery.isInBatterySaveMode; + // print("XXX Battery:$isInBatterySaveMode"); + // if (isInBatterySaveMode != isBatterySaveMode) { + // _batterySaveModeController.add(isInBatterySaveMode); + // } + // }); + // } + // + // void listenToBatterySaveMode() { + // batterySaveModeStream.listen((isInBatterySaveMode) { + // if (isInBatterySaveMode) { + // isBatterySaveMode = true; + // print("XXX Battery is in save mode"); + // // showNotification(true); + // } else { + // isBatterySaveMode = false; + // print("XXX Battery is not in battery save mode"); + // // showNotification(false); + // } + // }); + // } + + // void showNotification(bool isEnable) async { + // final text = (isEnable) + // ? "Battery save mode is enable" + // : "Battery save mode is disable"; + // // notificationService.initializeService(); + // notificationService.showNotification( + // id: 101, + // title: "Group Track", + // body: text, + // payload: "battery_save_mode_notification"); + // } + // + // void listenNotification() => notificationService.onNotificationClick.stream + // .listen((onNotificationListener)); + // + // void onNotificationListener(String? payload) { + // // if (payload != null && payload.isNotEmpty) { + // print("XXX payload:$payload"); + // // } + // } }