mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-10-31 13:33:28 +01:00 
			
		
		
		
	Compare commits
	
		
			16 Commits
		
	
	
		
			v0.10.1-be
			...
			v0.10.5-be
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 4252c2711b | ||
|  | 52913b0450 | ||
|  | 427b0ed8d2 | ||
|  | a85d6d4f08 | ||
|  | 05f712603c | ||
|  | fa2a80e34c | ||
|  | f43e5a2ff1 | ||
|  | b72aa8273e | ||
|  | 520f186e4a | ||
|  | e1e97672cf | ||
|  | 1494bcd013 | ||
|  | 3457a0a12f | ||
|  | b165400a6e | ||
|  | c47bf937f1 | ||
|  | 2e19a8c04c | ||
|  | 05d4da86ec | 
| @@ -2,4 +2,5 @@ | ||||
| <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|   <background android:drawable="@color/ic_launcher_background"/> | ||||
|   <foreground android:drawable="@drawable/ic_launcher_foreground"/> | ||||
|   <monochrome android:drawable="@drawable/ic_launcher_foreground"/> | ||||
| </adaptive-icon> | ||||
|   | ||||
| @@ -211,6 +211,7 @@ | ||||
|     "language": "Sprache", | ||||
|     "storagePermissionDenied": "Storage permission denied", | ||||
|     "selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.", | ||||
|     "filterAPKsByRegEx": "Filter APKs by Regular Expression", | ||||
|     "tooManyRequestsTryAgainInMinutes": { | ||||
|         "one": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minute erneut", | ||||
|         "other": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minuten erneut" | ||||
|   | ||||
| @@ -211,6 +211,7 @@ | ||||
|     "language": "Language", | ||||
|     "storagePermissionDenied": "Storage permission denied", | ||||
|     "selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.", | ||||
|     "filterAPKsByRegEx": "Filter APKs by Regular Expression", | ||||
|     "tooManyRequestsTryAgainInMinutes": { | ||||
|         "one": "Too many requests (rate limited) - try again in {} minute", | ||||
|         "other": "Too many requests (rate limited) - try again in {} minutes" | ||||
|   | ||||
| @@ -207,9 +207,10 @@ | ||||
|     "categoryDeleteWarning": "A(z) {} összes app kategorizálatlan állapotba kerül.", | ||||
|     "addCategory": "Új kategória", | ||||
|     "label": "Címke", | ||||
|     "language": "Language", | ||||
|     "storagePermissionDenied": "Storage permission denied", | ||||
|     "selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.", | ||||
|     "language": "Nyelv", | ||||
|     "storagePermissionDenied": "Tárhely engedély megtagadva", | ||||
|     "selectedCategorizeWarning": "Ez felváltja a kiválasztott alkalmazások meglévő kategória-beállításait.", | ||||
|     "filterAPKsByRegEx": "Filter APKs by Regular Expression", | ||||
|     "tooManyRequestsTryAgainInMinutes": { | ||||
|         "one": "Túl sok kérés (korlátozott arány) – próbálja újra {} perc múlva", | ||||
|         "other": "Túl sok kérés (korlátozott arány) – próbálja újra {} perc múlva" | ||||
|   | ||||
| @@ -211,6 +211,7 @@ | ||||
|     "language": "Lingua", | ||||
|     "storagePermissionDenied": "Storage permission denied", | ||||
|     "selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.", | ||||
|     "filterAPKsByRegEx": "Filter APKs by Regular Expression", | ||||
|     "tooManyRequestsTryAgainInMinutes": { | ||||
|         "one": "Troppe richieste (traffico limitato) - riprova tra {} minuto", | ||||
|         "other": "Troppe richieste (traffico limitato) - riprova tra {} minuti" | ||||
|   | ||||
| @@ -211,6 +211,7 @@ | ||||
|     "language": "言語", | ||||
|     "storagePermissionDenied": "ストレージ権限が拒否されました", | ||||
|     "selectedCategorizeWarning": "これにより、選択したアプリの既存のカテゴリ設定がすべて置き換えられます。", | ||||
|     "filterAPKsByRegEx": "Filter APKs by Regular Expression", | ||||
|     "tooManyRequestsTryAgainInMinutes": { | ||||
|         "one": "リクエストが多すぎます(レート制限)- {}分後に再試行してください", | ||||
|         "other": "リクエストが多すぎます(レート制限)- {}分後に再試行してください" | ||||
|   | ||||
| @@ -211,6 +211,7 @@ | ||||
|     "language": "语言", | ||||
|     "storagePermissionDenied": "存储权限已被拒绝", | ||||
|     "selectedCategorizeWarning": "这将取代所选应用程序的任何现有类别", | ||||
|     "filterAPKsByRegEx": "Filter APKs by Regular Expression", | ||||
|     "tooManyRequestsTryAgainInMinutes": { | ||||
|         "one": "请求过多 (API 限制) - 在 {} 分钟后重试", | ||||
|         "other": "请求过多 (API 限制) - 在 {} 分钟后重试" | ||||
|   | ||||
| @@ -26,15 +26,7 @@ class Codeberg extends AppSource { | ||||
|             required: false, | ||||
|             additionalValidators: [ | ||||
|               (value) { | ||||
|                 if (value == null || value.isEmpty) { | ||||
|                   return null; | ||||
|                 } | ||||
|                 try { | ||||
|                   RegExp(value); | ||||
|                 } catch (e) { | ||||
|                   return tr('invalidRegEx'); | ||||
|                 } | ||||
|                 return null; | ||||
|                 return regExValidator(value); | ||||
|               } | ||||
|             ]) | ||||
|       ] | ||||
| @@ -72,7 +64,7 @@ class Codeberg extends AppSource { | ||||
|             ? additionalSettings['filterReleaseTitlesByRegEx'] | ||||
|             : null; | ||||
|     Response res = await get(Uri.parse( | ||||
|         'https://$host/api/v1/repos${standardUrl.substring('https://$host'.length)}/releases')); | ||||
|         'https://$host/api/v1/repos${standardUrl.substring('https://$host'.length)}/releases?per_page=100')); | ||||
|     if (res.statusCode == 200) { | ||||
|       var releases = jsonDecode(res.body) as List<dynamic>; | ||||
|  | ||||
| @@ -99,8 +91,8 @@ class Codeberg extends AppSource { | ||||
|         if (releases[i]['draft'] == true) { | ||||
|           // Draft releases not supported | ||||
|         } | ||||
|         var nameToFilter = releases[i]['name'] as String; | ||||
|         if (nameToFilter.trim().isEmpty) { | ||||
|         var nameToFilter = releases[i]['name'] as String?; | ||||
|         if (nameToFilter == null || nameToFilter.trim().isEmpty) { | ||||
|           // Some leave titles empty so tag is used | ||||
|           nameToFilter = releases[i]['tag_name'] as String; | ||||
|         } | ||||
|   | ||||
| @@ -65,15 +65,7 @@ class GitHub extends AppSource { | ||||
|             required: false, | ||||
|             additionalValidators: [ | ||||
|               (value) { | ||||
|                 if (value == null || value.isEmpty) { | ||||
|                   return null; | ||||
|                 } | ||||
|                 try { | ||||
|                   RegExp(value); | ||||
|                 } catch (e) { | ||||
|                   return tr('invalidRegEx'); | ||||
|                 } | ||||
|                 return null; | ||||
|                 return regExValidator(value); | ||||
|               } | ||||
|             ]) | ||||
|       ] | ||||
| @@ -119,7 +111,7 @@ class GitHub extends AppSource { | ||||
|             ? additionalSettings['filterReleaseTitlesByRegEx'] | ||||
|             : null; | ||||
|     Response res = await get(Uri.parse( | ||||
|         'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/releases')); | ||||
|         'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/releases?per_page=100')); | ||||
|     if (res.statusCode == 200) { | ||||
|       var releases = jsonDecode(res.body) as List<dynamic>; | ||||
|  | ||||
| @@ -141,8 +133,8 @@ class GitHub extends AppSource { | ||||
|         if (!includePrereleases && releases[i]['prerelease'] == true) { | ||||
|           continue; | ||||
|         } | ||||
|         var nameToFilter = releases[i]['name'] as String; | ||||
|         if (nameToFilter.trim().isEmpty) { | ||||
|         var nameToFilter = releases[i]['name'] as String?; | ||||
|         if (nameToFilter == null || nameToFilter.trim().isEmpty) { | ||||
|           // Some leave titles empty so tag is used | ||||
|           nameToFilter = releases[i]['tag_name'] as String; | ||||
|         } | ||||
|   | ||||
| @@ -29,7 +29,7 @@ class NoReleasesError extends ObtainiumError { | ||||
| } | ||||
|  | ||||
| class NoAPKError extends ObtainiumError { | ||||
|   NoAPKError() : super(tr('noReleaseFound')); | ||||
|   NoAPKError() : super(tr('noAPKFound')); | ||||
| } | ||||
|  | ||||
| class NoVersionError extends ObtainiumError { | ||||
|   | ||||
| @@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart'; | ||||
| // ignore: implementation_imports | ||||
| import 'package:easy_localization/src/localization.dart'; | ||||
|  | ||||
| const String currentVersion = '0.10.1'; | ||||
| const String currentVersion = '0.10.5'; | ||||
| const String currentReleaseTag = | ||||
|     'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES | ||||
|  | ||||
|   | ||||
| @@ -70,6 +70,7 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|             additionalSettings['noVersionDetection'] == true; | ||||
|         var cont = true; | ||||
|         if ((userPickedTrackOnly || pickedSource!.enforceTrackOnly) && | ||||
|             // ignore: use_build_context_synchronously | ||||
|             await showDialog( | ||||
|                     context: context, | ||||
|                     builder: (BuildContext ctx) { | ||||
| @@ -88,6 +89,7 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|           cont = false; | ||||
|         } | ||||
|         if (userPickedNoVersionDetection && | ||||
|             // ignore: use_build_context_synchronously | ||||
|             await showDialog( | ||||
|                     context: context, | ||||
|                     builder: (BuildContext ctx) { | ||||
|   | ||||
| @@ -317,7 +317,7 @@ class _AppPageState extends State<AppPage> { | ||||
|                               tooltip: tr('more')), | ||||
|                         const SizedBox(width: 16.0), | ||||
|                         Expanded( | ||||
|                             child: ElevatedButton( | ||||
|                             child: TextButton( | ||||
|                                 onPressed: (app?.app.installedVersion == null || | ||||
|                                             app?.app.installedVersion != | ||||
|                                                 app?.app.latestVersion) && | ||||
| @@ -356,7 +356,8 @@ class _AppPageState extends State<AppPage> { | ||||
|                                         ? tr('update') | ||||
|                                         : tr('markUpdated')))), | ||||
|                         const SizedBox(width: 16.0), | ||||
|                         ElevatedButton( | ||||
|                         Expanded( | ||||
|                             child: TextButton( | ||||
|                           onPressed: app?.downloadProgress != null | ||||
|                               ? null | ||||
|                               : () { | ||||
| @@ -401,7 +402,7 @@ class _AppPageState extends State<AppPage> { | ||||
|                               surfaceTintColor: | ||||
|                                   Theme.of(context).colorScheme.error), | ||||
|                           child: Text(tr('remove')), | ||||
|                         ), | ||||
|                         )), | ||||
|                       ])), | ||||
|               if (app?.downloadProgress != null) | ||||
|                 Padding( | ||||
|   | ||||
| @@ -344,13 +344,15 @@ class AppsPageState extends State<AppsPage> { | ||||
|                   )); | ||||
|             }, childCount: sortedApps.length)) | ||||
|           ])), | ||||
|       persistentFooterButtons: [ | ||||
|       persistentFooterButtons: appsProvider.apps.isEmpty | ||||
|           ? null | ||||
|           : [ | ||||
|               Row( | ||||
|                 children: [ | ||||
|                   selectedApps.isEmpty | ||||
|                       ? TextButton.icon( | ||||
|                     style: | ||||
|                         const ButtonStyle(visualDensity: VisualDensity.compact), | ||||
|                           style: const ButtonStyle( | ||||
|                               visualDensity: VisualDensity.compact), | ||||
|                           onPressed: () { | ||||
|                             selectThese(sortedApps.map((e) => e.app).toList()); | ||||
|                           }, | ||||
| @@ -360,11 +362,12 @@ class AppsPageState extends State<AppsPage> { | ||||
|                           ), | ||||
|                           label: Text(sortedApps.length.toString())) | ||||
|                       : TextButton.icon( | ||||
|                     style: | ||||
|                         const ButtonStyle(visualDensity: VisualDensity.compact), | ||||
|                           style: const ButtonStyle( | ||||
|                               visualDensity: VisualDensity.compact), | ||||
|                           onPressed: () { | ||||
|                             selectedApps.isEmpty | ||||
|                           ? selectThese(sortedApps.map((e) => e.app).toList()) | ||||
|                                 ? selectThese( | ||||
|                                     sortedApps.map((e) => e.app).toList()) | ||||
|                                 : clearSelected(); | ||||
|                           }, | ||||
|                           icon: Icon( | ||||
| @@ -390,15 +393,15 @@ class AppsPageState extends State<AppsPage> { | ||||
|                                             context: context, | ||||
|                                             builder: (BuildContext ctx) { | ||||
|                                               return GeneratedFormModal( | ||||
|                                           title: | ||||
|                                               tr('removeSelectedAppsQuestion'), | ||||
|                                                 title: tr( | ||||
|                                                     'removeSelectedAppsQuestion'), | ||||
|                                                 items: const [], | ||||
|                                                 initValid: true, | ||||
|                                                 message: tr( | ||||
|                                                     'xWillBeRemovedButRemainInstalled', | ||||
|                                                     args: [ | ||||
|                                                 plural( | ||||
|                                                     'apps', selectedApps.length) | ||||
|                                                       plural('apps', | ||||
|                                                           selectedApps.length) | ||||
|                                                     ]), | ||||
|                                               ); | ||||
|                                             }).then((values) { | ||||
| @@ -414,14 +417,19 @@ class AppsPageState extends State<AppsPage> { | ||||
|                               ), | ||||
|                               IconButton( | ||||
|                                   visualDensity: VisualDensity.compact, | ||||
|                             onPressed: appsProvider.areDownloadsRunning() || | ||||
|                                     (existingUpdateIdsAllOrSelected.isEmpty && | ||||
|                                         newInstallIdsAllOrSelected.isEmpty && | ||||
|                                         trackOnlyUpdateIdsAllOrSelected.isEmpty) | ||||
|                                   onPressed: appsProvider | ||||
|                                               .areDownloadsRunning() || | ||||
|                                           (existingUpdateIdsAllOrSelected | ||||
|                                                   .isEmpty && | ||||
|                                               newInstallIdsAllOrSelected | ||||
|                                                   .isEmpty && | ||||
|                                               trackOnlyUpdateIdsAllOrSelected | ||||
|                                                   .isEmpty) | ||||
|                                       ? null | ||||
|                                       : () { | ||||
|                                           HapticFeedback.heavyImpact(); | ||||
|                                     List<GeneratedFormItem> formItems = []; | ||||
|                                           List<GeneratedFormItem> formItems = | ||||
|                                               []; | ||||
|                                           if (existingUpdateIdsAllOrSelected | ||||
|                                               .isNotEmpty) { | ||||
|                                             formItems.add(GeneratedFormSwitch( | ||||
| @@ -434,7 +442,8 @@ class AppsPageState extends State<AppsPage> { | ||||
|                                                 ]), | ||||
|                                                 defaultValue: true)); | ||||
|                                           } | ||||
|                                     if (newInstallIdsAllOrSelected.isNotEmpty) { | ||||
|                                           if (newInstallIdsAllOrSelected | ||||
|                                               .isNotEmpty) { | ||||
|                                             formItems.add(GeneratedFormSwitch( | ||||
|                                                 'installs', | ||||
|                                                 label: tr('installX', args: [ | ||||
| @@ -451,7 +460,8 @@ class AppsPageState extends State<AppsPage> { | ||||
|                                               .isNotEmpty) { | ||||
|                                             formItems.add(GeneratedFormSwitch( | ||||
|                                                 'trackonlies', | ||||
|                                           label: tr('markXTrackOnlyAsUpdated', | ||||
|                                                 label: tr( | ||||
|                                                     'markXTrackOnlyAsUpdated', | ||||
|                                                     args: [ | ||||
|                                                       plural( | ||||
|                                                           'apps', | ||||
| @@ -467,8 +477,8 @@ class AppsPageState extends State<AppsPage> { | ||||
|                                           showDialog<Map<String, dynamic>?>( | ||||
|                                               context: context, | ||||
|                                               builder: (BuildContext ctx) { | ||||
|                                           var totalApps = | ||||
|                                               existingUpdateIdsAllOrSelected.length + | ||||
|                                                 var totalApps = existingUpdateIdsAllOrSelected | ||||
|                                                         .length + | ||||
|                                                     newInstallIdsAllOrSelected | ||||
|                                                         .length + | ||||
|                                                     trackOnlyUpdateIdsAllOrSelected | ||||
| @@ -560,7 +570,8 @@ class AppsPageState extends State<AppsPage> { | ||||
|                                             cont = await showDialog< | ||||
|                                                         Map<String, dynamic>?>( | ||||
|                                                     context: context, | ||||
|                                               builder: (BuildContext ctx) { | ||||
|                                                     builder: | ||||
|                                                         (BuildContext ctx) { | ||||
|                                                       return GeneratedFormModal( | ||||
|                                                         title: tr('categorize'), | ||||
|                                                         items: const [], | ||||
| @@ -572,7 +583,9 @@ class AppsPageState extends State<AppsPage> { | ||||
|                                                 null; | ||||
|                                           } | ||||
|                                           if (cont) { | ||||
|                                       await showDialog<Map<String, dynamic>?>( | ||||
|                                             // ignore: use_build_context_synchronously | ||||
|                                             await showDialog< | ||||
|                                                     Map<String, dynamic>?>( | ||||
|                                                 context: context, | ||||
|                                                 builder: (BuildContext ctx) { | ||||
|                                                   return GeneratedFormModal( | ||||
| @@ -586,11 +599,15 @@ class AppsPageState extends State<AppsPage> { | ||||
|                                                         preselected: !showPrompt | ||||
|                                                             ? preselected ?? {} | ||||
|                                                             : {}, | ||||
|                                                   showLabelWhenNotEmpty: false, | ||||
|                                                   onSelected: (categories) { | ||||
|                                                         showLabelWhenNotEmpty: | ||||
|                                                             false, | ||||
|                                                         onSelected: | ||||
|                                                             (categories) { | ||||
|                                                           appsProvider.saveApps( | ||||
|                                                         selectedApps.map((e) { | ||||
|                                                       e.categories = categories; | ||||
|                                                               selectedApps | ||||
|                                                                   .map((e) { | ||||
|                                                             e.categories = | ||||
|                                                                 categories; | ||||
|                                                             return e; | ||||
|                                                           }).toList()); | ||||
|                                                         }, | ||||
| @@ -618,7 +635,8 @@ class AppsPageState extends State<AppsPage> { | ||||
|                                                 scrollable: true, | ||||
|                                                 content: Padding( | ||||
|                                                   padding: | ||||
|                                                 const EdgeInsets.only(top: 6), | ||||
|                                                       const EdgeInsets.only( | ||||
|                                                           top: 6), | ||||
|                                                   child: Row( | ||||
|                                                       mainAxisAlignment: | ||||
|                                                           MainAxisAlignment | ||||
| @@ -636,30 +654,23 @@ class AppsPageState extends State<AppsPage> { | ||||
|                                                                             (BuildContext | ||||
|                                                                                 ctx) { | ||||
|                                                                           return AlertDialog( | ||||
|                                                                       title: Text(tr( | ||||
|                                                                           'markXSelectedAppsAsUpdated', | ||||
|                                                                           args: [ | ||||
|                                                                             title: | ||||
|                                                                                 Text(tr('markXSelectedAppsAsUpdated', args: [ | ||||
|                                                                               selectedApps.length.toString() | ||||
|                                                                             ])), | ||||
|                                                                             content: | ||||
|                                                                                 Text( | ||||
|                                                                               tr('onlyWorksWithNonEVDApps'), | ||||
|                                                                         style: const TextStyle( | ||||
|                                                                             fontWeight: | ||||
|                                                                                 FontWeight.bold, | ||||
|                                                                             fontStyle: FontStyle.italic), | ||||
|                                                                               style: const TextStyle(fontWeight: FontWeight.bold, fontStyle: FontStyle.italic), | ||||
|                                                                             ), | ||||
|                                                                             actions: [ | ||||
|                                                                               TextButton( | ||||
|                                                                             onPressed: | ||||
|                                                                                 () { | ||||
|                                                                                   onPressed: () { | ||||
|                                                                                     Navigator.of(context).pop(); | ||||
|                                                                                   }, | ||||
|                                                                             child: | ||||
|                                                                                 Text(tr('no'))), | ||||
|                                                                                   child: Text(tr('no'))), | ||||
|                                                                               TextButton( | ||||
|                                                                             onPressed: | ||||
|                                                                                 () { | ||||
|                                                                                   onPressed: () { | ||||
|                                                                                     HapticFeedback.selectionClick(); | ||||
|                                                                                     appsProvider.saveApps(selectedApps.map((a) { | ||||
|                                                                                       if (a.installedVersion != null) { | ||||
| @@ -670,8 +681,7 @@ class AppsPageState extends State<AppsPage> { | ||||
|  | ||||
|                                                                                     Navigator.of(context).pop(); | ||||
|                                                                                   }, | ||||
|                                                                             child: | ||||
|                                                                                 Text(tr('yes'))) | ||||
|                                                                                   child: Text(tr('yes'))) | ||||
|                                                                             ], | ||||
|                                                                           ); | ||||
|                                                                         }).whenComplete(() { | ||||
| @@ -686,29 +696,36 @@ class AppsPageState extends State<AppsPage> { | ||||
|                                                                 Icons.done)), | ||||
|                                                         IconButton( | ||||
|                                                           onPressed: () { | ||||
|                                                       var pinStatus = | ||||
|                                                           selectedApps | ||||
|                                                             var pinStatus = selectedApps | ||||
|                                                                 .where((element) => | ||||
|                                                                     element | ||||
|                                                                         .pinned) | ||||
|                                                                 .isEmpty; | ||||
|                                                       appsProvider.saveApps( | ||||
|                                                           selectedApps.map((e) { | ||||
|                                                         e.pinned = pinStatus; | ||||
|                                                             appsProvider | ||||
|                                                                 .saveApps( | ||||
|                                                                     selectedApps | ||||
|                                                                         .map( | ||||
|                                                                             (e) { | ||||
|                                                               e.pinned = | ||||
|                                                                   pinStatus; | ||||
|                                                               return e; | ||||
|                                                             }).toList()); | ||||
|                                                       Navigator.of(context) | ||||
|                                                             Navigator.of( | ||||
|                                                                     context) | ||||
|                                                                 .pop(); | ||||
|                                                           }, | ||||
|                                                           tooltip: selectedApps | ||||
|                                                                   .where((element) => | ||||
|                                                                 element.pinned) | ||||
|                                                                       element | ||||
|                                                                           .pinned) | ||||
|                                                                   .isEmpty | ||||
|                                                               ? tr('pinToTop') | ||||
|                                                         : tr('unpinFromTop'), | ||||
|                                                               : tr( | ||||
|                                                                   'unpinFromTop'), | ||||
|                                                           icon: Icon(selectedApps | ||||
|                                                                   .where((element) => | ||||
|                                                                 element.pinned) | ||||
|                                                                       element | ||||
|                                                                           .pinned) | ||||
|                                                                   .isEmpty | ||||
|                                                               ? Icons | ||||
|                                                                   .bookmark_outline_rounded | ||||
| @@ -720,53 +737,63 @@ class AppsPageState extends State<AppsPage> { | ||||
|                                                             String urls = ''; | ||||
|                                                             for (var a | ||||
|                                                                 in selectedApps) { | ||||
|                                                         urls += '${a.url}\n'; | ||||
|                                                               urls += | ||||
|                                                                   '${a.url}\n'; | ||||
|                                                             } | ||||
|                                                       urls = urls.substring( | ||||
|                                                           0, urls.length - 1); | ||||
|                                                             urls = | ||||
|                                                                 urls.substring( | ||||
|                                                                     0, | ||||
|                                                                     urls.length - | ||||
|                                                                         1); | ||||
|                                                             Share.share(urls, | ||||
|                                                                 subject: tr( | ||||
|                                                                     'selectedAppURLsFromObtainium')); | ||||
|                                                       Navigator.of(context) | ||||
|                                                             Navigator.of( | ||||
|                                                                     context) | ||||
|                                                                 .pop(); | ||||
|                                                           }, | ||||
|                                                           tooltip: tr( | ||||
|                                                               'shareSelectedAppURLs'), | ||||
|                                                     icon: | ||||
|                                                         const Icon(Icons.share), | ||||
|                                                           icon: const Icon( | ||||
|                                                               Icons.share), | ||||
|                                                         ), | ||||
|                                                         IconButton( | ||||
|                                                           onPressed: () { | ||||
|                                                             showDialog( | ||||
|                                                           context: context, | ||||
|                                                           builder: (BuildContext | ||||
|                                                                 context: | ||||
|                                                                     context, | ||||
|                                                                 builder: | ||||
|                                                                     (BuildContext | ||||
|                                                                         ctx) { | ||||
|                                                                   return GeneratedFormModal( | ||||
|                                                                     title: tr( | ||||
|                                                                         'resetInstallStatusForSelectedAppsQuestion'), | ||||
|                                                                     items: const [], | ||||
|                                                               initValid: true, | ||||
|                                                                     initValid: | ||||
|                                                                         true, | ||||
|                                                                     message: tr( | ||||
|                                                                         'installStatusOfXWillBeResetExplanation', | ||||
|                                                                         args: [ | ||||
|                                                                           plural( | ||||
|                                                                               'app', | ||||
|                                                                         selectedApps | ||||
|                                                                             .length) | ||||
|                                                                               selectedApps.length) | ||||
|                                                                         ]), | ||||
|                                                                   ); | ||||
|                                                                 }).then((values) { | ||||
|                                                         if (values != null) { | ||||
|                                                               if (values != | ||||
|                                                                   null) { | ||||
|                                                                 appsProvider.saveApps( | ||||
|                                                                     selectedApps | ||||
|                                                                   .map((e) { | ||||
|                                                                         .map( | ||||
|                                                                             (e) { | ||||
|                                                                   e.installedVersion = | ||||
|                                                                       null; | ||||
|                                                                   return e; | ||||
|                                                                 }).toList()); | ||||
|                                                               } | ||||
|                                                             }).whenComplete(() { | ||||
|                                                         Navigator.of(context) | ||||
|                                                               Navigator.of( | ||||
|                                                                       context) | ||||
|                                                                   .pop(); | ||||
|                                                             }); | ||||
|                                                           }, | ||||
| @@ -807,11 +834,9 @@ class AppsPageState extends State<AppsPage> { | ||||
|                       color: Theme.of(context).colorScheme.primary, | ||||
|                     ), | ||||
|                   ), | ||||
|             appsProvider.apps.isEmpty | ||||
|                 ? const SizedBox() | ||||
|                 : TextButton.icon( | ||||
|                     style: | ||||
|                         const ButtonStyle(visualDensity: VisualDensity.compact), | ||||
|                   TextButton.icon( | ||||
|                       style: const ButtonStyle( | ||||
|                           visualDensity: VisualDensity.compact), | ||||
|                       label: Text( | ||||
|                         filter.isIdenticalTo(neutralFilter, settingsProvider) | ||||
|                             ? tr('filter') | ||||
| @@ -859,7 +884,8 @@ class AppsPageState extends State<AppsPage> { | ||||
|                                   CategoryEditorSelector( | ||||
|                                     preselected: filter.categoryFilter, | ||||
|                                     onSelected: (categories) { | ||||
|                                     filter.categoryFilter = categories.toSet(); | ||||
|                                       filter.categoryFilter = | ||||
|                                           categories.toSet(); | ||||
|                                     }, | ||||
|                                   ) | ||||
|                                 ], | ||||
|   | ||||
| @@ -4,10 +4,8 @@ import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:obtainium/components/custom_app_bar.dart'; | ||||
| import 'package:obtainium/components/generated_form.dart'; | ||||
| import 'package:obtainium/components/generated_form_modal.dart'; | ||||
| import 'package:obtainium/custom_errors.dart'; | ||||
| import 'package:obtainium/main.dart'; | ||||
| import 'package:obtainium/providers/apps_provider.dart'; | ||||
| import 'package:obtainium/providers/logs_provider.dart'; | ||||
| import 'package:obtainium/providers/settings_provider.dart'; | ||||
| import 'package:obtainium/providers/source_provider.dart'; | ||||
|   | ||||
| @@ -262,6 +262,7 @@ class AppsProvider with ChangeNotifier { | ||||
|     List<String> archs = (await DeviceInfoPlugin().androidInfo).supportedAbis; | ||||
|  | ||||
|     if (app.apkUrls.length > 1 && context != null) { | ||||
|       // ignore: use_build_context_synchronously | ||||
|       apkUrl = await showDialog( | ||||
|           context: context, | ||||
|           builder: (BuildContext ctx) { | ||||
| @@ -281,6 +282,7 @@ class AppsProvider with ChangeNotifier { | ||||
|     if (apkUrl != null && | ||||
|         getHost(apkUrl) != getHost(app.url) && | ||||
|         context != null) { | ||||
|       // ignore: use_build_context_synchronously | ||||
|       if (await showDialog( | ||||
|               context: context, | ||||
|               builder: (BuildContext ctx) { | ||||
|   | ||||
| @@ -225,7 +225,19 @@ class AppSource { | ||||
|         label: tr('trackOnly'), | ||||
|       ) | ||||
|     ], | ||||
|     [GeneratedFormSwitch('noVersionDetection', label: tr('noVersionDetection'))] | ||||
|     [ | ||||
|       GeneratedFormSwitch('noVersionDetection', label: tr('noVersionDetection')) | ||||
|     ], | ||||
|     [ | ||||
|       GeneratedFormTextField('apkFilterRegEx', | ||||
|           label: tr('filterAPKsByRegEx'), | ||||
|           required: false, | ||||
|           additionalValidators: [ | ||||
|             (value) { | ||||
|               return regExValidator(value); | ||||
|             } | ||||
|           ]) | ||||
|     ] | ||||
|   ]; | ||||
|  | ||||
|   // Previous 2 variables combined into one at runtime for convenient usage | ||||
| @@ -269,6 +281,18 @@ abstract class MassAppUrlSource { | ||||
|   Future<Map<String, String>> getUrlsWithDescriptions(List<String> args); | ||||
| } | ||||
|  | ||||
| regExValidator(String? value) { | ||||
|   if (value == null || value.isEmpty) { | ||||
|     return null; | ||||
|   } | ||||
|   try { | ||||
|     RegExp(value); | ||||
|   } catch (e) { | ||||
|     return tr('invalidRegEx'); | ||||
|   } | ||||
|   return null; | ||||
| } | ||||
|  | ||||
| class SourceProvider { | ||||
|   // Add more source classes here so they are available via the service | ||||
|   List<AppSource> sources = [ | ||||
| @@ -344,10 +368,13 @@ class SourceProvider { | ||||
|   } | ||||
|  | ||||
|   Future<App> getApp( | ||||
|       AppSource source, String url, Map<String, dynamic> additionalSettings, | ||||
|       {App? currentApp, | ||||
|     AppSource source, | ||||
|     String url, | ||||
|     Map<String, dynamic> additionalSettings, { | ||||
|     App? currentApp, | ||||
|     bool trackOnlyOverride = false, | ||||
|       noVersionDetectionOverride = false}) async { | ||||
|     noVersionDetectionOverride = false, | ||||
|   }) async { | ||||
|     if (trackOnlyOverride || source.enforceTrackOnly) { | ||||
|       additionalSettings['trackOnly'] = true; | ||||
|     } | ||||
| @@ -358,6 +385,11 @@ class SourceProvider { | ||||
|     String standardUrl = source.standardizeURL(preStandardizeUrl(url)); | ||||
|     APKDetails apk = | ||||
|         await source.getLatestAPKDetails(standardUrl, additionalSettings); | ||||
|     if (additionalSettings['apkFilterRegEx'] != null) { | ||||
|       var reg = RegExp(additionalSettings['apkFilterRegEx']); | ||||
|       apk.apkUrls = | ||||
|           apk.apkUrls.where((element) => reg.hasMatch(element)).toList(); | ||||
|     } | ||||
|     if (apk.apkUrls.isEmpty && !trackOnly) { | ||||
|       throw NoAPKError(); | ||||
|     } | ||||
|   | ||||
							
								
								
									
										402
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										402
									
								
								pubspec.lock
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -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.10.1+107 # When changing this, update the tag in main() accordingly | ||||
| version: 0.10.5+111 # When changing this, update the tag in main() accordingly | ||||
|  | ||||
| environment: | ||||
|   sdk: '>=2.18.2 <3.0.0' | ||||
|   | ||||
		Reference in New Issue
	
	Block a user