mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-16 06:36:44 +02:00
Added F-Droid search (#526) + search UI improvements
This commit is contained in:
@ -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',
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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',
|
||||||
|
@ -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 =
|
||||||
|
@ -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) {
|
||||||
|
@ -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),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
Reference in New Issue
Block a user