mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-10-30 21:13: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/components/generated_form_modal.dart'; | ||||||
| import 'package:obtainium/custom_errors.dart'; | import 'package:obtainium/custom_errors.dart'; | ||||||
| import 'package:obtainium/pages/app.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/apps_provider.dart'; | ||||||
| import 'package:obtainium/providers/settings_provider.dart'; | import 'package:obtainium/providers/settings_provider.dart'; | ||||||
| import 'package:obtainium/providers/source_provider.dart'; | import 'package:obtainium/providers/source_provider.dart'; | ||||||
| @@ -22,6 +23,7 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|   bool gettingAppInfo = false; |   bool gettingAppInfo = false; | ||||||
|  |  | ||||||
|   String userInput = ''; |   String userInput = ''; | ||||||
|  |   String searchQuery = ''; | ||||||
|   AppSource? pickedSource; |   AppSource? pickedSource; | ||||||
|   List<String> sourceSpecificAdditionalData = []; |   List<String> sourceSpecificAdditionalData = []; | ||||||
|   bool sourceSpecificDataIsValid = true; |   bool sourceSpecificDataIsValid = true; | ||||||
| @@ -31,6 +33,107 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     SourceProvider sourceProvider = SourceProvider(); |     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( |     return Scaffold( | ||||||
|         backgroundColor: Theme.of(context).colorScheme.surface, |         backgroundColor: Theme.of(context).colorScheme.surface, | ||||||
|         body: CustomScrollView(slivers: <Widget>[ |         body: CustomScrollView(slivers: <Widget>[ | ||||||
| @@ -70,34 +173,8 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|                                     ] |                                     ] | ||||||
|                                   ], |                                   ], | ||||||
|                                   onValueChanges: (values, valid, isBuilding) { |                                   onValueChanges: (values, valid, isBuilding) { | ||||||
|                                     fn() { |                                     changeUserInput( | ||||||
|                                       userInput = values[0]; |                                         values[0], valid, isBuilding); | ||||||
|                                       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(); |  | ||||||
|                                       }); |  | ||||||
|                                     } |  | ||||||
|                                   }, |                                   }, | ||||||
|                                   defaultValues: const [])), |                                   defaultValues: const [])), | ||||||
|                           const SizedBox( |                           const SizedBox( | ||||||
| @@ -117,109 +194,89 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|                                                   .isNotEmpty && |                                                   .isNotEmpty && | ||||||
|                                               !otherAdditionalDataIsValid) |                                               !otherAdditionalDataIsValid) | ||||||
|                                       ? null |                                       ? null | ||||||
|                                       : () async { |                                       : 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(() { |                                       setState(() { | ||||||
|                                             gettingAppInfo = true; |                                         searchQuery = values[0].trim(); | ||||||
|                                       }); |                                       }); | ||||||
|                                           var appsProvider = |                                     } | ||||||
|                                               context.read<AppsProvider>(); |                                   }, | ||||||
|                                           var settingsProvider = |                                   defaultValues: const ['']), | ||||||
|                                               context.read<SettingsProvider>(); |                             ), | ||||||
|                                           () async { |                             const SizedBox( | ||||||
|                                             var userPickedTrackOnly = |                               width: 16, | ||||||
|                                                 findGeneratedFormValueByKey( |                             ), | ||||||
|                                                         pickedSource! |                             ElevatedButton( | ||||||
|                                                             .additionalAppSpecificSourceAgnosticFormItems, |                                 onPressed: searchQuery.isEmpty || gettingAppInfo | ||||||
|                                                         otherAdditionalData, |                                     ? null | ||||||
|                                                         'trackOnlyFormItemKey') == |                                     : () { | ||||||
|                                                     'true'; |                                         Future.wait(sourceProvider.sources | ||||||
|                                             var cont = true; |                                                 .where((e) => e.canSearch) | ||||||
|                                             if ((userPickedTrackOnly || |                                                 .map((e) => | ||||||
|                                                     pickedSource! |                                                     e.search(searchQuery))) | ||||||
|                                                         .enforceTrackOnly) && |                                             .then((results) async { | ||||||
|                                                 await showDialog( |                                           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, |                                                   context: context, | ||||||
|                                                         builder: |                                                   builder: (BuildContext ctx) { | ||||||
|                                                             (BuildContext ctx) { |                                                     return UrlSelectionModal( | ||||||
|                                                           return GeneratedFormModal( |                                                       urlsWithDescriptions: res, | ||||||
|                                                             title: |                                                       selectedByDefault: false, | ||||||
|                                                                 '${pickedSource!.enforceTrackOnly ? 'Source' : 'App'} is Track-Only', |                                                       onlyOneSelectionAllowed: | ||||||
|                                                             items: const [], |                                                           true, | ||||||
|                                                             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) { |                                           if (selectedUrls != null && | ||||||
|                                               cont = false; |                                               selectedUrls.isNotEmpty) { | ||||||
|                                             } |                                             changeUserInput( | ||||||
|                                             if (cont) { |                                                 selectedUrls[0], true, true); | ||||||
|                                               HapticFeedback.selectionClick(); |                                             addApp(resetUserInputAfter: true); | ||||||
|                                               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) { |                                         }).catchError((e) { | ||||||
|                                           showError(e, context); |                                           showError(e, context); | ||||||
|                                           }).whenComplete(() { |  | ||||||
|                                             setState(() { |  | ||||||
|                                               gettingAppInfo = false; |  | ||||||
|                                             }); |  | ||||||
|                                         }); |                                         }); | ||||||
|                                       }, |                                       }, | ||||||
|                                   child: const Text('Add')) |                                 child: const Text('Search')) | ||||||
|                           ], |                           ], | ||||||
|                         ), |                         ), | ||||||
|                       if (pickedSource != null && |                       if (pickedSource != null && | ||||||
| @@ -314,7 +371,7 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|                                                 LaunchMode.externalApplication); |                                                 LaunchMode.externalApplication); | ||||||
|                                       }, |                                       }, | ||||||
|                                       child: Text( |                                       child: Text( | ||||||
|                                         '${e.runtimeType.toString()}${e.enforceTrackOnly ? ' (Track-Only)' : ''}', |                                         '${e.runtimeType.toString()}${e.enforceTrackOnly ? ' (Track-Only)' : ''}${e.canSearch ? ' (Searchable)' : ''}', | ||||||
|                                         style: const TextStyle( |                                         style: const TextStyle( | ||||||
|                                             decoration: |                                             decoration: | ||||||
|                                                 TextDecoration.underline, |                                                 TextDecoration.underline, | ||||||
| @@ -322,6 +379,9 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|                                       ))) |                                       ))) | ||||||
|                                   .toList() |                                   .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( |     return Scaffold( | ||||||
|         backgroundColor: Theme.of(context).colorScheme.surface, |         backgroundColor: Theme.of(context).colorScheme.surface, | ||||||
|         body: CustomScrollView(slivers: <Widget>[ |         body: CustomScrollView(slivers: <Widget>[ | ||||||
| @@ -194,7 +177,9 @@ class _ImportExportPageState extends State<ImportExportPage> { | |||||||
|                                       setState(() { |                                       setState(() { | ||||||
|                                         importInProgress = true; |                                         importInProgress = true; | ||||||
|                                       }); |                                       }); | ||||||
|                                       addApps(urls).then((errors) { |                                       appsProvider | ||||||
|  |                                           .addAppsByURL(urls) | ||||||
|  |                                           .then((errors) { | ||||||
|                                         if (errors.isEmpty) { |                                         if (errors.isEmpty) { | ||||||
|                                           showError( |                                           showError( | ||||||
|                                               'Imported ${urls.length} Apps', |                                               'Imported ${urls.length} Apps', | ||||||
| @@ -272,7 +257,7 @@ class _ImportExportPageState extends State<ImportExportPage> { | |||||||
|                                                                 return UrlSelectionModal( |                                                                 return UrlSelectionModal( | ||||||
|                                                                   urlsWithDescriptions: |                                                                   urlsWithDescriptions: | ||||||
|                                                                       urlsWithDescriptions, |                                                                       urlsWithDescriptions, | ||||||
|                                                                   defaultSelected: |                                                                   selectedByDefault: | ||||||
|                                                                       false, |                                                                       false, | ||||||
|                                                                 ); |                                                                 ); | ||||||
|                                                               }); |                                                               }); | ||||||
| @@ -281,7 +266,8 @@ class _ImportExportPageState extends State<ImportExportPage> { | |||||||
|                                                           selectedUrls |                                                           selectedUrls | ||||||
|                                                               .isNotEmpty) { |                                                               .isNotEmpty) { | ||||||
|                                                         var errors = |                                                         var errors = | ||||||
|                                                             await addApps( |                                                             await appsProvider | ||||||
|  |                                                                 .addAppsByURL( | ||||||
|                                                                     selectedUrls); |                                                                     selectedUrls); | ||||||
|                                                         if (errors.isEmpty) { |                                                         if (errors.isEmpty) { | ||||||
|                                                           // ignore: use_build_context_synchronously |                                                           // ignore: use_build_context_synchronously | ||||||
| @@ -371,7 +357,8 @@ class _ImportExportPageState extends State<ImportExportPage> { | |||||||
|                                                             }); |                                                             }); | ||||||
|                                                     if (selectedUrls != null) { |                                                     if (selectedUrls != null) { | ||||||
|                                                       var errors = |                                                       var errors = | ||||||
|                                                           await addApps( |                                                           await appsProvider | ||||||
|  |                                                               .addAppsByURL( | ||||||
|                                                                   selectedUrls); |                                                                   selectedUrls); | ||||||
|                                                       if (errors.isEmpty) { |                                                       if (errors.isEmpty) { | ||||||
|                                                         // ignore: use_build_context_synchronously |                                                         // ignore: use_build_context_synchronously | ||||||
| @@ -483,10 +470,12 @@ class UrlSelectionModal extends StatefulWidget { | |||||||
|   UrlSelectionModal( |   UrlSelectionModal( | ||||||
|       {super.key, |       {super.key, | ||||||
|       required this.urlsWithDescriptions, |       required this.urlsWithDescriptions, | ||||||
|       this.defaultSelected = true}); |       this.selectedByDefault = true, | ||||||
|  |       this.onlyOneSelectionAllowed = false}); | ||||||
|  |  | ||||||
|   Map<String, String> urlsWithDescriptions; |   Map<String, String> urlsWithDescriptions; | ||||||
|   bool defaultSelected; |   bool selectedByDefault; | ||||||
|  |   bool onlyOneSelectionAllowed; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   State<UrlSelectionModal> createState() => _UrlSelectionModalState(); |   State<UrlSelectionModal> createState() => _UrlSelectionModalState(); | ||||||
| @@ -498,8 +487,17 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> { | |||||||
|   void initState() { |   void initState() { | ||||||
|     super.initState(); |     super.initState(); | ||||||
|     for (var url in widget.urlsWithDescriptions.entries) { |     for (var url in widget.urlsWithDescriptions.entries) { | ||||||
|       urlWithDescriptionSelections.putIfAbsent( |       urlWithDescriptionSelections.putIfAbsent(url, | ||||||
|           url, () => widget.defaultSelected); |           () => 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) { |   Widget build(BuildContext context) { | ||||||
|     return AlertDialog( |     return AlertDialog( | ||||||
|       scrollable: true, |       scrollable: true, | ||||||
|       title: const Text('Select URLs to Import'), |       title: | ||||||
|  |           Text(widget.onlyOneSelectionAllowed ? 'Select URL' : 'Select URLs'), | ||||||
|       content: Column(children: [ |       content: Column(children: [ | ||||||
|         ...urlWithDescriptionSelections.keys.map((urlWithD) { |         ...urlWithDescriptionSelections.keys.map((urlWithD) { | ||||||
|           return Row(children: [ |           return Row(children: [ | ||||||
| @@ -515,7 +514,12 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> { | |||||||
|                 value: urlWithDescriptionSelections[urlWithD], |                 value: urlWithDescriptionSelections[urlWithD], | ||||||
|                 onChanged: (value) { |                 onChanged: (value) { | ||||||
|                   setState(() { |                   setState(() { | ||||||
|                     urlWithDescriptionSelections[urlWithD] = value ?? false; |                     value ??= false; | ||||||
|  |                     if (value! && widget.onlyOneSelectionAllowed) { | ||||||
|  |                       selectOnlyOne(urlWithD.key); | ||||||
|  |                     } else { | ||||||
|  |                       urlWithDescriptionSelections[urlWithD] = value!; | ||||||
|  |                     } | ||||||
|                   }); |                   }); | ||||||
|                 }), |                 }), | ||||||
|             const SizedBox( |             const SizedBox( | ||||||
| @@ -562,14 +566,19 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> { | |||||||
|             }, |             }, | ||||||
|             child: const Text('Cancel')), |             child: const Text('Cancel')), | ||||||
|         TextButton( |         TextButton( | ||||||
|             onPressed: () { |             onPressed: | ||||||
|               Navigator.of(context).pop(urlWithDescriptionSelections.entries |                 urlWithDescriptionSelections.values.where((b) => b).isEmpty | ||||||
|  |                     ? null | ||||||
|  |                     : () { | ||||||
|  |                         Navigator.of(context).pop(urlWithDescriptionSelections | ||||||
|  |                             .entries | ||||||
|                             .where((entry) => entry.value) |                             .where((entry) => entry.value) | ||||||
|                             .map((e) => e.key.key) |                             .map((e) => e.key.key) | ||||||
|                             .toList()); |                             .toList()); | ||||||
|                       }, |                       }, | ||||||
|             child: Text( |             child: Text(widget.onlyOneSelectionAllowed | ||||||
|                 'Import ${urlWithDescriptionSelections.values.where((b) => b).length} URLs')) |                 ? 'Pick' | ||||||
|  |                 : 'Import ${urlWithDescriptionSelections.values.where((b) => b).length} URLs')) | ||||||
|       ], |       ], | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -635,6 +635,23 @@ class AppsProvider with ChangeNotifier { | |||||||
|     foregroundSubscription?.cancel(); |     foregroundSubscription?.cancel(); | ||||||
|     super.dispose(); |     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 { | class APKPicker extends StatefulWidget { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user