From f99cdfe9266b57a185a1341da70e4c3057254151 Mon Sep 17 00:00:00 2001 From: Ax333l Date: Thu, 4 Jul 2024 19:36:25 +0200 Subject: [PATCH] feat: improve UX for failed or missing bundles --- .../bundle/BundleInformationDialog.kt | 112 ++++++++++++++++-- .../manager/ui/component/bundle/BundleItem.kt | 2 +- .../component/bundle/BundlePatchesDialog.kt | 2 +- .../ui/component/bundle/BundleTopBar.kt | 4 +- .../ui/component/bundle/ImportBundleDialog.kt | 2 +- .../manager/ui/screen/DashboardScreen.kt | 2 +- app/src/main/res/values/strings.xml | 3 + 7 files changed, 114 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleInformationDialog.kt b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleInformationDialog.kt index 1c67f360..9a9573a5 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleInformationDialog.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleInformationDialog.kt @@ -1,21 +1,30 @@ package app.revanced.manager.ui.component.bundle +import android.content.Intent +import androidx.compose.foundation.clickable +import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.automirrored.outlined.ArrowRight import androidx.compose.material.icons.outlined.DeleteOutline +import androidx.compose.material.icons.outlined.Share import androidx.compose.material.icons.outlined.Update import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties @@ -26,7 +35,7 @@ import app.revanced.manager.domain.bundles.PatchBundleSource import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.asRemoteOrNull import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.isDefault import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.nameState -import kotlinx.coroutines.flow.map +import app.revanced.manager.ui.component.ColumnWithScrollbar import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @@ -35,17 +44,18 @@ fun BundleInformationDialog( onDismissRequest: () -> Unit, onDeleteRequest: () -> Unit, bundle: PatchBundleSource, - onRefreshButton: () -> Unit, + onUpdate: () -> Unit, ) { val composableScope = rememberCoroutineScope() var viewCurrentBundlePatches by remember { mutableStateOf(false) } val isLocal = bundle is LocalPatchBundle - val patchCount by remember(bundle) { - bundle.state.map { it.patchBundleOrNull()?.patches?.size ?: 0 } - }.collectAsStateWithLifecycle(0) + val state by bundle.state.collectAsStateWithLifecycle() val props by remember(bundle) { bundle.propsFlow() }.collectAsStateWithLifecycle(null) + val patchCount = remember(state) { + state.patchBundleOrNull()?.patches?.size ?: 0 + } if (viewCurrentBundlePatches) { BundlePatchesDialog( @@ -70,7 +80,7 @@ fun BundleInformationDialog( BundleTopBar( title = bundleName, onBackClick = onDismissRequest, - onBackIcon = { + backIcon = { Icon( imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back) @@ -86,7 +96,7 @@ fun BundleInformationDialog( } } if (!isLocal) { - IconButton(onClick = onRefreshButton) { + IconButton(onClick = onUpdate) { Icon( Icons.Outlined.Update, stringResource(R.string.refresh) @@ -114,7 +124,95 @@ fun BundleInformationDialog( onPatchesClick = { viewCurrentBundlePatches = true }, + extraFields = { + (state as? PatchBundleSource.State.Failed)?.throwable?.let { + var showDialog by rememberSaveable { + mutableStateOf(false) + } + if (showDialog) BundleErrorViewerDialog( + onDismiss = { showDialog = false }, + text = remember(it) { it.stackTraceToString() } + ) + + BundleListItem( + headlineText = stringResource(R.string.bundle_error), + supportingText = stringResource(R.string.bundle_error_description), + trailingContent = { + Icon( + Icons.AutoMirrored.Outlined.ArrowRight, + null + ) + }, + modifier = Modifier.clickable { showDialog = true } + ) + } + + if (state is PatchBundleSource.State.Missing && !isLocal) { + BundleListItem( + headlineText = stringResource(R.string.bundle_error), + supportingText = stringResource(R.string.bundle_not_downloaded), + modifier = Modifier.clickable(onClick = onUpdate) + ) + } + } ) } } } + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun BundleErrorViewerDialog(onDismiss: () -> Unit, text: String) { + val context = LocalContext.current + + Dialog( + onDismissRequest = onDismiss, + properties = DialogProperties( + usePlatformDefaultWidth = false, + dismissOnBackPress = true + ) + ) { + Scaffold( + topBar = { + BundleTopBar( + title = stringResource(R.string.bundle_error), + onBackClick = onDismiss, + backIcon = { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource(R.string.back) + ) + }, + actions = { + IconButton( + onClick = { + val sendIntent: Intent = Intent().apply { + action = Intent.ACTION_SEND + putExtra( + Intent.EXTRA_TEXT, + text + ) + type = "text/plain" + } + + val shareIntent = Intent.createChooser(sendIntent, null) + context.startActivity(shareIntent) + } + ) { + Icon( + Icons.Outlined.Share, + contentDescription = stringResource(R.string.share) + ) + } + } + ) + } + ) { paddingValues -> + ColumnWithScrollbar( + modifier = Modifier.padding(paddingValues) + ) { + Text(text, modifier = Modifier.horizontalScroll(rememberScrollState())) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleItem.kt b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleItem.kt index 31f4fef6..617c384f 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleItem.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleItem.kt @@ -57,7 +57,7 @@ fun BundleItem( onDelete() }, bundle = bundle, - onRefreshButton = onUpdate, + onUpdate = onUpdate, ) } diff --git a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundlePatchesDialog.kt b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundlePatchesDialog.kt index df2e8e67..a4fbce81 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundlePatchesDialog.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundlePatchesDialog.kt @@ -50,7 +50,7 @@ fun BundlePatchesDialog( BundleTopBar( title = stringResource(R.string.bundle_patches), onBackClick = onDismissRequest, - onBackIcon = { + backIcon = { Icon( imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back) diff --git a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleTopBar.kt b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleTopBar.kt index 7f98f014..543d2d34 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleTopBar.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleTopBar.kt @@ -19,7 +19,7 @@ fun BundleTopBar( onBackClick: (() -> Unit)? = null, actions: @Composable (RowScope.() -> Unit) = {}, scrollBehavior: TopAppBarScrollBehavior? = null, - onBackIcon: @Composable () -> Unit, + backIcon: @Composable () -> Unit, ) { val containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.0.dp) @@ -34,7 +34,7 @@ fun BundleTopBar( navigationIcon = { if (onBackClick != null) { IconButton(onClick = onBackClick) { - onBackIcon() + backIcon() } } }, diff --git a/app/src/main/java/app/revanced/manager/ui/component/bundle/ImportBundleDialog.kt b/app/src/main/java/app/revanced/manager/ui/component/bundle/ImportBundleDialog.kt index 481056df..cb23a36a 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/bundle/ImportBundleDialog.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/bundle/ImportBundleDialog.kt @@ -81,7 +81,7 @@ fun ImportBundleDialog( BundleTopBar( title = stringResource(R.string.import_bundle), onBackClick = onDismissRequest, - onBackIcon = { + backIcon = { Icon( imageVector = Icons.Default.Close, contentDescription = stringResource(R.string.close) diff --git a/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt index 59ed6f4e..5f5ff1fa 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt @@ -131,7 +131,7 @@ fun DashboardScreen( BundleTopBar( title = stringResource(R.string.bundles_selected, vm.selectedSources.size), onBackClick = vm::cancelSourceSelection, - onBackIcon = { + backIcon = { Icon( imageVector = Icons.Default.Close, contentDescription = stringResource(R.string.back) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 41bdfa16..95ca818b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -25,6 +25,8 @@ Missing Error + Bundle could not be loaded. Click to view the error + Bundle has not been downloaded. Click here to download it Default Unnamed @@ -121,6 +123,7 @@ Edit Value Reset + Share Patch Select from storage Select an APK file from storage using file picker