diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index e2a2e3d..fe3e2c9 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -3,6 +3,7 @@
+
@@ -84,6 +85,11 @@
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
+
+
_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',
+ ),
+ ),
+ );
+ }
+ },
+ );
+}
+
Future _initializePlatformComponents() async {
if (!PlatformInfo.isWeb) {
await protocolHandler.register('solink');
@@ -144,5 +228,7 @@ class SolianApp extends StatelessWidget {
Get.lazyPut(() => LinkExpandProvider());
Get.lazyPut(() => DailySignProvider());
Get.lazyPut(() => LastReadProvider());
+
+ Get.find().requestPermissions();
}
}
diff --git a/lib/providers/websocket.dart b/lib/providers/websocket.dart
index ff4a4ca..0c804da 100644
--- a/lib/providers/websocket.dart
+++ b/lib/providers/websocket.dart
@@ -5,7 +5,9 @@ import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:firebase_messaging/firebase_messaging.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/exceptions/request.dart';
import 'package:solian/models/notification.dart';
import 'package:solian/models/packet.dart';
@@ -29,20 +31,46 @@ class WebSocketProvider extends GetxController {
@override
onInit() {
- FirebaseMessaging.instance
- .requestPermission(
- alert: true,
- announcement: true,
- carPlay: true,
- badge: true,
- sound: true)
- .then((status) {
- notifyPrefetch();
- });
+ notifyPrefetch();
super.onInit();
}
+ void requestPermissions() {
+ try {
+ FirebaseMessaging.instance.requestPermission(
+ alert: true,
+ announcement: true,
+ carPlay: true,
+ badge: true,
+ sound: true);
+ } catch (_) {
+ // When firebase isn't initialized (background service)
+ FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
+ FlutterLocalNotificationsPlugin();
+ flutterLocalNotificationsPlugin
+ .resolvePlatformSpecificImplementation<
+ AndroidFlutterLocalNotificationsPlugin>()
+ ?.requestNotificationsPermission();
+ flutterLocalNotificationsPlugin
+ .resolvePlatformSpecificImplementation<
+ IOSFlutterLocalNotificationsPlugin>()
+ ?.requestPermissions(
+ alert: true,
+ badge: true,
+ sound: true,
+ );
+ flutterLocalNotificationsPlugin
+ .resolvePlatformSpecificImplementation<
+ MacOSFlutterLocalNotificationsPlugin>()
+ ?.requestPermissions(
+ alert: true,
+ badge: true,
+ sound: true,
+ );
+ }
+ }
+
Future connect({noRetry = false}) async {
if (isConnected.value) {
return;
@@ -120,6 +148,12 @@ class WebSocketProvider extends GetxController {
}
Future registerPushNotifications() async {
+ final prefs = await SharedPreferences.getInstance();
+ if (prefs.getBool('service_background_notification') == true) {
+ log('Background notification service has been enabled, skip register push notifications');
+ return;
+ }
+
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) return;
diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart
index b2bf35c..955730a 100644
--- a/lib/screens/settings.dart
+++ b/lib/screens/settings.dart
@@ -3,6 +3,7 @@ import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:solian/exts.dart';
+import 'package:solian/platform.dart';
import 'package:solian/providers/database/database.dart';
import 'package:solian/providers/theme_switcher.dart';
import 'package:solian/router.dart';
@@ -16,7 +17,7 @@ class SettingScreen extends StatefulWidget {
}
class _SettingScreenState extends State {
- late final SharedPreferences _prefs;
+ SharedPreferences? _prefs;
Widget _buildCaptionHeader(String title) {
return Container(
@@ -42,7 +43,7 @@ class _SettingScreenState extends State {
seedColor: color,
),
);
- _prefs.setInt('global_theme_color', color.value);
+ _prefs?.setInt('global_theme_color', color.value);
context.clearSnackbar();
context.showSnackbar('themeColorApplied'.tr);
},
@@ -62,6 +63,9 @@ class _SettingScreenState extends State {
super.initState();
SharedPreferences.getInstance().then((inst) {
_prefs = inst;
+ if (mounted) {
+ setState(() {});
+ }
});
}
@@ -81,6 +85,35 @@ class _SettingScreenState extends State {
.toList(),
).paddingSymmetric(horizontal: 12, vertical: 8),
),
+ _buildCaptionHeader('notification'.tr),
+ Tooltip(
+ message: 'settingsNotificationBgServiceDesc'.tr,
+ child: CheckboxListTile(
+ contentPadding: const EdgeInsets.symmetric(horizontal: 22),
+ secondary: const Icon(Icons.system_security_update_warning),
+ enabled: PlatformInfo.isAndroid,
+ title: Text('settingsNotificationBgService'.tr),
+ subtitle: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text('holdToSeeDetail'.tr),
+ Text(
+ 'needRestartToApply'.tr,
+ style: const TextStyle(fontWeight: FontWeight.bold),
+ )
+ ],
+ ),
+ value:
+ _prefs?.getBool('service_background_notification') ?? false,
+ onChanged: (value) {
+ _prefs
+ ?.setBool('service_background_notification', value ?? false)
+ .then((_) {
+ setState(() {});
+ });
+ },
+ ),
+ ),
_buildCaptionHeader('more'.tr),
ListTile(
leading: const Icon(Icons.delete_sweep),
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
index 573eb29..9c19bdd 100644
--- a/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -13,6 +13,7 @@ import firebase_analytics
import firebase_core
import firebase_crashlytics
import firebase_messaging
+import flutter_local_notifications
import flutter_secure_storage_macos
import flutter_webrtc
import gal
@@ -41,6 +42,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
FLTFirebaseCrashlyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCrashlyticsPlugin"))
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
+ FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin"))
diff --git a/pubspec.lock b/pubspec.lock
index 5be538d..3a8d937 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -635,6 +635,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.5.0"
+ flutter_background_service:
+ dependency: "direct main"
+ description:
+ name: flutter_background_service
+ sha256: d32f078ec57647c9cfd6e1a8da9297f7d8f021d4dcc204a35aaad2cdbfe255f0
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.0.10"
+ flutter_background_service_android:
+ dependency: transitive
+ description:
+ name: flutter_background_service_android
+ sha256: "39da42dddf877beeef82bc2583130d8bedb4d0765e99ca9e7b4a32e8c6abd239"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.2.7"
+ flutter_background_service_ios:
+ dependency: transitive
+ description:
+ name: flutter_background_service_ios
+ sha256: "6037ffd45c4d019dab0975c7feb1d31012dd697e25edc05505a4a9b0c7dc9fba"
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.0.3"
+ flutter_background_service_platform_interface:
+ dependency: transitive
+ description:
+ name: flutter_background_service_platform_interface
+ sha256: ca74aa95789a8304f4d3f57f07ba404faa86bed6e415f83e8edea6ad8b904a41
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.1.2"
flutter_cache_manager:
dependency: "direct main"
description:
@@ -715,6 +747,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.0"
+ flutter_local_notifications:
+ dependency: "direct main"
+ description:
+ name: flutter_local_notifications
+ sha256: c500d5d9e7e553f06b61877ca6b9c8b92c570a4c8db371038702e8ce57f8a50f
+ url: "https://pub.dev"
+ source: hosted
+ version: "17.2.2"
+ flutter_local_notifications_linux:
+ dependency: transitive
+ description:
+ name: flutter_local_notifications_linux
+ sha256: c49bd06165cad9beeb79090b18cd1eb0296f4bf4b23b84426e37dd7c027fc3af
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.0.1"
+ flutter_local_notifications_platform_interface:
+ dependency: transitive
+ description:
+ name: flutter_local_notifications_platform_interface
+ sha256: "85f8d07fe708c1bdcf45037f2c0109753b26ae077e9d9e899d55971711a4ea66"
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.2.0"
flutter_markdown:
dependency: "direct main"
description:
@@ -1930,6 +1986,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.7.0"
+ 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/pubspec.yaml b/pubspec.yaml
index 3626806..49f261d 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -79,6 +79,8 @@ dependencies:
drift_flutter: ^0.2.0
very_good_infinite_list: ^0.8.0
path_provider: ^2.1.4
+ flutter_background_service: ^5.0.10
+ flutter_local_notifications: ^17.2.2
dev_dependencies:
flutter_test: