diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index e987e98..7ebdb49 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,25 +1,29 @@ - - + + - - - - - - - - - - - + + + + + + + + + + + + + android:stopWithTask="false" + android:foregroundServiceType="specialUse" /> + + + + + + + + + diff --git a/android/app/src/main/res/drawable/ic_launcher.png b/android/app/src/main/res/drawable/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/android/app/src/main/res/drawable/ic_launcher.png differ diff --git a/android/build.gradle b/android/build.gradle index 0804b52..5c96e9e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'com.google.gms.google-services' version '4.4.2' apply false + id 'com.google.gms.google-services' version '4.3.15' apply false } allprojects { repositories { diff --git a/android/settings.gradle b/android/settings.gradle index 23e8403..a44b68a 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -18,8 +18,11 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.3.0" apply false - id "org.jetbrains.kotlin.android" version "1.8.22" apply false + id "com.android.application" version "8.3.2" apply false + // START: FlutterFire Configuration + id "com.google.gms.google-services" version "4.3.15" apply false + // END: FlutterFire Configuration + id "org.jetbrains.kotlin.android" version "2.1.0" apply false } include ":app" diff --git a/assets/sounds/warning_alarm.mp3 b/assets/sounds/warning_alarm.mp3 new file mode 100644 index 0000000..9879346 Binary files /dev/null and b/assets/sounds/warning_alarm.mp3 differ diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 50e32cd..519036e 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -39,6 +39,7 @@ UIBackgroundModes fetch + audio remote-notification UILaunchStoryboardName @@ -59,6 +60,10 @@ UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance + BGTaskSchedulerPermittedIdentifiers + + com.gdelataillade.fetch + diff --git a/lib/feature/main/main_screen.dart b/lib/feature/main/main_screen.dart index 3bdae22..ddc479d 100644 --- a/lib/feature/main/main_screen.dart +++ b/lib/feature/main/main_screen.dart @@ -8,7 +8,9 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:go_router/go_router.dart'; import 'package:badges/badges.dart' as badges; import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'; +import 'package:sfm_app/product/shared/shared_snack_bar.dart'; +import '../../product/services/notification_services.dart'; import '../../product/utils/permission_handler.dart'; import '../../product/permission/notification_permission.dart'; import '../settings/profile/profile_model.dart'; @@ -43,20 +45,10 @@ class MainScreen extends StatefulWidget { State createState() => _MainScreenState(); } -// @pragma('vm:entry-point') -// Future firebaseMessagingBackgroundHandler(RemoteMessage message) async { -// log("Full background message payload: ${message.toMap()}"); -// await Firebase.initializeApp(); -// final notificationServices = NotificationServices(); -// await notificationServices.initLocalNotifications(controller); -// await notificationServices.showNotification(message); -// log("Background message handled: ${message.data['title']}"); -// } - class _MainScreenState extends State with WidgetsBindingObserver { - PersistentTabController controller = PersistentTabController(initialIndex: 0); + APIServices apiServices = APIServices(); - // final NotificationServices notificationServices = NotificationServices(); + final NotificationServices notificationServices = NotificationServices(); late MainBloc mainBloc; bool isVN = true; bool isLight = true; @@ -84,7 +76,12 @@ class _MainScreenState extends State with WidgetsBindingObserver { mainBloc.sinkIsVNIcon.add(isVN); mainBloc.sinkThemeMode.add(isLight); checkAndRequestPermission(); + NotificationServices.requestNotificationPermission(); NotificationPermission.instance.checkNotificationPermission(context); + bool? notificationStatus = await NotificationPermission.instance.requestNotificationPermission(); + if(!notificationStatus!){ + showNoIconTopSnackBar(context, "Yêu cầu quyền thông báo không thành công", Colors.orange, Colors.white12); + } } @override @@ -103,44 +100,11 @@ class _MainScreenState extends State with WidgetsBindingObserver { mainBloc.sendNotificationToken(newToken); }); - FirebaseMessaging.onMessage.listen((RemoteMessage message) { - RemoteNotification? notification = message.notification; - AndroidNotification? android = message.notification?.android; - if (notification != null && android != null) { - const AndroidNotificationDetails androidPlatformChannelSpecifics = - AndroidNotificationDetails('your channel id', 'your channel name', - importance: Importance.max, - priority: Priority.high, - ticker: 'ticker'); - const NotificationDetails platformChannelSpecifics = - NotificationDetails(android: androidPlatformChannelSpecifics); - flutterLocalNotificationsPlugin.show( - notification.hashCode, - notification.title, - notification.body, - platformChannelSpecifics, - ); - } - }); // notificationServices.initLocalNotifications(controller); // notificationServices.firebaseInit(context); // NotificationServices().setupInteractMessage(controller); } - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - super.didChangeAppLifecycleState(state); - if (state == AppLifecycleState.inactive) { - log("App Inactive"); - } else if (state == AppLifecycleState.resumed) { - log("App Resumed"); - } else if (state == AppLifecycleState.paused) { - log("App paused"); - } else if (state == AppLifecycleState.detached) { - log("App detached"); - } - } - @override void dispose() { super.dispose(); diff --git a/lib/main.dart b/lib/main.dart index f7b717d..bef3be9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,12 +1,12 @@ import 'dart:developer'; +import 'package:alarm/alarm.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; -import 'firebase_options.dart'; +import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart' show PersistentTabController; +import 'firebase_options.dart'; import 'product/lang/l10n/app_localizations.dart'; import 'product/services/api_services.dart'; import 'product/services/notification_services.dart'; @@ -16,88 +16,16 @@ import 'bloc/main_bloc.dart'; import 'product/base/bloc/base_bloc.dart'; import 'product/constant/navigation/navigation_router.dart'; -@pragma('vm:entry-point') -Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { - await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); - await setupFlutterNotifications(); - showFlutterNotification(message); - print('Handling a background message ${message.messageId}'); -} -/// Create a [AndroidNotificationChannel] for heads up notifications -late AndroidNotificationChannel channel; +PersistentTabController controller = PersistentTabController(initialIndex: 0); -bool isFlutterLocalNotificationsInitialized = false; - -Future setupFlutterNotifications() async { - if (isFlutterLocalNotificationsInitialized) { - return; - } - channel = const AndroidNotificationChannel( - 'high_importance_channel', // id - 'High Importance Notifications', // title - description: - 'This channel is used for important notifications.', // description - importance: Importance.high, - ); - - flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); - - /// Create an Android Notification Channel. - /// - /// We use this channel in the `AndroidManifest.xml` file to override the - /// default FCM channel to enable heads up notifications. - await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin>() - ?.createNotificationChannel(channel); - - /// Update the iOS foreground notification presentation options to allow - /// heads up notifications. - await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions( - alert: true, - badge: true, - sound: true, - ); - isFlutterLocalNotificationsInitialized = true; -} - -void showFlutterNotification(RemoteMessage message) { - RemoteNotification? notification = message.notification; - AndroidNotification? android = message.notification?.android; - if (notification != null && android != null && !kIsWeb) { - flutterLocalNotificationsPlugin.show( - notification.hashCode, - notification.title, - notification.body, - NotificationDetails( - android: AndroidNotificationDetails( - channel.id, - channel.name, - channelDescription: channel.description, - // TODO add a proper drawable resource to android, for now using - // one that already exists in example app. - icon: 'launch_background', - ), - ), - ); - } -} - -/// Initialize the [FlutterLocalNotificationsPlugin] package. -late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin; void main() async { WidgetsFlutterBinding.ensureInitialized(); - await Firebase.initializeApp(); - // Set the background messaging handler early on, as a named top-level function - FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); - - if (!kIsWeb) { - await setupFlutterNotifications(); - } - + await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform, name: "sfm-notification"); + FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler); + await Alarm.init(); runApp( BlocProvider( child: const MyApp(), @@ -129,7 +57,7 @@ class _MyAppState extends State { late MainBloc mainBloc; LanguageServices languageServices = LanguageServices(); ThemeServices themeServices = ThemeServices(); - // final NotificationServices notificationServices = NotificationServices(); + final NotificationServices notificationServices = NotificationServices(); APIServices apiServices = APIServices(); setLocale(Locale locale) { _locale = locale; @@ -145,20 +73,11 @@ class _MyAppState extends State { void initState() { super.initState(); mainBloc = BlocProvider.of(context); - // // notificationServices.initLocalNotifications(); - // // notificationServices.firebaseInit(context); - // // notificationServices.setupInteractMessage(); - // notificationServices.getDeviceToken().then((token){ - // log("Firebase Token: $token"); - // sendNotificationToken(token); - // }); + notificationServices.initialize(); + notificationServices.firebaseInit(context); + notificationServices.setupInteractMessage(controller); } - // void sendNotificationToken (String token) async { - // int statusCode = await apiServices.sendNotificationToken(token); - // log("Notification Send StatusCode : $statusCode"); - // } - @override void didChangeDependencies() { languageServices.getLocale().then((locale) => {setLocale(locale)}); diff --git a/lib/product/permission/notification_permission.dart b/lib/product/permission/notification_permission.dart index 672a2cb..9be5c32 100644 --- a/lib/product/permission/notification_permission.dart +++ b/lib/product/permission/notification_permission.dart @@ -1,8 +1,10 @@ import 'dart:developer'; +import 'dart:io'; import 'package:app_settings/app_settings.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:permission_handler/permission_handler.dart'; import '../base/widget/dialog/request_permission_dialog.dart'; @@ -11,7 +13,7 @@ class NotificationPermission { static NotificationPermission? _instance; static NotificationPermission get instance => _instance ??= NotificationPermission._init(); - + static final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin(); Future checkNotificationPermission(context) async { var status = await Permission.notification.status; log("Status: $status"); @@ -44,4 +46,21 @@ class NotificationPermission { Icons.location_on_outlined, "ABCDE", AppSettingsType.notification); } } + Future requestNotificationPermission() async { + try { + if (Platform.isAndroid) { + return await _notificationsPlugin + .resolvePlatformSpecificImplementation() + ?.requestNotificationsPermission(); + } else if (Platform.isIOS) { + return await _notificationsPlugin + .resolvePlatformSpecificImplementation() + ?.requestPermissions(alert: true, sound: true, badge: true); + } + return null; + } catch (e) { + log("Error requesting notification permission: $e"); + return null; + } + } } diff --git a/lib/product/services/alarm_services.dart b/lib/product/services/alarm_services.dart new file mode 100644 index 0000000..7ab11b0 --- /dev/null +++ b/lib/product/services/alarm_services.dart @@ -0,0 +1,34 @@ +import 'dart:io'; +import 'package:alarm/alarm.dart'; + +class AlarmServices { + Future showAlarm(String title, String body) async { + final DateTime now = DateTime.now(); + final AlarmSettings alarmSettings = AlarmSettings( + id: 42, + dateTime: now, + assetAudioPath: 'assets/sounds/warning_alarm.mp3', + loopAudio: true, + vibrate: true, + warningNotificationOnKill: Platform.isIOS, + androidFullScreenIntent: true, + allowAlarmOverlap: true, + volumeSettings: VolumeSettings.fade( + volume: 1.0, + fadeDuration: const Duration(seconds: 3), + volumeEnforced: true, + ), + notificationSettings: NotificationSettings( + title: title, + body: body, + stopButton: 'Dừng cảnh báo', + icon: 'ic_launcher', + ), + ); + await Alarm.set(alarmSettings: alarmSettings); + } + + void cancelAlarm({int id = 42}) async { + await Alarm.stop(id); + } +} diff --git a/lib/product/services/notification_services.dart b/lib/product/services/notification_services.dart index 23d28d5..68282cb 100644 --- a/lib/product/services/notification_services.dart +++ b/lib/product/services/notification_services.dart @@ -1,117 +1,162 @@ import 'dart:developer' as dev; +import 'dart:io'; import 'dart:math' as math; -import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:alarm/alarm.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_messaging/firebase_messaging.dart' hide NotificationSettings; import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'; +import 'package:sfm_app/product/utils/app_logger_utils.dart'; + +import '../../firebase_options.dart'; +import '../../main.dart'; +import 'alarm_services.dart'; class NotificationServices { - FirebaseMessaging messaging = FirebaseMessaging.instance; - final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + static final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin(); + final FirebaseMessaging _messaging = FirebaseMessaging.instance; + AlarmServices alarmServices = AlarmServices(); - Future initLocalNotifications(PersistentTabController controller) async { - const AndroidInitializationSettings androidInitializationSettings = AndroidInitializationSettings('@mipmap/ic_launcher'); - const DarwinInitializationSettings iosInitializationSettings = DarwinInitializationSettings(); - const InitializationSettings initializationSettings = InitializationSettings( - android: androidInitializationSettings, - iOS: iosInitializationSettings, - ); + Future initialize() async { + await initializeLocalNotifications(); + dev.log("NotificationService initialized"); + } - await _flutterLocalNotificationsPlugin.initialize( - initializationSettings, - onDidReceiveNotificationResponse: (NotificationResponse response) { - dev.log("Người dùng click thông báo ở foreground với payload: ${response.payload}"); - handleMessage(response.payload,controller); - }, - ); - dev.log("Local notifications initialized"); + Future initializeLocalNotifications() async { + try{ + const AndroidInitializationSettings androidInitializationSettings = AndroidInitializationSettings('@mipmap/ic_launcher'); + const DarwinInitializationSettings iosInitializationSettings = DarwinInitializationSettings(); + const InitializationSettings initializationSettings = InitializationSettings( + android: androidInitializationSettings, + iOS: iosInitializationSettings, + ); + + await _notificationsPlugin.initialize( + initializationSettings, + onDidReceiveNotificationResponse: (NotificationResponse response) { + dev.log("Người dùng click thông báo ở foreground với payload: ${response.payload}"); + handleMessage(response.payload,controller); + }, + ); + dev.log("Local notifications initialized"); + }catch(e){ + dev.log("Error initializing local notifications: $e"); + } } void firebaseInit(BuildContext context) { FirebaseMessaging.onMessage.listen((message) { dev.log("Foreground message payload: ${message.toMap()}"); - showNotification(message); - }); - } - - Future getDeviceToken() async { - print("GET FB TOKEN"); - String? token = await messaging.getToken(); - print("GET FB: ${token}"); - return token!; + _showNotification(message); + }); } void isTokenRefresh() { - messaging.onTokenRefresh.listen((newToken) { + _messaging.onTokenRefresh.listen((newToken) { dev.log("Refresh Firebase Messaging Token: $newToken"); }); } - Future showNotification(RemoteMessage message) async { - dev.log(message.toString()); - dev.log(message.data.toString()); - dev.log(message.data["notification"].toString()); - String? title = message.data['title']; - String? body = message.data['body']; - String type = message.data['type'] ?? "normal"; - - if (title == null || body == null) { - dev.log("Skipping notification due to missing title or body"); - return; + static Future requestNotificationPermission() async { + try { + if (Platform.isAndroid) { + return await _notificationsPlugin + .resolvePlatformSpecificImplementation() + ?.requestNotificationsPermission(); + } else if (Platform.isIOS) { + return await _notificationsPlugin + .resolvePlatformSpecificImplementation() + ?.requestPermissions(alert: true, sound: true, badge: true); + } + return null; + } catch (e) { + dev.log("Error requesting notification permission: $e"); + return null; } + } - AndroidNotificationChannel androidNotificationChannel = AndroidNotificationChannel( - math.Random.secure().nextInt(1000000).toString(), - 'High Importance Notification', - importance: Importance.max, - ); + Future _showNotification(RemoteMessage message) async { + try { + // Early validation of notification data + final title = message.data['title'] as String?; + final body = message.data['body'] as String?; + final type = message.data['type'] as String? ?? 'normal'; - final androidPlugin = _flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation(); - await androidPlugin?.deleteNotificationChannel(androidNotificationChannel.id); + if (title == null || body == null) { + dev.log('Skipping notification: missing title or body', name: 'Notification'); + return; + } - AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - androidNotificationChannel.id, - androidNotificationChannel.name, - channelDescription: "Channel description", - sound: getSound(type), - importance: androidNotificationChannel.importance, - priority: Priority.high, - ticker: 'ticker', - actions: type == "smoke_warning" - ? [ - const AndroidNotificationAction( - "id1", - "Hogg xóa được", - // true thì khi nhấn vào button sẽ mở giao diện ra - showsUserInterface: true, - cancelNotification: false, - ) - ] - : null - ); + // Handle smoke warning notifications + if (type == 'smoke_warning') { + await alarmServices.showAlarm(title, body); + dev.log('Displayed smoke warning notification', name: 'Notification'); + return; + } - const DarwinNotificationDetails darwinNotificationDetails = DarwinNotificationDetails( - presentAlert: true, - presentBadge: true, - presentBanner: true, - presentSound: true, - ); + // Create notification channel + final channelId = math.Random.secure().nextInt(1000000).toString(); + const channelName = 'High Importance Notification'; + const channelDescription = 'Channel description'; - NotificationDetails notificationDetails = NotificationDetails( - android: androidNotificationDetails, - iOS: darwinNotificationDetails, - ); + final androidChannel = AndroidNotificationChannel( + channelId, + channelName, + importance: Importance.max, + description: channelDescription, + ); - // Truyền payload vào thông báo - String payload = message.data['type'] ?? "default"; - await _flutterLocalNotificationsPlugin.show( - math.Random.secure().nextInt(1000000), - title, - body, - notificationDetails, - payload: payload, - ); - dev.log("Displayed notification with title: $title, body: $body, type: $type"); + final androidPlugin = _notificationsPlugin + .resolvePlatformSpecificImplementation(); + + // Delete existing channel to prevent conflicts + await androidPlugin?.deleteNotificationChannel(channelId); + + // Configure notification details + final androidDetails = AndroidNotificationDetails( + channelId, + channelName, + channelDescription: channelDescription, + sound: getSound(type), + importance: Importance.max, + priority: Priority.high, + ticker: 'ticker', + ); + + const iosDetails = DarwinNotificationDetails( + presentAlert: true, + presentBadge: true, + presentBanner: true, + presentSound: true, + ); + + final notificationDetails = NotificationDetails( + android: androidDetails, + iOS: iosDetails, + ); + + // Show notification + await _notificationsPlugin.show( + int.parse(channelId), + title, + body, + notificationDetails, + payload: type, + ); + + dev.log( + 'Displayed notification - title: $title, body: $body, type: $type', + name: 'Notification', + ); + } catch (e, stackTrace) { + dev.log( + 'Failed to show notification: $e', + name: 'Notification', + error: e, + stackTrace: stackTrace, + ); + } } AndroidNotificationSound getSound(String type) { @@ -155,4 +200,120 @@ class NotificationServices { } }); } + + Future showBackgroundOrTerminateNotification(RemoteMessage message) async { + try { + // Early validation of notification data + final title = message.data['title'] as String?; + final body = message.data['body'] as String?; + final type = message.data['type'] as String? ?? 'normal'; + + if (title == null || body == null) { + dev.log('Skipping notification: missing title or body', name: 'Notification'); + return; + } + + // Create notification channel + final channelId = math.Random.secure().nextInt(1000000).toString(); + const channelName = 'High Importance Notification'; + const channelDescription = 'Channel description'; + + + // Configure notification details + final androidDetails = AndroidNotificationDetails( + channelId, + channelName, + channelDescription: channelDescription, + sound: getSound(type), + importance: Importance.max, + priority: Priority.high, + ticker: 'ticker', + ); + + const iosDetails = DarwinNotificationDetails( + presentAlert: true, + presentBadge: true, + presentBanner: true, + presentSound: true, + ); + + final notificationDetails = NotificationDetails( + android: androidDetails, + iOS: iosDetails, + ); + + // Show notification + await _notificationsPlugin.show( + int.parse(channelId), + title, + body, + notificationDetails, + payload: type, + ); + + dev.log( + 'Displayed notification - title: $title, body: $body, type: $type', + name: 'Notification', + ); + } catch (e, stackTrace) { + dev.log( + 'Failed to show notification: $e', + name: 'Notification', + error: e, + stackTrace: stackTrace, + ); + } + } +} + +@pragma('vm:entry-point') +Future firebaseMessagingBackgroundHandler(RemoteMessage message) async { + try { + AppLoggerUtils.warning("Background handler started: ${message.data}"); + await Alarm.init(); + if (Firebase.apps.isEmpty) { + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + name: "sfm-notification" + ); + AppLoggerUtils.warning("Firebase đã được khởi tạo trong background handler"); + } else { + AppLoggerUtils.warning("Firebase đã được khởi tạo trước đó"); + } + AppLoggerUtils.warning(message.toString()); + AppLoggerUtils.warning(message.data.toString()); + AppLoggerUtils.warning(message.data["notification"].toString()); + String? title = message.data['title']; + String? body = message.data['body']; + String type = message.data['type'] ?? "normal"; + final notificationService = NotificationServices(); + await notificationService.initialize(); + final alarmSettings = AlarmSettings( + id: 42, + dateTime: DateTime.now(), + assetAudioPath: 'assets/sounds/warning_alarm.mp3', + loopAudio: true, + vibrate: true, + warningNotificationOnKill: Platform.isIOS, + androidFullScreenIntent: true, + volumeSettings: VolumeSettings.fade( + volume: 0.8, + fadeDuration: const Duration(seconds: 5), + volumeEnforced: true, + ), + notificationSettings: NotificationSettings( + title: title ?? "SFM", + body: body ?? "", + stopButton: 'Dừng thông báo', + icon: "ic_launcher", + ), + ); + if (type == "smoke_warning") { + await Alarm.set(alarmSettings: alarmSettings); + } else { + await notificationService.showBackgroundOrTerminateNotification(message); + } + } catch (e) { + AppLoggerUtils.warning("Error in background handler: $e"); + } } \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 5c739cf..d8cfd73 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.54" + alarm: + dependency: "direct main" + description: + name: alarm + sha256: "1eef91f0b803a2370137e0dada9c7c24cc31edf4f1c30b06442dcf486cc192e0" + url: "https://pub.dev" + source: hosted + version: "5.1.4" app_settings: dependency: "direct main" description: @@ -294,6 +302,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.8" + flutter_fgbg: + dependency: transitive + description: + name: flutter_fgbg + sha256: eb6da9b2047372566a6e17b505975fe5bace94af01f6fc825c4b6f81baa6c447 + url: "https://pub.dev" + source: hosted + version: "0.7.1" flutter_launcher_icons: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index b3c15cd..6b1acec 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,7 +22,7 @@ dependencies: permission_handler: ^11.0.1 # flex_color_scheme: ^7.2.0 flex_color_scheme: ^8.2.0 - go_router: ^15.2.3 + go_router: ^15.1.2 http: ^1.3.0 top_snackbar_flutter: ^3.1.0 badges: ^3.1.2 @@ -48,6 +48,7 @@ dependencies: app_settings: ^5.1.1 lottie: ^3.3.1 logger: ^2.5.0 + alarm: ^5.1.4 dev_dependencies: flutter_test: