Store APK names with URLs (#432)

This commit is contained in:
Imran Remtulla
2023-04-05 18:50:19 -04:00
parent 361a3e1bc2
commit 202ce4f0d5
18 changed files with 65 additions and 41 deletions

View File

@ -68,7 +68,7 @@ class Codeberg extends AppSource {
if (res.statusCode == 200) { if (res.statusCode == 200) {
var releases = jsonDecode(res.body) as List<dynamic>; var releases = jsonDecode(res.body) as List<dynamic>;
List<String> getReleaseAPKUrls(dynamic release) => List<MapEntry<String, String>> getReleaseAPKUrls(dynamic release) =>
(release['assets'] as List<dynamic>?) (release['assets'] as List<dynamic>?)
?.map((e) { ?.map((e) {
return e['name'] != null && e['browser_download_url'] != null return e['name'] != null && e['browser_download_url'] != null
@ -77,7 +77,6 @@ class Codeberg extends AppSource {
: const MapEntry('', ''); : const MapEntry('', '');
}) })
.where((element) => element.key.toLowerCase().endsWith('.apk')) .where((element) => element.key.toLowerCase().endsWith('.apk'))
.map((e) => e.value)
.toList() ?? .toList() ??
[]; [];
@ -119,7 +118,9 @@ class Codeberg extends AppSource {
throw NoVersionError(); throw NoVersionError();
} }
var changeLog = targetRelease['body'].toString(); var changeLog = targetRelease['body'].toString();
return APKDetails(version, targetRelease['apkUrls'] as List<String>, return APKDetails(
version,
targetRelease['apkUrls'] as List<MapEntry<String, String>>,
getAppNames(standardUrl), getAppNames(standardUrl),
releaseDate: releaseDate, releaseDate: releaseDate,
changeLog: changeLog.isEmpty ? null : changeLog); changeLog: changeLog.isEmpty ? null : changeLog);

View File

@ -50,7 +50,7 @@ class FDroid extends AppSource {
.where((element) => element['versionName'] == latestVersion) .where((element) => element['versionName'] == latestVersion)
.map((e) => '${apkUrlPrefix}_${e['versionCode']}.apk') .map((e) => '${apkUrlPrefix}_${e['versionCode']}.apk')
.toList(); .toList();
return APKDetails(latestVersion, apkUrls, return APKDetails(latestVersion, getApkUrlsFromUrls(apkUrls),
AppNames(name, Uri.parse(standardUrl).pathSegments.last)); AppNames(name, Uri.parse(standardUrl).pathSegments.last));
} else { } else {
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);

View File

@ -80,7 +80,8 @@ class FDroidRepo extends AppSource {
element.querySelector('apkname') != null) element.querySelector('apkname') != null)
.map((e) => '$standardUrl/${e.querySelector('apkname')!.innerHtml}') .map((e) => '$standardUrl/${e.querySelector('apkname')!.innerHtml}')
.toList(); .toList();
return APKDetails(latestVersion, apkUrls, AppNames(authorName, appName), return APKDetails(latestVersion, getApkUrlsFromUrls(apkUrls),
AppNames(authorName, appName),
releaseDate: releaseDate); releaseDate: releaseDate);
} else { } else {
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);

View File

@ -161,7 +161,9 @@ class GitHub extends AppSource {
throw NoVersionError(); throw NoVersionError();
} }
var changeLog = targetRelease['body'].toString(); var changeLog = targetRelease['body'].toString();
return APKDetails(version, targetRelease['apkUrls'] as List<String>, return APKDetails(
version,
getApkUrlsFromUrls(targetRelease['apkUrls'] as List<String>),
getAppNames(standardUrl), getAppNames(standardUrl),
releaseDate: releaseDate, releaseDate: releaseDate,
changeLog: changeLog.isEmpty ? null : changeLog); changeLog: changeLog.isEmpty ? null : changeLog);

View File

@ -60,7 +60,8 @@ class GitLab extends AppSource {
if (version == null) { if (version == null) {
throw NoVersionError(); throw NoVersionError();
} }
return APKDetails(version, apkUrls, GitHub().getAppNames(standardUrl), return APKDetails(version, getApkUrlsFromUrls(apkUrls),
GitHub().getAppNames(standardUrl),
releaseDate: releaseDate); releaseDate: releaseDate);
} else { } else {
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);

View File

@ -42,7 +42,8 @@ class HTML extends AppSource {
? '${uri.origin}/$e' ? '${uri.origin}/$e'
: '${uri.origin}/${uri.path}/$e') : '${uri.origin}/${uri.path}/$e')
.toList(); .toList();
return APKDetails(version, apkUrls, AppNames(uri.host, tr('app'))); return APKDetails(
version, getApkUrlsFromUrls(apkUrls), AppNames(uri.host, tr('app')));
} else { } else {
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);
} }

View File

@ -58,7 +58,7 @@ class Mullvad extends AppSource {
} }
return APKDetails( return APKDetails(
versions[0], versions[0],
['https://mullvad.net/download/app/apk/latest'], getApkUrlsFromUrls(['https://mullvad.net/download/app/apk/latest']),
AppNames(name, 'Mullvad-VPN'), AppNames(name, 'Mullvad-VPN'),
changeLog: changeLog); changeLog: changeLog);
} else { } else {

View File

@ -98,7 +98,7 @@ class NeutronCode extends AppSource {
? (customDateParse(dateStringOriginal)) ? (customDateParse(dateStringOriginal))
: null; : null;
var changeLogElements = http.querySelectorAll('.pd-fdesc p'); var changeLogElements = http.querySelectorAll('.pd-fdesc p');
return APKDetails(version, [apkUrl], return APKDetails(version, getApkUrlsFromUrls([apkUrl]),
AppNames(runtimeType.toString(), name ?? standardUrl.split('/').last), AppNames(runtimeType.toString(), name ?? standardUrl.split('/').last),
releaseDate: dateString != null ? DateTime.parse(dateString) : null, releaseDate: dateString != null ? DateTime.parse(dateString) : null,
changeLog: changeLogElements.isNotEmpty changeLog: changeLogElements.isNotEmpty

View File

@ -28,7 +28,8 @@ class Signal extends AppSource {
if (version == null) { if (version == null) {
throw NoVersionError(); throw NoVersionError();
} }
return APKDetails(version, apkUrls, AppNames(name, 'Signal')); return APKDetails(
version, getApkUrlsFromUrls(apkUrls), AppNames(name, 'Signal'));
} else { } else {
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);
} }

View File

@ -50,7 +50,7 @@ class SourceForge extends AppSource {
.toList(); .toList();
return APKDetails( return APKDetails(
version, version,
apkUrlList, getApkUrlsFromUrls(apkUrlList),
AppNames( AppNames(
name, standardUrl.substring(standardUrl.lastIndexOf('/') + 1))); name, standardUrl.substring(standardUrl.lastIndexOf('/') + 1)));
} else { } else {

View File

@ -53,7 +53,8 @@ class SteamMobile extends AppSource {
var version = links[0].substring( var version = links[0].substring(
versionMatch.start + apkNamePrefix.length + 2, versionMatch.end - 4); versionMatch.start + apkNamePrefix.length + 2, versionMatch.end - 4);
var apkUrls = [links[0]]; var apkUrls = [links[0]];
return APKDetails(version, apkUrls, AppNames(name, apks[apkNamePrefix]!)); return APKDetails(version, getApkUrlsFromUrls(apkUrls),
AppNames(name, apks[apkNamePrefix]!));
} else { } else {
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);
} }

View File

@ -32,7 +32,8 @@ class TelegramApp extends AppSource {
throw NoVersionError(); throw NoVersionError();
} }
String? apkUrl = 'https://telegram.org/dl/android/apk'; String? apkUrl = 'https://telegram.org/dl/android/apk';
return APKDetails(version, [apkUrl], AppNames('Telegram', 'Telegram')); return APKDetails(version, getApkUrlsFromUrls([apkUrl]),
AppNames('Telegram', 'Telegram'));
} else { } else {
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);
} }

View File

@ -54,7 +54,8 @@ class VLC extends AppSource {
throw getObtainiumHttpError(res2); throw getObtainiumHttpError(res2);
} }
return APKDetails(version, apkUrls, AppNames('VideoLAN', 'VLC')); return APKDetails(
version, getApkUrlsFromUrls(apkUrls), AppNames('VideoLAN', 'VLC'));
} else { } else {
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);
} }

View File

@ -64,9 +64,9 @@ class WhatsApp extends AppSource {
vLines[0].substring(versionMatch.start, versionMatch.end); vLines[0].substring(versionMatch.start, versionMatch.end);
return APKDetails( return APKDetails(
version, version,
[ getApkUrlsFromUrls([
'https://www.whatsapp.com/android?v=$version&=thisIsaPlaceholder&a=realURLPrefetchedAtDownloadTime' 'https://www.whatsapp.com/android?v=$version&=thisIsaPlaceholder&a=realURLPrefetchedAtDownloadTime'
], ]),
AppNames('Meta', 'WhatsApp')); AppNames('Meta', 'WhatsApp'));
} else { } else {
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);

View File

@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart';
// ignore: implementation_imports // ignore: implementation_imports
import 'package:easy_localization/src/localization.dart'; import 'package:easy_localization/src/localization.dart';
const String currentVersion = '0.11.24'; const String currentVersion = '0.11.25';
const String currentReleaseTag = const String currentReleaseTag =
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES 'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES

View File

@ -169,7 +169,7 @@ class AppsProvider with ChangeNotifier {
'${app.id}-${app.latestVersion}-${app.preferredApkIndex}.apk'; '${app.id}-${app.latestVersion}-${app.preferredApkIndex}.apk';
String downloadUrl = await SourceProvider() String downloadUrl = await SourceProvider()
.getSource(app.url) .getSource(app.url)
.apkUrlPrefetchModifier(app.apkUrls[app.preferredApkIndex]); .apkUrlPrefetchModifier(app.apkUrls[app.preferredApkIndex].value);
var notif = DownloadNotification(app.name, 100); var notif = DownloadNotification(app.name, 100);
notificationsProvider?.cancel(notif.id); notificationsProvider?.cancel(notif.id);
int? prevProg; int? prevProg;
@ -296,9 +296,10 @@ class AppsProvider with ChangeNotifier {
await intent.launch(); await intent.launch();
} }
Future<String?> confirmApkUrl(App app, BuildContext? context) async { Future<MapEntry<String, String>?> confirmApkUrl(
App app, BuildContext? context) async {
// If the App has more than one APK, the user should pick one (if context provided) // If the App has more than one APK, the user should pick one (if context provided)
String? apkUrl = app.apkUrls[app.preferredApkIndex]; MapEntry<String, String>? apkUrl = app.apkUrls[app.preferredApkIndex];
// get device supported architecture // get device supported architecture
List<String> archs = (await DeviceInfoPlugin().androidInfo).supportedAbis; List<String> archs = (await DeviceInfoPlugin().androidInfo).supportedAbis;
@ -321,14 +322,14 @@ class AppsProvider with ChangeNotifier {
// If the picked APK comes from an origin different from the source, get user confirmation (if context provided) // If the picked APK comes from an origin different from the source, get user confirmation (if context provided)
if (apkUrl != null && if (apkUrl != null &&
getHost(apkUrl) != getHost(app.url) && getHost(apkUrl.value) != getHost(app.url) &&
context != null) { context != null) {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
if (await showDialog( if (await showDialog(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
return APKOriginWarningDialog( return APKOriginWarningDialog(
sourceUrl: app.url, apkUrl: apkUrl!); sourceUrl: app.url, apkUrl: apkUrl!.value);
}) != }) !=
true) { true) {
apkUrl = null; apkUrl = null;
@ -353,7 +354,7 @@ class AppsProvider with ChangeNotifier {
if (apps[id] == null) { if (apps[id] == null) {
throw ObtainiumError(tr('appNotFound')); throw ObtainiumError(tr('appNotFound'));
} }
String? apkUrl; MapEntry<String, String>? apkUrl;
var trackOnly = apps[id]!.app.additionalSettings['trackOnly'] == true; var trackOnly = apps[id]!.app.additionalSettings['trackOnly'] == true;
if (!trackOnly) { if (!trackOnly) {
apkUrl = await confirmApkUrl(apps[id]!.app, context); apkUrl = await confirmApkUrl(apps[id]!.app, context);
@ -923,7 +924,7 @@ class APKPicker extends StatefulWidget {
const APKPicker({super.key, required this.app, this.initVal, this.archs}); const APKPicker({super.key, required this.app, this.initVal, this.archs});
final App app; final App app;
final String? initVal; final MapEntry<String, String>? initVal;
final List<String>? archs; final List<String>? archs;
@override @override
@ -931,7 +932,7 @@ class APKPicker extends StatefulWidget {
} }
class _APKPickerState extends State<APKPicker> { class _APKPickerState extends State<APKPicker> {
String? apkUrl; MapEntry<String, String>? apkUrl;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -944,15 +945,13 @@ class _APKPickerState extends State<APKPicker> {
const SizedBox(height: 16), const SizedBox(height: 16),
...widget.app.apkUrls.map( ...widget.app.apkUrls.map(
(u) => RadioListTile<String>( (u) => RadioListTile<String>(
title: Text(Uri.parse(u) title: Text(u.key),
.pathSegments value: u.value,
.where((element) => element.isNotEmpty) groupValue: apkUrl!.value,
.last),
value: u,
groupValue: apkUrl,
onChanged: (String? val) { onChanged: (String? val) {
setState(() { setState(() {
apkUrl = val; apkUrl =
widget.app.apkUrls.where((e) => e.value == val).first;
}); });
}), }),
), ),

View File

@ -34,7 +34,7 @@ class AppNames {
class APKDetails { class APKDetails {
late String version; late String version;
late List<String> apkUrls; late List<MapEntry<String, String>> apkUrls;
late AppNames names; late AppNames names;
late DateTime? releaseDate; late DateTime? releaseDate;
late String? changeLog; late String? changeLog;
@ -50,7 +50,7 @@ class App {
late String name; late String name;
String? installedVersion; String? installedVersion;
late String latestVersion; late String latestVersion;
List<String> apkUrls = []; List<MapEntry<String, String>> apkUrls = [];
late int preferredApkIndex; late int preferredApkIndex;
late Map<String, dynamic> additionalSettings; late Map<String, dynamic> additionalSettings;
late DateTime? lastUpdateCheck; late DateTime? lastUpdateCheck;
@ -134,6 +134,18 @@ class App {
if (preferredApkIndex < 0) { if (preferredApkIndex < 0) {
preferredApkIndex = 0; preferredApkIndex = 0;
} }
// apkUrls can either be old list or new named list apkUrls
List<MapEntry<String, String>> apkUrls = [];
if (json['apkUrls'] != null) {
var apkUrlJson = jsonDecode(json['apkUrls']);
try {
apkUrls = getApkUrlsFromUrls(List<String>.from(apkUrlJson));
} catch (e) {
apkUrls = List<dynamic>.from(apkUrlJson)
.map((e) => MapEntry(e[0] as String, e[1] as String))
.toList();
}
}
return App( return App(
json['id'] as String, json['id'] as String,
json['url'] as String, json['url'] as String,
@ -143,9 +155,7 @@ class App {
? null ? null
: json['installedVersion'] as String, : json['installedVersion'] as String,
json['latestVersion'] as String, json['latestVersion'] as String,
json['apkUrls'] == null apkUrls,
? []
: List<String>.from(jsonDecode(json['apkUrls'])),
preferredApkIndex, preferredApkIndex,
additionalSettings, additionalSettings,
json['lastUpdateCheck'] == null json['lastUpdateCheck'] == null
@ -173,7 +183,7 @@ class App {
'name': name, 'name': name,
'installedVersion': installedVersion, 'installedVersion': installedVersion,
'latestVersion': latestVersion, 'latestVersion': latestVersion,
'apkUrls': jsonEncode(apkUrls), 'apkUrls': jsonEncode(apkUrls.map((e) => [e.key, e.value]).toList()),
'preferredApkIndex': preferredApkIndex, 'preferredApkIndex': preferredApkIndex,
'additionalSettings': jsonEncode(additionalSettings), 'additionalSettings': jsonEncode(additionalSettings),
'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch, 'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch,
@ -225,6 +235,11 @@ Map<String, dynamic> getDefaultValuesFromFormItems(
.reduce((value, element) => [...value, ...element])); .reduce((value, element) => [...value, ...element]));
} }
getApkUrlsFromUrls(List<String> urls) => urls
.map((e) =>
MapEntry(e.split('/').where((el) => el.trim().isNotEmpty).last, e))
.toList();
class AppSource { class AppSource {
String? host; String? host;
late String name; late String name;
@ -422,7 +437,7 @@ class SourceProvider {
if (additionalSettings['apkFilterRegEx'] != null) { if (additionalSettings['apkFilterRegEx'] != null) {
var reg = RegExp(additionalSettings['apkFilterRegEx']); var reg = RegExp(additionalSettings['apkFilterRegEx']);
apk.apkUrls = apk.apkUrls =
apk.apkUrls.where((element) => reg.hasMatch(element)).toList(); apk.apkUrls.where((element) => reg.hasMatch(element.key)).toList();
} }
if (apk.apkUrls.isEmpty && !trackOnly) { if (apk.apkUrls.isEmpty && !trackOnly) {
throw NoAPKError(); throw NoAPKError();

View File

@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 0.11.24+146 # When changing this, update the tag in main() accordingly version: 0.11.25+147 # When changing this, update the tag in main() accordingly
environment: environment:
sdk: '>=2.18.2 <3.0.0' sdk: '>=2.18.2 <3.0.0'