mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-10-31 13:33:28 +01:00 
			
		
		
		
	Progress
This commit is contained in:
		| @@ -25,8 +25,9 @@ class APKMirror extends AppSource { | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|       String standardUrl, Map<String, String> additionalData, | ||||
|       {bool trackOnly = false}) async { | ||||
|     String standardUrl, | ||||
|     Map<String, String> additionalSettings, | ||||
|   ) async { | ||||
|     Response res = await get(Uri.parse('$standardUrl/feed')); | ||||
|     if (res.statusCode == 200) { | ||||
|       String? titleString = parse(res.body) | ||||
|   | ||||
| @@ -32,7 +32,7 @@ class FDroid extends AppSource { | ||||
|  | ||||
|   @override | ||||
|   String? tryInferringAppId(String standardUrl, | ||||
|       {Map<String, String> additionalData = const {}}) { | ||||
|       {Map<String, String> additionalSettings = const {}}) { | ||||
|     return Uri.parse(standardUrl).pathSegments.last; | ||||
|   } | ||||
|  | ||||
| @@ -60,8 +60,9 @@ class FDroid extends AppSource { | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|       String standardUrl, Map<String, String> additionalData, | ||||
|       {bool trackOnly = false}) async { | ||||
|     String standardUrl, | ||||
|     Map<String, String> additionalSettings, | ||||
|   ) async { | ||||
|     String? appId = tryInferringAppId(standardUrl); | ||||
|     return getAPKUrlsFromFDroidPackagesAPIResponse( | ||||
|         await get(Uri.parse('https://f-droid.org/api/v1/packages/$appId')), | ||||
|   | ||||
| @@ -9,7 +9,7 @@ class FDroidRepo extends AppSource { | ||||
|   FDroidRepo() { | ||||
|     name = tr('fdroidThirdPartyRepo'); | ||||
|  | ||||
|     additionalSourceAppSpecificFormItems = [ | ||||
|     additionalSourceAppSpecificSettingFormItems = [ | ||||
|       [ | ||||
|         GeneratedFormItem('appIdOrName', | ||||
|             label: tr('appIdOrName'), | ||||
| @@ -32,9 +32,10 @@ class FDroidRepo extends AppSource { | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|       String standardUrl, Map<String, String> additionalData, | ||||
|       {bool trackOnly = false}) async { | ||||
|     String? appIdOrName = additionalData['appIdOrName']; | ||||
|     String standardUrl, | ||||
|     Map<String, String> additionalSettings, | ||||
|   ) async { | ||||
|     String? appIdOrName = additionalSettings['appIdOrName']; | ||||
|     if (appIdOrName == null) { | ||||
|       throw NoReleasesError(); | ||||
|     } | ||||
|   | ||||
| @@ -49,7 +49,7 @@ class GitHub extends AppSource { | ||||
|           ]) | ||||
|     ]; | ||||
|  | ||||
|     additionalSourceAppSpecificFormItems = [ | ||||
|     additionalSourceAppSpecificSettingFormItems = [ | ||||
|       [ | ||||
|         GeneratedFormItem('includePrereleases', | ||||
|             label: tr('includePrereleases'), | ||||
| @@ -110,14 +110,15 @@ class GitHub extends AppSource { | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|       String standardUrl, Map<String, String> additionalData, | ||||
|       {bool trackOnly = false}) async { | ||||
|     var includePrereleases = additionalData['includePrereleases'] == 'true'; | ||||
|     String standardUrl, | ||||
|     Map<String, String> additionalSettings, | ||||
|   ) async { | ||||
|     var includePrereleases = additionalSettings['includePrereleases'] == 'true'; | ||||
|     var fallbackToOlderReleases = | ||||
|         additionalData['fallbackToOlderReleases'] == 'true'; | ||||
|         additionalSettings['fallbackToOlderReleases'] == 'true'; | ||||
|     var regexFilter = | ||||
|         additionalData['filterReleaseTitlesByRegEx']?.isNotEmpty == true | ||||
|             ? additionalData['filterReleaseTitlesByRegEx'] | ||||
|         additionalSettings['filterReleaseTitlesByRegEx']?.isNotEmpty == true | ||||
|             ? additionalSettings['filterReleaseTitlesByRegEx'] | ||||
|             : null; | ||||
|     Response res = await get(Uri.parse( | ||||
|         'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/releases')); | ||||
| @@ -149,7 +150,7 @@ class GitHub extends AppSource { | ||||
|           continue; | ||||
|         } | ||||
|         var apkUrls = getReleaseAPKUrls(releases[i]); | ||||
|         if (apkUrls.isEmpty && !trackOnly) { | ||||
|         if (apkUrls.isEmpty && additionalSettings['trackOnly'] != 'true') { | ||||
|           continue; | ||||
|         } | ||||
|         targetRelease = releases[i]; | ||||
|   | ||||
| @@ -25,8 +25,9 @@ class GitLab extends AppSource { | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|       String standardUrl, Map<String, String> additionalData, | ||||
|       {bool trackOnly = false}) async { | ||||
|     String standardUrl, | ||||
|     Map<String, String> additionalSettings, | ||||
|   ) async { | ||||
|     Response res = await get(Uri.parse('$standardUrl/-/tags?format=atom')); | ||||
|     if (res.statusCode == 200) { | ||||
|       var standardUri = Uri.parse(standardUrl); | ||||
|   | ||||
| @@ -23,14 +23,15 @@ class IzzyOnDroid extends AppSource { | ||||
|  | ||||
|   @override | ||||
|   String? tryInferringAppId(String standardUrl, | ||||
|       {Map<String, String> additionalData = const {}}) { | ||||
|       {Map<String, String> additionalSettings = const {}}) { | ||||
|     return FDroid().tryInferringAppId(standardUrl); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|       String standardUrl, Map<String, String> additionalData, | ||||
|       {bool trackOnly = false}) async { | ||||
|     String standardUrl, | ||||
|     Map<String, String> additionalSettings, | ||||
|   ) async { | ||||
|     String? appId = tryInferringAppId(standardUrl); | ||||
|     return FDroid().getAPKUrlsFromFDroidPackagesAPIResponse( | ||||
|         await get( | ||||
|   | ||||
| @@ -24,8 +24,9 @@ class Mullvad extends AppSource { | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|       String standardUrl, Map<String, String> additionalData, | ||||
|       {bool trackOnly = false}) async { | ||||
|     String standardUrl, | ||||
|     Map<String, String> additionalSettings, | ||||
|   ) async { | ||||
|     Response res = await get(Uri.parse('$standardUrl/en/download/android')); | ||||
|     if (res.statusCode == 200) { | ||||
|       var version = parse(res.body) | ||||
|   | ||||
| @@ -18,8 +18,9 @@ class Signal extends AppSource { | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|       String standardUrl, Map<String, String> additionalData, | ||||
|       {bool trackOnly = false}) async { | ||||
|     String standardUrl, | ||||
|     Map<String, String> additionalSettings, | ||||
|   ) async { | ||||
|     Response res = | ||||
|         await get(Uri.parse('https://updates.$host/android/latest.json')); | ||||
|     if (res.statusCode == 200) { | ||||
|   | ||||
| @@ -23,8 +23,9 @@ class SourceForge extends AppSource { | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|       String standardUrl, Map<String, String> additionalData, | ||||
|       {bool trackOnly = false}) async { | ||||
|     String standardUrl, | ||||
|     Map<String, String> additionalSettings, | ||||
|   ) async { | ||||
|     Response res = await get(Uri.parse('$standardUrl/rss?path=/')); | ||||
|     if (res.statusCode == 200) { | ||||
|       var parsedHtml = parse(res.body); | ||||
|   | ||||
| @@ -9,7 +9,7 @@ class SteamMobile extends AppSource { | ||||
|   SteamMobile() { | ||||
|     host = 'store.steampowered.com'; | ||||
|     name = tr('steam'); | ||||
|     additionalSourceAppSpecificFormItems = [ | ||||
|     additionalSourceAppSpecificSettingFormItems = [ | ||||
|       [ | ||||
|         GeneratedFormItem('app', | ||||
|             label: tr('app'), required: true, opts: apks.entries.toList()) | ||||
| @@ -29,11 +29,12 @@ class SteamMobile extends AppSource { | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|       String standardUrl, Map<String, String> additionalData, | ||||
|       {bool trackOnly = false}) async { | ||||
|     String standardUrl, | ||||
|     Map<String, String> additionalSettings, | ||||
|   ) async { | ||||
|     Response res = await get(Uri.parse('https://$host/mobile')); | ||||
|     if (res.statusCode == 200) { | ||||
|       var apkNamePrefix = additionalData['app']; | ||||
|       var apkNamePrefix = additionalSettings['app']; | ||||
|       if (apkNamePrefix == null) { | ||||
|         throw NoReleasesError(); | ||||
|       } | ||||
|   | ||||
| @@ -202,7 +202,6 @@ class _ObtainiumState extends State<Obtainium> { | ||||
|               0, | ||||
|               {}, | ||||
|               null, | ||||
|               false, | ||||
|               false) | ||||
|         ]); | ||||
|       } | ||||
|   | ||||
| @@ -27,10 +27,8 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|   String userInput = ''; | ||||
|   String searchQuery = ''; | ||||
|   AppSource? pickedSource; | ||||
|   Map<String, String> sourceSpecificAdditionalData = {}; | ||||
|   bool sourceSpecificDataIsValid = true; | ||||
|   Map<String, String> otherAdditionalData = {}; | ||||
|   bool otherAdditionalDataIsValid = true; | ||||
|   Map<String, String> additionalSettings = {}; | ||||
|   bool additionalSettingsValid = true; | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
| @@ -43,12 +41,12 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|         var source = valid ? sourceProvider.getSource(userInput) : null; | ||||
|         if (pickedSource.runtimeType != source.runtimeType) { | ||||
|           pickedSource = source; | ||||
|           sourceSpecificAdditionalData = source != null | ||||
|           additionalSettings = source != null | ||||
|               ? getDefaultValuesFromFormItems( | ||||
|                   source.additionalSourceAppSpecificFormItems) | ||||
|                   source.combinedAppSpecificSettingFormItems) | ||||
|               : {}; | ||||
|           sourceSpecificDataIsValid = source != null | ||||
|               ? !sourceProvider.ifSourceAppsRequireAdditionalData(source) | ||||
|           additionalSettingsValid = source != null | ||||
|               ? !sourceProvider.ifRequiredAppSpecificSettingsExist(source) | ||||
|               : true; | ||||
|         } | ||||
|       } | ||||
| @@ -68,10 +66,9 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|       }); | ||||
|       var settingsProvider = context.read<SettingsProvider>(); | ||||
|       () async { | ||||
|         var userPickedTrackOnly = | ||||
|             otherAdditionalData['trackOnlyFormItemKey'] == 'true'; | ||||
|         var userPickedTrackOnly = additionalSettings['trackOnly'] == 'true'; | ||||
|         var userPickedNoVersionDetection = | ||||
|             otherAdditionalData['noVersionDetectionKey'] == 'true'; | ||||
|             additionalSettings['noVersionDetection'] == 'true'; | ||||
|         var cont = true; | ||||
|         if ((userPickedTrackOnly || pickedSource!.enforceTrackOnly) && | ||||
|             await showDialog( | ||||
| @@ -108,14 +105,15 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|           HapticFeedback.selectionClick(); | ||||
|           var trackOnly = pickedSource!.enforceTrackOnly || userPickedTrackOnly; | ||||
|           App app = await sourceProvider.getApp( | ||||
|               pickedSource!, userInput, sourceSpecificAdditionalData, | ||||
|               pickedSource!, userInput, additionalSettings, | ||||
|               trackOnlyOverride: trackOnly, | ||||
|               noVersionDetectionOverride: userPickedNoVersionDetection); | ||||
|           if (!trackOnly) { | ||||
|             await settingsProvider.getInstallPermission(); | ||||
|           } | ||||
|           // Only download the APK here if you need to for the package ID | ||||
|           if (sourceProvider.isTempId(app.id) && !app.trackOnly) { | ||||
|           if (sourceProvider.isTempId(app.id) && | ||||
|               app.additionalSettings['trackOnly'] != 'true') { | ||||
|             // ignore: use_build_context_synchronously | ||||
|             var apkUrl = await appsProvider.confirmApkUrl(app, context); | ||||
|             if (apkUrl == null) { | ||||
| @@ -130,7 +128,7 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|           if (appsProvider.apps.containsKey(app.id)) { | ||||
|             throw ObtainiumError(tr('appAlreadyAdded')); | ||||
|           } | ||||
|           if (app.trackOnly) { | ||||
|           if (app.additionalSettings['trackOnly'] == 'true') { | ||||
|             app.installedVersion = app.latestVersion; | ||||
|           } | ||||
|           await appsProvider.saveApps([app]); | ||||
| @@ -206,13 +204,9 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|                                   onPressed: gettingAppInfo || | ||||
|                                           pickedSource == null || | ||||
|                                           (pickedSource! | ||||
|                                                   .additionalSourceAppSpecificFormItems | ||||
|                                                   .combinedAppSpecificSettingFormItems | ||||
|                                                   .isNotEmpty && | ||||
|                                               !sourceSpecificDataIsValid) || | ||||
|                                           (pickedSource! | ||||
|                                                   .additionalAppSpecificSourceAgnosticFormItems | ||||
|                                                   .isNotEmpty && | ||||
|                                               !otherAdditionalDataIsValid) | ||||
|                                               !additionalSettingsValid) | ||||
|                                       ? null | ||||
|                                       : addApp, | ||||
|                                   child: Text(tr('add'))) | ||||
| @@ -305,15 +299,8 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|                           ], | ||||
|                         ), | ||||
|                       if (pickedSource != null && | ||||
|                           (pickedSource!.additionalSourceAppSpecificFormItems | ||||
|                                   .isNotEmpty || | ||||
|                               pickedSource! | ||||
|                                   .additionalAppSpecificSourceAgnosticFormItems | ||||
|                                   .where((e) => pickedSource!.enforceTrackOnly | ||||
|                                       ? e.key != 'trackOnlyFormItemKey' | ||||
|                                       : true) | ||||
|                                   .map((e) => [e]) | ||||
|                                   .isNotEmpty)) | ||||
|                           (pickedSource! | ||||
|                               .combinedAppSpecificSettingFormItems.isNotEmpty)) | ||||
|                         Column( | ||||
|                           crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                           children: [ | ||||
| @@ -329,39 +316,14 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|                             const SizedBox( | ||||
|                               height: 16, | ||||
|                             ), | ||||
|                             if (pickedSource! | ||||
|                                 .additionalSourceAppSpecificFormItems | ||||
|                                 .isNotEmpty) | ||||
|                               GeneratedForm( | ||||
|                                   items: pickedSource! | ||||
|                                       .additionalSourceAppSpecificFormItems, | ||||
|                                   onValueChanges: (values, valid, isBuilding) { | ||||
|                                     if (!isBuilding) { | ||||
|                                       setState(() { | ||||
|                                         sourceSpecificAdditionalData = values; | ||||
|                                         sourceSpecificDataIsValid = valid; | ||||
|                                       }); | ||||
|                                     } | ||||
|                                   }), | ||||
|                             if (pickedSource! | ||||
|                                 .additionalAppSpecificSourceAgnosticFormItems | ||||
|                                 .isNotEmpty) | ||||
|                               const SizedBox( | ||||
|                                 height: 8, | ||||
|                               ), | ||||
|                             GeneratedForm( | ||||
|                                 items: pickedSource! | ||||
|                                     .additionalAppSpecificSourceAgnosticFormItems | ||||
|                                     .where((e) => pickedSource!.enforceTrackOnly | ||||
|                                         ? e.key != 'trackOnlyFormItemKey' | ||||
|                                         : true) | ||||
|                                     .map((e) => [e]) | ||||
|                                     .toList(), | ||||
|                                     .combinedAppSpecificSettingFormItems, | ||||
|                                 onValueChanges: (values, valid, isBuilding) { | ||||
|                                   if (!isBuilding) { | ||||
|                                     setState(() { | ||||
|                                       otherAdditionalData = values; | ||||
|                                       otherAdditionalDataIsValid = valid; | ||||
|                                       additionalSettings = values; | ||||
|                                       additionalSettingsValid = valid; | ||||
|                                     }); | ||||
|                                   } | ||||
|                                 }), | ||||
|   | ||||
| @@ -40,6 +40,7 @@ class _AppPageState extends State<AppPage> { | ||||
|       prevApp = app; | ||||
|       getUpdate(app.app.id); | ||||
|     } | ||||
|     var trackOnly = app?.app.additionalSettings['trackOnly'] == 'true'; | ||||
|     return Scaffold( | ||||
|       appBar: settingsProvider.showAppWebpage ? AppBar() : null, | ||||
|       backgroundColor: Theme.of(context).colorScheme.surface, | ||||
| @@ -111,7 +112,7 @@ class _AppPageState extends State<AppPage> { | ||||
|                         Text( | ||||
|                           '${tr('installedVersionX', args: [ | ||||
|                                 app?.app.installedVersion ?? tr('none') | ||||
|                               ])}${app?.app.trackOnly == true ? ' ${tr('estimateInBrackets')}\n\n${tr('xIsTrackOnly', args: [ | ||||
|                               ])}${trackOnly ? ' ${tr('estimateInBrackets')}\n\n${tr('xIsTrackOnly', args: [ | ||||
|                                   tr('app') | ||||
|                                 ])}' : ''}', | ||||
|                           textAlign: TextAlign.center, | ||||
| @@ -151,7 +152,7 @@ class _AppPageState extends State<AppPage> { | ||||
|                       mainAxisAlignment: MainAxisAlignment.spaceEvenly, | ||||
|                       children: [ | ||||
|                         if (app?.app.installedVersion != null && | ||||
|                             app?.app.trackOnly == false && | ||||
|                             !trackOnly && | ||||
|                             app?.app.installedVersion != app?.app.latestVersion) | ||||
|                           IconButton( | ||||
|                               onPressed: app?.downloadProgress != null | ||||
| @@ -202,8 +203,8 @@ class _AppPageState extends State<AppPage> { | ||||
|                               tooltip: 'Mark as Updated', | ||||
|                               icon: const Icon(Icons.done)), | ||||
|                         if (source != null && | ||||
|                             source.additionalSourceAppSpecificFormItems | ||||
|                                 .isNotEmpty) | ||||
|                             source | ||||
|                                 .combinedAppSpecificSettingFormItems.isNotEmpty) | ||||
|                           IconButton( | ||||
|                               onPressed: app?.downloadProgress != null | ||||
|                                   ? null | ||||
| @@ -211,14 +212,36 @@ class _AppPageState extends State<AppPage> { | ||||
|                                       showDialog<Map<String, String>>( | ||||
|                                           context: context, | ||||
|                                           builder: (BuildContext ctx) { | ||||
|                                             var items = source | ||||
|                                                 .combinedAppSpecificSettingFormItems | ||||
|                                                 .map((row) { | ||||
|                                               row.map((e) { | ||||
|                                                 if (app?.app.additionalSettings[ | ||||
|                                                         e.key] != | ||||
|                                                     null) { | ||||
|                                                   e.defaultValue = app?.app | ||||
|                                                           .additionalSettings[ | ||||
|                                                       e.key]; | ||||
|                                                 } | ||||
|                                                 return e; | ||||
|                                               }).toList(); | ||||
|                                               return row; | ||||
|                                             }).toList(); | ||||
|                                             return GeneratedFormModal( | ||||
|                                                 title: 'Additional Options', | ||||
|                                                 items: source | ||||
|                                                     .additionalSourceAppSpecificFormItems); | ||||
|                                                 items: items); | ||||
|                                           }).then((values) { | ||||
|                                         if (app != null && values != null) { | ||||
|                                           var changedApp = app.app; | ||||
|                                           changedApp.additionalData = values; | ||||
|                                           changedApp.additionalSettings = | ||||
|                                               values; | ||||
|                                           if (source.enforceTrackOnly) { | ||||
|                                             changedApp.additionalSettings[ | ||||
|                                                 'trackOnly'] = 'true'; | ||||
|                                             showError( | ||||
|                                                 tr('appsFromSourceAreTrackOnly'), | ||||
|                                                 context); | ||||
|                                           } | ||||
|                                           appsProvider.saveApps( | ||||
|                                               [changedApp]).then((value) { | ||||
|                                             getUpdate(changedApp.id); | ||||
| @@ -238,7 +261,9 @@ class _AppPageState extends State<AppPage> { | ||||
|                                     ? () { | ||||
|                                         HapticFeedback.heavyImpact(); | ||||
|                                         () async { | ||||
|                                           if (app?.app.trackOnly != true) { | ||||
|                                           if (app?.app.additionalSettings[ | ||||
|                                                   'trackOnly'] != | ||||
|                                               'true') { | ||||
|                                             await settingsProvider | ||||
|                                                 .getInstallPermission(); | ||||
|                                           } | ||||
| @@ -260,10 +285,10 @@ class _AppPageState extends State<AppPage> { | ||||
|                                       } | ||||
|                                     : null, | ||||
|                                 child: Text(app?.app.installedVersion == null | ||||
|                                     ? app?.app.trackOnly == false | ||||
|                                     ? !trackOnly | ||||
|                                         ? 'Install' | ||||
|                                         : 'Mark Installed' | ||||
|                                     : app?.app.trackOnly == false | ||||
|                                     : !trackOnly | ||||
|                                         ? 'Update' | ||||
|                                         : 'Mark Updated'))), | ||||
|                         const SizedBox(width: 16.0), | ||||
|   | ||||
| @@ -139,14 +139,16 @@ class AppsPageState extends State<AppsPage> { | ||||
|  | ||||
|     List<String> trackOnlyUpdateIdsAllOrSelected = []; | ||||
|     existingUpdateIdsAllOrSelected = existingUpdateIdsAllOrSelected.where((id) { | ||||
|       if (appsProvider.apps[id]!.app.trackOnly) { | ||||
|       if (appsProvider.apps[id]!.app.additionalSettings['trackOnly'] == | ||||
|           'true') { | ||||
|         trackOnlyUpdateIdsAllOrSelected.add(id); | ||||
|         return false; | ||||
|       } | ||||
|       return true; | ||||
|     }).toList(); | ||||
|     newInstallIdsAllOrSelected = newInstallIdsAllOrSelected.where((id) { | ||||
|       if (appsProvider.apps[id]!.app.trackOnly) { | ||||
|       if (appsProvider.apps[id]!.app.additionalSettings['trackOnly'] == | ||||
|           'true') { | ||||
|         trackOnlyUpdateIdsAllOrSelected.add(id); | ||||
|         return false; | ||||
|       } | ||||
| @@ -271,7 +273,7 @@ class AppsPageState extends State<AppsPage> { | ||||
|                               SizedBox( | ||||
|                                   width: 100, | ||||
|                                   child: Text( | ||||
|                                     '${sortedApps[index].app.installedVersion ?? tr('notInstalled')}${sortedApps[index].app.trackOnly == true ? ' ${tr('estimateInBrackets')}' : ''}', | ||||
|                                     '${sortedApps[index].app.installedVersion ?? tr('notInstalled')}${sortedApps[index].app.additionalSettings['trackOnly'] == 'true' ? ' ${tr('estimateInBrackets')}' : ''}', | ||||
|                                     overflow: TextOverflow.fade, | ||||
|                                     textAlign: TextAlign.end, | ||||
|                                   )), | ||||
| @@ -289,7 +291,7 @@ class AppsPageState extends State<AppsPage> { | ||||
|                                       child: appsProvider.areDownloadsRunning() | ||||
|                                           ? Text(tr('pleaseWait')) | ||||
|                                           : Text( | ||||
|                                               '${tr('updateAvailable')}${sortedApps[index].app.trackOnly ? ' ${tr('estimateInBracketsShort')}' : ''}', | ||||
|                                               '${tr('updateAvailable')}${sortedApps[index].app.additionalSettings['trackOnly'] == 'true' ? ' ${tr('estimateInBracketsShort')}' : ''}', | ||||
|                                               style: TextStyle( | ||||
|                                                   fontStyle: FontStyle.italic, | ||||
|                                                   decoration: changesUrl == null | ||||
| @@ -343,7 +345,7 @@ class AppsPageState extends State<AppsPage> { | ||||
|                     : IconButton( | ||||
|                         visualDensity: VisualDensity.compact, | ||||
|                         onPressed: () { | ||||
|                           showDialog<List<String>?>( | ||||
|                           showDialog<Map<String, String>?>( | ||||
|                               context: context, | ||||
|                               builder: (BuildContext ctx) { | ||||
|                                 return GeneratedFormModal( | ||||
|   | ||||
| @@ -313,7 +313,8 @@ class AppsProvider with ChangeNotifier { | ||||
|         throw ObtainiumError(tr('appNotFound')); | ||||
|       } | ||||
|       String? apkUrl; | ||||
|       if (!apps[id]!.app.trackOnly) { | ||||
|       var trackOnly = apps[id]!.app.additionalSettings['trackOnly'] == 'true'; | ||||
|       if (!trackOnly) { | ||||
|         apkUrl = await confirmApkUrl(apps[id]!.app, context); | ||||
|       } | ||||
|       if (apkUrl != null) { | ||||
| @@ -326,7 +327,7 @@ class AppsProvider with ChangeNotifier { | ||||
|           appsToInstall.add(id); | ||||
|         } | ||||
|       } | ||||
|       if (apps[id]!.app.trackOnly) { | ||||
|       if (trackOnly) { | ||||
|         trackOnlyAppsToUpdate.add(id); | ||||
|       } | ||||
|     } | ||||
| @@ -451,9 +452,10 @@ class AppsProvider with ChangeNotifier { | ||||
|   // Don't save changes, just return the object if changes were made (else null) | ||||
|   App? getCorrectedInstallStatusAppIfPossible(App app, AppInfo? installedInfo) { | ||||
|     var modded = false; | ||||
|     if (installedInfo == null && | ||||
|         app.installedVersion != null && | ||||
|         !app.trackOnly) { | ||||
|     var trackOnly = app.additionalSettings['trackOnly'] == 'true'; | ||||
|     var noVersionDetection = | ||||
|         app.additionalSettings['noVersionDetection'] == 'true'; | ||||
|     if (installedInfo == null && app.installedVersion != null && !trackOnly) { | ||||
|       app.installedVersion = null; | ||||
|       modded = true; | ||||
|     } else if (installedInfo?.versionName != null && | ||||
| @@ -462,7 +464,7 @@ class AppsProvider with ChangeNotifier { | ||||
|       modded = true; | ||||
|     } else if (installedInfo?.versionName != null && | ||||
|         installedInfo!.versionName != app.installedVersion && | ||||
|         !app.noVersionDetection) { | ||||
|         !noVersionDetection) { | ||||
|       String? correctedInstalledVersion = reconcileRealAndInternalVersions( | ||||
|           installedInfo.versionName!, app.installedVersion!); | ||||
|       if (correctedInstalledVersion != null) { | ||||
| @@ -472,7 +474,7 @@ class AppsProvider with ChangeNotifier { | ||||
|     } | ||||
|     if (app.installedVersion != null && | ||||
|         app.installedVersion != app.latestVersion && | ||||
|         !app.noVersionDetection) { | ||||
|         !noVersionDetection) { | ||||
|       app.installedVersion = reconcileRealAndInternalVersions( | ||||
|               app.installedVersion!, app.latestVersion, | ||||
|               matchMode: true) ?? | ||||
| @@ -625,7 +627,7 @@ class AppsProvider with ChangeNotifier { | ||||
|     App newApp = await sourceProvider.getApp( | ||||
|         sourceProvider.getSource(currentApp.url), | ||||
|         currentApp.url, | ||||
|         currentApp.additionalData, | ||||
|         currentApp.additionalSettings, | ||||
|         currentApp: currentApp); | ||||
|     if (currentApp.preferredApkIndex < newApp.apkUrls.length) { | ||||
|       newApp.preferredApkIndex = currentApp.preferredApkIndex; | ||||
|   | ||||
| @@ -44,11 +44,9 @@ class App { | ||||
|   late String latestVersion; | ||||
|   List<String> apkUrls = []; | ||||
|   late int preferredApkIndex; | ||||
|   late Map<String, String> additionalData; | ||||
|   late Map<String, String> additionalSettings; | ||||
|   late DateTime? lastUpdateCheck; | ||||
|   bool pinned = false; | ||||
|   bool trackOnly = false; | ||||
|   bool noVersionDetection = false; | ||||
|   App( | ||||
|       this.id, | ||||
|       this.url, | ||||
| @@ -58,38 +56,37 @@ class App { | ||||
|       this.latestVersion, | ||||
|       this.apkUrls, | ||||
|       this.preferredApkIndex, | ||||
|       this.additionalData, | ||||
|       this.additionalSettings, | ||||
|       this.lastUpdateCheck, | ||||
|       this.pinned, | ||||
|       this.trackOnly, | ||||
|       {this.noVersionDetection = false}); | ||||
|       this.pinned); | ||||
|  | ||||
|   @override | ||||
|   String toString() { | ||||
|     return 'ID: $id URL: $url INSTALLED: $installedVersion LATEST: $latestVersion APK: $apkUrls PREFERREDAPK: $preferredApkIndex ADDITIONALDATA: ${additionalData.toString()} LASTCHECK: ${lastUpdateCheck.toString()} PINNED $pinned'; | ||||
|     return 'ID: $id URL: $url INSTALLED: $installedVersion LATEST: $latestVersion APK: $apkUrls PREFERREDAPK: $preferredApkIndex ADDITIONALSETTINGS: ${additionalSettings.toString()} LASTCHECK: ${lastUpdateCheck.toString()} PINNED $pinned'; | ||||
|   } | ||||
|  | ||||
|   factory App.fromJson(Map<String, dynamic> json) { | ||||
|     var formItems = SourceProvider() | ||||
|         .getSource(json['url']) | ||||
|         .additionalSourceAppSpecificFormItems | ||||
|     var source = SourceProvider().getSource(json['url']); | ||||
|     var formItems = source.combinedAppSpecificSettingFormItems | ||||
|         .reduce((value, element) => [...value, ...element]); | ||||
|     Map<String, String> additionalData = | ||||
|     Map<String, String> additionalSettings = | ||||
|         getDefaultValuesFromFormItems([formItems]); | ||||
|     if (json['additionalSettings'] != null) { | ||||
|       additionalSettings.addEntries( | ||||
|           Map<String, String>.from(jsonDecode(json['additionalSettings'])) | ||||
|               .entries); | ||||
|     } | ||||
|     // If needed, migrate old-style additionalData to new-style additionalSettings | ||||
|     if (json['additionalData'] != null) { | ||||
|       try { | ||||
|         additionalData = | ||||
|             Map<String, String>.from(jsonDecode(json['additionalData'])); | ||||
|       } catch (e) { | ||||
|         // Migrate old-style additionalData List to new-style Map | ||||
|         List<String> temp = | ||||
|             List<String>.from(jsonDecode(json['additionalData'])); | ||||
|         temp.asMap().forEach((i, value) { | ||||
|           if (i < formItems.length) { | ||||
|             additionalData[formItems[i].key] = value; | ||||
|           } | ||||
|         }); | ||||
|       } | ||||
|       List<String> temp = List<String>.from(jsonDecode(json['additionalData'])); | ||||
|       temp.asMap().forEach((i, value) { | ||||
|         if (i < formItems.length) { | ||||
|           additionalSettings[formItems[i].key] = value; | ||||
|         } | ||||
|       }); | ||||
|       additionalSettings['trackOnly'] = (json['trackOnly'] ?? false).toString(); | ||||
|       additionalSettings['noVersionDetection'] = | ||||
|           (json['noVersionDetection'] ?? false).toString(); | ||||
|     } | ||||
|     return App( | ||||
|         json['id'] as String, | ||||
| @@ -106,13 +103,11 @@ class App { | ||||
|         json['preferredApkIndex'] == null | ||||
|             ? 0 | ||||
|             : json['preferredApkIndex'] as int, | ||||
|         additionalData, | ||||
|         additionalSettings, | ||||
|         json['lastUpdateCheck'] == null | ||||
|             ? null | ||||
|             : DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']), | ||||
|         json['pinned'] ?? false, | ||||
|         json['trackOnly'] ?? false, | ||||
|         noVersionDetection: json['noVersionDetection'] ?? false); | ||||
|         json['pinned'] ?? false); | ||||
|   } | ||||
|  | ||||
|   Map<String, dynamic> toJson() => { | ||||
| @@ -124,11 +119,9 @@ class App { | ||||
|         'latestVersion': latestVersion, | ||||
|         'apkUrls': jsonEncode(apkUrls), | ||||
|         'preferredApkIndex': preferredApkIndex, | ||||
|         'additionalData': jsonEncode(additionalData), | ||||
|         'additionalSettings': jsonEncode(additionalSettings), | ||||
|         'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch, | ||||
|         'pinned': pinned, | ||||
|         'trackOnly': trackOnly, | ||||
|         'noVersionDetection': noVersionDetection | ||||
|         'pinned': pinned | ||||
|       }; | ||||
| } | ||||
|  | ||||
| @@ -183,26 +176,39 @@ class AppSource { | ||||
|   } | ||||
|  | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|       String standardUrl, Map<String, String> additionalData, | ||||
|       {bool trackOnly = false}) { | ||||
|       String standardUrl, Map<String, String> additionalSettings) { | ||||
|     throw NotImplementedError(); | ||||
|   } | ||||
|  | ||||
|   // Different Sources may need different kinds of additional data for Apps | ||||
|   List<List<GeneratedFormItem>> additionalSourceAppSpecificFormItems = []; | ||||
|   List<List<GeneratedFormItem>> additionalSourceAppSpecificSettingFormItems = | ||||
|       []; | ||||
|  | ||||
|   // Some additional data may be needed for Apps regardless of Source | ||||
|   final List<GeneratedFormItem> additionalAppSpecificSourceAgnosticFormItems = [ | ||||
|     GeneratedFormItem( | ||||
|       'trackOnlyFormItemKey', | ||||
|       label: tr('trackOnly'), | ||||
|       type: FormItemType.bool, | ||||
|     ), | ||||
|     GeneratedFormItem('noVersionDetectionKey', | ||||
|         label: 'Do not attempt version detection', // TODO | ||||
|         type: FormItemType.bool) | ||||
|   final List<List<GeneratedFormItem>> | ||||
|       additionalAppSpecificSourceAgnosticSettingFormItems = [ | ||||
|     [ | ||||
|       GeneratedFormItem( | ||||
|         'trackOnly', | ||||
|         label: tr('trackOnly'), | ||||
|         type: FormItemType.bool, | ||||
|       ) | ||||
|     ], | ||||
|     [ | ||||
|       GeneratedFormItem('noVersionDetection', | ||||
|           label: 'Do not attempt version detection', // TODO | ||||
|           type: FormItemType.bool) | ||||
|     ] | ||||
|   ]; | ||||
|  | ||||
|   // Previous 2 variables combined into one at runtime for convenient usage | ||||
|   List<List<GeneratedFormItem>> get combinedAppSpecificSettingFormItems { | ||||
|     return [ | ||||
|       ...additionalSourceAppSpecificSettingFormItems, | ||||
|       ...additionalAppSpecificSourceAgnosticSettingFormItems | ||||
|     ]; | ||||
|   } | ||||
|  | ||||
|   // Some Sources may have additional settings at the Source level (not specific to Apps) - these use SettingsProvider | ||||
|   List<GeneratedFormItem> additionalSourceSpecificSettingFormItems = []; | ||||
|  | ||||
| @@ -220,7 +226,7 @@ class AppSource { | ||||
|   } | ||||
|  | ||||
|   String? tryInferringAppId(String standardUrl, | ||||
|       {Map<String, String> additionalData = const {}}) { | ||||
|       {Map<String, String> additionalSettings = const {}}) { | ||||
|     return null; | ||||
|   } | ||||
| } | ||||
| @@ -280,8 +286,8 @@ class SourceProvider { | ||||
|     return source; | ||||
|   } | ||||
|  | ||||
|   bool ifSourceAppsRequireAdditionalData(AppSource source) { | ||||
|     for (var row in source.additionalSourceAppSpecificFormItems) { | ||||
|   bool ifRequiredAppSpecificSettingsExist(AppSource source) { | ||||
|     for (var row in source.combinedAppSpecificSettingFormItems) { | ||||
|       for (var element in row) { | ||||
|         if (element.required && element.opts == null) { | ||||
|           return true; | ||||
| @@ -309,15 +315,20 @@ class SourceProvider { | ||||
|   } | ||||
|  | ||||
|   Future<App> getApp( | ||||
|       AppSource source, String url, Map<String, String> additionalData, | ||||
|       AppSource source, String url, Map<String, String> additionalSettings, | ||||
|       {App? currentApp, | ||||
|       bool trackOnlyOverride = false, | ||||
|       noVersionDetectionOverride = false}) async { | ||||
|     var trackOnly = trackOnlyOverride || (currentApp?.trackOnly ?? false); | ||||
|  | ||||
|     if (trackOnlyOverride) { | ||||
|       additionalSettings['trackOnly'] = 'true'; | ||||
|     } | ||||
|     if (noVersionDetectionOverride) { | ||||
|       additionalSettings['noVersionDetection'] = 'true'; | ||||
|     } | ||||
|     var trackOnly = currentApp?.additionalSettings['trackOnly'] == 'true'; | ||||
|     String standardUrl = source.standardizeURL(preStandardizeUrl(url)); | ||||
|     APKDetails apk = await source | ||||
|         .getLatestAPKDetails(standardUrl, additionalData, trackOnly: trackOnly); | ||||
|     APKDetails apk = | ||||
|         await source.getLatestAPKDetails(standardUrl, additionalSettings); | ||||
|     if (apk.apkUrls.isEmpty && !trackOnly) { | ||||
|       throw NoAPKError(); | ||||
|     } | ||||
| @@ -327,7 +338,7 @@ class SourceProvider { | ||||
|     return App( | ||||
|         currentApp?.id ?? | ||||
|             source.tryInferringAppId(standardUrl, | ||||
|                 additionalData: additionalData) ?? | ||||
|                 additionalSettings: additionalSettings) ?? | ||||
|             generateTempID(apk.names, source), | ||||
|         standardUrl, | ||||
|         apk.names.author[0].toUpperCase() + apk.names.author.substring(1), | ||||
| @@ -338,12 +349,9 @@ class SourceProvider { | ||||
|         apkVersion, | ||||
|         apk.apkUrls, | ||||
|         apk.apkUrls.length - 1, | ||||
|         additionalData, | ||||
|         additionalSettings, | ||||
|         DateTime.now(), | ||||
|         currentApp?.pinned ?? false, | ||||
|         trackOnly, | ||||
|         noVersionDetection: noVersionDetectionOverride || | ||||
|             (currentApp?.noVersionDetection ?? false)); | ||||
|         currentApp?.pinned ?? false); | ||||
|   } | ||||
|  | ||||
|   // Returns errors in [results, errors] instead of throwing them | ||||
| @@ -358,7 +366,7 @@ class SourceProvider { | ||||
|             source, | ||||
|             url, | ||||
|             getDefaultValuesFromFormItems( | ||||
|                 source.additionalSourceAppSpecificFormItems))); | ||||
|                 source.combinedAppSpecificSettingFormItems))); | ||||
|       } catch (e) { | ||||
|         errors.addAll(<String, dynamic>{url: e}); | ||||
|       } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user