mirror of
https://github.com/revanced/revanced-manager
synced 2024-05-14 13:56:57 +02:00
feat: improve bundle dialog UI
This commit is contained in:
parent
379ce917a9
commit
1707a9690a
@ -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
|
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.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@ -12,23 +14,27 @@ import androidx.compose.material3.FilledTonalButton
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
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.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
|
import app.revanced.manager.ui.component.TextInputDialog
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BaseBundleDialog(
|
fun BaseBundleDialog(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
isDefault: Boolean,
|
isDefault: Boolean,
|
||||||
name: String,
|
name: String,
|
||||||
onNameChange: (String) -> Unit = {},
|
onNameChange: ((String) -> Unit)? = null,
|
||||||
remoteUrl: String?,
|
remoteUrl: String?,
|
||||||
onRemoteUrlChange: (String) -> Unit = {},
|
onRemoteUrlChange: ((String) -> Unit)? = null,
|
||||||
patchCount: Int,
|
patchCount: Int,
|
||||||
version: String?,
|
version: String?,
|
||||||
autoUpdate: Boolean,
|
autoUpdate: Boolean,
|
||||||
@ -40,79 +46,108 @@ fun BaseBundleDialog(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
.then(modifier)
|
.padding(
|
||||||
) {
|
|
||||||
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(
|
|
||||||
start = 8.dp,
|
start = 8.dp,
|
||||||
top = 8.dp,
|
top = 8.dp,
|
||||||
end = 4.dp,
|
end = 4.dp,
|
||||||
)
|
)
|
||||||
) Info@{
|
.then(modifier)
|
||||||
if (remoteUrl != null) {
|
) {
|
||||||
BundleListItem(
|
var showNameInputDialog by rememberSaveable {
|
||||||
headlineText = stringResource(R.string.automatically_update),
|
mutableStateOf(false)
|
||||||
supportingText = stringResource(R.string.automatically_update_description),
|
}
|
||||||
trailingContent = {
|
if (showNameInputDialog) {
|
||||||
Switch(
|
TextInputDialog(
|
||||||
checked = autoUpdate,
|
initial = name,
|
||||||
onCheckedChange = onAutoUpdateChange
|
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(
|
BundleListItem(
|
||||||
headlineText = stringResource(R.string.bundle_type),
|
modifier = Modifier.clickable(enabled = onRemoteUrlChange != null) {
|
||||||
supportingText = stringResource(R.string.bundle_type_description)
|
showUrlInputDialog = true
|
||||||
) {
|
},
|
||||||
FilledTonalButton(
|
headlineText = stringResource(R.string.bundle_input_source_url),
|
||||||
onClick = onBundleTypeClick,
|
supportingText = url.ifEmpty { stringResource(R.string.field_not_set) }
|
||||||
content = {
|
)
|
||||||
if (remoteUrl == null) {
|
}
|
||||||
Text(stringResource(R.string.local))
|
|
||||||
} else {
|
extraFields()
|
||||||
Text(stringResource(R.string.remote))
|
|
||||||
}
|
if (remoteUrl != null) {
|
||||||
}
|
BundleListItem(
|
||||||
)
|
headlineText = stringResource(R.string.automatically_update),
|
||||||
|
supportingText = stringResource(R.string.automatically_update_description),
|
||||||
|
trailingContent = {
|
||||||
|
Switch(
|
||||||
|
checked = autoUpdate,
|
||||||
|
onCheckedChange = onAutoUpdateChange
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
onAutoUpdateChange(!autoUpdate)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
BundleListItem(
|
||||||
|
headlineText = stringResource(R.string.bundle_type),
|
||||||
|
supportingText = stringResource(R.string.bundle_type_description),
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
onBundleTypeClick()
|
||||||
}
|
}
|
||||||
|
) {
|
||||||
|
FilledTonalButton(
|
||||||
|
onClick = onBundleTypeClick,
|
||||||
|
content = {
|
||||||
|
if (remoteUrl == null) {
|
||||||
|
Text(stringResource(R.string.local))
|
||||||
|
} else {
|
||||||
|
Text(stringResource(R.string.remote))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (version == null && patchCount < 1) return@Info
|
if (version != null || patchCount > 0) {
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.information),
|
text = stringResource(R.string.information),
|
||||||
modifier = Modifier.padding(
|
modifier = Modifier.padding(
|
||||||
@ -122,7 +157,9 @@ fun BaseBundleDialog(
|
|||||||
style = MaterialTheme.typography.labelLarge,
|
style = MaterialTheme.typography.labelLarge,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patchCount > 0) {
|
||||||
BundleListItem(
|
BundleListItem(
|
||||||
headlineText = stringResource(R.string.patches),
|
headlineText = stringResource(R.string.patches),
|
||||||
supportingText = if (patchCount == 0) stringResource(R.string.no_patches)
|
supportingText = if (patchCount == 0) stringResource(R.string.no_patches)
|
||||||
@ -138,12 +175,12 @@ fun BaseBundleDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (version == null) return@Info
|
version?.let {
|
||||||
|
|
||||||
BundleListItem(
|
BundleListItem(
|
||||||
headlineText = stringResource(R.string.version),
|
headlineText = stringResource(R.string.version),
|
||||||
supportingText = version,
|
supportingText = it,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -67,7 +67,7 @@ fun BundleInformationDialog(
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
BundleTopBar(
|
BundleTopBar(
|
||||||
title = stringResource(R.string.bundle_information),
|
title = bundle.name,
|
||||||
onBackClick = onDismissRequest,
|
onBackClick = onDismissRequest,
|
||||||
onBackIcon = {
|
onBackIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
|
@ -4,9 +4,11 @@ import androidx.compose.material3.ListItem
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BundleListItem(
|
fun BundleListItem(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
headlineText: String,
|
headlineText: String,
|
||||||
supportingText: String = "",
|
supportingText: String = "",
|
||||||
trailingContent: @Composable (() -> Unit)? = null,
|
trailingContent: @Composable (() -> Unit)? = null,
|
||||||
@ -26,5 +28,6 @@ fun BundleListItem(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
trailingContent = trailingContent,
|
trailingContent = trailingContent,
|
||||||
|
modifier = modifier
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -1,10 +1,9 @@
|
|||||||
package app.revanced.manager.ui.component.bundle
|
package app.revanced.manager.ui.component.bundle
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.webkit.URLUtil
|
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
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.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Close
|
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.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.OutlinedTextField
|
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
@ -46,17 +44,9 @@ fun ImportBundleDialog(
|
|||||||
var patchBundle by rememberSaveable { mutableStateOf<Uri?>(null) }
|
var patchBundle by rememberSaveable { mutableStateOf<Uri?>(null) }
|
||||||
var integrations 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 {
|
val inputsAreValid by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
val nameSize = name.length
|
name.isNotEmpty() && if (isLocal) patchBundle != null else remoteUrl.isNotEmpty()
|
||||||
when {
|
|
||||||
nameSize !in 1..19 -> false
|
|
||||||
isLocal -> patchBundle != null
|
|
||||||
else -> remoteUrl.isNotEmpty() && URLUtil.isValidUrl(remoteUrl)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,10 +54,17 @@ fun ImportBundleDialog(
|
|||||||
rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
||||||
uri?.let { patchBundle = it }
|
uri?.let { patchBundle = it }
|
||||||
}
|
}
|
||||||
|
fun launchPatchActivity() {
|
||||||
|
patchActivityLauncher.launch(JAR_MIMETYPE)
|
||||||
|
}
|
||||||
|
|
||||||
val integrationsActivityLauncher =
|
val integrationsActivityLauncher =
|
||||||
rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
||||||
uri?.let { integrations = it }
|
uri?.let { integrations = it }
|
||||||
}
|
}
|
||||||
|
fun launchIntegrationsActivity() {
|
||||||
|
integrationsActivityLauncher.launch(APK_MIMETYPE)
|
||||||
|
}
|
||||||
|
|
||||||
Dialog(
|
Dialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
@ -123,53 +120,43 @@ fun ImportBundleDialog(
|
|||||||
onPatchesClick = {},
|
onPatchesClick = {},
|
||||||
onBundleTypeClick = { isLocal = !isLocal },
|
onBundleTypeClick = { isLocal = !isLocal },
|
||||||
) {
|
) {
|
||||||
if (isLocal) {
|
if (!isLocal) return@BaseBundleDialog
|
||||||
OutlinedTextField(
|
|
||||||
modifier = Modifier
|
BundleListItem(
|
||||||
.fillMaxWidth()
|
headlineText = stringResource(R.string.patch_bundle_field),
|
||||||
.padding(bottom = 16.dp),
|
supportingText = stringResource(if (patchBundle != null) R.string.file_field_set else R.string.file_field_not_set),
|
||||||
value = patchBundleText,
|
trailingContent = {
|
||||||
onValueChange = {},
|
IconButton(
|
||||||
label = {
|
onClick = ::launchPatchActivity
|
||||||
Text("Patches Source File")
|
) {
|
||||||
},
|
Icon(
|
||||||
trailingIcon = {
|
imageVector = Icons.Default.Topic,
|
||||||
IconButton(
|
contentDescription = null
|
||||||
onClick = {
|
)
|
||||||
patchActivityLauncher.launch(JAR_MIMETYPE)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Topic,
|
|
||||||
contentDescription = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
},
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
launchPatchActivity()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
OutlinedTextField(
|
BundleListItem(
|
||||||
modifier = Modifier
|
headlineText = stringResource(R.string.integrations_field),
|
||||||
.fillMaxWidth()
|
supportingText = stringResource(if (integrations != null) R.string.file_field_set else R.string.file_field_not_set),
|
||||||
.padding(bottom = 16.dp),
|
trailingContent = {
|
||||||
value = integrationText,
|
IconButton(
|
||||||
onValueChange = {},
|
onClick = ::launchIntegrationsActivity
|
||||||
label = {
|
) {
|
||||||
Text("Integrations Source File")
|
Icon(
|
||||||
},
|
imageVector = Icons.Default.Topic,
|
||||||
trailingIcon = {
|
contentDescription = null
|
||||||
IconButton(
|
)
|
||||||
onClick = {
|
|
||||||
integrationsActivityLauncher.launch(APK_MIMETYPE)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Topic,
|
|
||||||
contentDescription = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
},
|
||||||
}
|
modifier = Modifier.clickable {
|
||||||
|
launchIntegrationsActivity()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,13 @@
|
|||||||
|
|
||||||
<string name="import_">Import</string>
|
<string name="import_">Import</string>
|
||||||
<string name="import_bundle">Import patch bundle</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="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_missing">Missing</string>
|
||||||
<string name="bundle_error">Error</string>
|
<string name="bundle_error">Error</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user