mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-10-31 21:43:29 +01:00 
			
		
		
		
	Compare commits
	
		
			16 Commits
		
	
	
		
			v0.14.11-b
			...
			v0.14.13-b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 17b5604f2a | ||
|  | fdb6eed6d0 | ||
|  | 13de0437b8 | ||
|  | a43c45f310 | ||
|  | 9c56a4d1fc | ||
|  | 2aea1d2631 | ||
|  | 118e05a0fa | ||
|  | 05f497787e | ||
|  | 53cf4d0234 | ||
|  | 6e735b1763 | ||
|  | 873a1a0683 | ||
|  | 27b1149d1c | ||
|  | c1e64f111e | ||
|  | b2af8448fd | ||
|  | 8f44338e76 | ||
|  | e4a55abcb3 | 
| @@ -254,6 +254,8 @@ | ||||
|     "versionExtractionRegEx": "Version Extraction RegEx", | ||||
|     "matchGroupToUse": "Match Group to Use", | ||||
|     "highlightTouchTargets": "Highlight less obvious touch targets", | ||||
|     "pickExportDir": "Pick Export Directory", | ||||
|     "autoExportOnChanges": "Auto-export on changes", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "Remover App?", | ||||
|         "other": "Remover Apps?" | ||||
|   | ||||
| @@ -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", | ||||
|    "autoExportOnChanges": "Auto-export on changes", | ||||
|      "removeAppQuestion": { | ||||
|       "one": "Želite li ukloniti aplikaciju?", | ||||
|       "other": "Želite li ukloniti aplikacije?" | ||||
|    }, | ||||
|   | ||||
| @@ -251,6 +251,8 @@ | ||||
|     "versionExtractionRegEx": "Version Extraction RegEx", | ||||
|     "matchGroupToUse": "Match Group to Use", | ||||
|     "highlightTouchTargets": "Highlight less obvious touch targets", | ||||
|     "pickExportDir": "Pick Export Directory", | ||||
|     "autoExportOnChanges": "Auto-export on changes", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "App entfernen?", | ||||
|         "other": "Apps entfernen?" | ||||
|   | ||||
| @@ -254,6 +254,8 @@ | ||||
|     "versionExtractionRegEx": "Version Extraction RegEx", | ||||
|     "matchGroupToUse": "Match Group to Use", | ||||
|     "highlightTouchTargets": "Highlight less obvious touch targets", | ||||
|     "pickExportDir": "Pick Export Directory", | ||||
|     "autoExportOnChanges": "Auto-export on changes", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "Remove App?", | ||||
|         "other": "Remove Apps?" | ||||
|   | ||||
| @@ -251,6 +251,8 @@ | ||||
|     "versionExtractionRegEx": "Version Extraction RegEx", | ||||
|     "matchGroupToUse": "Match Group to Use", | ||||
|     "highlightTouchTargets": "Highlight less obvious touch targets", | ||||
|     "pickExportDir": "Pick Export Directory", | ||||
|     "autoExportOnChanges": "Auto-export on changes", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "¿Eliminar Aplicación?", | ||||
|         "other": "¿Eliminar Aplicaciones?" | ||||
|   | ||||
| @@ -251,6 +251,8 @@ | ||||
|     "versionExtractionRegEx": "Version Extraction RegEx", | ||||
|     "matchGroupToUse": "Match Group to Use", | ||||
|     "highlightTouchTargets": "Highlight less obvious touch targets", | ||||
|     "pickExportDir": "Pick Export Directory", | ||||
|     "autoExportOnChanges": "Auto-export on changes", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "برنامه حذف شود؟", | ||||
|         "other": "برنامه ها حذف شوند؟" | ||||
|   | ||||
| @@ -251,6 +251,8 @@ | ||||
|     "versionExtractionRegEx": "Version Extraction RegEx", | ||||
|     "matchGroupToUse": "Match Group to Use", | ||||
|     "highlightTouchTargets": "Highlight less obvious touch targets", | ||||
|     "pickExportDir": "Pick Export Directory", | ||||
|     "autoExportOnChanges": "Auto-export on changes", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "Supprimer l'application ?", | ||||
|         "other": "Supprimer les applications ?" | ||||
|   | ||||
| @@ -250,6 +250,8 @@ | ||||
|     "versionExtractionRegEx": "Version Extraction RegEx", | ||||
|     "matchGroupToUse": "Match Group to Use", | ||||
|     "highlightTouchTargets": "Highlight less obvious touch targets", | ||||
|     "pickExportDir": "Pick Export Directory", | ||||
|     "autoExportOnChanges": "Auto-export on changes", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "Eltávolítja az alkalmazást?", | ||||
|         "other": "Eltávolítja az alkalmazást?" | ||||
|   | ||||
| @@ -251,6 +251,8 @@ | ||||
|     "versionExtractionRegEx": "Version Extraction RegEx", | ||||
|     "matchGroupToUse": "Match Group to Use", | ||||
|     "highlightTouchTargets": "Highlight less obvious touch targets", | ||||
|     "pickExportDir": "Pick Export Directory", | ||||
|     "autoExportOnChanges": "Auto-export on changes", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "Rimuovere l'app?", | ||||
|         "other": "Rimuovere le app?" | ||||
|   | ||||
| @@ -252,6 +252,8 @@ | ||||
|     "versionExtractionRegEx": "Version Extraction RegEx", | ||||
|     "matchGroupToUse": "Match Group to Use", | ||||
|     "highlightTouchTargets": "Highlight less obvious touch targets", | ||||
|     "pickExportDir": "Pick Export Directory", | ||||
|     "autoExportOnChanges": "Auto-export on changes", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "アプリを削除しますか?", | ||||
|         "other": "アプリを削除しますか?" | ||||
|   | ||||
| @@ -257,6 +257,8 @@ | ||||
|     "versionExtractionRegEx": "Version Extraction RegEx", | ||||
|     "matchGroupToUse": "Match Group to Use", | ||||
|     "highlightTouchTargets": "Highlight less obvious touch targets", | ||||
|     "pickExportDir": "Pick Export Directory", | ||||
|     "autoExportOnChanges": "Auto-export on changes", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "Usunąć aplikację?", | ||||
|         "few": "Usunąć aplikacje?", | ||||
|   | ||||
| @@ -251,6 +251,8 @@ | ||||
|     "versionExtractionRegEx": "Version Extraction RegEx", | ||||
|     "matchGroupToUse": "Match Group to Use", | ||||
|     "highlightTouchTargets": "Highlight less obvious touch targets", | ||||
|     "pickExportDir": "Pick Export Directory", | ||||
|     "autoExportOnChanges": "Auto-export on changes", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "Удалить приложение?", | ||||
|         "other": "Удалить приложения?" | ||||
|   | ||||
| @@ -252,6 +252,8 @@ | ||||
|     "versionExtractionRegEx": "Version Extraction RegEx", | ||||
|     "matchGroupToUse": "Match Group to Use", | ||||
|     "highlightTouchTargets": "Highlight less obvious touch targets", | ||||
|     "pickExportDir": "Pick Export Directory", | ||||
|     "autoExportOnChanges": "Auto-export on changes", | ||||
|     "removeAppQuestion": { | ||||
|         "one": "是否删除应用?", | ||||
|         "other": "是否删除应用?" | ||||
|   | ||||
							
								
								
									
										4
									
								
								build.sh
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								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 | ||||
|   | ||||
| @@ -252,8 +252,10 @@ class GitHub extends AppSource { | ||||
|       List<MapEntry<String, String>> getReleaseAPKUrls(dynamic release) => | ||||
|           (release['assets'] as List<dynamic>?) | ||||
|               ?.map((e) { | ||||
|                 return e['name'] != null && e['url'] != null | ||||
|                     ? MapEntry(e['name'] as String, e['url'] as String) | ||||
|                 return (e['name'] != null) && | ||||
|                         ((e['url'] ?? e['browser_download_url']) != null) | ||||
|                     ? MapEntry(e['name'] as String, | ||||
|                         (e['url'] ?? e['browser_download_url']) as String) | ||||
|                     : const MapEntry('', ''); | ||||
|               }) | ||||
|               .where((element) => element.key.toLowerCase().endsWith('.apk')) | ||||
|   | ||||
| @@ -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); | ||||
|               } | ||||
|             ]) | ||||
|       ] | ||||
|   | ||||
| @@ -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.11'; | ||||
| const String currentVersion = '0.14.13'; | ||||
| const String currentReleaseTag = | ||||
|     'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES | ||||
|  | ||||
|   | ||||
| @@ -338,9 +338,9 @@ class _AppPageState extends State<AppPage> { | ||||
|                 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); | ||||
|   | ||||
| @@ -381,8 +381,7 @@ class AppsPageState extends State<AppsPage> { | ||||
|               : () { | ||||
|                   appsProvider.downloadAndInstallLatestApps( | ||||
|                       [listedApps[appIndex].app.id], | ||||
|                       globalNavigatorKey.currentContext, | ||||
|                       settingsProvider).catchError((e) { | ||||
|                       globalNavigatorKey.currentContext).catchError((e) { | ||||
|                     showError(e, context); | ||||
|                     return <String>[]; | ||||
|                   }); | ||||
| @@ -452,12 +451,16 @@ class AppsPageState extends State<AppsPage> { | ||||
|               child: Container( | ||||
|                   decoration: BoxDecoration( | ||||
|                       borderRadius: BorderRadius.circular(12), | ||||
|                       color: Theme.of(context).primaryColor.withAlpha( | ||||
|                           (settingsProvider.highlightTouchTargets && | ||||
|                                   showChangesFn != null) | ||||
|                               ? 20 | ||||
|                               : 0)), | ||||
|                   padding: const EdgeInsets.fromLTRB(12, 0, 12, 0), | ||||
|                       color: settingsProvider.highlightTouchTargets && | ||||
|                               showChangesFn != null | ||||
|                           ? (Theme.of(context).brightness == Brightness.light | ||||
|                                   ? Theme.of(context).primaryColor | ||||
|                                   : Theme.of(context).primaryColorLight) | ||||
|                               .withAlpha(20) | ||||
|                           : null), | ||||
|                   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, | ||||
| @@ -695,8 +698,8 @@ class AppsPageState extends State<AppsPage> { | ||||
|                     toInstall.addAll(trackOnlyUpdateIdsAllOrSelected); | ||||
|                   } | ||||
|                   appsProvider | ||||
|                       .downloadAndInstallLatestApps(toInstall, | ||||
|                           globalNavigatorKey.currentContext, settingsProvider) | ||||
|                       .downloadAndInstallLatestApps( | ||||
|                           toInstall, globalNavigatorKey.currentContext) | ||||
|                       .catchError((e) { | ||||
|                     showError(e, context); | ||||
|                     return <String>[]; | ||||
|   | ||||
| @@ -28,8 +28,8 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     SourceProvider sourceProvider = SourceProvider(); | ||||
|     var appsProvider = context.read<AppsProvider>(); | ||||
|     var settingsProvider = context.read<SettingsProvider>(); | ||||
|     var appsProvider = context.watch<AppsProvider>(); | ||||
|     var settingsProvider = context.watch<SettingsProvider>(); | ||||
|  | ||||
|     var outlineButtonStyle = ButtonStyle( | ||||
|       shape: MaterialStateProperty.all( | ||||
| @@ -102,10 +102,16 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     runObtainiumExport() { | ||||
|     runObtainiumExport() async { | ||||
|       HapticFeedback.selectionClick(); | ||||
|       appsProvider.exportApps().then((String path) { | ||||
|         showError(tr('exportedTo', args: [path]), context); | ||||
|       appsProvider | ||||
|           .exportApps( | ||||
|               pickOnly: (await settingsProvider.getExportDir()) == null, | ||||
|               sp: settingsProvider) | ||||
|           .then((String? result) { | ||||
|         if (result != null) { | ||||
|           showError(tr('exportedTo', args: [result]), context); | ||||
|         } | ||||
|       }).catchError((e) { | ||||
|         showError(e, context); | ||||
|       }); | ||||
| @@ -301,27 +307,68 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|                   child: Column( | ||||
|                     crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                     children: [ | ||||
|                       Row( | ||||
|                         children: [ | ||||
|                           Expanded( | ||||
|                               child: TextButton( | ||||
|                                   style: outlineButtonStyle, | ||||
|                                   onPressed: appsProvider.apps.isEmpty || | ||||
|                                           importInProgress | ||||
|                                       ? null | ||||
|                                       : runObtainiumExport, | ||||
|                                   child: Text(tr('obtainiumExport')))), | ||||
|                           const SizedBox( | ||||
|                             width: 16, | ||||
|                           ), | ||||
|                           Expanded( | ||||
|                               child: TextButton( | ||||
|                                   style: outlineButtonStyle, | ||||
|                                   onPressed: importInProgress | ||||
|                                       ? null | ||||
|                                       : runObtainiumImport, | ||||
|                                   child: Text(tr('obtainiumImport')))) | ||||
|                         ], | ||||
|                       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')))) | ||||
|                                 ], | ||||
|                               ), | ||||
|                               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( | ||||
| @@ -399,7 +446,7 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|                               fontStyle: FontStyle.italic, fontSize: 12)), | ||||
|                       const SizedBox( | ||||
|                         height: 8, | ||||
|                       ) | ||||
|                       ), | ||||
|                     ], | ||||
|                   ))) | ||||
|         ])); | ||||
|   | ||||
| @@ -31,6 +31,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(); | ||||
|  | ||||
| @@ -150,6 +151,7 @@ class AppsProvider with ChangeNotifier { | ||||
|   late Stream<FGBGType>? foregroundStream; | ||||
|   late StreamSubscription<FGBGType>? foregroundSubscription; | ||||
|   late Directory APKDir; | ||||
|   late SettingsProvider settingsProvider = SettingsProvider(); | ||||
|  | ||||
|   Iterable<AppInMemory> getAppValues() => apps.values.map((a) => a.deepCopy()); | ||||
|  | ||||
| @@ -161,6 +163,7 @@ class AppsProvider with ChangeNotifier { | ||||
|       if (isForeground) await loadApps(); | ||||
|     }); | ||||
|     () async { | ||||
|       await settingsProvider.initializeSettings(); | ||||
|       var cacheDirs = await getExternalCacheDirectories(); | ||||
|       if (cacheDirs?.isNotEmpty ?? false) { | ||||
|         APKDir = cacheDirs!.first; | ||||
| @@ -369,8 +372,7 @@ class AppsProvider with ChangeNotifier { | ||||
|       .where((element) => element.downloadProgress != null) | ||||
|       .isNotEmpty; | ||||
|  | ||||
|   Future<bool> canInstallSilently( | ||||
|       App app, SettingsProvider settingsProvider) async { | ||||
|   Future<bool> canInstallSilently(App app) async { | ||||
|     if (app.id == obtainiumId) { | ||||
|       return false; | ||||
|     } | ||||
| @@ -539,7 +541,6 @@ class AppsProvider with ChangeNotifier { | ||||
|         getHost(apkUrl.value) != getHost(app.url) && | ||||
|         context != null) { | ||||
|       // ignore: use_build_context_synchronously | ||||
|       var settingsProvider = context.read<SettingsProvider>(); | ||||
|       if (!(settingsProvider.hideAPKOriginWarning) && | ||||
|           // ignore: use_build_context_synchronously | ||||
|           await showDialog( | ||||
| @@ -560,8 +561,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<List<String>> downloadAndInstallLatestApps(List<String> appIds, | ||||
|       BuildContext? context, SettingsProvider settingsProvider, | ||||
|   Future<List<String>> downloadAndInstallLatestApps( | ||||
|       List<String> appIds, BuildContext? context, | ||||
|       {NotificationsProvider? notificationsProvider}) async { | ||||
|     notificationsProvider = | ||||
|         notificationsProvider ?? context?.read<NotificationsProvider>(); | ||||
| @@ -590,8 +591,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 +628,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 +677,8 @@ class AppsProvider with ChangeNotifier { | ||||
|   } | ||||
|  | ||||
|   Future<Directory> getAppsDir() async { | ||||
|     Directory appsDir = Directory( | ||||
|         '${(await getExternalStorageDirectory())?.path as String}/app_data'); | ||||
|     Directory appsDir = | ||||
|         Directory('${(await getExternalStorageDirectory())!.path}/app_data'); | ||||
|     if (!appsDir.existsSync()) { | ||||
|       appsDir.createSync(); | ||||
|     } | ||||
| @@ -879,8 +878,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); | ||||
|         } | ||||
| @@ -919,6 +916,7 @@ class AppsProvider with ChangeNotifier { | ||||
|       } | ||||
|     } | ||||
|     notifyListeners(); | ||||
|     exportApps(isAuto: true); | ||||
|   } | ||||
|  | ||||
|   Future<void> removeApps(List<String> appIds) async { | ||||
| @@ -940,6 +938,7 @@ class AppsProvider with ChangeNotifier { | ||||
|     } | ||||
|     if (appIds.isNotEmpty) { | ||||
|       notifyListeners(); | ||||
|       exportApps(isAuto: true); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -1096,32 +1095,51 @@ class AppsProvider with ChangeNotifier { | ||||
|     return updateAppIds; | ||||
|   } | ||||
|  | ||||
|   Future<String> exportApps() async { | ||||
|     if ((await DeviceInfoPlugin().androidInfo).version.sdkInt <= 29) { | ||||
|       if (await Permission.storage.isDenied) { | ||||
|         await Permission.storage.request(); | ||||
|   Future<String?> exportApps( | ||||
|       {bool pickOnly = false, isAuto = false, SettingsProvider? sp}) async { | ||||
|     SettingsProvider settingsProvider = sp ?? this.settingsProvider; | ||||
|     var exportDir = await settingsProvider.getExportDir(); | ||||
|     if (isAuto) { | ||||
|       if (settingsProvider.autoExportOnChanges != true) { | ||||
|         return null; | ||||
|       } | ||||
|       if (await Permission.storage.isDenied) { | ||||
|         throw ObtainiumError(tr('storagePermissionDenied')); | ||||
|       if (exportDir == null) { | ||||
|         logs.add('Skipping auto-export as dir is not set.'); | ||||
|         return null; | ||||
|       } | ||||
|       logs.add('Started auto-export.'); | ||||
|       var files = await saf | ||||
|           .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); | ||||
|         } | ||||
|         logs.add('Previous auto-export deleted.'); | ||||
|       } | ||||
|     } | ||||
|     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 (exportDir == null || pickOnly) { | ||||
|       await settingsProvider.pickExportDir(); | ||||
|       exportDir = await settingsProvider.getExportDir(); | ||||
|     } | ||||
|     if (!downloadsAccessible) { | ||||
|       exportDir = await getExternalStorageDirectory(); | ||||
|       path = exportDir!.path; | ||||
|     if (exportDir == null) { | ||||
|       return null; | ||||
|     } | ||||
|     File export = File( | ||||
|         '${exportDir.path}/${tr('obtainiumExportHyphenatedLowercase')}-${DateTime.now().millisecondsSinceEpoch}.json'); | ||||
|     export.writeAsStringSync( | ||||
|         jsonEncode(apps.values.map((e) => e.app.toJson()).toList())); | ||||
|     return path; | ||||
|     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')); | ||||
|       } | ||||
|       returnPath = | ||||
|           exportDir.pathSegments.join('/').replaceFirst('tree/primary:', '/'); | ||||
|     } | ||||
|     return returnPath; | ||||
|   } | ||||
|  | ||||
|   Future<int> importApps(String appsJSON) async { | ||||
| @@ -1298,14 +1316,12 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? 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<MapEntry<String, int>> toCheck = <MapEntry<String, int>>[ | ||||
|     ...(params['toCheck'] | ||||
| @@ -1335,7 +1351,7 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? 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 +1371,7 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? 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 +1457,7 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? 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: | ||||
|   | ||||
| @@ -9,8 +9,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 +37,7 @@ List<int> 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 +45,7 @@ class SettingsProvider with ChangeNotifier { | ||||
|   // Not done in constructor as we want to be able to await it | ||||
|   Future<void> initializeSettings() async { | ||||
|     prefs = await SharedPreferences.getInstance(); | ||||
|     defaultAppDir = (await getExternalStorageDirectory())!.path; | ||||
|     notifyListeners(); | ||||
|   } | ||||
|  | ||||
| @@ -357,4 +361,49 @@ class SettingsProvider with ChangeNotifier { | ||||
|     prefs?.setBool('highlightTouchTargets', val); | ||||
|     notifyListeners(); | ||||
|   } | ||||
|  | ||||
|   Future<Uri?> getExportDir() async { | ||||
|     var uriString = prefs?.getString('exportDir'); | ||||
|     if (uriString != null) { | ||||
|       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<void> pickExportDir({bool remove = false}) async { | ||||
|     var existingSAFPerms = (await saf.persistedUriPermissions()) ?? []; | ||||
|     var currentOneWayDataSyncDir = await getExportDir(); | ||||
|     Uri? newOneWayDataSyncDir; | ||||
|     if (!remove) { | ||||
|       newOneWayDataSyncDir = (await saf.openDocumentTree()); | ||||
|     } | ||||
|     if (currentOneWayDataSyncDir?.path != newOneWayDataSyncDir?.path) { | ||||
|       if (newOneWayDataSyncDir == null) { | ||||
|         prefs?.remove('exportDir'); | ||||
|       } else { | ||||
|         prefs?.setString('exportDir', newOneWayDataSyncDir.toString()); | ||||
|       } | ||||
|       notifyListeners(); | ||||
|     } | ||||
|     for (var e in existingSAFPerms) { | ||||
|       await saf.releasePersistableUriPermission(e.uri); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   bool get autoExportOnChanges { | ||||
|     return prefs?.getBool('autoExportOnChanges') ?? false; | ||||
|   } | ||||
|  | ||||
|   set autoExportOnChanges(bool val) { | ||||
|     prefs?.setBool('autoExportOnChanges', val); | ||||
|     notifyListeners(); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -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<AppSource> get sources => [ | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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.11+203 # 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' | ||||
| @@ -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: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user