Optimize notification background service

This commit is contained in:
LittleSheep 2024-09-15 16:02:56 +08:00
parent a487924300
commit 306ce9e2b4
4 changed files with 122 additions and 86 deletions

109
lib/background.dart Normal file
View File

@ -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<AuthProvider>();
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<WebSocketProvider>();
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',
),
),
);
}
},
);
}

View File

@ -4,16 +4,13 @@ import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/material.dart' hide Notification; import 'package:flutter/material.dart' hide Notification;
import 'package:flutter_acrylic/flutter_acrylic.dart'; 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:get/get.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:protocol_handler/protocol_handler.dart'; import 'package:protocol_handler/protocol_handler.dart';
import 'package:provider/provider.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/bootstrapper.dart';
import 'package:solian/firebase_options.dart'; import 'package:solian/firebase_options.dart';
import 'package:solian/models/notification.dart';
import 'package:solian/platform.dart'; import 'package:solian/platform.dart';
import 'package:solian/providers/attachment_uploader.dart'; import 'package:solian/providers/attachment_uploader.dart';
import 'package:solian/providers/daily_sign.dart'; import 'package:solian/providers/daily_sign.dart';
@ -70,82 +67,7 @@ Future<void> _initializeFirebase() async {
} }
Future<void> _initializeBackgroundNotificationService() async { Future<void> _initializeBackgroundNotificationService() async {
final prefs = await SharedPreferences.getInstance(); autoConfigureBackgroundNotificationService();
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<AuthProvider>();
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<WebSocketProvider>();
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',
),
),
);
}
},
);
} }
Future<void> _initializePlatformComponents() async { Future<void> _initializePlatformComponents() async {

View File

@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get/get_connect/http/src/request/request.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/request.dart';
import 'package:solian/exceptions/unauthorized.dart'; import 'package:solian/exceptions/unauthorized.dart';
import 'package:solian/providers/database/database.dart'; import 'package:solian/providers/database/database.dart';
@ -200,6 +201,7 @@ class AuthProvider extends GetConnect {
Get.find<WebSocketProvider>().notificationUnread.value = 0; Get.find<WebSocketProvider>().notificationUnread.value = 0;
AppDatabase.removeDatabase(); AppDatabase.removeDatabase();
autoStopBackgroundNotificationService();
storage.deleteAll(); storage.deleteAll();
} }

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:protocol_handler/protocol_handler.dart'; import 'package:protocol_handler/protocol_handler.dart';
import 'package:solian/background.dart';
import 'package:solian/exts.dart'; import 'package:solian/exts.dart';
import 'package:solian/providers/websocket.dart'; import 'package:solian/providers/websocket.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
@ -22,7 +23,7 @@ class _SignInPopupState extends State<SignInPopup> with ProtocolListener {
final _usernameController = TextEditingController(); final _usernameController = TextEditingController();
final _passwordController = TextEditingController(); final _passwordController = TextEditingController();
void requestResetPassword() async { void _requestResetPassword() async {
final username = _usernameController.value.text; final username = _usernameController.value.text;
if (username.isEmpty) { if (username.isEmpty) {
context.showErrorDialog('signinResetPasswordHint'.tr); context.showErrorDialog('signinResetPasswordHint'.tr);
@ -52,7 +53,7 @@ class _SignInPopupState extends State<SignInPopup> with ProtocolListener {
context.showModalDialog('done'.tr, 'signinResetPasswordSent'.tr); context.showModalDialog('done'.tr, 'signinResetPasswordSent'.tr);
} }
void performAction() async { void _performAction() async {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
final username = _usernameController.value.text; final username = _usernameController.value.text;
@ -100,6 +101,8 @@ class _SignInPopupState extends State<SignInPopup> with ProtocolListener {
} }
Get.find<WebSocketProvider>().registerPushNotifications(); Get.find<WebSocketProvider>().registerPushNotifications();
autoConfigureBackgroundNotificationService();
autoStartBackgroundNotificationService();
Navigator.pop(context, true); Navigator.pop(context, true);
} }
@ -121,7 +124,7 @@ class _SignInPopupState extends State<SignInPopup> with ProtocolListener {
final uri = url.replaceFirst('solink://', ''); final uri = url.replaceFirst('solink://', '');
if (uri == 'auth?status=done') { if (uri == 'auth?status=done') {
closeInAppWebView(); closeInAppWebView();
performAction(); _performAction();
} }
} }
@ -175,19 +178,19 @@ class _SignInPopupState extends State<SignInPopup> with ProtocolListener {
), ),
onTapOutside: (_) => onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
onSubmitted: (_) => performAction(), onSubmitted: (_) => _performAction(),
), ),
const Gap(12), const Gap(12),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
TextButton( TextButton(
onPressed: _isBusy ? null : () => requestResetPassword(), onPressed: _isBusy ? null : () => _requestResetPassword(),
style: TextButton.styleFrom(foregroundColor: Colors.grey), style: TextButton.styleFrom(foregroundColor: Colors.grey),
child: Text('forgotPassword'.tr), child: Text('forgotPassword'.tr),
), ),
TextButton( TextButton(
onPressed: _isBusy ? null : () => performAction(), onPressed: _isBusy ? null : () => _performAction(),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [