Added F-Droid search (#526) + search UI improvements

This commit is contained in:
Imran Remtulla
2023-05-03 18:40:34 -04:00
parent 9ec345761e
commit 639fc20fcb
7 changed files with 76 additions and 28 deletions

View File

@ -70,7 +70,7 @@ class Codeberg extends AppSource {
} }
@override @override
Future<Map<String, String>> search(String query) async { Future<Map<String, List<String>>> search(String query) async {
return gh.searchCommon( return gh.searchCommon(
query, query,
'https://$host/api/v1/repos/search?q=${Uri.encodeQueryComponent(query)}&limit=100', 'https://$host/api/v1/repos/search?q=${Uri.encodeQueryComponent(query)}&limit=100',

View File

@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
@ -9,6 +10,7 @@ class FDroid extends AppSource {
FDroid() { FDroid() {
host = 'f-droid.org'; host = 'f-droid.org';
name = tr('fdroid'); name = tr('fdroid');
canSearch = true;
} }
@override @override
@ -68,4 +70,32 @@ class FDroid extends AppSource {
'https://$host/repo/$appId', 'https://$host/repo/$appId',
standardUrl); standardUrl);
} }
@override
Future<Map<String, List<String>>> search(String query) async {
Response res = await get(Uri.parse('https://search.$host/?q=$query'));
if (res.statusCode == 200) {
Map<String, List<String>> 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);
}
}
} }

View File

@ -213,19 +213,21 @@ class GitHub extends AppSource {
return AppNames(names[0], names[1]); return AppNames(names[0], names[1]);
} }
Future<Map<String, String>> searchCommon( Future<Map<String, List<String>>> searchCommon(
String query, String requestUrl, String rootProp, String query, String requestUrl, String rootProp,
{Function(Response)? onHttpErrorCode}) async { {Function(Response)? onHttpErrorCode}) async {
Response res = await get(Uri.parse(requestUrl)); Response res = await get(Uri.parse(requestUrl));
if (res.statusCode == 200) { if (res.statusCode == 200) {
Map<String, String> urlsWithDescriptions = {}; Map<String, List<String>> urlsWithDescriptions = {};
for (var e in (jsonDecode(res.body)[rootProp] as List<dynamic>)) { for (var e in (jsonDecode(res.body)[rootProp] as List<dynamic>)) {
urlsWithDescriptions.addAll({ urlsWithDescriptions.addAll({
e['html_url'] as String: e['html_url'] as String: [
((e['archived'] == true ? '[ARCHIVED] ' : '') + e['full_name'] as String,
(e['description'] != null ((e['archived'] == true ? '[ARCHIVED] ' : '') +
? e['description'] as String (e['description'] != null
: tr('noDescription'))) ? e['description'] as String
: tr('noDescription')))
]
}); });
} }
return urlsWithDescriptions; return urlsWithDescriptions;
@ -238,7 +240,7 @@ class GitHub extends AppSource {
} }
@override @override
Future<Map<String, String>> search(String query) async { Future<Map<String, List<String>>> search(String query) async {
return searchCommon( return searchCommon(
query, query,
'https://${await getCredentialPrefixIfAny()}api.$host/search/repositories?q=${Uri.encodeQueryComponent(query)}&per_page=100', 'https://${await getCredentialPrefixIfAny()}api.$host/search/repositories?q=${Uri.encodeQueryComponent(query)}&per_page=100',

View File

@ -13,17 +13,20 @@ class GitHubStars implements MassAppUrlSource {
@override @override
late List<String> requiredArgs = [tr('uname')]; late List<String> requiredArgs = [tr('uname')];
Future<Map<String, String>> getOnePageOfUserStarredUrlsWithDescriptions( Future<Map<String, List<String>>> getOnePageOfUserStarredUrlsWithDescriptions(
String username, int page) async { String username, int page) async {
Response res = await get(Uri.parse( Response res = await get(Uri.parse(
'https://${await GitHub().getCredentialPrefixIfAny()}api.github.com/users/$username/starred?per_page=100&page=$page')); 'https://${await GitHub().getCredentialPrefixIfAny()}api.github.com/users/$username/starred?per_page=100&page=$page'));
if (res.statusCode == 200) { if (res.statusCode == 200) {
Map<String, String> urlsWithDescriptions = {}; Map<String, List<String>> urlsWithDescriptions = {};
for (var e in (jsonDecode(res.body) as List<dynamic>)) { for (var e in (jsonDecode(res.body) as List<dynamic>)) {
urlsWithDescriptions.addAll({ urlsWithDescriptions.addAll({
e['html_url'] as String: e['description'] != null e['html_url'] as String: [
? e['description'] as String e['full_name'] as String,
: tr('noDescription') e['description'] != null
? e['description'] as String
: tr('noDescription')
]
}); });
} }
return urlsWithDescriptions; return urlsWithDescriptions;
@ -35,11 +38,12 @@ class GitHubStars implements MassAppUrlSource {
} }
@override @override
Future<Map<String, String>> getUrlsWithDescriptions(List<String> args) async { Future<Map<String, List<String>>> getUrlsWithDescriptions(
List<String> args) async {
if (args.length != requiredArgs.length) { if (args.length != requiredArgs.length) {
throw ObtainiumError(tr('wrongArgNum')); throw ObtainiumError(tr('wrongArgNum'));
} }
Map<String, String> urlsWithDescriptions = {}; Map<String, List<String>> urlsWithDescriptions = {};
var page = 1; var page = 1;
while (true) { while (true) {
var pageUrls = var pageUrls =

View File

@ -254,7 +254,7 @@ class _AddAppPageState extends State<AddAppPage> {
// .then((results) async { // .then((results) async {
// Interleave results instead of simple reduce // Interleave results instead of simple reduce
Map<String, String> res = {}; Map<String, List<String>> res = {};
var si = 0; var si = 0;
var done = false; var done = false;
while (!done) { while (!done) {

View File

@ -470,7 +470,7 @@ class UrlSelectionModal extends StatefulWidget {
this.selectedByDefault = true, this.selectedByDefault = true,
this.onlyOneSelectionAllowed = false}); this.onlyOneSelectionAllowed = false});
Map<String, String> urlsWithDescriptions; Map<String, List<String>> urlsWithDescriptions;
bool selectedByDefault; bool selectedByDefault;
bool onlyOneSelectionAllowed; bool onlyOneSelectionAllowed;
@ -479,7 +479,7 @@ class UrlSelectionModal extends StatefulWidget {
} }
class _UrlSelectionModalState extends State<UrlSelectionModal> { class _UrlSelectionModalState extends State<UrlSelectionModal> {
Map<MapEntry<String, String>, bool> urlWithDescriptionSelections = {}; Map<MapEntry<String, List<String>>, bool> urlWithDescriptionSelections = {};
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -522,16 +522,28 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
launchUrlString(urlWithD.key, launchUrlString(urlWithD.key,
mode: LaunchMode.externalApplication); mode: LaunchMode.externalApplication);
}, },
child: Text( child: Column(
Uri.parse(urlWithD.key).path.substring(1), crossAxisAlignment: CrossAxisAlignment.start,
style: const TextStyle(decoration: TextDecoration.underline), children: [
textAlign: TextAlign.start, 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( var descriptionText = Text(
urlWithD.value.length > 128 urlWithD.value[1].length > 128
? '${urlWithD.value.substring(0, 128)}...' ? '${urlWithD.value[1].substring(0, 128)}...'
: urlWithD.value, : urlWithD.value[1],
style: const TextStyle(fontStyle: FontStyle.italic, fontSize: 12), style: const TextStyle(fontStyle: FontStyle.italic, fontSize: 12),
); );

View File

@ -415,7 +415,7 @@ abstract class AppSource {
} }
bool canSearch = false; bool canSearch = false;
Future<Map<String, String>> search(String query) { Future<Map<String, List<String>>> search(String query) {
throw NotImplementedError(); throw NotImplementedError();
} }
@ -433,7 +433,7 @@ ObtainiumError getObtainiumHttpError(Response res) {
abstract class MassAppUrlSource { abstract class MassAppUrlSource {
late String name; late String name;
late List<String> requiredArgs; late List<String> requiredArgs;
Future<Map<String, String>> getUrlsWithDescriptions(List<String> args); Future<Map<String, List<String>>> getUrlsWithDescriptions(List<String> args);
} }
regExValidator(String? value) { regExValidator(String? value) {