mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-10-31 05:23:28 +01:00 
			
		
		
		
	Added search bar on Add App page
This commit is contained in:
		| @@ -5,6 +5,7 @@ import 'package:obtainium/components/generated_form.dart'; | ||||
| import 'package:obtainium/components/generated_form_modal.dart'; | ||||
| import 'package:obtainium/custom_errors.dart'; | ||||
| import 'package:obtainium/pages/app.dart'; | ||||
| import 'package:obtainium/pages/import_export.dart'; | ||||
| import 'package:obtainium/providers/apps_provider.dart'; | ||||
| import 'package:obtainium/providers/settings_provider.dart'; | ||||
| import 'package:obtainium/providers/source_provider.dart'; | ||||
| @@ -22,6 +23,7 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|   bool gettingAppInfo = false; | ||||
|  | ||||
|   String userInput = ''; | ||||
|   String searchQuery = ''; | ||||
|   AppSource? pickedSource; | ||||
|   List<String> sourceSpecificAdditionalData = []; | ||||
|   bool sourceSpecificDataIsValid = true; | ||||
| @@ -31,6 +33,107 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     SourceProvider sourceProvider = SourceProvider(); | ||||
|     AppsProvider appsProvider = context.read<AppsProvider>(); | ||||
|  | ||||
|     changeUserInput(String input, bool valid, bool isBuilding) { | ||||
|       userInput = input; | ||||
|       fn() { | ||||
|         var source = valid ? sourceProvider.getSource(userInput) : null; | ||||
|         if (pickedSource != source) { | ||||
|           pickedSource = source; | ||||
|           sourceSpecificAdditionalData = | ||||
|               source != null ? source.additionalSourceAppSpecificDefaults : []; | ||||
|           sourceSpecificDataIsValid = source != null | ||||
|               ? sourceProvider.ifSourceAppsRequireAdditionalData(source) | ||||
|               : true; | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       if (isBuilding) { | ||||
|         fn(); | ||||
|       } else { | ||||
|         setState(() { | ||||
|           fn(); | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     addApp({bool resetUserInputAfter = false}) async { | ||||
|       setState(() { | ||||
|         gettingAppInfo = true; | ||||
|       }); | ||||
|       var settingsProvider = context.read<SettingsProvider>(); | ||||
|       () async { | ||||
|         var userPickedTrackOnly = findGeneratedFormValueByKey( | ||||
|                 pickedSource!.additionalAppSpecificSourceAgnosticFormItems, | ||||
|                 otherAdditionalData, | ||||
|                 'trackOnlyFormItemKey') == | ||||
|             'true'; | ||||
|         var cont = true; | ||||
|         if ((userPickedTrackOnly || pickedSource!.enforceTrackOnly) && | ||||
|             await showDialog( | ||||
|                     context: context, | ||||
|                     builder: (BuildContext ctx) { | ||||
|                       return GeneratedFormModal( | ||||
|                         title: | ||||
|                             '${pickedSource!.enforceTrackOnly ? 'Source' : 'App'} is Track-Only', | ||||
|                         items: const [], | ||||
|                         defaultValues: const [], | ||||
|                         message: | ||||
|                             '${pickedSource!.enforceTrackOnly ? 'Apps from this source are \'Track-Only\'.' : 'You have selected the \'Track-Only\' option.'}\n\nThe App will be tracked for updates, but Obtainium will not be able to download or install it.', | ||||
|                       ); | ||||
|                     }) == | ||||
|                 null) { | ||||
|           cont = false; | ||||
|         } | ||||
|         if (cont) { | ||||
|           HapticFeedback.selectionClick(); | ||||
|           var trackOnly = pickedSource!.enforceTrackOnly || userPickedTrackOnly; | ||||
|           App app = await sourceProvider.getApp( | ||||
|               pickedSource!, userInput, sourceSpecificAdditionalData, | ||||
|               trackOnly: trackOnly); | ||||
|           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) { | ||||
|             // ignore: use_build_context_synchronously | ||||
|             var apkUrl = await appsProvider.confirmApkUrl(app, context); | ||||
|             if (apkUrl == null) { | ||||
|               throw ObtainiumError('Cancelled'); | ||||
|             } | ||||
|             app.preferredApkIndex = app.apkUrls.indexOf(apkUrl); | ||||
|             var downloadedApk = await appsProvider.downloadApp(app); | ||||
|             app.id = downloadedApk.appId; | ||||
|           } | ||||
|           if (appsProvider.apps.containsKey(app.id)) { | ||||
|             throw ObtainiumError('App already added'); | ||||
|           } | ||||
|           if (app.trackOnly) { | ||||
|             app.installedVersion = app.latestVersion; | ||||
|           } | ||||
|           await appsProvider.saveApps([app]); | ||||
|  | ||||
|           return app; | ||||
|         } | ||||
|       }() | ||||
|           .then((app) { | ||||
|         if (app != null) { | ||||
|           Navigator.push(context, | ||||
|               MaterialPageRoute(builder: (context) => AppPage(appId: app.id))); | ||||
|         } | ||||
|       }).catchError((e) { | ||||
|         showError(e, context); | ||||
|       }).whenComplete(() { | ||||
|         setState(() { | ||||
|           gettingAppInfo = false; | ||||
|           if (resetUserInputAfter) { | ||||
|             changeUserInput('', false, true); | ||||
|           } | ||||
|         }); | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     return Scaffold( | ||||
|         backgroundColor: Theme.of(context).colorScheme.surface, | ||||
|         body: CustomScrollView(slivers: <Widget>[ | ||||
| @@ -70,34 +173,8 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|                                     ] | ||||
|                                   ], | ||||
|                                   onValueChanges: (values, valid, isBuilding) { | ||||
|                                     fn() { | ||||
|                                       userInput = values[0]; | ||||
|                                       var source = valid | ||||
|                                           ? sourceProvider.getSource(userInput) | ||||
|                                           : null; | ||||
|                                       if (pickedSource != source) { | ||||
|                                         pickedSource = source; | ||||
|                                         sourceSpecificAdditionalData = source != | ||||
|                                                 null | ||||
|                                             ? source | ||||
|                                                 .additionalSourceAppSpecificDefaults | ||||
|                                             : []; | ||||
|                                         sourceSpecificDataIsValid = source != | ||||
|                                                 null | ||||
|                                             ? sourceProvider | ||||
|                                                 .ifSourceAppsRequireAdditionalData( | ||||
|                                                     source) | ||||
|                                             : true; | ||||
|                                       } | ||||
|                                     } | ||||
|  | ||||
|                                     if (isBuilding) { | ||||
|                                       fn(); | ||||
|                                     } else { | ||||
|                                       setState(() { | ||||
|                                         fn(); | ||||
|                                       }); | ||||
|                                     } | ||||
|                                     changeUserInput( | ||||
|                                         values[0], valid, isBuilding); | ||||
|                                   }, | ||||
|                                   defaultValues: const [])), | ||||
|                           const SizedBox( | ||||
| @@ -117,111 +194,91 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|                                                   .isNotEmpty && | ||||
|                                               !otherAdditionalDataIsValid) | ||||
|                                       ? null | ||||
|                                       : () async { | ||||
|                                           setState(() { | ||||
|                                             gettingAppInfo = true; | ||||
|                                           }); | ||||
|                                           var appsProvider = | ||||
|                                               context.read<AppsProvider>(); | ||||
|                                           var settingsProvider = | ||||
|                                               context.read<SettingsProvider>(); | ||||
|                                           () async { | ||||
|                                             var userPickedTrackOnly = | ||||
|                                                 findGeneratedFormValueByKey( | ||||
|                                                         pickedSource! | ||||
|                                                             .additionalAppSpecificSourceAgnosticFormItems, | ||||
|                                                         otherAdditionalData, | ||||
|                                                         'trackOnlyFormItemKey') == | ||||
|                                                     'true'; | ||||
|                                             var cont = true; | ||||
|                                             if ((userPickedTrackOnly || | ||||
|                                                     pickedSource! | ||||
|                                                         .enforceTrackOnly) && | ||||
|                                                 await showDialog( | ||||
|                                                         context: context, | ||||
|                                                         builder: | ||||
|                                                             (BuildContext ctx) { | ||||
|                                                           return GeneratedFormModal( | ||||
|                                                             title: | ||||
|                                                                 '${pickedSource!.enforceTrackOnly ? 'Source' : 'App'} is Track-Only', | ||||
|                                                             items: const [], | ||||
|                                                             defaultValues: const [], | ||||
|                                                             message: | ||||
|                                                                 '${pickedSource!.enforceTrackOnly ? 'Apps from this source are \'Track-Only\'.' : 'You have selected the \'Track-Only\' option.'}\n\nThe App will be tracked for updates, but Obtainium will not be able to download or install it.', | ||||
|                                                           ); | ||||
|                                                         }) == | ||||
|                                                     null) { | ||||
|                                               cont = false; | ||||
|                                             } | ||||
|                                             if (cont) { | ||||
|                                               HapticFeedback.selectionClick(); | ||||
|                                               var trackOnly = pickedSource! | ||||
|                                                       .enforceTrackOnly || | ||||
|                                                   userPickedTrackOnly; | ||||
|                                               App app = | ||||
|                                                   await sourceProvider.getApp( | ||||
|                                                       pickedSource!, | ||||
|                                                       userInput, | ||||
|                                                       sourceSpecificAdditionalData, | ||||
|                                                       trackOnly: trackOnly); | ||||
|                                               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) { | ||||
|                                                 // ignore: use_build_context_synchronously | ||||
|                                                 var apkUrl = await appsProvider | ||||
|                                                     .confirmApkUrl( | ||||
|                                                         app, context); | ||||
|                                                 if (apkUrl == null) { | ||||
|                                                   throw ObtainiumError( | ||||
|                                                       'Cancelled'); | ||||
|                                                 } | ||||
|                                                 app.preferredApkIndex = | ||||
|                                                     app.apkUrls.indexOf(apkUrl); | ||||
|                                                 var downloadedApk = | ||||
|                                                     await appsProvider | ||||
|                                                         .downloadApp(app); | ||||
|                                                 app.id = downloadedApk.appId; | ||||
|                                               } | ||||
|                                               if (appsProvider.apps | ||||
|                                                   .containsKey(app.id)) { | ||||
|                                                 throw ObtainiumError( | ||||
|                                                     'App already added'); | ||||
|                                               } | ||||
|                                               if (app.trackOnly) { | ||||
|                                                 app.installedVersion = | ||||
|                                                     app.latestVersion; | ||||
|                                               } | ||||
|                                               await appsProvider | ||||
|                                                   .saveApps([app]); | ||||
|  | ||||
|                                               return app; | ||||
|                                             } | ||||
|                                           }() | ||||
|                                               .then((app) { | ||||
|                                             if (app != null) { | ||||
|                                               Navigator.push( | ||||
|                                                   context, | ||||
|                                                   MaterialPageRoute( | ||||
|                                                       builder: (context) => | ||||
|                                                           AppPage( | ||||
|                                                               appId: app.id))); | ||||
|                                             } | ||||
|                                           }).catchError((e) { | ||||
|                                             showError(e, context); | ||||
|                                           }).whenComplete(() { | ||||
|                                             setState(() { | ||||
|                                               gettingAppInfo = false; | ||||
|                                             }); | ||||
|                                           }); | ||||
|                                         }, | ||||
|                                       : addApp, | ||||
|                                   child: const Text('Add')) | ||||
|                         ], | ||||
|                       ), | ||||
|                       const SizedBox( | ||||
|                         height: 16, | ||||
|                       ), | ||||
|                       if (sourceProvider.sources | ||||
|                               .where((e) => e.canSearch) | ||||
|                               .isNotEmpty && | ||||
|                           pickedSource == null) | ||||
|                         Row( | ||||
|                           children: [ | ||||
|                             Expanded( | ||||
|                               child: GeneratedForm( | ||||
|                                   items: [ | ||||
|                                     [ | ||||
|                                       GeneratedFormItem( | ||||
|                                           label: 'Search (Some Sources Only)', | ||||
|                                           required: false), | ||||
|                                     ] | ||||
|                                   ], | ||||
|                                   onValueChanges: (values, valid, isBuilding) { | ||||
|                                     if (values.isNotEmpty && valid) { | ||||
|                                       setState(() { | ||||
|                                         searchQuery = values[0].trim(); | ||||
|                                       }); | ||||
|                                     } | ||||
|                                   }, | ||||
|                                   defaultValues: const ['']), | ||||
|                             ), | ||||
|                             const SizedBox( | ||||
|                               width: 16, | ||||
|                             ), | ||||
|                             ElevatedButton( | ||||
|                                 onPressed: searchQuery.isEmpty || gettingAppInfo | ||||
|                                     ? null | ||||
|                                     : () { | ||||
|                                         Future.wait(sourceProvider.sources | ||||
|                                                 .where((e) => e.canSearch) | ||||
|                                                 .map((e) => | ||||
|                                                     e.search(searchQuery))) | ||||
|                                             .then((results) async { | ||||
|                                           var res = // TODO: Interleave results | ||||
|                                               results.reduce((value, element) { | ||||
|                                             value.addAll(element); | ||||
|                                             return value; | ||||
|                                           }); | ||||
|                                           // Map<String, String> res = {}; | ||||
|                                           // var si = 0; | ||||
|                                           // var done = false; | ||||
|                                           // for (var r in results) { | ||||
|                                           //   if (r.length > si) { | ||||
|                                           //     res.addEntries(r.entries.toList()[si]); | ||||
|                                           //   } | ||||
|                                           // } | ||||
|                                           // for (var rs in results) { | ||||
|                                           //   for (var r in rs.entries) {} | ||||
|                                           // } | ||||
|                                           List<String>? selectedUrls = res | ||||
|                                                   .isEmpty | ||||
|                                               ? [] | ||||
|                                               : await showDialog<List<String>?>( | ||||
|                                                   context: context, | ||||
|                                                   builder: (BuildContext ctx) { | ||||
|                                                     return UrlSelectionModal( | ||||
|                                                       urlsWithDescriptions: res, | ||||
|                                                       selectedByDefault: false, | ||||
|                                                       onlyOneSelectionAllowed: | ||||
|                                                           true, | ||||
|                                                     ); | ||||
|                                                   }); | ||||
|                                           if (selectedUrls != null && | ||||
|                                               selectedUrls.isNotEmpty) { | ||||
|                                             changeUserInput( | ||||
|                                                 selectedUrls[0], true, true); | ||||
|                                             addApp(resetUserInputAfter: true); | ||||
|                                           } | ||||
|                                         }).catchError((e) { | ||||
|                                           showError(e, context); | ||||
|                                         }); | ||||
|                                       }, | ||||
|                                 child: const Text('Search')) | ||||
|                           ], | ||||
|                         ), | ||||
|                       if (pickedSource != null && | ||||
|                           (pickedSource!.additionalSourceAppSpecificDefaults | ||||
|                                   .isNotEmpty || | ||||
| @@ -314,7 +371,7 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|                                                 LaunchMode.externalApplication); | ||||
|                                       }, | ||||
|                                       child: Text( | ||||
|                                         '${e.runtimeType.toString()}${e.enforceTrackOnly ? ' (Track-Only)' : ''}', | ||||
|                                         '${e.runtimeType.toString()}${e.enforceTrackOnly ? ' (Track-Only)' : ''}${e.canSearch ? ' (Searchable)' : ''}', | ||||
|                                         style: const TextStyle( | ||||
|                                             decoration: | ||||
|                                                 TextDecoration.underline, | ||||
| @@ -322,6 +379,9 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|                                       ))) | ||||
|                                   .toList() | ||||
|                             ])), | ||||
|                       const SizedBox( | ||||
|                         height: 8, | ||||
|                       ), | ||||
|                     ])), | ||||
|           ) | ||||
|         ])); | ||||
|   | ||||
| @@ -38,23 +38,6 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|       ), | ||||
|     ); | ||||
|  | ||||
|     Future<List<List<String>>> addApps(List<String> urls) async { | ||||
|       List<dynamic> results = await sourceProvider.getAppsByURLNaive(urls, | ||||
|           ignoreUrls: appsProvider.apps.values.map((e) => e.app.url).toList()); | ||||
|       List<App> apps = results[0]; | ||||
|       Map<String, dynamic> errorsMap = results[1]; | ||||
|       for (var app in apps) { | ||||
|         if (appsProvider.apps.containsKey(app.id)) { | ||||
|           errorsMap.addAll({app.id: 'App already added'}); | ||||
|         } else { | ||||
|           await appsProvider.saveApps([app]); | ||||
|         } | ||||
|       } | ||||
|       List<List<String>> errors = | ||||
|           errorsMap.keys.map((e) => [e, errorsMap[e].toString()]).toList(); | ||||
|       return errors; | ||||
|     } | ||||
|  | ||||
|     return Scaffold( | ||||
|         backgroundColor: Theme.of(context).colorScheme.surface, | ||||
|         body: CustomScrollView(slivers: <Widget>[ | ||||
| @@ -194,7 +177,9 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|                                       setState(() { | ||||
|                                         importInProgress = true; | ||||
|                                       }); | ||||
|                                       addApps(urls).then((errors) { | ||||
|                                       appsProvider | ||||
|                                           .addAppsByURL(urls) | ||||
|                                           .then((errors) { | ||||
|                                         if (errors.isEmpty) { | ||||
|                                           showError( | ||||
|                                               'Imported ${urls.length} Apps', | ||||
| @@ -272,7 +257,7 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|                                                                 return UrlSelectionModal( | ||||
|                                                                   urlsWithDescriptions: | ||||
|                                                                       urlsWithDescriptions, | ||||
|                                                                   defaultSelected: | ||||
|                                                                   selectedByDefault: | ||||
|                                                                       false, | ||||
|                                                                 ); | ||||
|                                                               }); | ||||
| @@ -281,8 +266,9 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|                                                           selectedUrls | ||||
|                                                               .isNotEmpty) { | ||||
|                                                         var errors = | ||||
|                                                             await addApps( | ||||
|                                                                 selectedUrls); | ||||
|                                                             await appsProvider | ||||
|                                                                 .addAppsByURL( | ||||
|                                                                     selectedUrls); | ||||
|                                                         if (errors.isEmpty) { | ||||
|                                                           // ignore: use_build_context_synchronously | ||||
|                                                           showError( | ||||
| @@ -371,8 +357,9 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|                                                             }); | ||||
|                                                     if (selectedUrls != null) { | ||||
|                                                       var errors = | ||||
|                                                           await addApps( | ||||
|                                                               selectedUrls); | ||||
|                                                           await appsProvider | ||||
|                                                               .addAppsByURL( | ||||
|                                                                   selectedUrls); | ||||
|                                                       if (errors.isEmpty) { | ||||
|                                                         // ignore: use_build_context_synchronously | ||||
|                                                         showError( | ||||
| @@ -483,10 +470,12 @@ class UrlSelectionModal extends StatefulWidget { | ||||
|   UrlSelectionModal( | ||||
|       {super.key, | ||||
|       required this.urlsWithDescriptions, | ||||
|       this.defaultSelected = true}); | ||||
|       this.selectedByDefault = true, | ||||
|       this.onlyOneSelectionAllowed = false}); | ||||
|  | ||||
|   Map<String, String> urlsWithDescriptions; | ||||
|   bool defaultSelected; | ||||
|   bool selectedByDefault; | ||||
|   bool onlyOneSelectionAllowed; | ||||
|  | ||||
|   @override | ||||
|   State<UrlSelectionModal> createState() => _UrlSelectionModalState(); | ||||
| @@ -498,8 +487,17 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> { | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     for (var url in widget.urlsWithDescriptions.entries) { | ||||
|       urlWithDescriptionSelections.putIfAbsent( | ||||
|           url, () => widget.defaultSelected); | ||||
|       urlWithDescriptionSelections.putIfAbsent(url, | ||||
|           () => widget.selectedByDefault && !widget.onlyOneSelectionAllowed); | ||||
|     } | ||||
|     if (widget.selectedByDefault && widget.onlyOneSelectionAllowed) { | ||||
|       selectOnlyOne(widget.urlsWithDescriptions.entries.first.key); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   selectOnlyOne(String url) { | ||||
|     for (var uwd in urlWithDescriptionSelections.keys) { | ||||
|       urlWithDescriptionSelections[uwd] = uwd.key == url; | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -507,7 +505,8 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> { | ||||
|   Widget build(BuildContext context) { | ||||
|     return AlertDialog( | ||||
|       scrollable: true, | ||||
|       title: const Text('Select URLs to Import'), | ||||
|       title: | ||||
|           Text(widget.onlyOneSelectionAllowed ? 'Select URL' : 'Select URLs'), | ||||
|       content: Column(children: [ | ||||
|         ...urlWithDescriptionSelections.keys.map((urlWithD) { | ||||
|           return Row(children: [ | ||||
| @@ -515,7 +514,12 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> { | ||||
|                 value: urlWithDescriptionSelections[urlWithD], | ||||
|                 onChanged: (value) { | ||||
|                   setState(() { | ||||
|                     urlWithDescriptionSelections[urlWithD] = value ?? false; | ||||
|                     value ??= false; | ||||
|                     if (value! && widget.onlyOneSelectionAllowed) { | ||||
|                       selectOnlyOne(urlWithD.key); | ||||
|                     } else { | ||||
|                       urlWithDescriptionSelections[urlWithD] = value!; | ||||
|                     } | ||||
|                   }); | ||||
|                 }), | ||||
|             const SizedBox( | ||||
| @@ -562,14 +566,19 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> { | ||||
|             }, | ||||
|             child: const Text('Cancel')), | ||||
|         TextButton( | ||||
|             onPressed: () { | ||||
|               Navigator.of(context).pop(urlWithDescriptionSelections.entries | ||||
|                   .where((entry) => entry.value) | ||||
|                   .map((e) => e.key.key) | ||||
|                   .toList()); | ||||
|             }, | ||||
|             child: Text( | ||||
|                 'Import ${urlWithDescriptionSelections.values.where((b) => b).length} URLs')) | ||||
|             onPressed: | ||||
|                 urlWithDescriptionSelections.values.where((b) => b).isEmpty | ||||
|                     ? null | ||||
|                     : () { | ||||
|                         Navigator.of(context).pop(urlWithDescriptionSelections | ||||
|                             .entries | ||||
|                             .where((entry) => entry.value) | ||||
|                             .map((e) => e.key.key) | ||||
|                             .toList()); | ||||
|                       }, | ||||
|             child: Text(widget.onlyOneSelectionAllowed | ||||
|                 ? 'Pick' | ||||
|                 : 'Import ${urlWithDescriptionSelections.values.where((b) => b).length} URLs')) | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
|   | ||||
| @@ -635,6 +635,23 @@ class AppsProvider with ChangeNotifier { | ||||
|     foregroundSubscription?.cancel(); | ||||
|     super.dispose(); | ||||
|   } | ||||
|  | ||||
|   Future<List<List<String>>> addAppsByURL(List<String> urls) async { | ||||
|     List<dynamic> results = await SourceProvider().getAppsByURLNaive(urls, | ||||
|         ignoreUrls: apps.values.map((e) => e.app.url).toList()); | ||||
|     List<App> pps = results[0]; | ||||
|     Map<String, dynamic> errorsMap = results[1]; | ||||
|     for (var app in pps) { | ||||
|       if (apps.containsKey(app.id)) { | ||||
|         errorsMap.addAll({app.id: 'App already added'}); | ||||
|       } else { | ||||
|         await saveApps([app]); | ||||
|       } | ||||
|     } | ||||
|     List<List<String>> errors = | ||||
|         errorsMap.keys.map((e) => [e, errorsMap[e].toString()]).toList(); | ||||
|     return errors; | ||||
|   } | ||||
| } | ||||
|  | ||||
| class APKPicker extends StatefulWidget { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user