From 713d308f3e7dac1e3a1c64180f7aa1b9f2049a01 Mon Sep 17 00:00:00 2001 From: rushiiMachine <33725716+rushiiMachine@users.noreply.github.com> Date: Sat, 29 Mar 2025 12:33:45 -0700 Subject: [PATCH] feat: patch options screen previews & improve layout --- .../screens/PatchOptionsScreenPreview.kt | 95 +++++++ .../ui/screens/patchopts/PatchOptionsModel.kt | 16 -- .../screens/patchopts/PatchOptionsScreen.kt | 241 +++++++++++------- ...eNameState.kt => PackageNameStateLabel.kt} | 2 +- .../components/PatchOptionsAppBar.kt | 5 +- app/src/main/res/values/strings.xml | 4 +- 6 files changed, 252 insertions(+), 111 deletions(-) create mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/previews/screens/PatchOptionsScreenPreview.kt rename app/src/main/kotlin/com/aliucord/manager/ui/screens/patchopts/components/{PackageNameState.kt => PackageNameStateLabel.kt} (98%) diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/previews/screens/PatchOptionsScreenPreview.kt b/app/src/main/kotlin/com/aliucord/manager/ui/previews/screens/PatchOptionsScreenPreview.kt new file mode 100644 index 0000000..94ac840 --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/previews/screens/PatchOptionsScreenPreview.kt @@ -0,0 +1,95 @@ +package com.aliucord.manager.ui.previews.screens + +import android.content.res.Configuration +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.* +import com.aliucord.manager.ui.components.ManagerTheme +import com.aliucord.manager.ui.screens.patchopts.* +import com.aliucord.manager.ui.util.DiscordVersion + +// This preview has scrollable/interactable content that cannot be tested from an IDE preview + +@Composable +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) +@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) +private fun PatchOptionsScreenPreview( + @PreviewParameter(ContributorsProvider::class) + parameters: PatchOptionsParameters, +) { + ManagerTheme { + PatchOptionsScreenContent( + isUpdate = parameters.isUpdate, + isDevMode = parameters.isDevMode, + supportedVersion = parameters.supportedVersion, + debuggable = parameters.debuggable, + setDebuggable = {}, + replaceIcon = parameters.replaceIcon, + setReplaceIcon = {}, + appName = parameters.appName, + appNameIsError = parameters.appNameIsError, + setAppName = {}, + packageName = parameters.packageName, + packageNameState = parameters.packageNameState, + setPackageName = {}, + isConfigValid = parameters.isConfigValid, + onInstall = {}, + ) + } +} + +private data class PatchOptionsParameters( + val isUpdate: Boolean, + val isDevMode: Boolean, + val supportedVersion: DiscordVersion, + val debuggable: Boolean, + val replaceIcon: Boolean, + val appName: String, + val appNameIsError: Boolean, + val packageName: String, + val packageNameState: PackageNameState, + val isConfigValid: Boolean, +) + +private class ContributorsProvider : PreviewParameterProvider { + private val stableVersion = DiscordVersion.Existing(DiscordVersion.Type.STABLE, "126.21", 126021) + + override val values = sequenceOf( + // Default initial install + PatchOptionsParameters( + isUpdate = false, + isDevMode = false, + supportedVersion = stableVersion, + debuggable = false, + replaceIcon = true, + appName = PatchOptions.Default.appName, + appNameIsError = false, + packageName = PatchOptions.Default.packageName, + packageNameState = PackageNameState.Ok, + isConfigValid = true, + ), + PatchOptionsParameters( + isUpdate = true, + isDevMode = false, + supportedVersion = stableVersion, + debuggable = false, + replaceIcon = true, + appName = "an invalid app name.", + appNameIsError = true, + packageName = "a b", + packageNameState = PackageNameState.Invalid, + isConfigValid = false, + ), + PatchOptionsParameters( + isUpdate = false, + isDevMode = true, + supportedVersion = stableVersion, + debuggable = true, + replaceIcon = false, + appName = PatchOptions.Default.appName, + appNameIsError = false, + packageName = PatchOptions.Default.packageName, + packageNameState = PackageNameState.Taken, + isConfigValid = true, + ), + ) +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/patchopts/PatchOptionsModel.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/patchopts/PatchOptionsModel.kt index ef72979..956c95b 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/patchopts/PatchOptionsModel.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/patchopts/PatchOptionsModel.kt @@ -24,19 +24,11 @@ class PatchOptionsModel( var packageNameState by mutableStateOf(PackageNameState.Ok) private set - val packageNameIsDefault by derivedStateOf { - packageName == PatchOptions.Default.packageName - } - fun changePackageName(newPackageName: String) { packageName = newPackageName fetchPkgNameStateDebounced() } - fun resetPackageName() { - changePackageName(PatchOptions.Default.packageName) - } - // ---------- App name state ---------- var appName by mutableStateOf(prefilledOptions.appName) private set @@ -44,19 +36,11 @@ class PatchOptionsModel( var appNameIsError by mutableStateOf(false) private set - val appNameIsDefault by derivedStateOf { - appName == PatchOptions.Default.appName - } - fun changeAppName(newAppName: String) { appName = newAppName appNameIsError = newAppName.length !in (1..150) } - fun resetAppName() { - appName = PatchOptions.Default.appName - } - // ---------- Icon patching state ---------- var replaceIcon by mutableStateOf(prefilledOptions.iconReplacement == PatchOptions.IconReplacement.Aliucord) private set diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/patchopts/PatchOptionsScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/patchopts/PatchOptionsScreen.kt index 33d8543..aacc524 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/patchopts/PatchOptionsScreen.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/patchopts/PatchOptionsScreen.kt @@ -6,25 +6,28 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.* import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.koin.getScreenModel import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import com.aliucord.manager.R -import com.aliucord.manager.ui.components.AnimatedVersionDisplay import com.aliucord.manager.ui.components.TextDivider +import com.aliucord.manager.ui.components.VersionDisplay import com.aliucord.manager.ui.components.dialogs.NetworkWarningDialog import com.aliucord.manager.ui.components.dialogs.UnknownSourcesPermissionDialog import com.aliucord.manager.ui.screens.patching.PatchingScreen -import com.aliucord.manager.ui.screens.patchopts.components.PackageNameState +import com.aliucord.manager.ui.screens.patchopts.components.PackageNameStateLabel import com.aliucord.manager.ui.screens.patchopts.components.PatchOptionsAppBar import com.aliucord.manager.ui.screens.patchopts.components.options.SwitchPatchOption import com.aliucord.manager.ui.screens.patchopts.components.options.TextPatchOption @@ -48,7 +51,7 @@ class PatchOptionsScreen( override fun Content() { val context = LocalContext.current val navigator = LocalNavigator.currentOrThrow - val model = getScreenModel() { parametersOf(prefilledOptions ?: PatchOptions.Default) } + val model = getScreenModel { parametersOf(prefilledOptions ?: PatchOptions.Default) } LaunchedEffect(Unit) { // Ensure that when popping this screen off the stack that permission requests don't get triggered @@ -74,100 +77,160 @@ class PatchOptionsScreen( UnknownSourcesPermissionDialog() - Scaffold( - topBar = { PatchOptionsAppBar(isUpdate = prefilledOptions != null) }, - ) { paddingValues -> - Column( - verticalArrangement = Arrangement.spacedByLastAtBottom(20.dp), - modifier = Modifier - .fillMaxSize() - .verticalScroll(rememberScrollState()) - .padding(paddingValues) - .padding(horizontal = 20.dp, vertical = 10.dp) - ) { - Text( - text = stringResource(R.string.patchopts_title), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - textAlign = TextAlign.Center, - ) + PatchOptionsScreenContent( + isUpdate = prefilledOptions != null, + isDevMode = model.isDevMode, + supportedVersion = supportedVersion, - var animatedVersion by rememberSaveable { mutableStateOf(DiscordVersion.None) } - LaunchedEffect(Unit) { - animatedVersion = supportedVersion + debuggable = model.debuggable, + setDebuggable = model::changeDebuggable, + + replaceIcon = model.replaceIcon, + setReplaceIcon = model::changeReplaceIcon, + + appName = model.appName, + appNameIsError = model.appNameIsError, + setAppName = model::changeAppName, + + packageName = model.packageName, + packageNameState = model.packageNameState, + setPackageName = model::changePackageName, + + isConfigValid = model.isConfigValid, + onInstall = { navigator.push(PatchingScreen(model.generateConfig())) }, + ) + } +} + +@Composable +fun PatchOptionsScreenContent( + isUpdate: Boolean, + isDevMode: Boolean, + supportedVersion: DiscordVersion, + + debuggable: Boolean, + setDebuggable: (Boolean) -> Unit, + + replaceIcon: Boolean, + setReplaceIcon: (Boolean) -> Unit, + + appName: String, + appNameIsError: Boolean, + setAppName: (String) -> Unit, + + packageName: String, + packageNameState: PackageNameState, + setPackageName: (String) -> Unit, + + isConfigValid: Boolean, + onInstall: () -> Unit, +) { + Scaffold( + topBar = { PatchOptionsAppBar(isUpdate = isUpdate) }, + ) { paddingValues -> + Column( + verticalArrangement = Arrangement.spacedByLastAtBottom(20.dp), + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(paddingValues) + .padding(horizontal = 20.dp, vertical = 10.dp) + ) { + Text( + text = stringResource(R.string.patchopts_title), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.Center, + ) + + TextDivider(text = stringResource(R.string.patchopts_divider_basic)) + + SwitchPatchOption( + icon = painterResource(R.drawable.ic_app_shortcut), + name = stringResource(R.string.patchopts_icon_title), + description = stringResource(R.string.patchopts_icon_desc), + value = replaceIcon, + onValueChange = setReplaceIcon, + modifier = Modifier.padding(bottom = 8.dp) + ) + + val appNameIsDefault by remember { + derivedStateOf { + appName == PatchOptions.Default.appName } - AnimatedVersionDisplay( - version = animatedVersion, - modifier = Modifier - .align(Alignment.CenterHorizontally) - .offset(y = (-10).dp), - ) + } + TextPatchOption( + name = stringResource(R.string.patchopts_appname_title), + description = stringResource(R.string.patchopts_appname_desc), + value = appName, + valueIsError = appNameIsError, + valueIsDefault = appNameIsDefault, + onValueChange = setAppName, + onValueReset = { setAppName(PatchOptions.Default.appName) }, + ) - TextDivider(text = stringResource(R.string.patchopts_divider_basic)) + val packageNameIsDefault by remember { + derivedStateOf { + packageName == PatchOptions.Default.packageName + } + } + TextPatchOption( + name = stringResource(R.string.patchopts_pkgname_title), + description = stringResource(R.string.patchopts_pkgname_desc), + value = packageName, + valueIsError = packageNameState == PackageNameState.Invalid, + valueIsDefault = packageNameIsDefault, + onValueChange = setPackageName, + onValueReset = { setPackageName(PatchOptions.Default.packageName) }, + ) { + PackageNameStateLabel( + state = packageNameState, + modifier = Modifier.padding(start = 4.dp), + ) + } + + if (isDevMode) { + TextDivider( + text = stringResource(R.string.patchopts_divider_advanced), + modifier = Modifier.padding(top = 12.dp), + ) SwitchPatchOption( - icon = painterResource(R.drawable.ic_app_shortcut), - name = stringResource(R.string.patchopts_icon_title), - description = stringResource(R.string.patchopts_icon_desc), - value = model.replaceIcon, - onValueChange = model::changeReplaceIcon, - modifier = Modifier.padding(bottom = 8.dp) + icon = painterResource(R.drawable.ic_bug), + name = stringResource(R.string.patchopts_debuggable_title), + description = stringResource(R.string.patchopts_debuggable_desc), + value = debuggable, + onValueChange = setDebuggable, ) + } - TextPatchOption( - name = stringResource(R.string.patchopts_appname_title), - description = stringResource(R.string.patchopts_appname_desc), - value = model.appName, - valueIsError = model.appNameIsError, - valueIsDefault = model.appNameIsDefault, - onValueChange = model::changeAppName, - onValueReset = model::resetAppName - ) + VersionDisplay( + version = supportedVersion, + prefix = { + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append(stringResource(R.string.version_supported)) + append(" ") + } + }, + modifier = Modifier + .alpha(.5f) + .align(Alignment.CenterHorizontally) + .padding(top = 20.dp) + ) - TextPatchOption( - name = stringResource(R.string.patchopts_pkgname_title), - description = stringResource(R.string.patchopts_pkgname_desc), - value = model.packageName, - valueIsError = model.packageNameState == PackageNameState.Invalid, - valueIsDefault = model.packageNameIsDefault, - onValueChange = model::changePackageName, - onValueReset = model::resetPackageName, - ) { - PackageNameState( - state = model.packageNameState, - modifier = Modifier.padding(start = 4.dp), - ) - } + Spacer(Modifier.weight(1f)) - if (model.isDevMode) { - TextDivider( - text = stringResource(R.string.patchopts_divider_advanced), - modifier = Modifier.padding(top = 12.dp), - ) - - SwitchPatchOption( - icon = painterResource(R.drawable.ic_bug), - name = stringResource(R.string.patchopts_debuggable_title), - description = stringResource(R.string.patchopts_debuggable_desc), - value = model.debuggable, - onValueChange = model::changeDebuggable, - ) - } - - Spacer(Modifier.weight(1f)) - - FilledTonalButton( - enabled = model.isConfigValid, - onClick = { navigator.push(PatchingScreen(model.generateConfig())) }, - colors = ButtonDefaults.filledTonalButtonColors( - contentColor = MaterialTheme.colorScheme.primary, - ), - modifier = Modifier - .padding(bottom = 10.dp) - .align(Alignment.End), - ) { - Text(stringResource(R.string.action_install)) - } + FilledTonalButton( + enabled = isConfigValid, + onClick = onInstall, + colors = ButtonDefaults.filledTonalButtonColors( + contentColor = MaterialTheme.colorScheme.primary, + ), + modifier = Modifier + .padding(bottom = 10.dp) + .align(Alignment.End), + ) { + Text(stringResource(R.string.action_install)) } } } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/patchopts/components/PackageNameState.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/patchopts/components/PackageNameStateLabel.kt similarity index 98% rename from app/src/main/kotlin/com/aliucord/manager/ui/screens/patchopts/components/PackageNameState.kt rename to app/src/main/kotlin/com/aliucord/manager/ui/screens/patchopts/components/PackageNameStateLabel.kt index 6e936ef..c583ec5 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/patchopts/components/PackageNameState.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/patchopts/components/PackageNameStateLabel.kt @@ -15,7 +15,7 @@ import com.aliucord.manager.R import com.aliucord.manager.ui.screens.patchopts.PackageNameState @Composable -fun PackageNameState( +fun PackageNameStateLabel( state: PackageNameState, modifier: Modifier = Modifier, ) { diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/patchopts/components/PatchOptionsAppBar.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/patchopts/components/PatchOptionsAppBar.kt index 2b7d9ed..8f96d63 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/patchopts/components/PatchOptionsAppBar.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/patchopts/components/PatchOptionsAppBar.kt @@ -5,7 +5,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import cafe.adriel.voyager.navigator.LocalNavigator -import cafe.adriel.voyager.navigator.currentOrThrow import com.aliucord.manager.R import com.aliucord.manager.ui.components.BackButton import com.aliucord.manager.ui.screens.settings.SettingsScreen @@ -18,9 +17,9 @@ fun PatchOptionsAppBar( navigationIcon = { BackButton() }, title = { Text(stringResource(if (!isUpdate) R.string.action_add_install else R.string.action_update_install)) }, actions = { - val navigator = LocalNavigator.currentOrThrow + val navigator = LocalNavigator.current - IconButton(onClick = { navigator.push(SettingsScreen()) }) { + IconButton(onClick = { navigator?.push(SettingsScreen()) }) { Icon( painter = painterResource(R.drawable.ic_settings), contentDescription = stringResource(R.string.navigation_settings) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6ed0b99..a79a376 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -16,8 +16,8 @@ Dismiss Install Uninstall - New Aliucord Installation - Update Aliucord Installation + New install + Update install Update Clear Launch