diff --git a/lib/main.dart b/lib/main.dart index a8f8781..17864a7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -26,9 +26,23 @@ void bgTaskCallback() { await notificationsProvider .cancel(ErrorCheckingUpdatesNotification('').id); await appsProvider.loadApps(); - List updates = await appsProvider.checkUpdates(); - if (updates.isNotEmpty) { - notificationsProvider.notify(UpdateNotification(updates), + // List existingUpdateIds = // TODO: Uncomment this and below when it works + // appsProvider.getExistingUpdates(installedOnly: true); + List newUpdates = await appsProvider.checkUpdates(); + // List silentlyUpdated = await appsProvider + // .downloadAndInstallLatestApp( + // [...newUpdates.map((e) => e.id), ...existingUpdateIds], null); + // if (silentlyUpdated.isNotEmpty) { + // newUpdates + // .where((element) => !silentlyUpdated.contains(element.id)) + // .toList(); + // notificationsProvider.notify( + // SilentUpdateNotification( + // silentlyUpdated.map((e) => appsProvider.apps[e]!.app).toList()), + // cancelExisting: true); + // } + if (newUpdates.isNotEmpty) { + notificationsProvider.notify(UpdateNotification(newUpdates), cancelExisting: true); } return Future.value(true); @@ -85,7 +99,7 @@ class MyApp extends StatelessWidget { if (settingsProvider.updateInterval > 0) { Workmanager().registerPeriodicTask('bg-update-check', 'bg-update-check', frequency: Duration(minutes: settingsProvider.updateInterval), - initialDelay: Duration(minutes: settingsProvider.updateInterval), + // initialDelay: Duration(minutes: settingsProvider.updateInterval), constraints: Constraints(networkType: NetworkType.connected), existingWorkPolicy: ExistingWorkPolicy.replace); } else { diff --git a/lib/pages/app.dart b/lib/pages/app.dart index 49cacbd..96236cf 100644 --- a/lib/pages/app.dart +++ b/lib/pages/app.dart @@ -25,8 +25,8 @@ class _AppPageState extends State { var sourceProvider = SourceProvider(); AppInMemory? app = appsProvider.apps[widget.appId]; var source = app != null ? sourceProvider.getSource(app.app.url) : null; - if (!appsProvider.areDownloadsRunning()) { - appsProvider.getUpdate(app!.app.id).catchError((e) { + if (!appsProvider.areDownloadsRunning() && app != null) { + appsProvider.getUpdate(app.app.id).catchError((e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(e.toString())), ); @@ -221,7 +221,7 @@ class _AppPageState extends State { .downloadAndInstallLatestApp( [app!.app.id], context).then((res) { - if (res && mounted) { + if (res.isNotEmpty && mounted) { Navigator.of(context).pop(); } }); diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index ca35304..3bb4317 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -98,6 +98,7 @@ class AppsProvider with ChangeNotifier { .isNotEmpty; Future canInstallSilently(App app) async { + // TODO: This is unreliable - try to get from OS var osInfo = await DeviceInfoPlugin().androidInfo; return app.installedVersion != null && osInfo.version.sdkInt! >= 30 && @@ -131,19 +132,20 @@ class AppsProvider with ChangeNotifier { // Given a list of AppIds, uses stored info about the apps to download APKs and install them // If the APKs can be installed silently, they are + // If no BuildContext is provided, apps that require user interaction are ignored // If user input is needed and the App is in the background, a notification is sent to get the user's attention - // Returns upon successful download, regardless of installation result - Future downloadAndInstallLatestApp( - List appIds, BuildContext context) async { + // Returns an array of Ids for Apps that were successfully downloaded, regardless of installation result + Future> downloadAndInstallLatestApp( + List appIds, BuildContext? context) async { Map appsToInstall = {}; for (var id in appIds) { if (apps[id] == null) { throw 'App not found'; } - // If the App has more than one APK, the user should pick one + // If the App has more than one APK, the user should pick one (if context provided) String? apkUrl = apps[id]!.app.apkUrls[apps[id]!.app.preferredApkIndex]; - if (apps[id]!.app.apkUrls.length > 1) { + if (apps[id]!.app.apkUrls.length > 1 && context != null) { // ignore: use_build_context_synchronously await askUserToReturnToForeground(context); apkUrl = await showDialog( @@ -152,9 +154,10 @@ class AppsProvider with ChangeNotifier { return APKPicker(app: apps[id]!.app, initVal: apkUrl); }); } - // If the picked APK comes from an origin different from the source, get user confirmation + // If the picked APK comes from an origin different from the source, get user confirmation (if context provided) if (apkUrl != null && - Uri.parse(apkUrl).origin != Uri.parse(apps[id]!.app.url).origin) { + Uri.parse(apkUrl).origin != Uri.parse(apps[id]!.app.url).origin && + context != null) { // ignore: use_build_context_synchronously await askUserToReturnToForeground(context); if (await showDialog( @@ -173,7 +176,11 @@ class AppsProvider with ChangeNotifier { apps[id]!.app.preferredApkIndex = urlInd; await saveApp(apps[id]!.app); } - appsToInstall.putIfAbsent(id, () => apkUrl!); + if (context != null || + (await canInstallSilently(apps[id]!.app) && + apps[id]!.app.apkUrls.length == 1)) { + appsToInstall.putIfAbsent(id, () => apkUrl!); + } } } @@ -195,13 +202,15 @@ class AppsProvider with ChangeNotifier { await installApk(u); } - for (var i in regularInstalls) { - // ignore: use_build_context_synchronously - await askUserToReturnToForeground(context); - await installApk(i); + if (context != null) { + for (var i in regularInstalls) { + // ignore: use_build_context_synchronously + await askUserToReturnToForeground(context); + await installApk(i); + } } - return downloadedFiles.isNotEmpty; + return downloadedFiles.map((e) => e.appId).toList(); } Future getAppsDir() async { @@ -300,12 +309,13 @@ class AppsProvider with ChangeNotifier { return updates; } - List getExistingUpdates() { + List getExistingUpdates({bool installedOnly = false}) { List updateAppIds = []; List appIds = apps.keys.toList(); for (int i = 0; i < appIds.length; i++) { App? app = apps[appIds[i]]!.app; - if (app.installedVersion != app.latestVersion) { + if (app.installedVersion != app.latestVersion && + (app.installedVersion != null || !installedOnly)) { updateAppIds.add(app.id); } } diff --git a/lib/providers/notifications_provider.dart b/lib/providers/notifications_provider.dart index de0fdd6..db5b196 100644 --- a/lib/providers/notifications_provider.dart +++ b/lib/providers/notifications_provider.dart @@ -33,6 +33,22 @@ class UpdateNotification extends ObtainiumNotification { } } +class SilentUpdateNotification extends ObtainiumNotification { + SilentUpdateNotification(List updates) + : super( + 3, + 'Apps Updated', + '', + 'APPS_UPDATED', + 'Apps Updated', + 'Notifies the user that updates to one or more Apps were applied in the background', + Importance.defaultImportance) { + message = updates.length == 1 + ? '${updates[0].name} was updated to ${updates[0].latestVersion}.' + : '${(updates.length == 2 ? '${updates[0].name} and ${updates[1].name}' : '${updates[0].name} and ${updates.length - 1} more apps')} were updated.'; + } +} + class ErrorCheckingUpdatesNotification extends ObtainiumNotification { ErrorCheckingUpdatesNotification(String error) : super( diff --git a/pubspec.lock b/pubspec.lock index 0255f80..3219b7b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,7 +7,7 @@ packages: name: animations url: "https://pub.dartlang.org" source: hosted - version: "2.0.4" + version: "2.0.5" archive: dependency: transitive description: @@ -175,7 +175,7 @@ packages: name: file_picker url: "https://pub.dartlang.org" source: hosted - version: "5.1.0" + version: "5.2.0" flutter: dependency: "direct main" description: flutter @@ -215,7 +215,7 @@ packages: name: flutter_local_notifications url: "https://pub.dartlang.org" source: hosted - version: "10.0.0" + version: "11.0.1" flutter_local_notifications_linux: dependency: transitive description: @@ -295,7 +295,7 @@ packages: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "4.6.0" + version: "4.7.0" lints: dependency: transitive description: @@ -393,7 +393,7 @@ packages: name: permission_handler url: "https://pub.dartlang.org" source: hosted - version: "10.0.0" + version: "10.0.1" permission_handler_android: dependency: transitive description: @@ -414,7 +414,7 @@ packages: name: permission_handler_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "3.7.0" + version: "3.7.1" permission_handler_windows: dependency: transitive description: @@ -538,7 +538,7 @@ packages: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" string_scanner: dependency: transitive description: @@ -566,7 +566,7 @@ packages: name: timezone url: "https://pub.dartlang.org" source: hosted - version: "0.8.0" + version: "0.9.0" typed_data: dependency: transitive description: @@ -636,7 +636,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.3" + version: "2.1.4" webview_flutter: dependency: "direct main" description: @@ -650,7 +650,7 @@ packages: name: webview_flutter_android url: "https://pub.dartlang.org" source: hosted - version: "2.10.1" + version: "2.10.2" webview_flutter_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 919ce6f..057dad2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,7 +38,7 @@ dependencies: cupertino_icons: ^1.0.5 path_provider: ^2.0.11 flutter_fgbg: ^0.2.0 # Try removing reliance on this - flutter_local_notifications: ^10.0.0 + flutter_local_notifications: ^11.0.1 provider: ^6.0.3 http: ^0.13.5 webview_flutter: ^3.0.4