diff --git a/lib/home/views/screens/home.dart b/lib/home/views/screens/home.dart index 63a18118..107bc20b 100644 --- a/lib/home/views/screens/home.dart +++ b/lib/home/views/screens/home.dart @@ -60,6 +60,14 @@ class _HomeScreenState extends State { onNotificationCreatedMethod: NotificationController.onNotificationCreatedMethod, onNotificationDisplayedMethod: NotificationController.onNotificationDisplayedMethod, onDismissActionReceivedMethod: NotificationController.onDismissActionReceivedMethod); + + AwesomeNotifications().getInitialNotificationAction().then((initialNotification) { + print('Initial notification ${initialNotification?.payload}'); + + if (initialNotification != null) { + NotificationController.onActionReceivedMethod(initialNotification); + } + }); } @override diff --git a/lib/main.dart b/lib/main.dart index a59d086b..dfcdeeda 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,7 +9,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:invidious/app/states/app.dart'; import 'package:invidious/downloads/states/download_manager.dart'; -import 'package:invidious/foreground_service.dart'; +import 'package:invidious/workmanager.dart'; import 'package:invidious/globals.dart'; import 'package:invidious/httpOverrides.dart'; import 'package:invidious/mediaHander.dart'; @@ -54,8 +54,6 @@ Future main() async { db = await DbClient.create(); initializeNotifications(); - var initialNotification = await AwesomeNotifications().getInitialNotificationAction(); - print('Initial notification ${initialNotification?.payload}'); isTv = await isDeviceTv(); runApp(MultiBlocProvider(providers: [ diff --git a/lib/notifications/views/components/bell_icon.dart b/lib/notifications/views/components/bell_icon.dart index 4c20423b..06d36ccf 100644 --- a/lib/notifications/views/components/bell_icon.dart +++ b/lib/notifications/views/components/bell_icon.dart @@ -27,22 +27,13 @@ class BellIcon extends StatelessWidget { var locals = AppLocalizations.of(context)!; okCancelDialog(context, locals.askToEnableBackgroundServiceTitle, locals.askToEnableBackgroundServiceContent, () async { var settings = context.read(); - var res = await settings.setBackgroundNotifications(true); + await settings.setBackgroundNotifications(true); if (context.mounted) { - if (res == EnableBackGroundNotificationResponse.needBatteryOptimization) { - showBatteryOptimizationDialog(context); - } else { - cubit.toggle(); - } + cubit.toggle(); } }); } break; - case TurnOnStatus.needToEnableBatteryOptimization: - if(context.mounted) { - showBatteryOptimizationDialog(context); - } - break; default: break; } diff --git a/lib/settings/states/settings.dart b/lib/settings/states/settings.dart index ae99a42a..767d5396 100644 --- a/lib/settings/states/settings.dart +++ b/lib/settings/states/settings.dart @@ -5,10 +5,9 @@ import 'package:easy_debounce/easy_debounce.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:invidious/app/states/app.dart'; -import 'package:invidious/foreground_service.dart'; +import 'package:invidious/workmanager.dart'; import 'package:locale_names/locale_names.dart'; import 'package:logging/logging.dart'; -import 'package:optimize_battery/optimize_battery.dart'; import 'package:package_info_plus/package_info_plus.dart'; import '../../database.dart'; @@ -26,6 +25,7 @@ const String searchHistoryDefaultLength = '12'; enum EnableBackGroundNotificationResponse { ok, + notificationsNotAllowed, needBatteryOptimization; } @@ -324,28 +324,23 @@ class SettingsCubit extends Cubit { if (!b) { var state = this.state.copyWith(); state.backgroundNotifications = b; - backgroundService.invoke('stopService'); + await stopTasks(); emit(state); } else { - AwesomeNotifications().isNotificationAllowed().then((isAllowed) { - if (!isAllowed) { - // This is just a basic example. For real apps, you must show some - // friendly dialog box before call the request method. - // This is very important to not harm the user experience - AwesomeNotifications().requestPermissionToSendNotifications(); + var isAllowed = await AwesomeNotifications().isNotificationAllowed(); + if (!isAllowed) { + var allowed = await AwesomeNotifications().requestPermissionToSendNotifications(); + if (!allowed) { + return EnableBackGroundNotificationResponse.notificationsNotAllowed; } - }); - - var ignoringBatterOptimization = await OptimizeBattery.isIgnoringBatteryOptimizations(); - if (!ignoringBatterOptimization) { - return EnableBackGroundNotificationResponse.needBatteryOptimization; - } else { - var state = this.state.copyWith(); - state.backgroundNotifications = b; - backgroundService.startService(); - emit(state); - return EnableBackGroundNotificationResponse.ok; } + + var state = this.state.copyWith(); + state.backgroundNotifications = b; + await configureBackgroundService(this); + await setupTasks(this); + emit(state); + return EnableBackGroundNotificationResponse.ok; } return EnableBackGroundNotificationResponse.ok; @@ -363,7 +358,7 @@ class SettingsCubit extends Cubit { state.backgroundNotificationFrequency = i; emit(state); EasyDebounce.debounce('restarting-background-service', const Duration(seconds: 2), () { - backgroundService.invoke(restartTimerMethod); + setupTasks(this); }); } } @@ -433,9 +428,7 @@ class SettingsState { Country get country => getCountryFromCode(_get(BROWSING_COUNTRY)?.value ?? 'US'); set country(Country c) { - String code = countryCodes - .firstWhere((element) => element.name == c.name, orElse: () => country) - .code; + String code = countryCodes.firstWhere((element) => element.name == c.name, orElse: () => country).code; _set(BROWSING_COUNTRY, code); } @@ -475,12 +468,11 @@ class SettingsState { set useSearchHistory(bool b) => _set(USE_SEARCH_HISTORY, b); - List get appLayout => - (_get(APP_LAYOUT)?.value ?? '${HomeDataSource.home.name},${HomeDataSource.subscription.name},${HomeDataSource.playlist.name},${HomeDataSource.history.name}') - .split(',') - .where((element) => element.isNotEmpty) - .map((e) => HomeDataSource.values.firstWhere((element) => element.name == e)) - .toList(); + List get appLayout => (_get(APP_LAYOUT)?.value ?? '${HomeDataSource.home.name},${HomeDataSource.subscription.name},${HomeDataSource.playlist.name},${HomeDataSource.history.name}') + .split(',') + .where((element) => element.isNotEmpty) + .map((e) => HomeDataSource.values.firstWhere((element) => element.name == e)) + .toList(); set appLayout(List layout) => _set(APP_LAYOUT, layout.map((e) => e.name).join(",")); diff --git a/lib/settings/views/screens/notifications.dart b/lib/settings/views/screens/notifications.dart index 7f1ddb33..0521e9d9 100644 --- a/lib/settings/views/screens/notifications.dart +++ b/lib/settings/views/screens/notifications.dart @@ -16,9 +16,6 @@ class NotificationSettingsScreen extends StatelessWidget { enableBackgroundService(BuildContext context, bool enable) async { var settings = context.read(); var result = await settings.setBackgroundNotifications(enable); - if (result == EnableBackGroundNotificationResponse.needBatteryOptimization && context.mounted) { - showBatteryOptimizationDialog(context); - } } @override diff --git a/lib/settings/views/screens/settings.dart b/lib/settings/views/screens/settings.dart index b9d85933..36350408 100644 --- a/lib/settings/views/screens/settings.dart +++ b/lib/settings/views/screens/settings.dart @@ -131,7 +131,7 @@ class SettingsScreen extends StatelessWidget { SettingsTile.navigation( leading: const Icon(Icons.notifications_outlined), title: Text('${locals.notifications} (beta)'), - description: Text(locals.notificationsDescription), + description: Text(_.backgroundNotifications ? locals.foregroundServiceNotificationContent(_.backgroundNotificationFrequency.toString()):locals.notificationsDescription), onPressed: openNotificationSettings, ), SettingsTile.navigation( diff --git a/lib/utils.dart b/lib/utils.dart index 3b224762..a4733f99 100644 --- a/lib/utils.dart +++ b/lib/utils.dart @@ -11,7 +11,6 @@ import 'package:invidious/utils/views/tv/components/tv_button.dart'; import 'package:invidious/utils/views/tv/components/tv_overscan.dart'; import 'package:invidious/videos/models/base_video.dart'; import 'package:logging/logging.dart'; -import 'package:optimize_battery/optimize_battery.dart'; import 'package:share_plus/share_plus.dart'; import 'utils/models/country.dart'; @@ -210,13 +209,6 @@ okCancelDialog(BuildContext context, String title, String message, Function() on ); } -showBatteryOptimizationDialog(BuildContext context) { - if (!context.mounted) return; - - var locals = AppLocalizations.of(context)!; - okCancelDialog(context, locals.askForDisableBatteryOptimizationTitle, locals.askForDisableBatteryOptimizationContent, - () => OptimizeBattery.openBatteryOptimizationSettings()); -} showTvAlertdialog(BuildContext context, String title, List body) { var locals = AppLocalizations.of(context)!; diff --git a/lib/foreground_service.dart b/lib/workmanager.dart similarity index 61% rename from lib/foreground_service.dart rename to lib/workmanager.dart index 82dd7f2d..1eb8fe93 100644 --- a/lib/foreground_service.dart +++ b/lib/workmanager.dart @@ -1,12 +1,8 @@ import 'dart:async'; import 'dart:ui'; -import 'package:awesome_notifications/awesome_notifications.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter_background_service/flutter_background_service.dart'; -import 'package:flutter_background_service_android/flutter_background_service_android.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:http/http.dart'; import 'package:intl/intl.dart'; import 'package:invidious/database.dart'; import 'package:invidious/globals.dart'; @@ -14,99 +10,48 @@ import 'package:invidious/notifications/models/db/subscription_notifications.dar import 'package:invidious/settings/states/settings.dart'; import 'package:invidious/videos/models/video_in_list.dart'; import 'package:logging/logging.dart'; +import 'package:workmanager/workmanager.dart'; import 'notifications/notifications.dart'; -const restartTimerMethod = 'restart-timer'; +var log = Logger('background-task'); +const taskName = "Clipious background refresh task"; -final backgroundService = FlutterBackgroundService(); - -final log = Logger('Background service'); - -const debugMode = kDebugMode; -// const debugMode = true; - -Timer? timer; - -void configureBackgroundService(SettingsCubit settings) async { - var notif = NotificationTypes.foregroundService; - - var locals = await getLocalization(); - - await backgroundService.configure( - iosConfiguration: IosConfiguration(), - androidConfiguration: AndroidConfiguration( - onStart: onStart, - autoStart: settings.state.backgroundNotifications, - autoStartOnBoot: settings.state.backgroundNotifications, - isForegroundMode: true, - foregroundServiceNotificationId: notif.idSpace, - initialNotificationTitle: locals.foregroundServiceNotificationTitle, - initialNotificationContent: locals.foregroundServiceNotificationContent(refreshRate), - notificationChannelId: notif.id)); -} - -String get refreshRate => db.getSettings(BACKGROUND_CHECK_FREQUENCY)?.value ?? "1"; - -@pragma('vm:entry-point') -onStart(ServiceInstance service) async { - print("Background service started"); - - DartPluginRegistrant.ensureInitialized(); - - if (service is AndroidServiceInstance) { - service.on('setAsForeground').listen((event) { - service.setAsForegroundService(); - }); - - service.on('setAsBackground').listen((event) { - service.setAsBackgroundService(); - }); +Future configureBackgroundService(SettingsCubit settings) async { + if (settings.state.backgroundNotifications) { + await Workmanager().initialize(callbackDispatcher, isInDebugMode: kDebugMode); } +} - service.on('stopService').listen((event) { - print('foreground service stopped'); - service.stopSelf(); - }); - - service.on(restartTimerMethod).listen((event) async { - await _restartTimer(); - }); +Future setupTasks(SettingsCubit settings) async { + await Workmanager().registerPeriodicTask(taskName, taskName, + frequency: kDebugMode ? Duration(seconds: 15) : Duration(hours: settings.state.backgroundNotificationFrequency), + constraints: Constraints(networkType: NetworkType.connected, requiresBatteryNotLow: true)); +} - // we run the background stuff once when it starts - _backgroundCheck(); - _restartTimer(); +Future stopTasks() async { + await Workmanager().cancelAll(); } -_restartTimer() async { - print('setting background timer'); - db = await DbClient.create(); - var locals = await getLocalization(); - var title = locals.foregroundServiceNotificationTitle; - sendNotification(title, locals.foregroundServiceNotificationContent(refreshRate), type: NotificationTypes.foregroundService); - timer?.cancel(); - timer = Timer.periodic(debugMode ? const Duration(seconds: 60) : Duration(hours: int.parse(refreshRate)), (timer) { - print('foreground service running'); - _backgroundCheck(); +@pragma('vm:entry-point') // Mandatory if the App is obfuscated or using Flutter 3.1+ +void callbackDispatcher() { + Workmanager().executeTask((task, inputData) async { + log.info("Native called background task: $task"); //simpleTask will be emitted here. + if (task == taskName) { + await _backgroundCheck(); + } + return Future.value(true); }); - - db.close(); } _backgroundCheck() async { try { db = await DbClient.create(); - var locals = await getLocalization(); - var title = locals.foregroundServiceNotificationTitle; print('we have a db ${db.isClosed}'); - sendNotification(title, locals.foregroundServiceUpdatingSubscriptions, type: NotificationTypes.foregroundService); await _handleSubscriptionsNotifications(); - sendNotification(title, locals.foregroundServiceUpdatingChannels, type: NotificationTypes.foregroundService); await _handleChannelNotifications(); - sendNotification(title, locals.foregroundServiceUpdatingPlaylist, type: NotificationTypes.foregroundService); await _handlePlaylistNotifications(); - sendNotification(title, locals.foregroundServiceNotificationContent(refreshRate), type: NotificationTypes.foregroundService); service.syncHistory(); } catch (e) { @@ -143,7 +88,7 @@ _handlePlaylistNotifications() async { var locals = await getLocalization(); print('$videosToNotifyAbout videos from playlist ${n.playlistName} to notify about'); - if (debugMode || videosToNotifyAbout > 0) { + if (kDebugMode || videosToNotifyAbout > 0) { sendNotification(locals.playlistNotificationTitle(n.playlistName), locals.playlistNotificationContent(n.playlistName, videosToNotifyAbout), type: NotificationTypes.playlist, payload: { @@ -181,7 +126,7 @@ _handleChannelNotifications() async { var locals = await getLocalization(); print('$videosToNotifyAbout videos from channel ${n.channelName} to notify about'); - if (debugMode || videosToNotifyAbout > 0) { + if (kDebugMode || videosToNotifyAbout > 0) { sendNotification(locals.channelNotificationTitle(n.channelName), locals.channelNotificationContent(n.channelName, videosToNotifyAbout), type: NotificationTypes.channel, payload: {channelId: n.channelId, lastSeenVideo: videos.videos.first.videoId}, id: n.id); } @@ -223,7 +168,7 @@ _handleSubscriptionsNotifications() async { var locals = await getLocalization(); print('$videosToNotifyAbout videos to notify about'); - if (debugMode || videosToNotifyAbout > 0) { + if (kDebugMode || videosToNotifyAbout > 0) { sendNotification(locals.subscriptionNotificationTitle, locals.subscriptionNotificationContent(videosToNotifyAbout), type: NotificationTypes.subscription, payload: {lastSeenVideo: videos.first.videoId}); } diff --git a/pubspec.lock b/pubspec.lock index 3c3ba3f7..c75cdda5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -455,38 +455,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.2.0+1" - flutter_background_service: - dependency: "direct main" - description: - name: flutter_background_service - sha256: "5ec79841c3e9f3bd1885b06c5d7502d6df415cb1665e6717792cc0e51716619f" - url: "https://pub.dev" - source: hosted - version: "5.0.1" - flutter_background_service_android: - dependency: transitive - description: - name: flutter_background_service_android - sha256: a295c7604782b3723fa356679e5b14c5e0fb694d77a7299af135364fa851ee1a - url: "https://pub.dev" - source: hosted - version: "6.0.1" - flutter_background_service_ios: - dependency: transitive - description: - name: flutter_background_service_ios - sha256: ab73657535876e16abc89e40f924df3e92ad3dee83f64d187081417e824709ed - url: "https://pub.dev" - source: hosted - version: "5.0.0" - flutter_background_service_platform_interface: - dependency: transitive - description: - name: flutter_background_service_platform_interface - sha256: cd5720ff5b051d551a4734fae16683aace779bd0425e8d3f15d84a0cdcc2d8d9 - url: "https://pub.dev" - source: hosted - version: "5.0.0" flutter_bloc: dependency: "direct main" description: @@ -849,14 +817,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" - optimize_battery: - dependency: "direct main" - description: - name: optimize_battery - sha256: "4f0f974addbe54d3a705c1da5bf3a4bdae39502b1b2d2a17f9da1e558a34a4f8" - url: "https://pub.dev" - source: hosted - version: "0.0.4" package_config: dependency: transitive description: @@ -1487,6 +1447,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + workmanager: + dependency: "direct main" + description: + name: workmanager + sha256: ed13530cccd28c5c9959ad42d657cd0666274ca74c56dea0ca183ddd527d3a00 + url: "https://pub.dev" + source: hosted + version: "0.5.2" xdg_directories: dependency: transitive description: @@ -1512,5 +1480,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.0-185.0.dev <4.0.0" + dart: ">=3.1.2 <4.0.0" flutter: ">=3.13.0" diff --git a/pubspec.yaml b/pubspec.yaml index ade29e31..0dc0e078 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -78,10 +78,9 @@ dependencies: copy_with_extension: 5.0.4 pretty_bytes: 6.1.0 simple_pip_mode: ^0.8.0 - flutter_background_service: 5.0.1 - optimize_battery: 0.0.4 awesome_notifications: ^0.7.6 auto_route: ^7.8.3 + workmanager: 0.5.2 dependency_overrides: # wakelock_windows: # git: # see https://github.com/creativecreatorormaybenot/wakelock/pull/203 for updates