From 306ce9e2b411a99f75e1db0083f1187898226b4c Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 15 Sep 2024 16:02:56 +0800 Subject: [PATCH] :wheelchair: Optimize notification background service --- lib/background.dart | 109 +++++++++++++++++++++++++++++++++++ lib/main.dart | 82 +------------------------- lib/providers/auth.dart | 2 + lib/screens/auth/signin.dart | 15 +++-- 4 files changed, 122 insertions(+), 86 deletions(-) create mode 100644 lib/background.dart diff --git a/lib/background.dart b/lib/background.dart new file mode 100644 index 0000000..21c3b2b --- /dev/null +++ b/lib/background.dart @@ -0,0 +1,109 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart' hide Notification; +import 'package:flutter_background_service/flutter_background_service.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:get/get.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:solian/models/notification.dart'; +import 'package:solian/providers/auth.dart'; +import 'package:solian/providers/websocket.dart'; + +FlutterBackgroundService? bgNotificationService; + +void autoConfigureBackgroundNotificationService() async { + if (bgNotificationService != null) return; + + final prefs = await SharedPreferences.getInstance(); + if (prefs.getBool('service_background_notification') != true) return; + + bgNotificationService = FlutterBackgroundService(); + + await bgNotificationService!.configure( + androidConfiguration: AndroidConfiguration( + onStart: onBackgroundNotificationServiceStart, + autoStart: true, + autoStartOnBoot: true, + isForegroundMode: false, + ), + // This feature won't be able to use on iOS + // We got APNs support covered + iosConfiguration: IosConfiguration( + autoStart: false, + ), + ); +} + +void autoStartBackgroundNotificationService() async { + final prefs = await SharedPreferences.getInstance(); + if (prefs.getBool('service_background_notification') != true) return; + if (bgNotificationService == null) return; + bgNotificationService!.startService(); +} + +void autoStopBackgroundNotificationService() async { + if (bgNotificationService == null) return; + if (await bgNotificationService!.isRunning()) { + bgNotificationService?.invoke('stopService'); + } +} + +@pragma('vm:entry-point') +void onBackgroundNotificationServiceStart(ServiceInstance service) async { + WidgetsFlutterBinding.ensureInitialized(); + DartPluginRegistrant.ensureInitialized(); + + Get.put(AuthProvider()); + Get.put(WebSocketProvider()); + + service.on('stopService').listen((event) { + service.stopSelf(); + }); + + final auth = Get.find(); + await auth.refreshAuthorizeStatus(); + await auth.ensureCredentials(); + if (!auth.isAuthorized.value) { + debugPrint( + 'Background notification do nothing due to user didn\'t sign in.', + ); + return; + } + + const notificationChannelId = 'solian_notification_service'; + + final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); + + final ws = Get.find(); + await ws.connect(); + debugPrint('Background notification has been started'); + ws.stream.stream.listen( + (event) { + debugPrint( + 'Background notification service incoming message: ${event.method} ${event.message}', + ); + + if (event.method == 'notifications.new' && event.payload != null) { + final data = Notification.fromJson(event.payload!); + debugPrint( + 'Background notification service got a notification id=${data.id}', + ); + flutterLocalNotificationsPlugin.show( + data.id, + data.title, + [data.subtitle, data.body].where((x) => x != null).join('\n'), + const NotificationDetails( + android: AndroidNotificationDetails( + notificationChannelId, + 'Solian Notification Service', + channelDescription: 'Notifications that sent via Solar Network', + importance: Importance.high, + icon: 'mipmap/ic_launcher', + ), + ), + ); + } + }, + ); +} diff --git a/lib/main.dart b/lib/main.dart index c197091..4a58f5b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,16 +4,13 @@ import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:flutter/material.dart' hide Notification; import 'package:flutter_acrylic/flutter_acrylic.dart'; -import 'package:flutter_background_service/flutter_background_service.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:get/get.dart'; import 'package:go_router/go_router.dart'; import 'package:protocol_handler/protocol_handler.dart'; import 'package:provider/provider.dart'; -import 'package:shared_preferences/shared_preferences.dart'; +import 'package:solian/background.dart'; import 'package:solian/bootstrapper.dart'; import 'package:solian/firebase_options.dart'; -import 'package:solian/models/notification.dart'; import 'package:solian/platform.dart'; import 'package:solian/providers/attachment_uploader.dart'; import 'package:solian/providers/daily_sign.dart'; @@ -70,82 +67,7 @@ Future _initializeFirebase() async { } Future _initializeBackgroundNotificationService() async { - final prefs = await SharedPreferences.getInstance(); - if (prefs.getBool('service_background_notification') != true) return; - - final service = FlutterBackgroundService(); - - await service.configure( - androidConfiguration: AndroidConfiguration( - onStart: onBackgroundNotificationServiceStart, - autoStart: true, - autoStartOnBoot: true, - isForegroundMode: false, - ), - // This feature won't be able to use on iOS - // We got APNs support covered - iosConfiguration: IosConfiguration( - autoStart: false, - ), - ); - - await service.startService(); -} - -@pragma('vm:entry-point') -void onBackgroundNotificationServiceStart(ServiceInstance service) async { - WidgetsFlutterBinding.ensureInitialized(); - DartPluginRegistrant.ensureInitialized(); - - Get.put(AuthProvider()); - Get.put(WebSocketProvider()); - - final auth = Get.find(); - await auth.refreshAuthorizeStatus(); - await auth.ensureCredentials(); - if (!auth.isAuthorized.value) { - debugPrint( - 'Background notification do nothing due to user didn\'t sign in.', - ); - return; - } - - const notificationChannelId = 'solian_notification_service'; - - final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = - FlutterLocalNotificationsPlugin(); - - final ws = Get.find(); - await ws.connect(); - debugPrint('Background notification has been started'); - ws.stream.stream.listen( - (event) { - debugPrint( - 'Background notification service incoming message: ${event.method} ${event.message}', - ); - - if (event.method == 'notifications.new' && event.payload != null) { - final data = Notification.fromJson(event.payload!); - debugPrint( - 'Background notification service got a notification id=${data.id}', - ); - flutterLocalNotificationsPlugin.show( - data.id, - data.title, - [data.subtitle, data.body].where((x) => x != null).join('\n'), - const NotificationDetails( - android: AndroidNotificationDetails( - notificationChannelId, - 'Solian Notification Service', - channelDescription: 'Notifications that sent via Solar Network', - importance: Importance.high, - icon: 'mipmap/ic_launcher', - ), - ), - ); - } - }, - ); + autoConfigureBackgroundNotificationService(); } Future _initializePlatformComponents() async { diff --git a/lib/providers/auth.dart b/lib/providers/auth.dart index 6dcf0c0..25801f7 100644 --- a/lib/providers/auth.dart +++ b/lib/providers/auth.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:get/get.dart'; import 'package:get/get_connect/http/src/request/request.dart'; +import 'package:solian/background.dart'; import 'package:solian/exceptions/request.dart'; import 'package:solian/exceptions/unauthorized.dart'; import 'package:solian/providers/database/database.dart'; @@ -200,6 +201,7 @@ class AuthProvider extends GetConnect { Get.find().notificationUnread.value = 0; AppDatabase.removeDatabase(); + autoStopBackgroundNotificationService(); storage.deleteAll(); } diff --git a/lib/screens/auth/signin.dart b/lib/screens/auth/signin.dart index 7a1d6b9..ebf10ea 100644 --- a/lib/screens/auth/signin.dart +++ b/lib/screens/auth/signin.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:get/get.dart'; import 'package:protocol_handler/protocol_handler.dart'; +import 'package:solian/background.dart'; import 'package:solian/exts.dart'; import 'package:solian/providers/websocket.dart'; import 'package:solian/providers/auth.dart'; @@ -22,7 +23,7 @@ class _SignInPopupState extends State with ProtocolListener { final _usernameController = TextEditingController(); final _passwordController = TextEditingController(); - void requestResetPassword() async { + void _requestResetPassword() async { final username = _usernameController.value.text; if (username.isEmpty) { context.showErrorDialog('signinResetPasswordHint'.tr); @@ -52,7 +53,7 @@ class _SignInPopupState extends State with ProtocolListener { context.showModalDialog('done'.tr, 'signinResetPasswordSent'.tr); } - void performAction() async { + void _performAction() async { final AuthProvider auth = Get.find(); final username = _usernameController.value.text; @@ -100,6 +101,8 @@ class _SignInPopupState extends State with ProtocolListener { } Get.find().registerPushNotifications(); + autoConfigureBackgroundNotificationService(); + autoStartBackgroundNotificationService(); Navigator.pop(context, true); } @@ -121,7 +124,7 @@ class _SignInPopupState extends State with ProtocolListener { final uri = url.replaceFirst('solink://', ''); if (uri == 'auth?status=done') { closeInAppWebView(); - performAction(); + _performAction(); } } @@ -175,19 +178,19 @@ class _SignInPopupState extends State with ProtocolListener { ), onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), - onSubmitted: (_) => performAction(), + onSubmitted: (_) => _performAction(), ), const Gap(12), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ TextButton( - onPressed: _isBusy ? null : () => requestResetPassword(), + onPressed: _isBusy ? null : () => _requestResetPassword(), style: TextButton.styleFrom(foregroundColor: Colors.grey), child: Text('forgotPassword'.tr), ), TextButton( - onPressed: _isBusy ? null : () => performAction(), + onPressed: _isBusy ? null : () => _performAction(), child: Row( mainAxisSize: MainAxisSize.min, children: [