From 734a1aeb01273113500e49c8e7bcf2f97db2f124 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Fri, 18 Aug 2023 22:12:16 -0400 Subject: [PATCH 1/4] Enable Android TV 'OK' Button (#281) --- lib/main.dart | 6 ++++-- lib/providers/apps_provider.dart | 2 +- pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index efcce43..5074433 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -22,7 +22,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart'; // ignore: implementation_imports import 'package:easy_localization/src/localization.dart'; -const String currentVersion = '0.13.25'; +const String currentVersion = '0.13.26'; const String currentReleaseTag = 'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES @@ -299,7 +299,9 @@ class _ObtainiumState extends State { ? lightColorScheme : darkColorScheme, fontFamily: 'Metropolis'), - home: const HomePage()); + home: Shortcuts(shortcuts: { + LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(), + }, child: const HomePage())); }); } } diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index bc03997..73cd19e 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -344,7 +344,7 @@ class AppsProvider with ChangeNotifier { // If we did not install the app (or it isn't installed), silent install is not possible return false; } - var targetSDK; + int? targetSDK; try { targetSDK = (await pm.getPackageInfo(packageName: app.id)) ?.applicationInfo diff --git a/pubspec.yaml b/pubspec.yaml index e2e43e4..83b618f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 # 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. -version: 0.13.25+189 # When changing this, update the tag in main() accordingly +version: 0.13.26+190 # When changing this, update the tag in main() accordingly environment: sdk: '>=2.18.2 <3.0.0' From f61824ff0d67c0685b2f390087bda30b3f354b91 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sat, 19 Aug 2023 00:19:52 -0400 Subject: [PATCH 2/4] Add Huawei AppGallery (#756) --- README.md | 1 + lib/app_sources/huaweiappgallery.dart | 90 +++++++++++++++++++++++++++ lib/providers/source_provider.dart | 12 +++- 3 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 lib/app_sources/huaweiappgallery.dart diff --git a/README.md b/README.md index 47ece7c..d0c3f1a 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Currently supported App sources: - [SourceHut](https://git.sr.ht/) - [APKMirror](https://apkmirror.com/) (Track-Only) - [APKPure](https://apkpure.com/) +- [Huawei AppGallery](https://appgallery.huawei.com/) - Third Party F-Droid Repos - Jenkins Jobs - [Steam](https://store.steampowered.com/mobile) diff --git a/lib/app_sources/huaweiappgallery.dart b/lib/app_sources/huaweiappgallery.dart new file mode 100644 index 0000000..1a97774 --- /dev/null +++ b/lib/app_sources/huaweiappgallery.dart @@ -0,0 +1,90 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:http/http.dart'; +import 'package:obtainium/custom_errors.dart'; +import 'package:obtainium/providers/source_provider.dart'; + +class HuaweiAppGallery extends AppSource { + HuaweiAppGallery() { + name = 'Huawei AppGallery'; + host = 'appgallery.huawei.com'; + overrideVersionDetectionFormDefault('releaseDateAsVersion', true); + } + + @override + String sourceSpecificStandardizeURL(String url) { + RegExp standardUrlRegEx = RegExp('^https?://$host/app/[^/]+'); + RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); + if (match == null) { + throw InvalidURLError(name); + } + return url.substring(0, match.end); + } + + getDlUrl(String standardUrl) => + 'https://${host!.replaceAll('appgallery.', 'appgallery.cloud.')}/appdl/${standardUrl.split('/').last}'; + + requestAppdlRedirect(String dlUrl) async { + Response res = await sourceRequest(dlUrl, followRedirects: false); + if (res.statusCode == 200 || + res.statusCode == 302 || + res.statusCode == 304) { + return res; + } else { + throw getObtainiumHttpError(res); + } + } + + appIdFromRedirectDlUrl(String redirectDlUrl) { + var parts = redirectDlUrl + .split('?')[0] + .split('/') + .last + .split('.') + .reversed + .toList(); + parts.removeAt(0); + parts.removeAt(0); + return parts.reversed.join('.'); + } + + @override + Future tryInferringAppId(String standardUrl, + {Map additionalSettings = const {}}) async { + String dlUrl = getDlUrl(standardUrl); + Response res = await requestAppdlRedirect(dlUrl); + return res.headers['location'] != null + ? appIdFromRedirectDlUrl(res.headers['location']!) + : null; + } + + @override + Future getLatestAPKDetails( + String standardUrl, + Map additionalSettings, + ) async { + String dlUrl = getDlUrl(standardUrl); + Response res = await requestAppdlRedirect(dlUrl); + if (res.headers['location'] == null) { + throw NoReleasesError(); + } + String appId = appIdFromRedirectDlUrl(res.headers['location']!); + var relDateStr = + res.headers['location']?.split('?')[0].split('.').reversed.toList()[1]; + var relDateStrAdj = relDateStr?.split(''); + var tempLen = relDateStrAdj?.length ?? 0; + var i = 2; + while (i < tempLen) { + relDateStrAdj?.insert((i + i ~/ 2 - 1), '-'); + i += 2; + } + var relDate = relDateStrAdj == null + ? null + : DateFormat('yy-MM-dd-HH-mm').parse(relDateStrAdj.join('')); + if (relDateStr == null) { + throw NoVersionError(); + } + return APKDetails( + relDateStr, [MapEntry('$appId.apk', dlUrl)], AppNames(name, appId), + releaseDate: relDate); + } +} diff --git a/lib/providers/source_provider.dart b/lib/providers/source_provider.dart index da88640..1fff134 100644 --- a/lib/providers/source_provider.dart +++ b/lib/providers/source_provider.dart @@ -14,6 +14,7 @@ import 'package:obtainium/app_sources/fdroid.dart'; import 'package:obtainium/app_sources/fdroidrepo.dart'; import 'package:obtainium/app_sources/github.dart'; import 'package:obtainium/app_sources/gitlab.dart'; +import 'package:obtainium/app_sources/huaweiappgallery.dart'; import 'package:obtainium/app_sources/izzyondroid.dart'; import 'package:obtainium/app_sources/html.dart'; import 'package:obtainium/app_sources/jenkins.dart'; @@ -355,10 +356,14 @@ abstract class AppSource { Map? get requestHeaders => null; - Future sourceRequest(String url) async { - if (requestHeaders != null) { + Future sourceRequest(String url, + {bool followRedirects = true}) async { + if (requestHeaders != null || followRedirects == false) { var req = Request('GET', Uri.parse(url)); - req.headers.addAll(requestHeaders!); + req.followRedirects = followRedirects; + if (requestHeaders != null) { + req.headers.addAll(requestHeaders!); + } return Response.fromStream(await Client().send(req)); } else { return get(Uri.parse(url)); @@ -508,6 +513,7 @@ class SourceProvider { SourceHut(), APKMirror(), APKPure(), + HuaweiAppGallery(), // APKCombo(), // Can't get past their scraping blocking yet (get 403 Forbidden) Mullvad(), Signal(), From a2571e61a85d043c2e8700e5524a0cc0df087443 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sat, 19 Aug 2023 01:28:25 -0400 Subject: [PATCH 3/4] Fix VLC Source (#758) --- lib/app_sources/vlc.dart | 70 ++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/lib/app_sources/vlc.dart b/lib/app_sources/vlc.dart index bbdec76..6787996 100644 --- a/lib/app_sources/vlc.dart +++ b/lib/app_sources/vlc.dart @@ -1,5 +1,6 @@ import 'package:html/parser.dart'; import 'package:http/http.dart'; +import 'package:obtainium/app_sources/html.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/providers/source_provider.dart'; @@ -7,55 +8,60 @@ class VLC extends AppSource { VLC() { host = 'videolan.org'; } + get dwUrlBase => 'https://get.$host/vlc-android/'; + + @override + Map? get requestHeaders => HTML().requestHeaders; @override String sourceSpecificStandardizeURL(String url) { return 'https://$host'; } + Future getLatestVersion(String standardUrl) async { + Response res = await sourceRequest(dwUrlBase); + if (res.statusCode == 200) { + var dwLinks = parse(res.body) + .querySelectorAll('a') + .where((element) => element.attributes['href'] != 'last/') + .map((e) => e.attributes['href']?.split('/')[0]) + .toList(); + String? version = dwLinks.isNotEmpty ? dwLinks.last : null; + if (version == null) { + throw NoVersionError(); + } + return version; + } else { + throw getObtainiumHttpError(res); + } + } + @override Future getLatestAPKDetails( String standardUrl, Map additionalSettings, ) async { - Response res = await sourceRequest( - 'https://www.videolan.org/vlc/download-android.html'); + String? version = await getLatestVersion(standardUrl); + if (version == null) { + throw NoVersionError(); + } + String? targetUrl = '$dwUrlBase$version/'; + Response res = await sourceRequest(targetUrl); + List apkUrls = []; if (res.statusCode == 200) { - var dwUrlBase = 'get.videolan.org/vlc-android'; - var dwLinks = parse(res.body) + apkUrls = parse(res.body) .querySelectorAll('a') - .where((element) => - element.attributes['href']?.contains(dwUrlBase) ?? false) + .map((e) => e.attributes['href']?.split('/').last) + .where((h) => + h != null && h.isNotEmpty && h.toLowerCase().endsWith('.apk')) + .map((e) => targetUrl + e!) .toList(); - String? version = dwLinks.isNotEmpty - ? dwLinks.first.attributes['href'] - ?.split('/') - .where((s) => s.isNotEmpty) - .last - : null; - if (version == null) { - throw NoVersionError(); - } - String? targetUrl = 'https://$dwUrlBase/$version/'; - Response res2 = await sourceRequest(targetUrl); - List apkUrls = []; - if (res2.statusCode == 200) { - apkUrls = parse(res2.body) - .querySelectorAll('a') - .map((e) => e.attributes['href']?.split('/').last) - .where((h) => - h != null && h.isNotEmpty && h.toLowerCase().endsWith('.apk')) - .map((e) => targetUrl + e!) - .toList(); - } else { - throw getObtainiumHttpError(res2); - } - - return APKDetails( - version, getApkUrlsFromUrls(apkUrls), AppNames('VideoLAN', 'VLC')); } else { throw getObtainiumHttpError(res); } + + return APKDetails( + version, getApkUrlsFromUrls(apkUrls), AppNames('VideoLAN', 'VLC')); } @override From 6baf6ccf4b89adca014a12c6d71139030786c4e8 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sat, 19 Aug 2023 01:40:14 -0400 Subject: [PATCH 4/4] Slightly better error reporting for failed xapk install --- lib/providers/apps_provider.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 73cd19e..4f5b475 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -391,7 +391,7 @@ class AppsProvider with ChangeNotifier { // If 0 APKs installed, throw the first install error encountered try { var somethingInstalled = false; - Object? firstError; + MultiAppMultiError errors = MultiAppMultiError(); for (var file in dir.extracted .listSync(recursive: true, followLinks: false) .whereType()) { @@ -402,7 +402,7 @@ class AppsProvider with ChangeNotifier { } catch (e) { logs.add( 'Could not install APK from XAPK \'${file.path}\': ${e.toString()}'); - firstError ??= e; + errors.add(dir.appId, e.toString()); } } else if (file.path.toLowerCase().endsWith('.obb')) { await moveObbFile(file, dir.appId); @@ -410,8 +410,8 @@ class AppsProvider with ChangeNotifier { } if (somethingInstalled) { dir.file.delete(recursive: true); - } else if (firstError != null) { - throw firstError; + } else if (errors.content.isNotEmpty) { + throw errors; } } finally { dir.extracted.delete(recursive: true);