feat: improve UX for failed or missing bundles

This commit is contained in:
Ax333l 2024-07-04 19:36:25 +02:00
parent ec0a077539
commit f99cdfe926
No known key found for this signature in database
GPG key ID: D2B4D85271127D23
7 changed files with 114 additions and 13 deletions

View file

@ -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()))
}
}
}
}

View file

@ -57,7 +57,7 @@ fun BundleItem(
onDelete()
},
bundle = bundle,
onRefreshButton = onUpdate,
onUpdate = onUpdate,
)
}

View file

@ -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)

View file

@ -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()
}
}
},

View file

@ -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)

View file

@ -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)

View file

@ -25,6 +25,8 @@
<string name="bundle_missing">Missing</string>
<string name="bundle_error">Error</string>
<string name="bundle_error_description">Bundle could not be loaded. Click to view the error</string>
<string name="bundle_not_downloaded">Bundle has not been downloaded. Click here to download it</string>
<string name="bundle_name_default">Default</string>
<string name="bundle_name_fallback">Unnamed</string>
@ -121,6 +123,7 @@
<string name="edit">Edit</string>
<string name="dialog_input_placeholder">Value</string>
<string name="reset">Reset</string>
<string name="share">Share</string>
<string name="patch">Patch</string>
<string name="select_from_storage">Select from storage</string>
<string name="select_from_storage_description">Select an APK file from storage using file picker</string>