Separated notification service

This commit is contained in:
Imran Remtulla
2022-08-26 23:57:09 -04:00
parent 4c4a9093e4
commit 7932b909c0
3 changed files with 140 additions and 65 deletions

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:obtainium/pages/home.dart'; import 'package:obtainium/pages/home.dart';
import 'package:obtainium/services/apps_provider.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/settings_provider.dart';
import 'package:obtainium/services/source_service.dart'; import 'package:obtainium/services/source_service.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
@@ -16,44 +17,23 @@ const String currentReleaseTag =
void bgTaskCallback() { void bgTaskCallback() {
Workmanager().executeTask((task, taskName) async { Workmanager().executeTask((task, taskName) async {
var appsProvider = AppsProvider(bg: true); var appsProvider = AppsProvider(bg: true);
await appsProvider.notify( var notificationsProvider = NotificationsProvider();
4, await notificationsProvider.notify(checkingUpdatesNotification);
'Checking for Updates',
'',
'BG_UPDATE_CHECK',
'Checking for Updates',
'Transient notification that appears when checking for updates',
important: false);
try { try {
await appsProvider.loadApps(); await appsProvider.loadApps();
List<App> updates = await appsProvider.checkUpdates(); List<App> updates = await appsProvider.checkUpdates();
if (updates.isNotEmpty) { if (updates.isNotEmpty) {
String message = updates.length == 1 notificationsProvider.notify(UpdateNotification(updates),
? '${updates[0].name} has an update.' cancelExisting: true);
: '${(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');
} }
return Future.value(true); return Future.value(true);
} catch (e) { } catch (e) {
await appsProvider.downloaderNotifications.cancel(5); notificationsProvider.notify(
await appsProvider.notify( ErrorCheckingUpdatesNotification(e.toString()),
5, cancelExisting: true);
'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);
return Future.value(false); return Future.value(false);
} finally { } finally {
await appsProvider.downloaderNotifications.cancel(4); await notificationsProvider.cancel(checkingUpdatesNotification.id);
} }
}); });
} }
@@ -70,7 +50,8 @@ void main() async {
runApp(MultiProvider( runApp(MultiProvider(
providers: [ providers: [
ChangeNotifierProvider(create: (context) => AppsProvider()), ChangeNotifierProvider(create: (context) => AppsProvider()),
ChangeNotifierProvider(create: (context) => SettingsProvider()) ChangeNotifierProvider(create: (context) => SettingsProvider()),
Provider(create: (context) => NotificationsProvider())
], ],
child: const MyApp(), child: const MyApp(),
)); ));

View File

@@ -6,9 +6,10 @@ import 'dart:io';
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/material.dart'; 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:path_provider/path_provider.dart';
import 'package:flutter_fgbg/flutter_fgbg.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:obtainium/services/source_service.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:install_plugin_v2/install_plugin_v2.dart'; import 'package:install_plugin_v2/install_plugin_v2.dart';
@@ -28,16 +29,11 @@ class AppsProvider with ChangeNotifier {
bool loadingApps = false; bool loadingApps = false;
bool gettingUpdates = 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) // Variables to keep track of the app foreground status (installs can't run in the background)
bool isForeground = true; bool isForeground = true;
StreamSubscription<FGBGType>? foregroundSubscription; StreamSubscription<FGBGType>? foregroundSubscription;
AppsProvider({bool bg = false}) { AppsProvider({bool bg = false}) {
initializeNotifs();
// Subscribe to changes in the app foreground status // Subscribe to changes in the app foreground status
foregroundSubscription = FGBGEvents.stream.listen((event) async { foregroundSubscription = FGBGEvents.stream.listen((event) async {
isForeground = event == FGBGType.foreground; isForeground = event == FGBGType.foreground;
@@ -46,30 +42,10 @@ class AppsProvider with ChangeNotifier {
loadApps(); loadApps();
} }
Future<void> initializeNotifs() async {
// Initialize the notifications service
await downloaderNotifications.initialize(const InitializationSettings(
android: AndroidInitializationSettings('ic_notification')));
}
Future<void> 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) // Given a App (assumed valid), initiate an APK download (will trigger install callback when complete)
Future<void> downloadAndInstallLatestApp( Future<void> downloadAndInstallLatestApp(
String appId, BuildContext context) async { String appId, BuildContext context) async {
var notificationsProvider = context.read<NotificationsProvider>();
if (apps[appId] == null) { if (apps[appId] == null) {
throw 'App not found'; throw 'App not found';
} }
@@ -132,14 +108,8 @@ class AppsProvider with ChangeNotifier {
} }
if (!isForeground) { if (!isForeground) {
await downloaderNotifications.cancel(1); await notificationsProvider.notify(completeInstallationNotification,
await notify( cancelExisting: true);
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');
while (await FGBGEvents.stream.first != FGBGType.foreground) { while (await FGBGEvents.stream.first != FGBGType.foreground) {
// We need to wait for the App to come to the foreground to install it // 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: // Can't try to call install plugin in a background isolate (may not have worked anyways) because of:

View File

@@ -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<App> 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<Importance, Priority> 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<void> initialize() async {
isInitialized = await notifications.initialize(const InitializationSettings(
android: AndroidInitializationSettings('ic_notification'))) ??
false;
}
Future<void> cancel(int id) async {
if (!isInitialized) {
await initialize();
}
await notifications.cancel(id);
}
Future<void> 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<void> notify(ObtainiumNotification notif,
{bool cancelExisting = false}) =>
notifyRaw(notif.id, notif.title, notif.message, notif.channelCode,
notif.channelName, notif.channelDescription, notif.importance,
cancelExisting: cancelExisting);
}