✨ Android background notification service
This commit is contained in:
parent
ad66c11593
commit
a487924300
@ -3,6 +3,7 @@
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
@ -84,6 +85,11 @@
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
|
||||
|
||||
<service
|
||||
android:name="id.flutter.flutter_background_service.BackgroundService"
|
||||
android:foregroundServiceType="remoteMessaging"
|
||||
/>
|
||||
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
|
@ -54,6 +54,8 @@
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"settings": "Settings",
|
||||
"settingsNotificationBgService": "Background Notification Service",
|
||||
"settingsNotificationBgServiceDesc": "A notification service is always installed on the device, so that some devices that do not support push notifications can receive notifications in the background. When this feature is enabled, push notifications will not be registered with the server, and you will always appear to be online in the eyes of others (except for invisible). You may need to turn off power and traffic optimization in the settings.",
|
||||
"search": "Search",
|
||||
"post": "Post",
|
||||
"article": "Article",
|
||||
@ -393,5 +395,7 @@
|
||||
"userLevel11": "Legend",
|
||||
"userLevel12": "Mythic",
|
||||
"userLevel13": "Immortal",
|
||||
"postBrowsingIn": "Browsing in @region"
|
||||
"postBrowsingIn": "Browsing in @region",
|
||||
"needRestartToApply": "Restart the application to take effect",
|
||||
"holdToSeeDetail": "Long press / Mouse hover to see detail"
|
||||
}
|
||||
|
@ -14,6 +14,8 @@
|
||||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
"settings": "设置",
|
||||
"settingsNotificationBgService": "常驻通知服务",
|
||||
"settingsNotificationBgServiceDesc": "在设备常驻一个通知服务,使得部分不支持推送通知的设备可以在后台收到通知;启用该功能的情况下不会向服务器注册推送通知,并且你会始终在他人眼中成为在线(隐身除外);可能需要在设置中关闭电量与流量优化。",
|
||||
"page": "页面",
|
||||
"draft": "草稿",
|
||||
"draftSave": "存为草稿",
|
||||
@ -394,5 +396,7 @@
|
||||
"userLevel11": "名垂千古",
|
||||
"userLevel12": "独占鳌头",
|
||||
"userLevel13": "万古流芳",
|
||||
"postBrowsingIn": "浏览 @region 内的帖子中"
|
||||
"postBrowsingIn": "浏览 @region 内的帖子中",
|
||||
"needRestartToApply": "需要重启应用来生效",
|
||||
"holdToSeeDetail": "长按 / 鼠标悬浮来查看详情"
|
||||
}
|
||||
|
@ -2,14 +2,18 @@ import 'dart:ui';
|
||||
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
||||
import 'package:flutter/material.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/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';
|
||||
@ -41,6 +45,7 @@ void main() async {
|
||||
await Future.wait([
|
||||
_initializeFirebase(),
|
||||
_initializePlatformComponents(),
|
||||
_initializeBackgroundNotificationService(),
|
||||
]);
|
||||
|
||||
GoRouter.optionURLReflectsImperativeAPIs = true;
|
||||
@ -64,6 +69,85 @@ Future<void> _initializeFirebase() async {
|
||||
};
|
||||
}
|
||||
|
||||
Future<void> _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<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 {
|
||||
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<WebSocketProvider>().requestPermissions();
|
||||
}
|
||||
}
|
||||
|
@ -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<void> connect({noRetry = false}) async {
|
||||
if (isConnected.value) {
|
||||
return;
|
||||
@ -120,6 +148,12 @@ class WebSocketProvider extends GetxController {
|
||||
}
|
||||
|
||||
Future<void> 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;
|
||||
|
||||
|
@ -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<SettingScreen> {
|
||||
late final SharedPreferences _prefs;
|
||||
SharedPreferences? _prefs;
|
||||
|
||||
Widget _buildCaptionHeader(String title) {
|
||||
return Container(
|
||||
@ -42,7 +43,7 @@ class _SettingScreenState extends State<SettingScreen> {
|
||||
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<SettingScreen> {
|
||||
super.initState();
|
||||
SharedPreferences.getInstance().then((inst) {
|
||||
_prefs = inst;
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -81,6 +85,35 @@ class _SettingScreenState extends State<SettingScreen> {
|
||||
.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),
|
||||
|
@ -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"))
|
||||
|
64
pubspec.lock
64
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:
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user