From cb1d70bb14393fc323887ad03abe9d37625f9604 Mon Sep 17 00:00:00 2001 From: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Date: Sun, 27 Sep 2026 14:18:29 -0400 Subject: [PATCH] fix(ui): flip booleans with a switch instead of on/off menus --- .../droiddeck/launcher/ui/FrontEndSetup.kt | 16 +++---- .../droiddeck/launcher/ui/SessionOverlay.kt | 2 +- .../droiddeck/launcher/ui/SettingsWidgets.kt | 43 ++++++++++++++++++- 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/droiddeck/launcher/ui/FrontEndSetup.kt b/app/src/main/java/com/droiddeck/launcher/ui/FrontEndSetup.kt index 3e24b2b2d..7665f5abe 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/FrontEndSetup.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/FrontEndSetup.kt @@ -278,15 +278,13 @@ internal fun SetupPanel( FrameGenMenu(s, a, host) } } - SettingsRow("Session logs", "${if (s.logsEnabled) "Enabled" else "Disabled"} ยท logs are saved after each session") { - Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { - SecondaryButton(if (s.logsEnabled) "Turn off" else "Turn on") { a.onLogs() } - SecondaryButton("Share latest") { a.onShareLogs() } - } - } - SettingsRow("Offline mode", s.offlineAccount?.let { if (s.offline) "Enabled for $it" else "Signed in as $it" } ?: "Sign in to Steam first") { - SecondaryButton(if (s.offline) "Turn off" else "Turn on", enabled = s.offlineAccount != null) { a.onOffline() } - } + ToggleRow(host, "logs", "Session logs", "Saved after each session", s.logsEnabled) { a.onLogs() } + ActionRow("Latest session logs", "Send them with a bug report", "Share logs", a.onShareLogs) + ToggleRow( + host, "offline", "Offline mode", + s.offlineAccount?.let { "Signed in as $it" } ?: "Sign in to Steam first", + s.offline, enabled = s.offlineAccount != null, + ) { a.onOffline() } } } 3 -> { diff --git a/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt b/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt index 82872433f..3fd19a2ed 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt @@ -420,7 +420,7 @@ fun SessionDrawer(open: Boolean, page: Int, controllerActive: Boolean, onPageCha } SettingsGroup("Support") { SettingsRow("Session logs", "Send this session's logs with a bug report") { - DrawerOutlineButton("Send logs", modifier = focus.track(page, "share-logs")) { + DrawerOutlineButton("Share logs", modifier = focus.track(page, "share-logs")) { host.open = null a.onShareLogs() } diff --git a/app/src/main/java/com/droiddeck/launcher/ui/SettingsWidgets.kt b/app/src/main/java/com/droiddeck/launcher/ui/SettingsWidgets.kt index 5c71788bc..ec70305de 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/SettingsWidgets.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/SettingsWidgets.kt @@ -19,6 +19,8 @@ import androidx.compose.foundation.layout.size import androidx.compose.ui.focus.focusProperties import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.stateDescription +import androidx.compose.foundation.shape.CircleShape import androidx.compose.ui.semantics.semantics import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -385,7 +387,46 @@ fun ChoiceRow( @Composable fun ToggleRow(host: MenuHost, key: String, label: String, hint: String?, checked: Boolean, enabled: Boolean = true, chipModifier: Modifier = Modifier, onChange: (Boolean) -> Unit) = - ChoiceRow(host, key, label, hint, listOf(true to "On", false to "Off"), checked, enabled, chipModifier = chipModifier, onPick = onChange) + SettingsRow(label, hint) { + ToggleSwitch(checked, enabled, label, chipModifier) { host.open = null; onChange(it) } + } + +/** An on/off switch: one tap or one A press flips it, where a menu of On and Off took three. */ +@Composable +fun ToggleSwitch(checked: Boolean, enabled: Boolean = true, label: String? = null, modifier: Modifier = Modifier, onChange: (Boolean) -> Unit) { + val colors = MaterialTheme.colorScheme + val pal = LocalPalette.current + val src = remember { MutableInteractionSource() } + val hot = src.collectIsFocusedAsState().value || src.collectIsHoveredAsState().value + val track by animateColorAsState(if (checked) pal.signal else colors.surfaceVariant, Motion.tw(180), label = "switchTrack") + val edge by animateColorAsState(if (hot) pal.signal else if (checked) pal.signal else pal.line2, Motion.tw(180), label = "switchEdge") + val knob by animateFloatAsState(if (checked) 1f else 0f, Motion.sp(0.7f), label = "switchKnob") + val flip = { onChange(!checked) } + val shape = RoundedCornerShape(99.dp) + // 48dp tall to touch; the visible track sits inside it. + Box( + contentAlignment = Alignment.Center, + modifier = modifier + .heightIn(min = 48.dp).widthIn(min = 64.dp) + .alpha(if (enabled) 1f else 0.5f) + .hoverable(src) + .clickable(interactionSource = src, indication = null, enabled = enabled, role = Role.Switch, onClick = flip) + .controllerConfirm(enabled = enabled, onClick = flip) + .semantics { stateDescription = if (checked) "On" else "Off"; if (label != null) contentDescription = label }, + ) { + Box( + Modifier.size(width = 52.dp, height = 30.dp).clip(shape).background(track) + .border(if (hot) 2.dp else 1.dp, if (hot && checked) colors.onBackground else edge, shape), + ) { + Box( + Modifier.padding(4.dp).size(22.dp) + .graphicsLayer { translationX = knob * 22.dp.toPx() } + .clip(CircleShape) + .background(if (checked) pal.onSignal else colors.onSurfaceVariant), + ) + } + } +} @Composable fun MultiRow(