From c30c692d870e7b1f3509ad8a7e3bf5f8700027dc Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sat, 3 Sep 2022 16:12:25 -0400 Subject: [PATCH] Added external APK support (GitLab only for now) --- lib/providers/apps_provider.dart | 53 +++++++++++++++++++++++++++++- lib/providers/source_provider.dart | 19 +++++++---- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 6a0efc9..a3ac000 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -108,6 +108,7 @@ class AppsProvider with ChangeNotifier { if (apps[id] == null) { throw 'App not found'; } + // If the App has more than one APK, the user should pick one String? apkUrl = apps[id]!.app.apkUrls[apps[id]!.app.preferredApkIndex]; if (apps[id]!.app.apkUrls.length > 1) { apkUrl = await showDialog( @@ -116,6 +117,19 @@ class AppsProvider with ChangeNotifier { return APKPicker(app: apps[id]!.app, initVal: apkUrl); }); } + // If the picked APK comes from an origin different from the source, get user confirmation + if (apkUrl != null && + !apkUrl.toLowerCase().startsWith(apps[id]!.app.url.toLowerCase())) { + if (await showDialog( + context: context, + builder: (BuildContext ctx) { + return APKOriginWarningDialog( + sourceUrl: apps[id]!.app.url, apkUrl: apkUrl!); + }) != + true) { + apkUrl = null; + } + } if (apkUrl != null) { int urlInd = apps[id]!.app.apkUrls.indexOf(apkUrl); if (urlInd != apps[id]!.app.preferredApkIndex) { @@ -331,7 +345,7 @@ class _APKPickerState extends State { child: const Text('Cancel')), TextButton( onPressed: () { - HapticFeedback.mediumImpact(); + HapticFeedback.heavyImpact(); Navigator.of(context).pop(apkUrl); }, child: const Text('Continue')) @@ -339,3 +353,40 @@ class _APKPickerState extends State { ); } } + +class APKOriginWarningDialog extends StatefulWidget { + const APKOriginWarningDialog( + {super.key, required this.sourceUrl, required this.apkUrl}); + + final String sourceUrl; + final String apkUrl; + + @override + State createState() => _APKOriginWarningDialogState(); +} + +class _APKOriginWarningDialogState extends State { + @override + Widget build(BuildContext context) { + return AlertDialog( + scrollable: true, + title: const Text('Warning'), + content: Text( + 'The App source is \'${Uri.parse(widget.sourceUrl).host}\' but the release package comes from \'${Uri.parse(widget.apkUrl).host}\'. Continue?'), + actions: [ + TextButton( + onPressed: () { + HapticFeedback.lightImpact(); + Navigator.of(context).pop(null); + }, + child: const Text('Cancel')), + TextButton( + onPressed: () { + HapticFeedback.heavyImpact(); + Navigator.of(context).pop(true); + }, + child: const Text('Continue')) + ], + ); + } +} diff --git a/lib/providers/source_provider.dart b/lib/providers/source_provider.dart index 8887d1c..8e6fab2 100644 --- a/lib/providers/source_provider.dart +++ b/lib/providers/source_provider.dart @@ -170,12 +170,19 @@ class GitLab implements AppSource { var entry = parsedHtml.querySelector('entry'); var entryContent = parse(parseFragment(entry?.querySelector('content')!.innerHtml).text); - var apkUrlList = getLinksFromParsedHTML( - entryContent, - RegExp( - '^${escapeRegEx(standardUri.path)}/uploads/[^/]+/[^/]+\\.apk\$', - caseSensitive: false), - standardUri.origin); + var apkUrlList = [ + ...getLinksFromParsedHTML( + entryContent, + RegExp( + '^${escapeRegEx(standardUri.path)}/uploads/[^/]+/[^/]+\\.apk\$', + caseSensitive: false), + standardUri.origin), + // GitLab releases may contain links to externally hosted APKs + ...getLinksFromParsedHTML(entryContent, + RegExp('/[^/]+\\.apk\$', caseSensitive: false), '') + .where((element) => Uri.parse(element).host != '') + .toList() + ]; if (apkUrlList.isEmpty) { throw 'No APK found'; }