feat: improve bundle dialog UI

This commit is contained in:
Ax333l 2023-08-04 12:46:07 +02:00
parent 379ce917a9
commit 1707a9690a
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 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,48 +46,72 @@ 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)
) {
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) { if (remoteUrl != null) {
BundleListItem( BundleListItem(
headlineText = stringResource(R.string.automatically_update), headlineText = stringResource(R.string.automatically_update),
@ -91,13 +121,19 @@ fun BaseBundleDialog(
checked = autoUpdate, checked = autoUpdate,
onCheckedChange = onAutoUpdateChange onCheckedChange = onAutoUpdateChange
) )
},
modifier = Modifier.clickable {
onAutoUpdateChange(!autoUpdate)
} }
) )
} }
BundleListItem( BundleListItem(
headlineText = stringResource(R.string.bundle_type), 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( FilledTonalButton(
onClick = onBundleTypeClick, onClick = onBundleTypeClick,
@ -111,8 +147,7 @@ fun BaseBundleDialog(
) )
} }
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,
) )
} }
} }

View File

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

View File

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

View File

@ -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,54 +120,44 @@ 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 = {},
label = {
Text("Patches Source File")
},
trailingIcon = {
IconButton( IconButton(
onClick = { onClick = ::launchPatchActivity
patchActivityLauncher.launch(JAR_MIMETYPE)
}
) { ) {
Icon( Icon(
imageVector = Icons.Default.Topic, imageVector = Icons.Default.Topic,
contentDescription = null 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,
onValueChange = {},
label = {
Text("Integrations Source File")
},
trailingIcon = {
IconButton( IconButton(
onClick = { onClick = ::launchIntegrationsActivity
integrationsActivityLauncher.launch(APK_MIMETYPE)
}
) { ) {
Icon( Icon(
imageVector = Icons.Default.Topic, imageVector = Icons.Default.Topic,
contentDescription = null contentDescription = null
) )
} }
},
modifier = Modifier.clickable {
launchIntegrationsActivity()
} }
) )
} }
} }
} }
} }
}

View File

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