mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2024-11-10 01:01:56 +01:00
feat: improve bundle dialog UI
This commit is contained in:
parent
379ce917a9
commit
1707a9690a
6 changed files with 209 additions and 126 deletions
|
@ -0,0 +1,51 @@
|
|||
package app.revanced.manager.ui.component
|
||||
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.revanced.manager.R
|
||||
|
||||
@Composable
|
||||
fun TextInputDialog(
|
||||
initial: String,
|
||||
title: String,
|
||||
onDismissRequest: () -> Unit,
|
||||
onConfirm: (String) -> Unit,
|
||||
validator: (String) -> Boolean = String::isNotEmpty,
|
||||
) {
|
||||
val (value, setValue) = rememberSaveable(initial) {
|
||||
mutableStateOf(initial)
|
||||
}
|
||||
val valid = remember(value, validator) {
|
||||
validator(value)
|
||||
}
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = { onConfirm(value) },
|
||||
enabled = valid
|
||||
) {
|
||||
Text(stringResource(R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(title)
|
||||
},
|
||||
text = {
|
||||
TextField(value = value, onValueChange = setValue)
|
||||
}
|
||||
)
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package app.revanced.manager.ui.component.bundle
|
||||
|
||||
import android.webkit.URLUtil
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
@ -12,23 +14,27 @@ import androidx.compose.material3.FilledTonalButton
|
|||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.ui.component.TextInputDialog
|
||||
|
||||
@Composable
|
||||
fun BaseBundleDialog(
|
||||
modifier: Modifier = Modifier,
|
||||
isDefault: Boolean,
|
||||
name: String,
|
||||
onNameChange: (String) -> Unit = {},
|
||||
onNameChange: ((String) -> Unit)? = null,
|
||||
remoteUrl: String?,
|
||||
onRemoteUrlChange: (String) -> Unit = {},
|
||||
onRemoteUrlChange: ((String) -> Unit)? = null,
|
||||
patchCount: Int,
|
||||
version: String?,
|
||||
autoUpdate: Boolean,
|
||||
|
@ -40,48 +46,72 @@ fun BaseBundleDialog(
|
|||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.then(modifier)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(
|
||||
start = 24.dp,
|
||||
top = 16.dp,
|
||||
end = 24.dp,
|
||||
)
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp),
|
||||
value = name,
|
||||
onValueChange = onNameChange,
|
||||
label = {
|
||||
Text(stringResource(R.string.bundle_input_name))
|
||||
}
|
||||
)
|
||||
remoteUrl?.takeUnless { isDefault }?.let {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp),
|
||||
value = it,
|
||||
onValueChange = onRemoteUrlChange,
|
||||
label = {
|
||||
Text(stringResource(R.string.bundle_input_source_url))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
extraFields()
|
||||
}
|
||||
|
||||
Column(
|
||||
Modifier.padding(
|
||||
.padding(
|
||||
start = 8.dp,
|
||||
top = 8.dp,
|
||||
end = 4.dp,
|
||||
)
|
||||
) Info@{
|
||||
.then(modifier)
|
||||
) {
|
||||
var showNameInputDialog by rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
if (showNameInputDialog) {
|
||||
TextInputDialog(
|
||||
initial = name,
|
||||
title = stringResource(R.string.bundle_input_name),
|
||||
onDismissRequest = {
|
||||
showNameInputDialog = false
|
||||
},
|
||||
onConfirm = {
|
||||
showNameInputDialog = false
|
||||
onNameChange?.invoke(it)
|
||||
},
|
||||
validator = {
|
||||
it.length in 1..19
|
||||
}
|
||||
)
|
||||
}
|
||||
BundleListItem(
|
||||
headlineText = stringResource(R.string.bundle_input_name),
|
||||
supportingText = name.ifEmpty { stringResource(R.string.field_not_set) },
|
||||
modifier = Modifier.clickable(enabled = onNameChange != null) {
|
||||
showNameInputDialog = true
|
||||
}
|
||||
)
|
||||
|
||||
remoteUrl?.takeUnless { isDefault }?.let { url ->
|
||||
var showUrlInputDialog by rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
if (showUrlInputDialog) {
|
||||
TextInputDialog(
|
||||
initial = url,
|
||||
title = stringResource(R.string.bundle_input_source_url),
|
||||
onDismissRequest = { showUrlInputDialog = false },
|
||||
onConfirm = {
|
||||
showUrlInputDialog = false
|
||||
onRemoteUrlChange?.invoke(it)
|
||||
},
|
||||
validator = {
|
||||
if (it.isEmpty()) return@TextInputDialog false
|
||||
|
||||
URLUtil.isValidUrl(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
BundleListItem(
|
||||
modifier = Modifier.clickable(enabled = onRemoteUrlChange != null) {
|
||||
showUrlInputDialog = true
|
||||
},
|
||||
headlineText = stringResource(R.string.bundle_input_source_url),
|
||||
supportingText = url.ifEmpty { stringResource(R.string.field_not_set) }
|
||||
)
|
||||
}
|
||||
|
||||
extraFields()
|
||||
|
||||
if (remoteUrl != null) {
|
||||
BundleListItem(
|
||||
headlineText = stringResource(R.string.automatically_update),
|
||||
|
@ -91,13 +121,19 @@ fun BaseBundleDialog(
|
|||
checked = autoUpdate,
|
||||
onCheckedChange = onAutoUpdateChange
|
||||
)
|
||||
},
|
||||
modifier = Modifier.clickable {
|
||||
onAutoUpdateChange(!autoUpdate)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
BundleListItem(
|
||||
headlineText = stringResource(R.string.bundle_type),
|
||||
supportingText = stringResource(R.string.bundle_type_description)
|
||||
supportingText = stringResource(R.string.bundle_type_description),
|
||||
modifier = Modifier.clickable {
|
||||
onBundleTypeClick()
|
||||
}
|
||||
) {
|
||||
FilledTonalButton(
|
||||
onClick = onBundleTypeClick,
|
||||
|
@ -111,8 +147,7 @@ fun BaseBundleDialog(
|
|||
)
|
||||
}
|
||||
|
||||
if (version == null && patchCount < 1) return@Info
|
||||
|
||||
if (version != null || patchCount > 0) {
|
||||
Text(
|
||||
text = stringResource(R.string.information),
|
||||
modifier = Modifier.padding(
|
||||
|
@ -122,7 +157,9 @@ fun BaseBundleDialog(
|
|||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
|
||||
if (patchCount > 0) {
|
||||
BundleListItem(
|
||||
headlineText = stringResource(R.string.patches),
|
||||
supportingText = if (patchCount == 0) stringResource(R.string.no_patches)
|
||||
|
@ -138,12 +175,12 @@ fun BaseBundleDialog(
|
|||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (version == null) return@Info
|
||||
|
||||
version?.let {
|
||||
BundleListItem(
|
||||
headlineText = stringResource(R.string.version),
|
||||
supportingText = version,
|
||||
supportingText = it,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -67,7 +67,7 @@ fun BundleInformationDialog(
|
|||
Scaffold(
|
||||
topBar = {
|
||||
BundleTopBar(
|
||||
title = stringResource(R.string.bundle_information),
|
||||
title = bundle.name,
|
||||
onBackClick = onDismissRequest,
|
||||
onBackIcon = {
|
||||
Icon(
|
||||
|
|
|
@ -4,9 +4,11 @@ import androidx.compose.material3.ListItem
|
|||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
@Composable
|
||||
fun BundleListItem(
|
||||
modifier: Modifier = Modifier,
|
||||
headlineText: String,
|
||||
supportingText: String = "",
|
||||
trailingContent: @Composable (() -> Unit)? = null,
|
||||
|
@ -26,5 +28,6 @@ fun BundleListItem(
|
|||
)
|
||||
},
|
||||
trailingContent = trailingContent,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
|
@ -1,10 +1,9 @@
|
|||
package app.revanced.manager.ui.component.bundle
|
||||
|
||||
import android.net.Uri
|
||||
import android.webkit.URLUtil
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
|
@ -12,7 +11,6 @@ import androidx.compose.material.icons.filled.Topic
|
|||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
|
@ -46,17 +44,9 @@ fun ImportBundleDialog(
|
|||
var patchBundle by rememberSaveable { mutableStateOf<Uri?>(null) }
|
||||
var integrations by rememberSaveable { mutableStateOf<Uri?>(null) }
|
||||
|
||||
val patchBundleText = patchBundle?.toString().orEmpty()
|
||||
val integrationText = integrations?.toString().orEmpty()
|
||||
|
||||
val inputsAreValid by remember {
|
||||
derivedStateOf {
|
||||
val nameSize = name.length
|
||||
when {
|
||||
nameSize !in 1..19 -> false
|
||||
isLocal -> patchBundle != null
|
||||
else -> remoteUrl.isNotEmpty() && URLUtil.isValidUrl(remoteUrl)
|
||||
}
|
||||
name.isNotEmpty() && if (isLocal) patchBundle != null else remoteUrl.isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,10 +54,17 @@ fun ImportBundleDialog(
|
|||
rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
||||
uri?.let { patchBundle = it }
|
||||
}
|
||||
fun launchPatchActivity() {
|
||||
patchActivityLauncher.launch(JAR_MIMETYPE)
|
||||
}
|
||||
|
||||
val integrationsActivityLauncher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
||||
uri?.let { integrations = it }
|
||||
}
|
||||
fun launchIntegrationsActivity() {
|
||||
integrationsActivityLauncher.launch(APK_MIMETYPE)
|
||||
}
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
|
@ -123,54 +120,44 @@ fun ImportBundleDialog(
|
|||
onPatchesClick = {},
|
||||
onBundleTypeClick = { isLocal = !isLocal },
|
||||
) {
|
||||
if (isLocal) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp),
|
||||
value = patchBundleText,
|
||||
onValueChange = {},
|
||||
label = {
|
||||
Text("Patches Source File")
|
||||
},
|
||||
trailingIcon = {
|
||||
if (!isLocal) return@BaseBundleDialog
|
||||
|
||||
BundleListItem(
|
||||
headlineText = stringResource(R.string.patch_bundle_field),
|
||||
supportingText = stringResource(if (patchBundle != null) R.string.file_field_set else R.string.file_field_not_set),
|
||||
trailingContent = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
patchActivityLauncher.launch(JAR_MIMETYPE)
|
||||
}
|
||||
onClick = ::launchPatchActivity
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Topic,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.clickable {
|
||||
launchPatchActivity()
|
||||
}
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp),
|
||||
value = integrationText,
|
||||
onValueChange = {},
|
||||
label = {
|
||||
Text("Integrations Source File")
|
||||
},
|
||||
trailingIcon = {
|
||||
BundleListItem(
|
||||
headlineText = stringResource(R.string.integrations_field),
|
||||
supportingText = stringResource(if (integrations != null) R.string.file_field_set else R.string.file_field_not_set),
|
||||
trailingContent = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
integrationsActivityLauncher.launch(APK_MIMETYPE)
|
||||
}
|
||||
onClick = ::launchIntegrationsActivity
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Topic,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.clickable {
|
||||
launchIntegrationsActivity()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,8 +13,13 @@
|
|||
|
||||
<string name="import_">Import</string>
|
||||
<string name="import_bundle">Import patch bundle</string>
|
||||
<string name="bundle_information">Bundle information</string>
|
||||
<string name="bundle_patches">Bundle patches</string>
|
||||
<string name="patch_bundle_field">Patch bundle</string>
|
||||
<string name="integrations_field">Integrations</string>
|
||||
<string name="file_field_set">Provided</string>
|
||||
<string name="file_field_not_set">Not provided</string>
|
||||
|
||||
<string name="field_not_set">Not set</string>
|
||||
|
||||
<string name="bundle_missing">Missing</string>
|
||||
<string name="bundle_error">Error</string>
|
||||
|
|
Loading…
Reference in a new issue