mirror of
https://github.com/revanced/revanced-manager.git
synced 2025-05-01 22:34:25 +02:00
feat: improve UX for failed or missing bundles
This commit is contained in:
parent
ec0a077539
commit
f99cdfe926
@ -1,21 +1,30 @@
|
|||||||
package app.revanced.manager.ui.component.bundle
|
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.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
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.DeleteOutline
|
||||||
|
import androidx.compose.material.icons.outlined.Share
|
||||||
import androidx.compose.material.icons.outlined.Update
|
import androidx.compose.material.icons.outlined.Update
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
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.asRemoteOrNull
|
||||||
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.isDefault
|
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.isDefault
|
||||||
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.nameState
|
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
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@ -35,17 +44,18 @@ fun BundleInformationDialog(
|
|||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
onDeleteRequest: () -> Unit,
|
onDeleteRequest: () -> Unit,
|
||||||
bundle: PatchBundleSource,
|
bundle: PatchBundleSource,
|
||||||
onRefreshButton: () -> Unit,
|
onUpdate: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val composableScope = rememberCoroutineScope()
|
val composableScope = rememberCoroutineScope()
|
||||||
var viewCurrentBundlePatches by remember { mutableStateOf(false) }
|
var viewCurrentBundlePatches by remember { mutableStateOf(false) }
|
||||||
val isLocal = bundle is LocalPatchBundle
|
val isLocal = bundle is LocalPatchBundle
|
||||||
val patchCount by remember(bundle) {
|
val state by bundle.state.collectAsStateWithLifecycle()
|
||||||
bundle.state.map { it.patchBundleOrNull()?.patches?.size ?: 0 }
|
|
||||||
}.collectAsStateWithLifecycle(0)
|
|
||||||
val props by remember(bundle) {
|
val props by remember(bundle) {
|
||||||
bundle.propsFlow()
|
bundle.propsFlow()
|
||||||
}.collectAsStateWithLifecycle(null)
|
}.collectAsStateWithLifecycle(null)
|
||||||
|
val patchCount = remember(state) {
|
||||||
|
state.patchBundleOrNull()?.patches?.size ?: 0
|
||||||
|
}
|
||||||
|
|
||||||
if (viewCurrentBundlePatches) {
|
if (viewCurrentBundlePatches) {
|
||||||
BundlePatchesDialog(
|
BundlePatchesDialog(
|
||||||
@ -70,7 +80,7 @@ fun BundleInformationDialog(
|
|||||||
BundleTopBar(
|
BundleTopBar(
|
||||||
title = bundleName,
|
title = bundleName,
|
||||||
onBackClick = onDismissRequest,
|
onBackClick = onDismissRequest,
|
||||||
onBackIcon = {
|
backIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
contentDescription = stringResource(R.string.back)
|
contentDescription = stringResource(R.string.back)
|
||||||
@ -86,7 +96,7 @@ fun BundleInformationDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isLocal) {
|
if (!isLocal) {
|
||||||
IconButton(onClick = onRefreshButton) {
|
IconButton(onClick = onUpdate) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Outlined.Update,
|
Icons.Outlined.Update,
|
||||||
stringResource(R.string.refresh)
|
stringResource(R.string.refresh)
|
||||||
@ -114,7 +124,95 @@ fun BundleInformationDialog(
|
|||||||
onPatchesClick = {
|
onPatchesClick = {
|
||||||
viewCurrentBundlePatches = true
|
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()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -57,7 +57,7 @@ fun BundleItem(
|
|||||||
onDelete()
|
onDelete()
|
||||||
},
|
},
|
||||||
bundle = bundle,
|
bundle = bundle,
|
||||||
onRefreshButton = onUpdate,
|
onUpdate = onUpdate,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ fun BundlePatchesDialog(
|
|||||||
BundleTopBar(
|
BundleTopBar(
|
||||||
title = stringResource(R.string.bundle_patches),
|
title = stringResource(R.string.bundle_patches),
|
||||||
onBackClick = onDismissRequest,
|
onBackClick = onDismissRequest,
|
||||||
onBackIcon = {
|
backIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
contentDescription = stringResource(R.string.back)
|
contentDescription = stringResource(R.string.back)
|
||||||
|
@ -19,7 +19,7 @@ fun BundleTopBar(
|
|||||||
onBackClick: (() -> Unit)? = null,
|
onBackClick: (() -> Unit)? = null,
|
||||||
actions: @Composable (RowScope.() -> Unit) = {},
|
actions: @Composable (RowScope.() -> Unit) = {},
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null,
|
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||||
onBackIcon: @Composable () -> Unit,
|
backIcon: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
val containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.0.dp)
|
val containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.0.dp)
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ fun BundleTopBar(
|
|||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
if (onBackClick != null) {
|
if (onBackClick != null) {
|
||||||
IconButton(onClick = onBackClick) {
|
IconButton(onClick = onBackClick) {
|
||||||
onBackIcon()
|
backIcon()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -81,7 +81,7 @@ fun ImportBundleDialog(
|
|||||||
BundleTopBar(
|
BundleTopBar(
|
||||||
title = stringResource(R.string.import_bundle),
|
title = stringResource(R.string.import_bundle),
|
||||||
onBackClick = onDismissRequest,
|
onBackClick = onDismissRequest,
|
||||||
onBackIcon = {
|
backIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Close,
|
imageVector = Icons.Default.Close,
|
||||||
contentDescription = stringResource(R.string.close)
|
contentDescription = stringResource(R.string.close)
|
||||||
|
@ -131,7 +131,7 @@ fun DashboardScreen(
|
|||||||
BundleTopBar(
|
BundleTopBar(
|
||||||
title = stringResource(R.string.bundles_selected, vm.selectedSources.size),
|
title = stringResource(R.string.bundles_selected, vm.selectedSources.size),
|
||||||
onBackClick = vm::cancelSourceSelection,
|
onBackClick = vm::cancelSourceSelection,
|
||||||
onBackIcon = {
|
backIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Close,
|
imageVector = Icons.Default.Close,
|
||||||
contentDescription = stringResource(R.string.back)
|
contentDescription = stringResource(R.string.back)
|
||||||
|
@ -25,6 +25,8 @@
|
|||||||
|
|
||||||
<string name="bundle_missing">Missing</string>
|
<string name="bundle_missing">Missing</string>
|
||||||
<string name="bundle_error">Error</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_default">Default</string>
|
||||||
<string name="bundle_name_fallback">Unnamed</string>
|
<string name="bundle_name_fallback">Unnamed</string>
|
||||||
|
|
||||||
@ -121,6 +123,7 @@
|
|||||||
<string name="edit">Edit</string>
|
<string name="edit">Edit</string>
|
||||||
<string name="dialog_input_placeholder">Value</string>
|
<string name="dialog_input_placeholder">Value</string>
|
||||||
<string name="reset">Reset</string>
|
<string name="reset">Reset</string>
|
||||||
|
<string name="share">Share</string>
|
||||||
<string name="patch">Patch</string>
|
<string name="patch">Patch</string>
|
||||||
<string name="select_from_storage">Select from storage</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>
|
<string name="select_from_storage_description">Select an APK file from storage using file picker</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user