diff --git a/lib/app_sources/codeberg.dart b/lib/app_sources/codeberg.dart index 382c7f9..a45d620 100644 --- a/lib/app_sources/codeberg.dart +++ b/lib/app_sources/codeberg.dart @@ -70,7 +70,7 @@ class Codeberg extends AppSource { } @override - Future> search(String query) async { + Future>> search(String query) async { return gh.searchCommon( query, 'https://$host/api/v1/repos/search?q=${Uri.encodeQueryComponent(query)}&limit=100', diff --git a/lib/app_sources/fdroid.dart b/lib/app_sources/fdroid.dart index c9c84e6..318a983 100644 --- a/lib/app_sources/fdroid.dart +++ b/lib/app_sources/fdroid.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:easy_localization/easy_localization.dart'; +import 'package:html/parser.dart'; import 'package:http/http.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/providers/source_provider.dart'; @@ -9,6 +10,7 @@ class FDroid extends AppSource { FDroid() { host = 'f-droid.org'; name = tr('fdroid'); + canSearch = true; } @override @@ -68,4 +70,32 @@ class FDroid extends AppSource { 'https://$host/repo/$appId', standardUrl); } + + @override + Future>> search(String query) async { + Response res = await get(Uri.parse('https://search.$host/?q=$query')); + if (res.statusCode == 200) { + Map> urlsWithDescriptions = {}; + parse(res.body).querySelectorAll('.package-header').forEach((e) { + String? url = e.attributes['href']; + if (url != null) { + try { + standardizeUrl(url); + } catch (e) { + url = null; + } + } + if (url != null) { + urlsWithDescriptions[url] = [ + e.querySelector('.package-name')?.text.trim() ?? '', + e.querySelector('.package-summary')?.text.trim() ?? + tr('noDescription') + ]; + } + }); + return urlsWithDescriptions; + } else { + throw getObtainiumHttpError(res); + } + } } diff --git a/lib/app_sources/github.dart b/lib/app_sources/github.dart index 55e1d8b..e6b67ae 100644 --- a/lib/app_sources/github.dart +++ b/lib/app_sources/github.dart @@ -213,19 +213,21 @@ class GitHub extends AppSource { return AppNames(names[0], names[1]); } - Future> searchCommon( + Future>> searchCommon( String query, String requestUrl, String rootProp, {Function(Response)? onHttpErrorCode}) async { Response res = await get(Uri.parse(requestUrl)); if (res.statusCode == 200) { - Map urlsWithDescriptions = {}; + Map> urlsWithDescriptions = {}; for (var e in (jsonDecode(res.body)[rootProp] as List)) { urlsWithDescriptions.addAll({ - e['html_url'] as String: - ((e['archived'] == true ? '[ARCHIVED] ' : '') + - (e['description'] != null - ? e['description'] as String - : tr('noDescription'))) + e['html_url'] as String: [ + e['full_name'] as String, + ((e['archived'] == true ? '[ARCHIVED] ' : '') + + (e['description'] != null + ? e['description'] as String + : tr('noDescription'))) + ] }); } return urlsWithDescriptions; @@ -238,7 +240,7 @@ class GitHub extends AppSource { } @override - Future> search(String query) async { + Future>> search(String query) async { return searchCommon( query, 'https://${await getCredentialPrefixIfAny()}api.$host/search/repositories?q=${Uri.encodeQueryComponent(query)}&per_page=100', diff --git a/lib/mass_app_sources/githubstars.dart b/lib/mass_app_sources/githubstars.dart index d8ca40e..5a086f1 100644 --- a/lib/mass_app_sources/githubstars.dart +++ b/lib/mass_app_sources/githubstars.dart @@ -13,17 +13,20 @@ class GitHubStars implements MassAppUrlSource { @override late List requiredArgs = [tr('uname')]; - Future> getOnePageOfUserStarredUrlsWithDescriptions( + Future>> getOnePageOfUserStarredUrlsWithDescriptions( String username, int page) async { Response res = await get(Uri.parse( 'https://${await GitHub().getCredentialPrefixIfAny()}api.github.com/users/$username/starred?per_page=100&page=$page')); if (res.statusCode == 200) { - Map urlsWithDescriptions = {}; + Map> urlsWithDescriptions = {}; for (var e in (jsonDecode(res.body) as List)) { urlsWithDescriptions.addAll({ - e['html_url'] as String: e['description'] != null - ? e['description'] as String - : tr('noDescription') + e['html_url'] as String: [ + e['full_name'] as String, + e['description'] != null + ? e['description'] as String + : tr('noDescription') + ] }); } return urlsWithDescriptions; @@ -35,11 +38,12 @@ class GitHubStars implements MassAppUrlSource { } @override - Future> getUrlsWithDescriptions(List args) async { + Future>> getUrlsWithDescriptions( + List args) async { if (args.length != requiredArgs.length) { throw ObtainiumError(tr('wrongArgNum')); } - Map urlsWithDescriptions = {}; + Map> urlsWithDescriptions = {}; var page = 1; while (true) { var pageUrls = diff --git a/lib/pages/add_app.dart b/lib/pages/add_app.dart index a81eb9c..b0654c3 100644 --- a/lib/pages/add_app.dart +++ b/lib/pages/add_app.dart @@ -254,7 +254,7 @@ class _AddAppPageState extends State { // .then((results) async { // Interleave results instead of simple reduce - Map res = {}; + Map> res = {}; var si = 0; var done = false; while (!done) { diff --git a/lib/pages/import_export.dart b/lib/pages/import_export.dart index d8fe3d2..7a896c9 100644 --- a/lib/pages/import_export.dart +++ b/lib/pages/import_export.dart @@ -470,7 +470,7 @@ class UrlSelectionModal extends StatefulWidget { this.selectedByDefault = true, this.onlyOneSelectionAllowed = false}); - Map urlsWithDescriptions; + Map> urlsWithDescriptions; bool selectedByDefault; bool onlyOneSelectionAllowed; @@ -479,7 +479,7 @@ class UrlSelectionModal extends StatefulWidget { } class _UrlSelectionModalState extends State { - Map, bool> urlWithDescriptionSelections = {}; + Map>, bool> urlWithDescriptionSelections = {}; @override void initState() { super.initState(); @@ -522,16 +522,28 @@ class _UrlSelectionModalState extends State { launchUrlString(urlWithD.key, mode: LaunchMode.externalApplication); }, - child: Text( - Uri.parse(urlWithD.key).path.substring(1), - style: const TextStyle(decoration: TextDecoration.underline), - textAlign: TextAlign.start, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + urlWithD.value[0], + style: const TextStyle( + decoration: TextDecoration.underline, + fontWeight: FontWeight.bold), + textAlign: TextAlign.start, + ), + Text( + Uri.parse(urlWithD.key).host, + style: const TextStyle( + decoration: TextDecoration.underline, fontSize: 12), + ) + ], )); var descriptionText = Text( - urlWithD.value.length > 128 - ? '${urlWithD.value.substring(0, 128)}...' - : urlWithD.value, + urlWithD.value[1].length > 128 + ? '${urlWithD.value[1].substring(0, 128)}...' + : urlWithD.value[1], style: const TextStyle(fontStyle: FontStyle.italic, fontSize: 12), ); diff --git a/lib/providers/source_provider.dart b/lib/providers/source_provider.dart index e27cdd8..556c294 100644 --- a/lib/providers/source_provider.dart +++ b/lib/providers/source_provider.dart @@ -415,7 +415,7 @@ abstract class AppSource { } bool canSearch = false; - Future> search(String query) { + Future>> search(String query) { throw NotImplementedError(); } @@ -433,7 +433,7 @@ ObtainiumError getObtainiumHttpError(Response res) { abstract class MassAppUrlSource { late String name; late List requiredArgs; - Future> getUrlsWithDescriptions(List args); + Future>> getUrlsWithDescriptions(List args); } regExValidator(String? value) {