Improve release asset download UI

This commit is contained in:
Imran Remtulla
2024-04-07 01:41:35 -04:00
parent 3d1113c057
commit 00988ed04d
3 changed files with 55 additions and 67 deletions

View File

@@ -155,7 +155,8 @@ class AddAppPageState extends State<AddAppPage> {
// Only download the APK here if you need to for the package ID // Only download the APK here if you need to for the package ID
if (isTempId(app) && app.additionalSettings['trackOnly'] != true) { if (isTempId(app) && app.additionalSettings['trackOnly'] != true) {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
var apkUrl = await appsProvider.confirmApkUrl(app, context); var apkUrl =
await appsProvider.confirmAppFileUrl(app, context, false);
if (apkUrl == null) { if (apkUrl == null) {
throw ObtainiumError(tr('cancelled')); throw ObtainiumError(tr('cancelled'));
} }

View File

@@ -1,8 +1,6 @@
import 'package:animations/animations.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/components/generated_form_modal.dart'; import 'package:obtainium/components/generated_form_modal.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/main.dart'; import 'package:obtainium/main.dart';
@@ -167,46 +165,19 @@ class _AppPageState extends State<AppPage> {
onTap: app?.app == null || updating onTap: app?.app == null || updating
? null ? null
: () async { : () async {
var allAssetUrls = [ var fileUrl = await appsProvider.confirmAppFileUrl(
...app!.app.apkUrls, app!.app, context, true);
...app.app.otherAssetUrls if (fileUrl != null) {
].map((e) => MapEntry(e.value, e.key)).toList();
var values = await showModal(
context: globalNavigatorKey.currentContext ?? context,
builder: (BuildContext ctx) {
return GeneratedFormModal(
title:
tr('downloadX', args: [tr('releaseAsset')]),
initValid: true,
items: [
[
GeneratedFormDropdown(
'assetToDownload', allAssetUrls,
defaultValue: allAssetUrls[0].key,
label: tr('selectX', args: [
tr('releaseAsset').toLowerCase()
]))
]
]);
},
);
if (values != null) {
var downloadUrl = values['assetToDownload'] as String;
var fileName = allAssetUrls
.where((e) => e.key == downloadUrl)
.first
.value;
NotificationsProvider notificationsProvider = NotificationsProvider notificationsProvider =
(globalNavigatorKey.currentContext ?? context) (globalNavigatorKey.currentContext ?? context)
.read<NotificationsProvider>(); .read<NotificationsProvider>();
try { try {
showMessage( showMessage(
'${tr('downloadingX', args: [fileName])}...', '${tr('downloadingX', args: [fileUrl.key])}...',
globalNavigatorKey.currentContext ?? context); globalNavigatorKey.currentContext ?? context);
await downloadFile( await downloadFile(
downloadUrl, fileUrl.value,
fileName fileUrl.key
.split('.') .split('.')
.reversed .reversed
.toList() .toList()
@@ -214,21 +185,21 @@ class _AppPageState extends State<AppPage> {
.reversed .reversed
.join('.'), (double? progress) { .join('.'), (double? progress) {
notificationsProvider.notify(DownloadNotification( notificationsProvider.notify(DownloadNotification(
fileName, progress?.ceil() ?? 0)); fileUrl.key, progress?.ceil() ?? 0));
}, '/storage/emulated/0/Download', }, '/storage/emulated/0/Download',
headers: await source?.getRequestHeaders( headers: await source?.getRequestHeaders(
app.app.additionalSettings, app.app.additionalSettings,
forAPKDownload: fileName.endsWith('.apk') forAPKDownload: fileUrl.key.endsWith('.apk')
? true ? true
: false)); : false));
notificationsProvider.notify( notificationsProvider.notify(DownloadedNotification(
DownloadedNotification(fileName, downloadUrl)); fileUrl.key, fileUrl.value));
} catch (e) { } catch (e) {
showError( showError(
e, globalNavigatorKey.currentContext ?? context); e, globalNavigatorKey.currentContext ?? context);
} finally { } finally {
notificationsProvider notificationsProvider
.cancel(DownloadNotification(fileName, 0).id); .cancel(DownloadNotification(fileUrl.key, 0).id);
} }
} }
}, },

View File

@@ -705,23 +705,28 @@ class AppsProvider with ChangeNotifier {
await intent.launch(); await intent.launch();
} }
Future<MapEntry<String, String>?> confirmApkUrl( Future<MapEntry<String, String>?> confirmAppFileUrl(
App app, BuildContext? context) async { App app, BuildContext? context, bool pickAnyAsset) async {
var urlsToSelectFrom = app.apkUrls;
if (pickAnyAsset) {
urlsToSelectFrom = [...urlsToSelectFrom, ...app.otherAssetUrls];
}
// 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)
MapEntry<String, String>? apkUrl = MapEntry<String, String>? appFileUrl = urlsToSelectFrom[
app.apkUrls[app.preferredApkIndex >= 0 ? app.preferredApkIndex : 0]; app.preferredApkIndex >= 0 ? app.preferredApkIndex : 0];
// get device supported architecture // get device supported architecture
List<String> archs = (await DeviceInfoPlugin().androidInfo).supportedAbis; List<String> archs = (await DeviceInfoPlugin().androidInfo).supportedAbis;
if (app.apkUrls.length > 1 && context != null) { if (urlsToSelectFrom.length > 1 && context != null) {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
apkUrl = await showDialog( appFileUrl = await showDialog(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
return APKPicker( return AppFilePicker(
app: app, app: app,
initVal: apkUrl, initVal: appFileUrl,
archs: archs, archs: archs,
pickAnyAsset: pickAnyAsset,
); );
}); });
} }
@@ -731,8 +736,8 @@ 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 (appFileUrl != null &&
getHost(apkUrl.value) != getHost(app.url) && getHost(appFileUrl.value) != getHost(app.url) &&
context != null) { context != null) {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
if (!(settingsProvider.hideAPKOriginWarning) && if (!(settingsProvider.hideAPKOriginWarning) &&
@@ -741,13 +746,13 @@ class AppsProvider with ChangeNotifier {
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
return APKOriginWarningDialog( return APKOriginWarningDialog(
sourceUrl: app.url, apkUrl: apkUrl!.value); sourceUrl: app.url, apkUrl: appFileUrl!.value);
}) != }) !=
true) { true) {
apkUrl = null; appFileUrl = null;
} }
} }
return apkUrl; return appFileUrl;
} }
// Given a list of AppIds, uses stored info about the apps to download APKs and install them // Given a list of AppIds, uses stored info about the apps to download APKs and install them
@@ -774,7 +779,7 @@ class AppsProvider with ChangeNotifier {
var trackOnly = apps[id]!.app.additionalSettings['trackOnly'] == true; var trackOnly = apps[id]!.app.additionalSettings['trackOnly'] == true;
if (!trackOnly) { if (!trackOnly) {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
apkUrl = await confirmApkUrl(apps[id]!.app, context); apkUrl = await confirmAppFileUrl(apps[id]!.app, context, false);
} }
if (apkUrl != null) { if (apkUrl != null) {
int urlInd = apps[id]! int urlInd = apps[id]!
@@ -1482,38 +1487,49 @@ class AppsProvider with ChangeNotifier {
} }
} }
class APKPicker extends StatefulWidget { class AppFilePicker extends StatefulWidget {
const APKPicker({super.key, required this.app, this.initVal, this.archs}); const AppFilePicker(
{super.key,
required this.app,
this.initVal,
this.archs,
this.pickAnyAsset = false});
final App app; final App app;
final MapEntry<String, String>? initVal; final MapEntry<String, String>? initVal;
final List<String>? archs; final List<String>? archs;
final bool pickAnyAsset;
@override @override
State<APKPicker> createState() => _APKPickerState(); State<AppFilePicker> createState() => _AppFilePickerState();
} }
class _APKPickerState extends State<APKPicker> { class _AppFilePickerState extends State<AppFilePicker> {
MapEntry<String, String>? apkUrl; MapEntry<String, String>? fileUrl;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
apkUrl ??= widget.initVal; fileUrl ??= widget.initVal;
var urlsToSelectFrom = widget.app.apkUrls;
if (widget.pickAnyAsset) {
urlsToSelectFrom = [...urlsToSelectFrom, ...widget.app.otherAssetUrls];
}
return AlertDialog( return AlertDialog(
scrollable: true, scrollable: true,
title: Text(tr('pickAnAPK')), title: Text(widget.pickAnyAsset
? tr('selectX', args: [tr('releaseAsset').toLowerCase()])
: tr('pickAnAPK')),
content: Column(children: [ content: Column(children: [
Text(tr('appHasMoreThanOnePackage', args: [widget.app.finalName])), Text(tr('appHasMoreThanOnePackage', args: [widget.app.finalName])),
const SizedBox(height: 16), const SizedBox(height: 16),
...widget.app.apkUrls.map( ...urlsToSelectFrom.map(
(u) => RadioListTile<String>( (u) => RadioListTile<String>(
title: Text(u.key), title: Text(u.key),
value: u.value, value: u.value,
groupValue: apkUrl!.value, groupValue: fileUrl!.value,
onChanged: (String? val) { onChanged: (String? val) {
setState(() { setState(() {
apkUrl = fileUrl = urlsToSelectFrom.where((e) => e.value == val).first;
widget.app.apkUrls.where((e) => e.value == val).first;
}); });
}), }),
), ),
@@ -1540,7 +1556,7 @@ class _APKPickerState extends State<APKPicker> {
TextButton( TextButton(
onPressed: () { onPressed: () {
HapticFeedback.selectionClick(); HapticFeedback.selectionClick();
Navigator.of(context).pop(apkUrl); Navigator.of(context).pop(fileUrl);
}, },
child: Text(tr('continue'))) child: Text(tr('continue')))
], ],