mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-10-26 03:03:45 +01:00 
			
		
		
		
	GitLab search (#422) + better settings UI
This commit is contained in:
		| @@ -73,7 +73,8 @@ class FDroid extends AppSource { | ||||
|  | ||||
|   @override | ||||
|   Future<Map<String, List<String>>> search(String query) async { | ||||
|     Response res = await sourceRequest('https://search.$host/?q=$query'); | ||||
|     Response res = await sourceRequest( | ||||
|         'https://search.$host/?q=${Uri.encodeQueryComponent(query)}'); | ||||
|     if (res.statusCode == 200) { | ||||
|       Map<String, List<String>> urlsWithDescriptions = {}; | ||||
|       parse(res.body).querySelectorAll('.package-header').forEach((e) { | ||||
|   | ||||
| @@ -35,7 +35,7 @@ class GitHub extends AppSource { | ||||
|           hint: tr('githubPATFormat'), | ||||
|           belowWidgets: [ | ||||
|             const SizedBox( | ||||
|               height: 8, | ||||
|               height: 4, | ||||
|             ), | ||||
|             GestureDetector( | ||||
|                 onTap: () { | ||||
| @@ -44,10 +44,13 @@ class GitHub extends AppSource { | ||||
|                       mode: LaunchMode.externalApplication); | ||||
|                 }, | ||||
|                 child: Text( | ||||
|                   tr('githubPATLinkText'), | ||||
|                   tr('about'), | ||||
|                   style: const TextStyle( | ||||
|                       decoration: TextDecoration.underline, fontSize: 12), | ||||
|                 )) | ||||
|                 )), | ||||
|             const SizedBox( | ||||
|               height: 4, | ||||
|             ), | ||||
|           ]) | ||||
|     ]; | ||||
|  | ||||
|   | ||||
| @@ -1,15 +1,47 @@ | ||||
| import 'dart:convert'; | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:html/parser.dart'; | ||||
| import 'package:http/http.dart'; | ||||
| import 'package:obtainium/app_sources/github.dart'; | ||||
| import 'package:obtainium/custom_errors.dart'; | ||||
| import 'package:obtainium/providers/settings_provider.dart'; | ||||
| import 'package:obtainium/providers/source_provider.dart'; | ||||
| import 'package:obtainium/components/generated_form.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:url_launcher/url_launcher_string.dart'; | ||||
|  | ||||
| class GitLab extends AppSource { | ||||
|   GitLab() { | ||||
|     host = 'gitlab.com'; | ||||
|     overrideEligible = true; | ||||
|     canSearch = true; | ||||
|  | ||||
|     additionalSourceSpecificSettingFormItems = [ | ||||
|       GeneratedFormTextField('gitlab-creds', | ||||
|           label: tr('gitlabPATLabel'), | ||||
|           password: true, | ||||
|           required: false, | ||||
|           belowWidgets: [ | ||||
|             const SizedBox( | ||||
|               height: 4, | ||||
|             ), | ||||
|             GestureDetector( | ||||
|                 onTap: () { | ||||
|                   launchUrlString( | ||||
|                       'https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#create-a-personal-access-token', | ||||
|                       mode: LaunchMode.externalApplication); | ||||
|                 }, | ||||
|                 child: Text( | ||||
|                   tr('about'), | ||||
|                   style: const TextStyle( | ||||
|                       decoration: TextDecoration.underline, fontSize: 12), | ||||
|                 )), | ||||
|             const SizedBox( | ||||
|               height: 4, | ||||
|             ) | ||||
|           ]) | ||||
|     ]; | ||||
|  | ||||
|     additionalSourceAppSpecificSettingFormItems = [ | ||||
|       [ | ||||
| @@ -29,6 +61,37 @@ class GitLab extends AppSource { | ||||
|     return url.substring(0, match.end); | ||||
|   } | ||||
|  | ||||
|   Future<String?> getPATIfAny() async { | ||||
|     SettingsProvider settingsProvider = SettingsProvider(); | ||||
|     await settingsProvider.initializeSettings(); | ||||
|     String? creds = settingsProvider | ||||
|         .getSettingString(additionalSourceSpecificSettingFormItems[0].key); | ||||
|     return creds != null && creds.isNotEmpty ? creds : null; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<Map<String, List<String>>> search(String query) async { | ||||
|     String? PAT = await getPATIfAny(); | ||||
|     if (PAT == null) { | ||||
|       throw CredsNeededError(name); | ||||
|     } | ||||
|     var url = | ||||
|         'https://$host/api/v4/search?private_token=$PAT&scope=projects&search=${Uri.encodeQueryComponent(query)}'; | ||||
|     var res = await sourceRequest(url); | ||||
|     if (res.statusCode != 200) { | ||||
|       throw getObtainiumHttpError(res); | ||||
|     } | ||||
|     var json = jsonDecode(res.body) as List<dynamic>; | ||||
|     Map<String, List<String>> results = {}; | ||||
|     json.forEach((element) { | ||||
|       results['https://$host/${element['path_with_namespace']}'] = [ | ||||
|         element['name_with_namespace'], | ||||
|         element['description'] ?? tr('noDescription') | ||||
|       ]; | ||||
|     }); | ||||
|     return results; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   String? changeLogPageFromStandardUrl(String standardUrl) => | ||||
|       '$standardUrl/-/releases'; | ||||
|   | ||||
| @@ -25,6 +25,11 @@ class InvalidURLError extends ObtainiumError { | ||||
|       : super(tr('invalidURLForSource', args: [sourceName])); | ||||
| } | ||||
|  | ||||
| class CredsNeededError extends ObtainiumError { | ||||
|   CredsNeededError(String sourceName) | ||||
|       : super(tr('requiresCredentialsInSettings', args: [sourceName])); | ||||
| } | ||||
|  | ||||
| class NoReleasesError extends ObtainiumError { | ||||
|   NoReleasesError() : super(tr('noReleaseFound')); | ||||
| } | ||||
|   | ||||
| @@ -248,9 +248,18 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|         searching = true; | ||||
|       }); | ||||
|       try { | ||||
|         var results = await Future.wait(sourceProvider.sources | ||||
|             .where((e) => e.canSearch) | ||||
|             .map((e) => e.search(searchQuery))); | ||||
|         var results = await Future.wait( | ||||
|             sourceProvider.sources.where((e) => e.canSearch).map((e) async { | ||||
|           try { | ||||
|             return await e.search(searchQuery); | ||||
|           } catch (err) { | ||||
|             if (err is! CredsNeededError) { | ||||
|               rethrow; | ||||
|             } else { | ||||
|               return <String, List<String>>{}; | ||||
|             } | ||||
|           } | ||||
|         })); | ||||
|  | ||||
|         // .then((results) async { | ||||
|         // Interleave results instead of simple reduce | ||||
|   | ||||
| @@ -205,6 +205,10 @@ class _SettingsPageState extends State<SettingsPage> { | ||||
|       height: 16, | ||||
|     ); | ||||
|  | ||||
|     const height32 = SizedBox( | ||||
|       height: 32, | ||||
|     ); | ||||
|  | ||||
|     return Scaffold( | ||||
|         backgroundColor: Theme.of(context).colorScheme.surface, | ||||
|         body: CustomScrollView(slivers: <Widget>[ | ||||
| @@ -217,9 +221,26 @@ class _SettingsPageState extends State<SettingsPage> { | ||||
|                       : Column( | ||||
|                           crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                           children: [ | ||||
|                             Text( | ||||
|                               tr('updates'), | ||||
|                               style: TextStyle( | ||||
|                                   fontWeight: FontWeight.bold, | ||||
|                                   color: Theme.of(context).colorScheme.primary), | ||||
|                             ), | ||||
|                             intervalDropdown, | ||||
|                             height32, | ||||
|                             Text( | ||||
|                               tr('sourceSpecific'), | ||||
|                               style: TextStyle( | ||||
|                                   fontWeight: FontWeight.bold, | ||||
|                                   color: Theme.of(context).colorScheme.primary), | ||||
|                             ), | ||||
|                             ...sourceSpecificFields, | ||||
|                             height32, | ||||
|                             Text( | ||||
|                               tr('appearance'), | ||||
|                               style: TextStyle( | ||||
|                                   fontWeight: FontWeight.bold, | ||||
|                                   color: Theme.of(context).colorScheme.primary), | ||||
|                             ), | ||||
|                             themeDropdown, | ||||
| @@ -332,31 +353,11 @@ class _SettingsPageState extends State<SettingsPage> { | ||||
|                                     }) | ||||
|                               ], | ||||
|                             ), | ||||
|                             const Divider( | ||||
|                               height: 16, | ||||
|                             ), | ||||
|                             height16, | ||||
|                             Text( | ||||
|                               tr('updates'), | ||||
|                               style: TextStyle( | ||||
|                                   color: Theme.of(context).colorScheme.primary), | ||||
|                             ), | ||||
|                             intervalDropdown, | ||||
|                             const Divider( | ||||
|                               height: 48, | ||||
|                             ), | ||||
|                             Text( | ||||
|                               tr('sourceSpecific'), | ||||
|                               style: TextStyle( | ||||
|                                   color: Theme.of(context).colorScheme.primary), | ||||
|                             ), | ||||
|                             ...sourceSpecificFields, | ||||
|                             const Divider( | ||||
|                               height: 48, | ||||
|                             ), | ||||
|                             height32, | ||||
|                             Text( | ||||
|                               tr('categories'), | ||||
|                               style: TextStyle( | ||||
|                                   fontWeight: FontWeight.bold, | ||||
|                                   color: Theme.of(context).colorScheme.primary), | ||||
|                             ), | ||||
|                             height16, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user