feat: improve bundle dialog UI

This commit is contained in:
Ax333l 2023-08-04 12:46:07 +02:00
parent d08f6f9ed8
commit 4eca2a3b5d
No known key found for this signature in database
GPG Key ID: D2B4D85271127D23
6 changed files with 209 additions and 126 deletions

View File

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

View File

@ -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,79 +46,108 @@ 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@{
if (remoteUrl != null) {
BundleListItem(
headlineText = stringResource(R.string.automatically_update),
supportingText = stringResource(R.string.automatically_update_description),
trailingContent = {
Switch(
checked = autoUpdate,
onCheckedChange = onAutoUpdateChange
)
.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(
headlineText = stringResource(R.string.bundle_type),
supportingText = stringResource(R.string.bundle_type_description)
) {
FilledTonalButton(
onClick = onBundleTypeClick,
content = {
if (remoteUrl == null) {
Text(stringResource(R.string.local))
} else {
Text(stringResource(R.string.remote))
}
}
)
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),
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 = 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,
)
}
}

View File

@ -67,7 +67,7 @@ fun BundleInformationDialog(
Scaffold(
topBar = {
BundleTopBar(
title = stringResource(R.string.bundle_information),
title = bundle.name,
onBackClick = onDismissRequest,
onBackIcon = {
Icon(

View File

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

View File

@ -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,53 +120,43 @@ 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 = {
IconButton(
onClick = {
patchActivityLauncher.launch(JAR_MIMETYPE)
}
) {
Icon(
imageVector = Icons.Default.Topic,
contentDescription = null
)
}
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 = ::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 = {
IconButton(
onClick = {
integrationsActivityLauncher.launch(APK_MIMETYPE)
}
) {
Icon(
imageVector = Icons.Default.Topic,
contentDescription = null
)
}
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 = ::launchIntegrationsActivity
) {
Icon(
imageVector = Icons.Default.Topic,
contentDescription = null
)
}
)
}
},
modifier = Modifier.clickable {
launchIntegrationsActivity()
}
)
}
}
}

View File

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