diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..c915a75 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..d739ed1 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..f279277 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..6ceb87e Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..652ac54 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/assets/graphics/icon.png b/assets/graphics/icon.png new file mode 100644 index 0000000..bbb75cf Binary files /dev/null and b/assets/graphics/icon.png differ diff --git a/lib/main.dart b/lib/main.dart index 089c1fb..a1494bc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart'; // ignore: implementation_imports import 'package:easy_localization/src/localization.dart'; -const String currentVersion = '0.13.4'; +const String currentVersion = '0.13.5'; const String currentReleaseTag = 'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index 9966f9c..173beb2 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -322,28 +322,28 @@ class AppsPageState extends State { getLoadingWidgets() { return [ - if (appsProvider.loadingApps || listedApps.isEmpty) + if (listedApps.isEmpty) SliverFillRemaining( child: Center( - child: appsProvider.loadingApps - ? const CircularProgressIndicator() - : Text( - appsProvider.apps.isEmpty - ? tr('noApps') - : tr('noAppsForFilter'), - style: Theme.of(context).textTheme.headlineMedium, - textAlign: TextAlign.center, - ))), - if (refreshingSince != null) + child: Text( + appsProvider.apps.isEmpty ? tr('noApps') : tr('noAppsForFilter'), + style: Theme.of(context).textTheme.headlineMedium, + textAlign: TextAlign.center, + ))), + if (refreshingSince != null || appsProvider.loadingApps) SliverToBoxAdapter( child: LinearProgressIndicator( - value: appsProvider - .getAppValues() - .where((element) => !(element.app.lastUpdateCheck - ?.isBefore(refreshingSince!) ?? - true)) - .length / - (appsProvider.apps.isNotEmpty ? appsProvider.apps.length : 1), + value: appsProvider.loadingApps + ? null + : appsProvider + .getAppValues() + .where((element) => !(element.app.lastUpdateCheck + ?.isBefore(refreshingSince!) ?? + true)) + .length / + (appsProvider.apps.isNotEmpty + ? appsProvider.apps.length + : 1), ), ) ]; diff --git a/lib/pages/home.dart b/lib/pages/home.dart index a830b1a..1e74fcc 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -27,6 +27,7 @@ class NavigationPageItem { class _HomePageState extends State { List selectedIndexHistory = []; int prevAppCount = -1; + bool prevIsLoading = true; List pages = [ NavigationPageItem(tr('appsString'), Icons.apps, @@ -64,13 +65,15 @@ class _HomePageState extends State { } } - if (prevAppCount >= 0 && + if (!prevIsLoading && + prevAppCount >= 0 && appsProvider.apps.length > prevAppCount && selectedIndexHistory.isNotEmpty && selectedIndexHistory.last == 1) { switchToPage(0); } prevAppCount = appsProvider.apps.length; + prevIsLoading = appsProvider.loadingApps; return WillPopScope( child: Scaffold( diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 38f10fe..9cc7b64 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -108,6 +108,7 @@ class AppsProvider with ChangeNotifier { bool isForeground = true; late Stream? foregroundStream; late StreamSubscription? foregroundSubscription; + late Directory APKDir; Iterable getAppValues() => apps.values.map((a) => a.deepCopy()); @@ -116,21 +117,29 @@ class AppsProvider with ChangeNotifier { foregroundStream = FGBGEvents.stream.asBroadcastStream(); foregroundSubscription = foregroundStream?.listen((event) async { isForeground = event == FGBGType.foreground; - if (isForeground) await loadApps(); + if (isForeground) await refreshInstallStatuses(); }); () async { + var cacheDirs = await getExternalCacheDirectories(); + if (cacheDirs?.isNotEmpty ?? false) { + APKDir = cacheDirs!.first; + } else { + APKDir = + Directory('${(await getExternalStorageDirectory())!.path}/apks'); + if (!APKDir.existsSync()) { + APKDir.createSync(); + } + } // Load Apps into memory (in background, this is done later instead of in the constructor) await loadApps(); // Delete any partial APKs var cutoff = DateTime.now().subtract(const Duration(days: 7)); - (await getExternalCacheDirectories()) - ?.first - .listSync() + APKDir.listSync() .where((element) => element.path.endsWith('.part') || element.statSync().modified.isBefore(cutoff)) .forEach((partialApk) { - partialApk.delete(); + partialApk.delete(recursive: true); }); }(); } @@ -138,7 +147,7 @@ class AppsProvider with ChangeNotifier { Future downloadFile( String url, String fileNameNoExt, Function? onProgress, {bool useExisting = true, Map? headers}) async { - var destDir = (await getExternalCacheDirectories())!.first.path; + var destDir = APKDir.path; var req = Request('GET', Uri.parse(url)); if (headers != null) { req.headers.addAll(headers); @@ -154,7 +163,7 @@ class AppsProvider with ChangeNotifier { if (!(downloadedFile.existsSync() && useExisting)) { File tempDownloadedFile = File('${downloadedFile.path}.part'); if (tempDownloadedFile.existsSync()) { - tempDownloadedFile.deleteSync(); + tempDownloadedFile.deleteSync(recursive: true); } var length = response.contentLength; var received = 0; @@ -174,7 +183,7 @@ class AppsProvider with ChangeNotifier { onProgress(progress); } if (response.statusCode != 200) { - tempDownloadedFile.deleteSync(); + tempDownloadedFile.deleteSync(recursive: true); throw response.reasonPhrase ?? tr('unexpectedError'); } tempDownloadedFile.renameSync(downloadedFile.path); @@ -266,7 +275,7 @@ class AppsProvider with ChangeNotifier { if (fn.startsWith('${app.id}-') && FileSystemEntity.isFileSync(file.path) && file.path != downloadedFile.path) { - file.delete(); + file.delete(recursive: true); } } if (isAPK) { @@ -349,7 +358,7 @@ class AppsProvider with ChangeNotifier { silent: silent); } if (somethingInstalled) { - dir.file.delete(); + dir.file.delete(recursive: true); } } finally { dir.extracted.delete(recursive: true); @@ -379,7 +388,7 @@ class AppsProvider with ChangeNotifier { installed = true; apps[file.appId]!.app.installedVersion = apps[file.appId]!.app.latestVersion; - file.file.delete(); + file.file.delete(recursive: true); } await saveApps([apps[file.appId]!.app]); return installed; @@ -703,41 +712,30 @@ class AppsProvider with ChangeNotifier { } loadingApps = true; notifyListeners(); - List newApps = (await getAppsDir()) - .listSync() - .where((item) => item.path.toLowerCase().endsWith('.json')) - .map((e) { - try { - return App.fromJson(jsonDecode(File(e.path).readAsStringSync())); - } catch (err) { - if (err is FormatException) { - logs.add('Corrupt JSON when loading App (will be ignored): $e'); - e.renameSync('${e.path}.corrupt'); - return App( - '', '', '', '', '', '', [], 0, {}, DateTime.now(), false); - } else { - rethrow; - } - } - }) - .where((element) => element.id.isNotEmpty) - .toList(); - var idsToDelete = apps.values - .map((e) => e.app.id) - .toSet() - .difference(newApps.map((e) => e.id).toSet()); - for (var id in idsToDelete) { - apps.remove(id); - } var sp = SourceProvider(); List> errors = []; - for (int i = 0; i < newApps.length; i++) { - var info = await getInstalledInfo(newApps[i].id); + List newApps = (await getAppsDir()) + .listSync() + .where((item) => item.path.toLowerCase().endsWith('.json')) + .toList(); + for (var e in newApps) { try { - sp.getSource(newApps[i].url, overrideSource: newApps[i].overrideSource); - apps[newApps[i].id] = AppInMemory(newApps[i], null, info); - } catch (e) { - errors.add([newApps[i].id, newApps[i].finalName, e.toString()]); + var app = App.fromJson(jsonDecode(File(e.path).readAsStringSync())); + try { + var info = await getInstalledInfo(app.id); + sp.getSource(app.url, overrideSource: app.overrideSource); + apps[app.id] = AppInMemory(app, null, info); + notifyListeners(); + } catch (e) { + errors.add([app.id, app.finalName, e.toString()]); + } + } catch (err) { + if (err is FormatException) { + logs.add('Corrupt JSON when loading App (will be ignored): $e'); + e.renameSync('${e.path}.corrupt'); + } else { + rethrow; + } } } if (errors.isNotEmpty) { @@ -747,6 +745,10 @@ class AppsProvider with ChangeNotifier { } loadingApps = false; notifyListeners(); + refreshInstallStatuses(); + } + + Future refreshInstallStatuses() async { if (await doesInstalledAppsPluginWork()) { List modifiedApps = []; for (var app in apps.values) { @@ -790,17 +792,17 @@ class AppsProvider with ChangeNotifier { } Future removeApps(List appIds) async { - var apkFiles = (await getExternalCacheDirectories())?.first.listSync(); + var apkFiles = APKDir.listSync(); for (var appId in appIds) { File file = File('${(await getAppsDir()).path}/$appId.json'); if (file.existsSync()) { - file.deleteSync(); + file.deleteSync(recursive: true); } apkFiles - ?.where( + .where( (element) => element.path.split('/').last.startsWith('$appId-')) .forEach((element) { - element.delete(); + element.delete(recursive: true); }); if (apps.containsKey(appId)) { apps.remove(appId); diff --git a/pubspec.lock b/pubspec.lock index d08183c..d45df69 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: "direct main" description: name: android_alarm_manager_plus - sha256: ed5fb34f8befc382fb4800b02aa86a34d279b911e1c05752f702d12fcfe26e0e + sha256: "80f963d47cb7ab0818144c7b0668aea4c038f9cb8626626e89a4ea77375defb7" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.0.1" android_intent_plus: dependency: "direct main" description: name: android_intent_plus - sha256: f79fbb8ccb64b5584d19caa9c3d15613bf21cfbd829a6ca7f089fb5dfd43f8aa + sha256: "2c87d8330ba5deef5fe20e77f4d178190b3b24531dce08368030ab4be40a9d4e" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.0.1" android_package_installer: dependency: "direct main" description: @@ -74,6 +74,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7 + url: "https://pub.dev" + source: hosted + version: "0.4.0" clock: dependency: transitive description: @@ -142,10 +158,10 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: "9b1a0c32b2a503f8fe9f8764fac7b5fcd4f6bd35d8f49de5350bccf9e2a33b8a" + sha256: "499c61743e13909c13374a8c209075385858c614b9c0f2487b5f9995eeaf7369" url: "https://pub.dev" source: hosted - version: "9.0.0" + version: "9.0.1" device_info_plus_platform_interface: dependency: transitive description: @@ -158,18 +174,18 @@ packages: dependency: "direct main" description: name: dynamic_color - sha256: bbebb1b7ebed819e0ec83d4abdc2a8482d934f6a85289ffc1c6acf7589fa2aad + sha256: "74dff1435a695887ca64899b8990004f8d1232b0e84bfc4faa1fdda7c6f57cc1" url: "https://pub.dev" source: hosted - version: "1.6.3" + version: "1.6.5" easy_localization: dependency: "direct main" description: name: easy_localization - sha256: f30e9b20ed4d1b890171c30241d9b9c43efe21fee55dee7bd68f94daf269ea75 + sha256: "30ebf25448ffe169e0bd9bc4b5da94faa8398967a2ad2ca09f438be8b6953645" url: "https://pub.dev" source: hosted - version: "3.0.2-dev.2" + version: "3.0.2" easy_logger: dependency: transitive description: @@ -223,6 +239,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.2" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea" + url: "https://pub.dev" + source: hosted + version: "0.13.1" flutter_lints: dependency: "direct dev" description: @@ -235,10 +259,10 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: ee6ee56855aa920899b68586b538474d086c149932220b47b92502cbfb5ba5e5 + sha256: "12f8abacca8bf29c042ec50c554f967da4c6f88ec99fc215e0325e5b43a25188" url: "https://pub.dev" source: hosted - version: "14.0.0+2" + version: "14.1.0" flutter_local_notifications_linux: dependency: transitive description: @@ -272,10 +296,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "96af49aa6b57c10a312106ad6f71deed5a754029c24789bbf620ba784f0bd0b0" + sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360" url: "https://pub.dev" source: hosted - version: "2.0.14" + version: "2.0.15" flutter_test: dependency: "direct dev" description: flutter @@ -306,10 +330,10 @@ packages: dependency: "direct main" description: name: http - sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" + sha256: "4c3f04bfb64d3efd508d06b41b825542f08122d30bda4933fb95c069d22a4fa3" url: "https://pub.dev" source: hosted - version: "0.13.6" + version: "1.0.0" http_parser: dependency: transitive description: @@ -318,6 +342,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + image: + dependency: transitive + description: + name: image + sha256: a72242c9a0ffb65d03de1b7113bc4e189686fc07c7147b8b41811d0dd0e0d9bf + url: "https://pub.dev" + source: hosted + version: "4.0.17" installed_apps: dependency: "direct main" description: @@ -342,6 +374,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev" + source: hosted + version: "4.8.1" lints: dependency: transitive description: @@ -482,10 +522,10 @@ packages: dependency: transitive description: name: permission_handler_android - sha256: "8028362b40c4a45298f1cbfccd227c8dd6caf0e27088a69f2ba2ab15464159e2" + sha256: d8cc6a62ded6d0f49c6eac337e080b066ee3bce4d405bd9439a61e1f1927bfe8 url: "https://pub.dev" source: hosted - version: "10.2.0" + version: "10.2.1" permission_handler_apple: dependency: transitive description: @@ -562,10 +602,10 @@ packages: dependency: "direct main" description: name: share_plus - sha256: "322a1ec9d9fe07e2e2252c098ce93d12dbd06133cc4c00ffe6a4ef505c295c17" + sha256: "44fc0bc2d35a8fafa1b564e1c6888bdc4fbb2d0197e4a4c21bac0e66123be9cd" url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" share_plus_platform_interface: dependency: transitive description: @@ -647,10 +687,10 @@ packages: dependency: "direct main" description: name: sqflite - sha256: "3a82c9a216b46b88617e3714dd74227eaca20c501c4abcc213e56db26b9caa00" + sha256: b4d6710e1200e96845747e37338ea8a819a12b51689a3bcf31eff0003b37a0b9 url: "https://pub.dev" source: hosted - version: "2.2.8+2" + version: "2.2.8+4" sqflite_common: dependency: transitive description: @@ -735,10 +775,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "7aac14be5f4731b923cc697ae2d42043945076cd0dbb8806baecc92c1dc88891" + sha256: "1a5848f598acc5b7d8f7c18b8cb834ab667e59a13edc3c93e9d09cf38cc6bc87" url: "https://pub.dev" source: hosted - version: "6.0.33" + version: "6.0.34" url_launcher_ios: dependency: transitive description: @@ -867,6 +907,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.3.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" sdks: - dart: ">=3.0.0-417 <4.0.0" + dart: ">=3.0.0 <4.0.0" flutter: ">=3.4.0-17.0.pre" diff --git a/pubspec.yaml b/pubspec.yaml index 12cdeee..54acc82 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 0.13.4+168 # When changing this, update the tag in main() accordingly +version: 0.13.5+169 # When changing this, update the tag in main() accordingly environment: sdk: '>=2.18.2 <3.0.0' @@ -40,7 +40,7 @@ dependencies: flutter_fgbg: ^0.2.0 # Try removing reliance on this flutter_local_notifications: ^14.0.0+1 provider: ^6.0.3 - http: ^0.13.5 + http: ^1.0.0 webview_flutter: ^4.0.0 dynamic_color: ^1.5.4 html: ^0.15.0 @@ -69,6 +69,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + flutter_launcher_icons: ^0.13.1 # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is @@ -77,6 +78,10 @@ dev_dependencies: # rules and activating additional ones. flutter_lints: ^2.0.1 +flutter_launcher_icons: + android: "ic_launcher" + image_path: "assets/graphics/icon.png" + # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec