mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-08-20 05:19:28 +02:00
Re-added APKMirror as a Track-Only source
This commit is contained in:
55
lib/app_sources/apkmirror.dart
Normal file
55
lib/app_sources/apkmirror.dart
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import 'package:html/parser.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
import 'package:obtainium/custom_errors.dart';
|
||||||
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
|
|
||||||
|
class APKMirror extends AppSource {
|
||||||
|
APKMirror() {
|
||||||
|
host = 'apkmirror.com';
|
||||||
|
enforceTrackOnly = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String standardizeURL(String url) {
|
||||||
|
RegExp standardUrlRegEx = RegExp('^https?://$host/apk/[^/]+/[^/]+');
|
||||||
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||||
|
if (match == null) {
|
||||||
|
throw InvalidURLError(runtimeType.toString());
|
||||||
|
}
|
||||||
|
return url.substring(0, match.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? changeLogPageFromStandardUrl(String standardUrl) =>
|
||||||
|
'$standardUrl/#whatsnew';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
|
String standardUrl, List<String> additionalData) async {
|
||||||
|
Response res = await get(Uri.parse('$standardUrl/feed'));
|
||||||
|
if (res.statusCode == 200) {
|
||||||
|
String? titleString = parse(res.body)
|
||||||
|
.querySelector('item')
|
||||||
|
?.querySelector('title')
|
||||||
|
?.innerHtml;
|
||||||
|
String? version = titleString
|
||||||
|
?.substring(0,
|
||||||
|
RegExp(' build ( |[0-9])+').firstMatch(titleString)?.start ?? 0)
|
||||||
|
.split(' ')
|
||||||
|
.last;
|
||||||
|
if (version == null) {
|
||||||
|
throw NoVersionError();
|
||||||
|
}
|
||||||
|
return APKDetails(version, []);
|
||||||
|
} else {
|
||||||
|
throw NoReleasesError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AppNames getAppNames(String standardUrl) {
|
||||||
|
String temp = standardUrl.substring(standardUrl.indexOf('://') + 3);
|
||||||
|
List<String> names = temp.substring(temp.indexOf('/') + 1).split('/');
|
||||||
|
return AppNames(names[1], names[2]);
|
||||||
|
}
|
||||||
|
}
|
@@ -143,7 +143,7 @@ class _AddAppPageState extends State<AddAppPage> {
|
|||||||
(BuildContext ctx) {
|
(BuildContext ctx) {
|
||||||
return GeneratedFormModal(
|
return GeneratedFormModal(
|
||||||
title:
|
title:
|
||||||
'App is Track-Only',
|
'${pickedSource!.enforceTrackOnly ? 'Source' : 'App'} is Track-Only',
|
||||||
items: const [],
|
items: const [],
|
||||||
defaultValues: const [],
|
defaultValues: const [],
|
||||||
message:
|
message:
|
||||||
@@ -222,7 +222,11 @@ class _AddAppPageState extends State<AddAppPage> {
|
|||||||
(pickedSource!.additionalSourceAppSpecificDefaults
|
(pickedSource!.additionalSourceAppSpecificDefaults
|
||||||
.isNotEmpty ||
|
.isNotEmpty ||
|
||||||
pickedSource!
|
pickedSource!
|
||||||
.additionalAppSpecificSourceAgnosticDefaults
|
.additionalAppSpecificSourceAgnosticFormItems
|
||||||
|
.where((e) => pickedSource!.enforceTrackOnly
|
||||||
|
? e.key != 'trackOnlyFormItemKey'
|
||||||
|
: true)
|
||||||
|
.map((e) => [e])
|
||||||
.isNotEmpty))
|
.isNotEmpty))
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
@@ -257,33 +261,27 @@ class _AddAppPageState extends State<AddAppPage> {
|
|||||||
},
|
},
|
||||||
defaultValues: pickedSource!
|
defaultValues: pickedSource!
|
||||||
.additionalSourceAppSpecificDefaults),
|
.additionalSourceAppSpecificDefaults),
|
||||||
if (pickedSource!
|
GeneratedForm(
|
||||||
.additionalSourceAppSpecificFormItems
|
items: pickedSource!
|
||||||
.isNotEmpty)
|
.additionalAppSpecificSourceAgnosticFormItems
|
||||||
const SizedBox(
|
.where((e) => pickedSource!.enforceTrackOnly
|
||||||
height: 8,
|
? e.key != 'trackOnlyFormItemKey'
|
||||||
),
|
: true)
|
||||||
if (pickedSource!
|
.map((e) => [e])
|
||||||
.additionalAppSpecificSourceAgnosticFormItems
|
.toList(),
|
||||||
.isNotEmpty)
|
onValueChanges: (values, valid, isBuilding) {
|
||||||
GeneratedForm(
|
if (isBuilding) {
|
||||||
items: pickedSource!
|
otherAdditionalData = values;
|
||||||
.additionalAppSpecificSourceAgnosticFormItems
|
otherAdditionalDataIsValid = valid;
|
||||||
.map((e) => [e])
|
} else {
|
||||||
.toList(),
|
setState(() {
|
||||||
onValueChanges: (values, valid, isBuilding) {
|
|
||||||
if (isBuilding) {
|
|
||||||
otherAdditionalData = values;
|
otherAdditionalData = values;
|
||||||
otherAdditionalDataIsValid = valid;
|
otherAdditionalDataIsValid = valid;
|
||||||
} else {
|
});
|
||||||
setState(() {
|
}
|
||||||
otherAdditionalData = values;
|
},
|
||||||
otherAdditionalDataIsValid = valid;
|
defaultValues: pickedSource!
|
||||||
});
|
.additionalAppSpecificSourceAgnosticDefaults),
|
||||||
}
|
|
||||||
},
|
|
||||||
defaultValues: pickedSource!
|
|
||||||
.additionalAppSpecificSourceAgnosticDefaults),
|
|
||||||
if (pickedSource!
|
if (pickedSource!
|
||||||
.additionalAppSpecificSourceAgnosticDefaults
|
.additionalAppSpecificSourceAgnosticDefaults
|
||||||
.isNotEmpty)
|
.isNotEmpty)
|
||||||
@@ -304,16 +302,15 @@ class _AddAppPageState extends State<AddAppPage> {
|
|||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 8,
|
height: 8,
|
||||||
),
|
),
|
||||||
...sourceProvider
|
...sourceProvider.sources
|
||||||
.getSourceHosts()
|
|
||||||
.map((e) => GestureDetector(
|
.map((e) => GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
launchUrlString('https://$e',
|
launchUrlString('https://${e.host}',
|
||||||
mode:
|
mode:
|
||||||
LaunchMode.externalApplication);
|
LaunchMode.externalApplication);
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
e,
|
'${e.runtimeType.toString()}${e.enforceTrackOnly ? ' (Track-Only)' : ''}',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
decoration:
|
decoration:
|
||||||
TextDecoration.underline,
|
TextDecoration.underline,
|
||||||
|
@@ -5,6 +5,7 @@ import 'dart:convert';
|
|||||||
|
|
||||||
import 'package:html/dom.dart';
|
import 'package:html/dom.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
|
import 'package:obtainium/app_sources/apkmirror.dart';
|
||||||
import 'package:obtainium/app_sources/fdroid.dart';
|
import 'package:obtainium/app_sources/fdroid.dart';
|
||||||
import 'package:obtainium/app_sources/github.dart';
|
import 'package:obtainium/app_sources/github.dart';
|
||||||
import 'package:obtainium/app_sources/gitlab.dart';
|
import 'package:obtainium/app_sources/gitlab.dart';
|
||||||
@@ -209,7 +210,8 @@ class SourceProvider {
|
|||||||
IzzyOnDroid(),
|
IzzyOnDroid(),
|
||||||
Mullvad(),
|
Mullvad(),
|
||||||
Signal(),
|
Signal(),
|
||||||
SourceForge()
|
SourceForge(),
|
||||||
|
APKMirror()
|
||||||
];
|
];
|
||||||
|
|
||||||
// Add more mass url source classes here so they are available via the service
|
// Add more mass url source classes here so they are available via the service
|
||||||
@@ -254,7 +256,7 @@ class SourceProvider {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return getSourceHosts().contains(parts.last);
|
return sources.map((e) => e.host).contains(parts.last);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<App> getApp(AppSource source, String url, List<String> additionalData,
|
Future<App> getApp(AppSource source, String url, List<String> additionalData,
|
||||||
@@ -306,6 +308,4 @@ class SourceProvider {
|
|||||||
}
|
}
|
||||||
return [apps, errors];
|
return [apps, errors];
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> getSourceHosts() => sources.map((e) => e.host).toList();
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user