diff --git a/lib/main.dart b/lib/main.dart index 7de6103..b6f7352 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:obtainium/pages/home.dart'; import 'package:obtainium/services/apps_provider.dart'; +import 'package:obtainium/services/notifications_provider.dart'; import 'package:obtainium/services/settings_provider.dart'; import 'package:obtainium/services/source_service.dart'; import 'package:permission_handler/permission_handler.dart'; @@ -16,44 +17,23 @@ const String currentReleaseTag = void bgTaskCallback() { Workmanager().executeTask((task, taskName) async { var appsProvider = AppsProvider(bg: true); - await appsProvider.notify( - 4, - 'Checking for Updates', - '', - 'BG_UPDATE_CHECK', - 'Checking for Updates', - 'Transient notification that appears when checking for updates', - important: false); + var notificationsProvider = NotificationsProvider(); + await notificationsProvider.notify(checkingUpdatesNotification); try { await appsProvider.loadApps(); List updates = await appsProvider.checkUpdates(); if (updates.isNotEmpty) { - String message = updates.length == 1 - ? '${updates[0].name} has an update.' - : '${(updates.length == 2 ? '${updates[0].name} and ${updates[1].name}' : '${updates[0].name} and ${updates.length - 1} more apps')} have updates.'; - await appsProvider.downloaderNotifications.cancel(2); - await appsProvider.notify( - 2, - 'Updates Available', - message, - 'UPDATES_AVAILABLE', - 'Updates Available', - 'Notifies the user that updates are available for one or more Apps tracked by Obtainium'); + notificationsProvider.notify(UpdateNotification(updates), + cancelExisting: true); } return Future.value(true); } catch (e) { - await appsProvider.downloaderNotifications.cancel(5); - await appsProvider.notify( - 5, - 'Error Checking for Updates', - e.toString(), - 'BG_UPDATE_CHECK_ERROR', - 'Error Checking for Updates', - 'A notification that shows when background update checking fails', - important: false); + notificationsProvider.notify( + ErrorCheckingUpdatesNotification(e.toString()), + cancelExisting: true); return Future.value(false); } finally { - await appsProvider.downloaderNotifications.cancel(4); + await notificationsProvider.cancel(checkingUpdatesNotification.id); } }); } @@ -70,7 +50,8 @@ void main() async { runApp(MultiProvider( providers: [ ChangeNotifierProvider(create: (context) => AppsProvider()), - ChangeNotifierProvider(create: (context) => SettingsProvider()) + ChangeNotifierProvider(create: (context) => SettingsProvider()), + Provider(create: (context) => NotificationsProvider()) ], child: const MyApp(), )); diff --git a/lib/services/apps_provider.dart b/lib/services/apps_provider.dart index a5ec84e..9a017be 100644 --- a/lib/services/apps_provider.dart +++ b/lib/services/apps_provider.dart @@ -6,9 +6,10 @@ import 'dart:io'; import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:obtainium/services/notifications_provider.dart'; +import 'package:provider/provider.dart'; import 'package:path_provider/path_provider.dart'; import 'package:flutter_fgbg/flutter_fgbg.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:obtainium/services/source_service.dart'; import 'package:http/http.dart'; import 'package:install_plugin_v2/install_plugin_v2.dart'; @@ -28,16 +29,11 @@ class AppsProvider with ChangeNotifier { bool loadingApps = false; bool gettingUpdates = false; - // Notifications plugin for downloads - FlutterLocalNotificationsPlugin downloaderNotifications = - FlutterLocalNotificationsPlugin(); - // Variables to keep track of the app foreground status (installs can't run in the background) bool isForeground = true; StreamSubscription? foregroundSubscription; AppsProvider({bool bg = false}) { - initializeNotifs(); // Subscribe to changes in the app foreground status foregroundSubscription = FGBGEvents.stream.listen((event) async { isForeground = event == FGBGType.foreground; @@ -46,30 +42,10 @@ class AppsProvider with ChangeNotifier { loadApps(); } - Future initializeNotifs() async { - // Initialize the notifications service - await downloaderNotifications.initialize(const InitializationSettings( - android: AndroidInitializationSettings('ic_notification'))); - } - - Future notify(int id, String title, String message, String channelCode, - String channelName, String channelDescription, - {bool important = true}) { - return downloaderNotifications.show( - id, - title, - message, - NotificationDetails( - android: AndroidNotificationDetails(channelCode, channelName, - channelDescription: channelDescription, - importance: important ? Importance.max : Importance.min, - priority: important ? Priority.max : Priority.min, - groupKey: 'dev.imranr.obtainium.$channelCode'))); - } - // Given a App (assumed valid), initiate an APK download (will trigger install callback when complete) Future downloadAndInstallLatestApp( String appId, BuildContext context) async { + var notificationsProvider = context.read(); if (apps[appId] == null) { throw 'App not found'; } @@ -132,14 +108,8 @@ class AppsProvider with ChangeNotifier { } if (!isForeground) { - await downloaderNotifications.cancel(1); - await notify( - 1, - 'Complete App Installation', - 'Obtainium must be open to install Apps', - 'COMPLETE_INSTALL', - 'Complete App Installation', - 'Asks the user to return to Obtanium to finish installing an App'); + await notificationsProvider.notify(completeInstallationNotification, + cancelExisting: true); while (await FGBGEvents.stream.first != FGBGType.foreground) { // We need to wait for the App to come to the foreground to install it // Can't try to call install plugin in a background isolate (may not have worked anyways) because of: diff --git a/lib/services/notifications_provider.dart b/lib/services/notifications_provider.dart new file mode 100644 index 0000000..efadf1e --- /dev/null +++ b/lib/services/notifications_provider.dart @@ -0,0 +1,124 @@ +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:obtainium/services/source_service.dart'; + +class ObtainiumNotification { + late int id; + late String title; + late String message; + late String channelCode; + late String channelName; + late String channelDescription; + Importance importance; + + ObtainiumNotification(this.id, this.title, this.message, this.channelCode, + this.channelName, this.channelDescription, this.importance); +} + +class UpdateNotification extends ObtainiumNotification { + UpdateNotification(List updates) + : super( + 2, + 'Updates Available', + '', + 'UPDATES_AVAILABLE', + 'Updates Available', + 'Notifies the user that updates are available for one or more Apps tracked by Obtainium', + Importance.max) { + message = updates.length == 1 + ? '${updates[0].name} has an update.' + : '${(updates.length == 2 ? '${updates[0].name} and ${updates[1].name}' : '${updates[0].name} and ${updates.length - 1} more apps')} have updates.'; + } +} + +class ErrorCheckingUpdatesNotification extends ObtainiumNotification { + ErrorCheckingUpdatesNotification(String error) + : super( + 5, + 'Error Checking for Updates', + error, + 'BG_UPDATE_CHECK_ERROR', + 'Error Checking for Updates', + 'A notification that shows when background update checking fails', + Importance.high); +} + +final completeInstallationNotification = ObtainiumNotification( + 1, + 'Complete App Installation', + 'Obtainium must be open to install Apps', + 'COMPLETE_INSTALL', + 'Complete App Installation', + 'Asks the user to return to Obtanium to finish installing an App', + Importance.max); + +final checkingUpdatesNotification = ObtainiumNotification( + 4, + 'Checking for Updates', + '', + 'BG_UPDATE_CHECK', + 'Checking for Updates', + 'Transient notification that appears when checking for updates', + Importance.min); + +class NotificationsProvider { + FlutterLocalNotificationsPlugin notifications = + FlutterLocalNotificationsPlugin(); + + bool isInitialized = false; + + Map importanceToPriority = { + Importance.defaultImportance: Priority.defaultPriority, + Importance.high: Priority.high, + Importance.low: Priority.low, + Importance.max: Priority.max, + Importance.min: Priority.min, + Importance.none: Priority.min, + Importance.unspecified: Priority.defaultPriority + }; + + Future initialize() async { + isInitialized = await notifications.initialize(const InitializationSettings( + android: AndroidInitializationSettings('ic_notification'))) ?? + false; + } + + Future cancel(int id) async { + if (!isInitialized) { + await initialize(); + } + await notifications.cancel(id); + } + + Future notifyRaw( + int id, + String title, + String message, + String channelCode, + String channelName, + String channelDescription, + Importance importance, + {bool cancelExisting = false}) async { + if (cancelExisting) { + await cancel(id); + } + if (!isInitialized) { + await initialize(); + } + await notifications.show( + id, + title, + message, + NotificationDetails( + android: AndroidNotificationDetails(channelCode, channelName, + channelDescription: channelDescription, + importance: importance, + priority: importanceToPriority[importance]!, + groupKey: 'dev.imranr.obtainium.$channelCode'))); + } + + Future notify(ObtainiumNotification notif, + {bool cancelExisting = false}) => + notifyRaw(notif.id, notif.title, notif.message, notif.channelCode, + notif.channelName, notif.channelDescription, notif.importance, + cancelExisting: cancelExisting); +}