From 27b1149d1cf1700582e7ad627b073f05184c4224 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sun, 10 Sep 2023 14:16:19 -0400 Subject: [PATCH 1/8] Fix padding when touch targets not highlighted --- lib/pages/apps.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index a980724..32eb8c3 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -459,7 +459,9 @@ class AppsPageState extends State { : Theme.of(context).primaryColorLight) .withAlpha(20) : null), - padding: const EdgeInsets.fromLTRB(12, 0, 12, 0), + padding: settingsProvider.highlightTouchTargets + ? const EdgeInsetsDirectional.fromSTEB(12, 0, 12, 0) + : const EdgeInsetsDirectional.fromSTEB(24, 0, 0, 0), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end, From 873a1a0683cf4c7df43b4094633df41374f41970 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sun, 10 Sep 2023 15:55:34 -0400 Subject: [PATCH 2/8] Allow for alternative app dirs (unfinished) --- lib/pages/app.dart | 6 ++-- lib/pages/apps.dart | 7 ++-- lib/providers/apps_provider.dart | 41 +++++++++------------ lib/providers/settings_provider.dart | 54 ++++++++++++++++++++++++++++ pubspec.lock | 8 +++++ pubspec.yaml | 1 + 6 files changed, 85 insertions(+), 32 deletions(-) diff --git a/lib/pages/app.dart b/lib/pages/app.dart index 45812d0..dcd52bd 100644 --- a/lib/pages/app.dart +++ b/lib/pages/app.dart @@ -338,9 +338,9 @@ class _AppPageState extends State { try { HapticFeedback.heavyImpact(); var res = await appsProvider.downloadAndInstallLatestApps( - app?.app.id != null ? [app!.app.id] : [], - globalNavigatorKey.currentContext, - settingsProvider); + app?.app.id != null ? [app!.app.id] : [], + globalNavigatorKey.currentContext, + ); if (app?.app.installedVersion != null && !trackOnly) { // ignore: use_build_context_synchronously showError(tr('appsUpdated'), context); diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index 32eb8c3..3f4cc30 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -381,8 +381,7 @@ class AppsPageState extends State { : () { appsProvider.downloadAndInstallLatestApps( [listedApps[appIndex].app.id], - globalNavigatorKey.currentContext, - settingsProvider).catchError((e) { + globalNavigatorKey.currentContext).catchError((e) { showError(e, context); return []; }); @@ -699,8 +698,8 @@ class AppsPageState extends State { toInstall.addAll(trackOnlyUpdateIdsAllOrSelected); } appsProvider - .downloadAndInstallLatestApps(toInstall, - globalNavigatorKey.currentContext, settingsProvider) + .downloadAndInstallLatestApps( + toInstall, globalNavigatorKey.currentContext) .catchError((e) { showError(e, context); return []; diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 208b222..63a061e 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -150,6 +150,7 @@ class AppsProvider with ChangeNotifier { late Stream? foregroundStream; late StreamSubscription? foregroundSubscription; late Directory APKDir; + late SettingsProvider settingsProvider = SettingsProvider(); Iterable getAppValues() => apps.values.map((a) => a.deepCopy()); @@ -161,12 +162,12 @@ class AppsProvider with ChangeNotifier { if (isForeground) await loadApps(); }); () async { + await settingsProvider.initializeSettings(); var cacheDirs = await getExternalCacheDirectories(); if (cacheDirs?.isNotEmpty ?? false) { APKDir = cacheDirs!.first; } else { - APKDir = - Directory('${(await getExternalStorageDirectory())!.path}/apks'); + APKDir = Directory('${await settingsProvider.getAppDir()}/apks'); if (!APKDir.existsSync()) { APKDir.createSync(); } @@ -369,8 +370,7 @@ class AppsProvider with ChangeNotifier { .where((element) => element.downloadProgress != null) .isNotEmpty; - Future canInstallSilently( - App app, SettingsProvider settingsProvider) async { + Future canInstallSilently(App app) async { if (app.id == obtainiumId) { return false; } @@ -539,7 +539,6 @@ class AppsProvider with ChangeNotifier { getHost(apkUrl.value) != getHost(app.url) && context != null) { // ignore: use_build_context_synchronously - var settingsProvider = context.read(); if (!(settingsProvider.hideAPKOriginWarning) && // ignore: use_build_context_synchronously await showDialog( @@ -560,8 +559,8 @@ class AppsProvider with ChangeNotifier { // 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 an array of Ids for Apps that were successfully downloaded, regardless of installation result - Future> downloadAndInstallLatestApps(List appIds, - BuildContext? context, SettingsProvider settingsProvider, + Future> downloadAndInstallLatestApps( + List appIds, BuildContext? context, {NotificationsProvider? notificationsProvider}) async { notificationsProvider = notificationsProvider ?? context?.read(); @@ -590,8 +589,7 @@ class AppsProvider with ChangeNotifier { apps[id]!.app.preferredApkIndex = urlInd; await saveApps([apps[id]!.app]); } - if (context != null || - await canInstallSilently(apps[id]!.app, settingsProvider)) { + if (context != null || await canInstallSilently(apps[id]!.app)) { appsToInstall.add(id); } } @@ -628,8 +626,7 @@ class AppsProvider with ChangeNotifier { downloadedDir = downloadedArtifact as DownloadedXApkDir; } var appId = downloadedFile?.appId ?? downloadedDir!.appId; - bool willBeSilent = - await canInstallSilently(apps[appId]!.app, settingsProvider); + bool willBeSilent = await canInstallSilently(apps[appId]!.app); if (!(await settingsProvider.getInstallPermission(enforce: false))) { throw ObtainiumError(tr('cancelled')); } @@ -678,8 +675,8 @@ class AppsProvider with ChangeNotifier { } Future getAppsDir() async { - Directory appsDir = Directory( - '${(await getExternalStorageDirectory())?.path as String}/app_data'); + Directory appsDir = + Directory('${await settingsProvider.getAppDir()}/app_data'); if (!appsDir.existsSync()) { appsDir.createSync(); } @@ -879,8 +876,6 @@ class AppsProvider with ChangeNotifier { .toList(); // After reconciliation, delete externally uninstalled Apps if needed if (removedAppIds.isNotEmpty) { - var settingsProvider = SettingsProvider(); - await settingsProvider.initializeSettings(); if (settingsProvider.removeOnExternalUninstall) { await removeApps(removedAppIds); } @@ -1114,8 +1109,8 @@ class AppsProvider with ChangeNotifier { logs.add('Error accessing Downloads (will use fallback): $e'); } if (!downloadsAccessible) { - exportDir = await getExternalStorageDirectory(); - path = exportDir!.path; + exportDir = Directory(await settingsProvider.getAppDir()); + path = exportDir.path; } File export = File( '${exportDir.path}/${tr('obtainiumExportHyphenatedLowercase')}-${DateTime.now().millisecondsSinceEpoch}.json'); @@ -1298,14 +1293,12 @@ Future bgUpdateCheck(int taskId, Map? params) async { NotificationsProvider notificationsProvider = NotificationsProvider(); AppsProvider appsProvider = AppsProvider(isBg: true); await appsProvider.loadApps(); - var settingsProvider = SettingsProvider(); - await settingsProvider.initializeSettings(); int maxAttempts = 4; params ??= {}; if (params['toCheck'] == null) { - settingsProvider.lastBGCheckTime = DateTime.now(); + appsProvider.settingsProvider.lastBGCheckTime = DateTime.now(); } List> toCheck = >[ ...(params['toCheck'] @@ -1335,7 +1328,7 @@ Future bgUpdateCheck(int taskId, Map? params) async { var didCompleteChecking = false; CheckingUpdatesNotification? notif; var networkRestricted = false; - if (settingsProvider.bgUpdatesOnWiFiOnly) { + if (appsProvider.settingsProvider.bgUpdatesOnWiFiOnly) { var netResult = await (Connectivity().checkConnectivity()); networkRestricted = (netResult != ConnectivityResult.wifi) && (netResult != ConnectivityResult.ethernet); @@ -1355,8 +1348,7 @@ Future bgUpdateCheck(int taskId, Map? params) async { App? newApp = await appsProvider.checkUpdate(appId); if (newApp != null) { if (networkRestricted || - !(await appsProvider.canInstallSilently( - app!.app, settingsProvider))) { + !(await appsProvider.canInstallSilently(app!.app))) { toNotify.add(newApp); } else { toInstall.add(MapEntry(appId, 0)); @@ -1442,8 +1434,7 @@ Future bgUpdateCheck(int taskId, Map? params) async { try { logs.add( 'BG install task $taskId: Attempting to update $appId in the background.'); - await appsProvider.downloadAndInstallLatestApps( - [appId], null, settingsProvider, + await appsProvider.downloadAndInstallLatestApps([appId], null, notificationsProvider: notificationsProvider); await Future.delayed(const Duration( seconds: diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index a95e2b3..e96b5a7 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -1,6 +1,7 @@ // Exposes functions used to save/load app settings import 'dart:convert'; +import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -9,8 +10,10 @@ import 'package:obtainium/app_sources/github.dart'; import 'package:obtainium/main.dart'; import 'package:obtainium/providers/apps_provider.dart'; import 'package:obtainium/providers/source_provider.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:shared_storage/shared_storage.dart' as saf; String obtainiumTempId = 'imranr98_obtainium_${GitHub().host}'; String obtainiumId = 'dev.imranr.obtainium'; @@ -35,6 +38,7 @@ List updateIntervals = [15, 30, 60, 120, 180, 360, 720, 1440, 4320, 0] class SettingsProvider with ChangeNotifier { SharedPreferences? prefs; + String? defaultAppDir; bool justStarted = true; String sourceUrl = 'https://github.com/ImranR98/Obtainium'; @@ -42,6 +46,7 @@ class SettingsProvider with ChangeNotifier { // Not done in constructor as we want to be able to await it Future initializeSettings() async { prefs = await SharedPreferences.getInstance(); + defaultAppDir = (await getExternalStorageDirectory())!.path; notifyListeners(); } @@ -357,4 +362,53 @@ class SettingsProvider with ChangeNotifier { prefs?.setBool('highlightTouchTargets', val); notifyListeners(); } + + Future getAppDir() async { + return prefs?.getString('appDir') ?? defaultAppDir!; + } + + pickAppDir({bool useDefault = false}) async { + var existingSAFPerms = (await saf.persistedUriPermissions()) ?? []; + var currentAppDir = await getAppDir(); + if (currentAppDir != defaultAppDir) { + currentAppDir = currentAppDir.replaceFirst( + '/storage/emulated/0/', '/tree/primary%3A'); + } + String? newAppDir; + if (!useDefault) { + var target = (await saf.openDocumentTree()); + if (target != null) { + newAppDir = target.path + .replaceFirst('/tree/primary%3A', '/storage/emulated/0/'); + } + } else { + newAppDir = defaultAppDir; + } + newAppDir ??= defaultAppDir; + if (currentAppDir != newAppDir) { + moveDirectoryContents(Directory(currentAppDir), Directory(newAppDir!)); + prefs?.setString('appDir', newAppDir); + notifyListeners(); + } + for (var e in existingSAFPerms) { + await saf.releasePersistableUriPermission(e.uri); + } + } +} + +void moveDirectoryContents(Directory sourceDir, Directory destinationDir) { + if (!destinationDir.existsSync()) { + destinationDir.createSync(recursive: true); + } + List contents = sourceDir.listSync(); + for (FileSystemEntity entity in contents) { + String newPath = '${destinationDir.path}/${entity.uri.pathSegments.last}'; + if (entity is File) { + entity.renameSync(newPath); + } else if (entity is Directory) { + Directory newDestinationDir = Directory(newPath); + moveDirectoryContents(entity, newDestinationDir); + entity.deleteSync(recursive: true); + } + } } diff --git a/pubspec.lock b/pubspec.lock index 04ca07a..cdacfb5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -686,6 +686,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.1" + shared_storage: + dependency: "direct main" + description: + name: shared_storage + sha256: "7c65a9d64f0f5521256be974cfd74010af12196657cec9f9fb7b03b2f11bcaf6" + url: "https://pub.dev" + source: hosted + version: "0.8.0" sky_engine: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 4f48012..355bbd4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -65,6 +65,7 @@ dependencies: flutter_archive: ^5.0.0 hsluv: ^1.1.3 connectivity_plus: ^4.0.2 + shared_storage: ^0.8.0 dev_dependencies: flutter_test: From 6e735b17633975dfcd4161b0aab5ec3eefb0f138 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sun, 10 Sep 2023 22:24:18 -0400 Subject: [PATCH 3/8] Enable auto-export on update checks --- assets/translations/br.json | 2 + assets/translations/bs.json | 6 +- assets/translations/de.json | 2 + assets/translations/en.json | 2 + assets/translations/es.json | 2 + assets/translations/fa.json | 2 + assets/translations/fr.json | 2 + assets/translations/hu.json | 2 + assets/translations/it.json | 2 + assets/translations/ja.json | 2 + assets/translations/pl.json | 2 + assets/translations/ru.json | 2 + assets/translations/zh.json | 2 + lib/app_sources/html.dart | 5 +- lib/pages/apps.dart | 5 ++ lib/pages/import_export.dart | 57 +++++++++++++++++-- lib/providers/apps_provider.dart | 83 +++++++++++++++++++--------- lib/providers/settings_provider.dart | 58 ++++++++----------- lib/providers/source_provider.dart | 14 +++++ 19 files changed, 182 insertions(+), 70 deletions(-) diff --git a/assets/translations/br.json b/assets/translations/br.json index 6a70a80..fa734f4 100644 --- a/assets/translations/br.json +++ b/assets/translations/br.json @@ -254,6 +254,8 @@ "versionExtractionRegEx": "Version Extraction RegEx", "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", + "pickExportDir": "Pick Export Directory", + "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", "removeAppQuestion": { "one": "Remover App?", "other": "Remover Apps?" diff --git a/assets/translations/bs.json b/assets/translations/bs.json index 254f11c..a6ca2ab 100644 --- a/assets/translations/bs.json +++ b/assets/translations/bs.json @@ -111,7 +111,7 @@ "dark": "Tamna", "light": "Svijetla", "followSystem": "Pratite sistem", - "obtainium": "Obtainium", + "obtainium": "Obtainium", "materialYou": "Material You", "useBlackTheme": "Koristite čisto crnu tamnu temu", "appSortBy": "Aplikacije sortirane po", @@ -251,7 +251,9 @@ "versionExtractionRegEx": "Version Extraction RegEx", "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", - "removeAppQuestion": { + "pickExportDir": "Pick Export Directory", + "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", + "removeAppQuestion": { "one": "Želite li ukloniti aplikaciju?", "other": "Želite li ukloniti aplikacije?" }, diff --git a/assets/translations/de.json b/assets/translations/de.json index 81c2ea7..d6a3a10 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -251,6 +251,8 @@ "versionExtractionRegEx": "Version Extraction RegEx", "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", + "pickExportDir": "Pick Export Directory", + "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", "removeAppQuestion": { "one": "App entfernen?", "other": "Apps entfernen?" diff --git a/assets/translations/en.json b/assets/translations/en.json index f286c68..185e1e9 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -254,6 +254,8 @@ "versionExtractionRegEx": "Version Extraction RegEx", "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", + "pickExportDir": "Pick Export Directory", + "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", "removeAppQuestion": { "one": "Remove App?", "other": "Remove Apps?" diff --git a/assets/translations/es.json b/assets/translations/es.json index 8893461..aaf5dcd 100644 --- a/assets/translations/es.json +++ b/assets/translations/es.json @@ -251,6 +251,8 @@ "versionExtractionRegEx": "Version Extraction RegEx", "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", + "pickExportDir": "Pick Export Directory", + "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", "removeAppQuestion": { "one": "¿Eliminar Aplicación?", "other": "¿Eliminar Aplicaciones?" diff --git a/assets/translations/fa.json b/assets/translations/fa.json index adf44c2..44b363b 100644 --- a/assets/translations/fa.json +++ b/assets/translations/fa.json @@ -251,6 +251,8 @@ "versionExtractionRegEx": "Version Extraction RegEx", "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", + "pickExportDir": "Pick Export Directory", + "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", "removeAppQuestion": { "one": "برنامه حذف شود؟", "other": "برنامه ها حذف شوند؟" diff --git a/assets/translations/fr.json b/assets/translations/fr.json index cfae07f..0081dbe 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -251,6 +251,8 @@ "versionExtractionRegEx": "Version Extraction RegEx", "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", + "pickExportDir": "Pick Export Directory", + "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", "removeAppQuestion": { "one": "Supprimer l'application ?", "other": "Supprimer les applications ?" diff --git a/assets/translations/hu.json b/assets/translations/hu.json index 1913544..b694f45 100644 --- a/assets/translations/hu.json +++ b/assets/translations/hu.json @@ -250,6 +250,8 @@ "versionExtractionRegEx": "Version Extraction RegEx", "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", + "pickExportDir": "Pick Export Directory", + "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", "removeAppQuestion": { "one": "Eltávolítja az alkalmazást?", "other": "Eltávolítja az alkalmazást?" diff --git a/assets/translations/it.json b/assets/translations/it.json index b211e4e..31e4f59 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -251,6 +251,8 @@ "versionExtractionRegEx": "Version Extraction RegEx", "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", + "pickExportDir": "Pick Export Directory", + "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", "removeAppQuestion": { "one": "Rimuovere l'app?", "other": "Rimuovere le app?" diff --git a/assets/translations/ja.json b/assets/translations/ja.json index 1181f88..33b3efa 100644 --- a/assets/translations/ja.json +++ b/assets/translations/ja.json @@ -252,6 +252,8 @@ "versionExtractionRegEx": "Version Extraction RegEx", "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", + "pickExportDir": "Pick Export Directory", + "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", "removeAppQuestion": { "one": "アプリを削除しますか?", "other": "アプリを削除しますか?" diff --git a/assets/translations/pl.json b/assets/translations/pl.json index 1b43982..25151f4 100644 --- a/assets/translations/pl.json +++ b/assets/translations/pl.json @@ -257,6 +257,8 @@ "versionExtractionRegEx": "Version Extraction RegEx", "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", + "pickExportDir": "Pick Export Directory", + "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", "removeAppQuestion": { "one": "Usunąć aplikację?", "few": "Usunąć aplikacje?", diff --git a/assets/translations/ru.json b/assets/translations/ru.json index 49bc22d..3635f30 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -251,6 +251,8 @@ "versionExtractionRegEx": "Version Extraction RegEx", "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", + "pickExportDir": "Pick Export Directory", + "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", "removeAppQuestion": { "one": "Удалить приложение?", "other": "Удалить приложения?" diff --git a/assets/translations/zh.json b/assets/translations/zh.json index ac43598..519425c 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -252,6 +252,8 @@ "versionExtractionRegEx": "Version Extraction RegEx", "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", + "pickExportDir": "Pick Export Directory", + "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", "removeAppQuestion": { "one": "是否删除应用?", "other": "是否删除应用?" diff --git a/lib/app_sources/html.dart b/lib/app_sources/html.dart index a2a25cc..8676af9 100644 --- a/lib/app_sources/html.dart +++ b/lib/app_sources/html.dart @@ -124,10 +124,7 @@ class HTML extends AppSource { additionalValidators: [ (value) { value ??= '1'; - if (int.tryParse(value) == null) { - return tr('invalidInput'); - } - return null; + return intValidator(value); } ]) ] diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index 3f4cc30..9eb0ff5 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -74,6 +74,11 @@ class AppsPageState extends State { setState(() { refreshingSince = null; }); + if (settingsProvider.autoExportOnUpdateCheckKeepNum > 0) { + appsProvider.exportApps(isAuto: true).then((value) { + appsProvider.trimAutoExports(); + }); + } }); } diff --git a/lib/pages/import_export.dart b/lib/pages/import_export.dart index e2ebdf9..8bbb09c 100644 --- a/lib/pages/import_export.dart +++ b/lib/pages/import_export.dart @@ -104,8 +104,12 @@ class _ImportExportPageState extends State { runObtainiumExport() { HapticFeedback.selectionClick(); - appsProvider.exportApps().then((String path) { - showError(tr('exportedTo', args: [path]), context); + appsProvider + .exportApps(pickOnly: settingsProvider.exportDir == null) + .then((String? result) { + if (result != null) { + showError(tr('exportedTo', args: [result]), context); + } }).catchError((e) { showError(e, context); }); @@ -310,7 +314,10 @@ class _ImportExportPageState extends State { importInProgress ? null : runObtainiumExport, - child: Text(tr('obtainiumExport')))), + child: Text(tr( + settingsProvider.exportDir != null + ? 'obtainiumExport' + : 'pickExportDirKeepLastN')))), const SizedBox( width: 16, ), @@ -323,6 +330,48 @@ class _ImportExportPageState extends State { child: Text(tr('obtainiumImport')))) ], ), + if (settingsProvider.exportDir != null) + Column( + children: [ + const SizedBox(height: 16), + GeneratedForm( + items: [ + [ + GeneratedFormTextField( + 'autoExportOnUpdateCheckKeepNum', + label: tr( + 'autoExportOnUpdateCheckKeepNum'), + required: false, + defaultValue: settingsProvider + .autoExportOnUpdateCheckKeepNum + .toString(), + textInputType: const TextInputType + .numberWithOptions(), + additionalValidators: [ + (value) { + value ??= settingsProvider + .autoExportOnUpdateCheckKeepNum + .toString(); + return intValidator(value, + positive: true); + } + ]) + ] + ], + onValueChanges: (value, valid, isBuilding) { + if (valid && !isBuilding) { + if (value[ + 'autoExportOnUpdateCheckKeepNum'] != + null) { + settingsProvider + .autoExportOnUpdateCheckKeepNum = + int.parse(value[ + 'autoExportOnUpdateCheckKeepNum']); + } + } + }), + ], + ), if (importInProgress) const Column( children: [ @@ -399,7 +448,7 @@ class _ImportExportPageState extends State { fontStyle: FontStyle.italic, fontSize: 12)), const SizedBox( height: 8, - ) + ), ], ))) ])); diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 63a061e..4158fb9 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -16,6 +16,7 @@ import 'package:device_info_plus/device_info_plus.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:obtainium/app_sources/html.dart'; import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/components/generated_form_modal.dart'; import 'package:obtainium/custom_errors.dart'; @@ -31,6 +32,7 @@ import 'package:obtainium/providers/source_provider.dart'; import 'package:http/http.dart'; import 'package:android_intent_plus/android_intent.dart'; import 'package:flutter_archive/flutter_archive.dart'; +import 'package:shared_storage/shared_storage.dart' as saf; final pm = AndroidPackageManager(); @@ -167,7 +169,8 @@ class AppsProvider with ChangeNotifier { if (cacheDirs?.isNotEmpty ?? false) { APKDir = cacheDirs!.first; } else { - APKDir = Directory('${await settingsProvider.getAppDir()}/apks'); + APKDir = + Directory('${(await getExternalStorageDirectory())!.path}/apks'); if (!APKDir.existsSync()) { APKDir.createSync(); } @@ -676,7 +679,7 @@ class AppsProvider with ChangeNotifier { Future getAppsDir() async { Directory appsDir = - Directory('${await settingsProvider.getAppDir()}/app_data'); + Directory('${(await getExternalStorageDirectory())!.path}/app_data'); if (!appsDir.existsSync()) { appsDir.createSync(); } @@ -1091,32 +1094,58 @@ class AppsProvider with ChangeNotifier { return updateAppIds; } - Future exportApps() async { - if ((await DeviceInfoPlugin().androidInfo).version.sdkInt <= 29) { - if (await Permission.storage.isDenied) { - await Permission.storage.request(); + Future exportApps({bool pickOnly = false, isAuto = false}) async { + if (isAuto) { + logs.add('Started auto-export.'); + } + var exportDir = settingsProvider.exportDir; + if (exportDir == null || pickOnly) { + await settingsProvider.pickExportDirKeepLastN(); + exportDir = settingsProvider.exportDir; + } + if (exportDir == null) { + throw ObtainiumError(tr('unexpectedError')); + } + String? returnPath; + if (!pickOnly) { + var result = await saf.createFile(exportDir, + displayName: + '${tr('obtainiumExportHyphenatedLowercase')}-${DateTime.now().toIso8601String().replaceAll(':', '-')}${isAuto ? '-auto' : ''}.json', + mimeType: 'application/json', + content: jsonEncode(apps.values.map((e) => e.app.toJson()).toList())); + if (result == null) { + throw ObtainiumError(tr('unexpectedError')); } - if (await Permission.storage.isDenied) { - throw ObtainiumError(tr('storagePermissionDenied')); + returnPath = + exportDir.pathSegments.join('/').replaceFirst('tree/primary:', ''); + } + return returnPath; + } + + Future trimAutoExports() async { + var exportDir = settingsProvider.exportDir; + if (exportDir != null) { + var files = await saf + .listFiles(exportDir, columns: [saf.DocumentFileColumn.id]).toList(); + var maxCount = settingsProvider.autoExportOnUpdateCheckKeepNum; + if (files.length > maxCount) { + files.sort((a, b) { + if (a.name == null) { + return -1; + } else if (b.name == null) { + return 1; + } else { + return compareAlphaNumeric(a.name!, b.name!); + } + }); + files = files.reversed.toList(); + logs.add( + 'Deleting auto-exports older than ${files[maxCount - 1].uri.pathSegments.last}.'); + files.sublist(maxCount).forEach((f) { + saf.delete(f.uri); + }); } } - Directory? exportDir = Directory('/storage/emulated/0/Download'); - String path = 'Downloads'; // TODO: See if hardcoding this can be avoided - var downloadsAccessible = false; - try { - downloadsAccessible = exportDir.existsSync(); - } catch (e) { - logs.add('Error accessing Downloads (will use fallback): $e'); - } - if (!downloadsAccessible) { - exportDir = Directory(await settingsProvider.getAppDir()); - path = exportDir.path; - } - File export = File( - '${exportDir.path}/${tr('obtainiumExportHyphenatedLowercase')}-${DateTime.now().millisecondsSinceEpoch}.json'); - export.writeAsStringSync( - jsonEncode(apps.values.map((e) => e.app.toJson()).toList())); - return path; } Future importApps(String appsJSON) async { @@ -1402,6 +1431,10 @@ Future bgUpdateCheck(int taskId, Map? params) async { if (toNotify.isNotEmpty) { notificationsProvider.notify(UpdateNotification(toNotify)); } + if (appsProvider.settingsProvider.autoExportOnUpdateCheckKeepNum > 0) { + await appsProvider.exportApps(isAuto: true); + await appsProvider.trimAutoExports(); + } } // If you're done checking and found some silently installable updates, schedule another task which will run in install mode if (didCompleteChecking && toInstall.isNotEmpty) { diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index e96b5a7..984d3d9 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -1,7 +1,6 @@ // Exposes functions used to save/load app settings import 'dart:convert'; -import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -363,52 +362,41 @@ class SettingsProvider with ChangeNotifier { notifyListeners(); } - Future getAppDir() async { - return prefs?.getString('appDir') ?? defaultAppDir!; + Uri? get exportDir { + var uriString = prefs?.getString('exportDir'); + if (uriString != null) { + return Uri.parse(uriString); + } else { + return null; + } } - pickAppDir({bool useDefault = false}) async { + Future pickExportDirKeepLastN({bool remove = false}) async { var existingSAFPerms = (await saf.persistedUriPermissions()) ?? []; - var currentAppDir = await getAppDir(); - if (currentAppDir != defaultAppDir) { - currentAppDir = currentAppDir.replaceFirst( - '/storage/emulated/0/', '/tree/primary%3A'); + var currentOneWayDataSyncDir = exportDir; + Uri? newOneWayDataSyncDir; + if (!remove) { + newOneWayDataSyncDir = (await saf.openDocumentTree()); } - String? newAppDir; - if (!useDefault) { - var target = (await saf.openDocumentTree()); - if (target != null) { - newAppDir = target.path - .replaceFirst('/tree/primary%3A', '/storage/emulated/0/'); + if (currentOneWayDataSyncDir?.path != newOneWayDataSyncDir?.path) { + if (newOneWayDataSyncDir == null) { + prefs?.remove('exportDir'); + } else { + prefs?.setString('exportDir', newOneWayDataSyncDir.toString()); } - } else { - newAppDir = defaultAppDir; - } - newAppDir ??= defaultAppDir; - if (currentAppDir != newAppDir) { - moveDirectoryContents(Directory(currentAppDir), Directory(newAppDir!)); - prefs?.setString('appDir', newAppDir); notifyListeners(); } for (var e in existingSAFPerms) { await saf.releasePersistableUriPermission(e.uri); } } -} -void moveDirectoryContents(Directory sourceDir, Directory destinationDir) { - if (!destinationDir.existsSync()) { - destinationDir.createSync(recursive: true); + int get autoExportOnUpdateCheckKeepNum { + return prefs?.getInt('autoExportOnUpdateCheckKeepNum') ?? 0; } - List contents = sourceDir.listSync(); - for (FileSystemEntity entity in contents) { - String newPath = '${destinationDir.path}/${entity.uri.pathSegments.last}'; - if (entity is File) { - entity.renameSync(newPath); - } else if (entity is Directory) { - Directory newDestinationDir = Directory(newPath); - moveDirectoryContents(entity, newDestinationDir); - entity.deleteSync(recursive: true); - } + + set autoExportOnUpdateCheckKeepNum(int val) { + prefs?.setInt('autoExportOnUpdateCheckKeepNum', val); + notifyListeners(); } } diff --git a/lib/providers/source_provider.dart b/lib/providers/source_provider.dart index 3c06be2..43a0f1e 100644 --- a/lib/providers/source_provider.dart +++ b/lib/providers/source_provider.dart @@ -521,6 +521,20 @@ regExValidator(String? value) { return null; } +intValidator(String? value, {bool positive = false}) { + if (value == null) { + return tr('invalidInput'); + } + var num = int.tryParse(value); + if (num == null) { + return tr('invalidInput'); + } + if (positive && num <= 0) { + return tr('invalidInput'); + } + return null; +} + class SourceProvider { // Add more source classes here so they are available via the service List get sources => [ From 53cf4d0234d410f481e6d3632e405b924696c6d2 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sun, 10 Sep 2023 22:35:28 -0400 Subject: [PATCH 4/8] Pick export dir + auto-export (#283, #600) --- assets/translations/br.json | 2 +- assets/translations/bs.json | 2 +- assets/translations/de.json | 2 +- assets/translations/en.json | 2 +- assets/translations/es.json | 2 +- assets/translations/fa.json | 2 +- assets/translations/fr.json | 2 +- assets/translations/hu.json | 2 +- assets/translations/it.json | 2 +- assets/translations/ja.json | 2 +- assets/translations/pl.json | 2 +- assets/translations/ru.json | 2 +- assets/translations/zh.json | 2 +- lib/main.dart | 2 +- lib/pages/apps.dart | 5 ---- lib/pages/import_export.dart | 35 ++++++---------------- lib/providers/apps_provider.dart | 44 ++++++++-------------------- lib/providers/settings_provider.dart | 8 ++--- pubspec.yaml | 2 +- 19 files changed, 41 insertions(+), 81 deletions(-) diff --git a/assets/translations/br.json b/assets/translations/br.json index fa734f4..96e9e7d 100644 --- a/assets/translations/br.json +++ b/assets/translations/br.json @@ -255,7 +255,7 @@ "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", "pickExportDir": "Pick Export Directory", - "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", + "autoExportOnChanges": "Auto-export on changes", "removeAppQuestion": { "one": "Remover App?", "other": "Remover Apps?" diff --git a/assets/translations/bs.json b/assets/translations/bs.json index a6ca2ab..01d0d2b 100644 --- a/assets/translations/bs.json +++ b/assets/translations/bs.json @@ -252,7 +252,7 @@ "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", "pickExportDir": "Pick Export Directory", - "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", + "autoExportOnChanges": "Auto-export on changes", "removeAppQuestion": { "one": "Želite li ukloniti aplikaciju?", "other": "Želite li ukloniti aplikacije?" diff --git a/assets/translations/de.json b/assets/translations/de.json index d6a3a10..f6f9d0e 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -252,7 +252,7 @@ "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", "pickExportDir": "Pick Export Directory", - "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", + "autoExportOnChanges": "Auto-export on changes", "removeAppQuestion": { "one": "App entfernen?", "other": "Apps entfernen?" diff --git a/assets/translations/en.json b/assets/translations/en.json index 185e1e9..4395c68 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -255,7 +255,7 @@ "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", "pickExportDir": "Pick Export Directory", - "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", + "autoExportOnChanges": "Auto-export on changes", "removeAppQuestion": { "one": "Remove App?", "other": "Remove Apps?" diff --git a/assets/translations/es.json b/assets/translations/es.json index aaf5dcd..b662041 100644 --- a/assets/translations/es.json +++ b/assets/translations/es.json @@ -252,7 +252,7 @@ "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", "pickExportDir": "Pick Export Directory", - "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", + "autoExportOnChanges": "Auto-export on changes", "removeAppQuestion": { "one": "¿Eliminar Aplicación?", "other": "¿Eliminar Aplicaciones?" diff --git a/assets/translations/fa.json b/assets/translations/fa.json index 44b363b..c3f5274 100644 --- a/assets/translations/fa.json +++ b/assets/translations/fa.json @@ -252,7 +252,7 @@ "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", "pickExportDir": "Pick Export Directory", - "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", + "autoExportOnChanges": "Auto-export on changes", "removeAppQuestion": { "one": "برنامه حذف شود؟", "other": "برنامه ها حذف شوند؟" diff --git a/assets/translations/fr.json b/assets/translations/fr.json index 0081dbe..8576909 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -252,7 +252,7 @@ "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", "pickExportDir": "Pick Export Directory", - "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", + "autoExportOnChanges": "Auto-export on changes", "removeAppQuestion": { "one": "Supprimer l'application ?", "other": "Supprimer les applications ?" diff --git a/assets/translations/hu.json b/assets/translations/hu.json index b694f45..845e313 100644 --- a/assets/translations/hu.json +++ b/assets/translations/hu.json @@ -251,7 +251,7 @@ "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", "pickExportDir": "Pick Export Directory", - "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", + "autoExportOnChanges": "Auto-export on changes", "removeAppQuestion": { "one": "Eltávolítja az alkalmazást?", "other": "Eltávolítja az alkalmazást?" diff --git a/assets/translations/it.json b/assets/translations/it.json index 31e4f59..7fec3c8 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -252,7 +252,7 @@ "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", "pickExportDir": "Pick Export Directory", - "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", + "autoExportOnChanges": "Auto-export on changes", "removeAppQuestion": { "one": "Rimuovere l'app?", "other": "Rimuovere le app?" diff --git a/assets/translations/ja.json b/assets/translations/ja.json index 33b3efa..541da17 100644 --- a/assets/translations/ja.json +++ b/assets/translations/ja.json @@ -253,7 +253,7 @@ "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", "pickExportDir": "Pick Export Directory", - "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", + "autoExportOnChanges": "Auto-export on changes", "removeAppQuestion": { "one": "アプリを削除しますか?", "other": "アプリを削除しますか?" diff --git a/assets/translations/pl.json b/assets/translations/pl.json index 25151f4..04f8322 100644 --- a/assets/translations/pl.json +++ b/assets/translations/pl.json @@ -258,7 +258,7 @@ "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", "pickExportDir": "Pick Export Directory", - "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", + "autoExportOnChanges": "Auto-export on changes", "removeAppQuestion": { "one": "Usunąć aplikację?", "few": "Usunąć aplikacje?", diff --git a/assets/translations/ru.json b/assets/translations/ru.json index 3635f30..690cd53 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -252,7 +252,7 @@ "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", "pickExportDir": "Pick Export Directory", - "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", + "autoExportOnChanges": "Auto-export on changes", "removeAppQuestion": { "one": "Удалить приложение?", "other": "Удалить приложения?" diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 519425c..66fc426 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -253,7 +253,7 @@ "matchGroupToUse": "Match Group to Use", "highlightTouchTargets": "Highlight less obvious touch targets", "pickExportDir": "Pick Export Directory", - "autoExportOnUpdateCheckKeepNum": "Auto-export on update check (keep last N auto-exports)", + "autoExportOnChanges": "Auto-export on changes", "removeAppQuestion": { "one": "是否删除应用?", "other": "是否删除应用?" diff --git a/lib/main.dart b/lib/main.dart index 1b9fe28..0b2338d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -19,7 +19,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart'; // ignore: implementation_imports import 'package:easy_localization/src/localization.dart'; -const String currentVersion = '0.14.12'; +const String currentVersion = '0.14.13'; 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 9eb0ff5..3f4cc30 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -74,11 +74,6 @@ class AppsPageState extends State { setState(() { refreshingSince = null; }); - if (settingsProvider.autoExportOnUpdateCheckKeepNum > 0) { - appsProvider.exportApps(isAuto: true).then((value) { - appsProvider.trimAutoExports(); - }); - } }); } diff --git a/lib/pages/import_export.dart b/lib/pages/import_export.dart index 8bbb09c..c398532 100644 --- a/lib/pages/import_export.dart +++ b/lib/pages/import_export.dart @@ -337,36 +337,19 @@ class _ImportExportPageState extends State { GeneratedForm( items: [ [ - GeneratedFormTextField( - 'autoExportOnUpdateCheckKeepNum', - label: tr( - 'autoExportOnUpdateCheckKeepNum'), - required: false, - defaultValue: settingsProvider - .autoExportOnUpdateCheckKeepNum - .toString(), - textInputType: const TextInputType - .numberWithOptions(), - additionalValidators: [ - (value) { - value ??= settingsProvider - .autoExportOnUpdateCheckKeepNum - .toString(); - return intValidator(value, - positive: true); - } - ]) + GeneratedFormSwitch( + 'autoExportOnChanges', + label: tr('autoExportOnChanges'), + defaultValue: + settingsProvider.autoExportOnChanges, + ) ] ], onValueChanges: (value, valid, isBuilding) { if (valid && !isBuilding) { - if (value[ - 'autoExportOnUpdateCheckKeepNum'] != - null) { - settingsProvider - .autoExportOnUpdateCheckKeepNum = - int.parse(value[ - 'autoExportOnUpdateCheckKeepNum']); + if (value['autoExportOnChanges'] != null) { + settingsProvider.autoExportOnChanges = + value['autoExportOnChanges'] == true; } } }), diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 4158fb9..fd10a09 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -16,7 +16,6 @@ import 'package:device_info_plus/device_info_plus.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:obtainium/app_sources/html.dart'; import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/components/generated_form_modal.dart'; import 'package:obtainium/custom_errors.dart'; @@ -917,6 +916,7 @@ class AppsProvider with ChangeNotifier { } } notifyListeners(); + await exportApps(isAuto: true); } Future removeApps(List appIds) async { @@ -938,6 +938,7 @@ class AppsProvider with ChangeNotifier { } if (appIds.isNotEmpty) { notifyListeners(); + await exportApps(isAuto: true); } } @@ -1097,6 +1098,17 @@ class AppsProvider with ChangeNotifier { Future exportApps({bool pickOnly = false, isAuto = false}) async { if (isAuto) { logs.add('Started auto-export.'); + var exportDir = settingsProvider.exportDir; + if (exportDir != null) { + var files = await saf.listFiles(exportDir, + columns: [saf.DocumentFileColumn.id]).toList(); + if (files.isNotEmpty) { + for (var f in files) { + saf.delete(f.uri); + } + logs.add('Previous auto-export deleted.'); + } + } } var exportDir = settingsProvider.exportDir; if (exportDir == null || pickOnly) { @@ -1122,32 +1134,6 @@ class AppsProvider with ChangeNotifier { return returnPath; } - Future trimAutoExports() async { - var exportDir = settingsProvider.exportDir; - if (exportDir != null) { - var files = await saf - .listFiles(exportDir, columns: [saf.DocumentFileColumn.id]).toList(); - var maxCount = settingsProvider.autoExportOnUpdateCheckKeepNum; - if (files.length > maxCount) { - files.sort((a, b) { - if (a.name == null) { - return -1; - } else if (b.name == null) { - return 1; - } else { - return compareAlphaNumeric(a.name!, b.name!); - } - }); - files = files.reversed.toList(); - logs.add( - 'Deleting auto-exports older than ${files[maxCount - 1].uri.pathSegments.last}.'); - files.sublist(maxCount).forEach((f) { - saf.delete(f.uri); - }); - } - } - } - Future importApps(String appsJSON) async { List importedApps = (jsonDecode(appsJSON) as List) .map((e) => App.fromJson(e)) @@ -1431,10 +1417,6 @@ Future bgUpdateCheck(int taskId, Map? params) async { if (toNotify.isNotEmpty) { notificationsProvider.notify(UpdateNotification(toNotify)); } - if (appsProvider.settingsProvider.autoExportOnUpdateCheckKeepNum > 0) { - await appsProvider.exportApps(isAuto: true); - await appsProvider.trimAutoExports(); - } } // If you're done checking and found some silently installable updates, schedule another task which will run in install mode if (didCompleteChecking && toInstall.isNotEmpty) { diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index 984d3d9..c1ee5f5 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -391,12 +391,12 @@ class SettingsProvider with ChangeNotifier { } } - int get autoExportOnUpdateCheckKeepNum { - return prefs?.getInt('autoExportOnUpdateCheckKeepNum') ?? 0; + bool get autoExportOnChanges { + return prefs?.getBool('autoExportOnChanges') ?? false; } - set autoExportOnUpdateCheckKeepNum(int val) { - prefs?.setInt('autoExportOnUpdateCheckKeepNum', val); + set autoExportOnChanges(bool val) { + prefs?.setBool('autoExportOnChanges', val); notifyListeners(); } } diff --git a/pubspec.yaml b/pubspec.yaml index 355bbd4..09d66b8 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.14.12+204 # When changing this, update the tag in main() accordingly +version: 0.14.13+205 # When changing this, update the tag in main() accordingly environment: sdk: '>=3.0.0 <4.0.0' From 05f497787ee87aec018a230bee84616ee721864e Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sun, 10 Sep 2023 22:37:32 -0400 Subject: [PATCH 5/8] Build script tweak --- build.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 0ad2412..76fcdde 100755 --- a/build.sh +++ b/build.sh @@ -4,7 +4,9 @@ CURR_DIR="$(pwd)" trap "cd "$CURR_DIR"" EXIT -git fetch && git merge origin/main && git push # Typically run after a PR to main, so bring dev up to date +if [ -z "$1" ]; then + git fetch && git merge origin/main && git push # Typically run after a PR to main, so bring dev up to date +fi rm ./build/app/outputs/flutter-apk/* 2>/dev/null # Get rid of older builds if any flutter build apk && flutter build apk --split-per-abi # Build (both split and combined APKs) for file in ./build/app/outputs/flutter-apk/*.sha1; do gpg --sign --detach-sig "$file"; done # Generate PGP signatures From 118e05a0fa3550ffe441b15cc16f65b3241161fa Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sun, 10 Sep 2023 22:47:04 -0400 Subject: [PATCH 6/8] Bugfix --- lib/providers/apps_provider.dart | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index fd10a09..9cf0eca 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -1096,21 +1096,23 @@ class AppsProvider with ChangeNotifier { } Future exportApps({bool pickOnly = false, isAuto = false}) async { + var exportDir = settingsProvider.exportDir; if (isAuto) { + if (exportDir == null) { + logs.add('Skipping auto-export as dir is not set.'); + return null; + } logs.add('Started auto-export.'); - var exportDir = settingsProvider.exportDir; - if (exportDir != null) { - var files = await saf.listFiles(exportDir, - columns: [saf.DocumentFileColumn.id]).toList(); - if (files.isNotEmpty) { - for (var f in files) { - saf.delete(f.uri); - } - logs.add('Previous auto-export deleted.'); + var files = await saf + .listFiles(exportDir, columns: [saf.DocumentFileColumn.id]).toList(); + if (files.isNotEmpty) { + for (var f in files) { + saf.delete(f.uri); } + logs.add('Previous auto-export deleted.'); } } - var exportDir = settingsProvider.exportDir; + exportDir = settingsProvider.exportDir; if (exportDir == null || pickOnly) { await settingsProvider.pickExportDirKeepLastN(); exportDir = settingsProvider.exportDir; From 2aea1d263171f587e976365ab79669fe73a2ebc6 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sun, 10 Sep 2023 23:11:06 -0400 Subject: [PATCH 7/8] Bugfixes --- lib/pages/import_export.dart | 121 +++++++++++++++------------ lib/providers/apps_provider.dart | 13 +-- lib/providers/settings_provider.dart | 15 +++- 3 files changed, 86 insertions(+), 63 deletions(-) diff --git a/lib/pages/import_export.dart b/lib/pages/import_export.dart index c398532..159d38a 100644 --- a/lib/pages/import_export.dart +++ b/lib/pages/import_export.dart @@ -28,8 +28,8 @@ class _ImportExportPageState extends State { @override Widget build(BuildContext context) { SourceProvider sourceProvider = SourceProvider(); - var appsProvider = context.read(); - var settingsProvider = context.read(); + var appsProvider = context.watch(); + var settingsProvider = context.watch(); var outlineButtonStyle = ButtonStyle( shape: MaterialStateProperty.all( @@ -102,10 +102,12 @@ class _ImportExportPageState extends State { }); } - runObtainiumExport() { + runObtainiumExport() async { HapticFeedback.selectionClick(); appsProvider - .exportApps(pickOnly: settingsProvider.exportDir == null) + .exportApps( + pickOnly: (await settingsProvider.getExportDir()) == null, + sp: settingsProvider) .then((String? result) { if (result != null) { showError(tr('exportedTo', args: [result]), context); @@ -305,56 +307,69 @@ class _ImportExportPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Row( - children: [ - Expanded( - child: TextButton( - style: outlineButtonStyle, - onPressed: appsProvider.apps.isEmpty || - importInProgress - ? null - : runObtainiumExport, - child: Text(tr( - settingsProvider.exportDir != null - ? 'obtainiumExport' - : 'pickExportDirKeepLastN')))), - const SizedBox( - width: 16, - ), - Expanded( - child: TextButton( - style: outlineButtonStyle, - onPressed: importInProgress - ? null - : runObtainiumImport, - child: Text(tr('obtainiumImport')))) - ], - ), - if (settingsProvider.exportDir != null) - Column( - children: [ - const SizedBox(height: 16), - GeneratedForm( - items: [ - [ - GeneratedFormSwitch( - 'autoExportOnChanges', - label: tr('autoExportOnChanges'), - defaultValue: - settingsProvider.autoExportOnChanges, - ) - ] + FutureBuilder( + future: settingsProvider.getExportDir(), + builder: (context, snapshot) { + return Column( + children: [ + Row( + children: [ + Expanded( + child: TextButton( + style: outlineButtonStyle, + onPressed: appsProvider.apps.isEmpty || + importInProgress + ? null + : runObtainiumExport, + child: Text(tr(snapshot.data != null + ? 'obtainiumExport' + : 'pickExportDir')), + )), + const SizedBox( + width: 16, + ), + Expanded( + child: TextButton( + style: outlineButtonStyle, + onPressed: importInProgress + ? null + : runObtainiumImport, + child: Text(tr('obtainiumImport')))) ], - onValueChanges: (value, valid, isBuilding) { - if (valid && !isBuilding) { - if (value['autoExportOnChanges'] != null) { - settingsProvider.autoExportOnChanges = - value['autoExportOnChanges'] == true; - } - } - }), - ], - ), + ), + if (snapshot.data != null) + Column( + children: [ + const SizedBox(height: 16), + GeneratedForm( + items: [ + [ + GeneratedFormSwitch( + 'autoExportOnChanges', + label: tr('autoExportOnChanges'), + defaultValue: settingsProvider + .autoExportOnChanges, + ) + ] + ], + onValueChanges: + (value, valid, isBuilding) { + if (valid && !isBuilding) { + if (value['autoExportOnChanges'] != + null) { + settingsProvider + .autoExportOnChanges = value[ + 'autoExportOnChanges'] == + true; + } + } + }), + ], + ), + ], + ); + }, + ), if (importInProgress) const Column( children: [ diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 9cf0eca..3776018 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -1095,8 +1095,10 @@ class AppsProvider with ChangeNotifier { return updateAppIds; } - Future exportApps({bool pickOnly = false, isAuto = false}) async { - var exportDir = settingsProvider.exportDir; + Future exportApps( + {bool pickOnly = false, isAuto = false, SettingsProvider? sp}) async { + SettingsProvider settingsProvider = sp ?? this.settingsProvider; + var exportDir = await settingsProvider.getExportDir(); if (isAuto) { if (exportDir == null) { logs.add('Skipping auto-export as dir is not set.'); @@ -1112,13 +1114,12 @@ class AppsProvider with ChangeNotifier { logs.add('Previous auto-export deleted.'); } } - exportDir = settingsProvider.exportDir; if (exportDir == null || pickOnly) { - await settingsProvider.pickExportDirKeepLastN(); - exportDir = settingsProvider.exportDir; + await settingsProvider.pickExportDir(); + exportDir = await settingsProvider.getExportDir(); } if (exportDir == null) { - throw ObtainiumError(tr('unexpectedError')); + return null; } String? returnPath; if (!pickOnly) { diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index c1ee5f5..2085168 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -362,18 +362,25 @@ class SettingsProvider with ChangeNotifier { notifyListeners(); } - Uri? get exportDir { + Future getExportDir() async { var uriString = prefs?.getString('exportDir'); if (uriString != null) { - return Uri.parse(uriString); + Uri? uri = Uri.parse(uriString); + if (!(await saf.canRead(uri) ?? false) || + !(await saf.canWrite(uri) ?? false)) { + uri = null; + prefs?.remove('exportDir'); + notifyListeners(); + } + return uri; } else { return null; } } - Future pickExportDirKeepLastN({bool remove = false}) async { + Future pickExportDir({bool remove = false}) async { var existingSAFPerms = (await saf.persistedUriPermissions()) ?? []; - var currentOneWayDataSyncDir = exportDir; + var currentOneWayDataSyncDir = await getExportDir(); Uri? newOneWayDataSyncDir; if (!remove) { newOneWayDataSyncDir = (await saf.openDocumentTree()); From 9c56a4d1fc63e19758af4a092f06ad77015b4c8a Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sun, 10 Sep 2023 23:15:16 -0400 Subject: [PATCH 8/8] Bugs --- lib/providers/apps_provider.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 3776018..715a558 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -1106,7 +1106,9 @@ class AppsProvider with ChangeNotifier { } logs.add('Started auto-export.'); var files = await saf - .listFiles(exportDir, columns: [saf.DocumentFileColumn.id]).toList(); + .listFiles(exportDir, columns: [saf.DocumentFileColumn.id]) + .where((f) => f.uri.pathSegments.last.endsWith('-auto.json')) + .toList(); if (files.isNotEmpty) { for (var f in files) { saf.delete(f.uri); @@ -1132,7 +1134,7 @@ class AppsProvider with ChangeNotifier { throw ObtainiumError(tr('unexpectedError')); } returnPath = - exportDir.pathSegments.join('/').replaceFirst('tree/primary:', ''); + exportDir.pathSegments.join('/').replaceFirst('tree/primary:', '/'); } return returnPath; }