Skip to content

Commit

Permalink
TriState Checkbox (#228)
Browse files Browse the repository at this point in the history
* Align checkbox with state of the art

* Add checkbox demo

* TriState checkbox running

* TriState checkbox with inmemory state
  • Loading branch information
alorma authored Mar 1, 2024
1 parent d6700b2 commit d75bbe3
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ package com.alorma.compose.settings.storage.base

import kotlin.reflect.KProperty

@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> SettingValueState<T>.getValue(thisObj: Any?, property: KProperty<*>): T =
value

@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> SettingValueState<T>.setValue(
thisObj: Any?,
property: KProperty<*>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,4 @@ class BooleanSettingValueState(
override fun reset() {
value = defaultValue
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.alorma.compose.settings.storage.disk

import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import com.alorma.compose.settings.storage.base.SettingValueState
import com.russhwolf.settings.Settings

@Composable
fun rememberTriStateSetting(
key: String,
defaultValue: Boolean?,
settings: Settings = Settings()
): TriStateSettingValueState {
return remember {
TriStateSettingValueState(
settings = settings,
key = key,
defaultValue = defaultValue,
)
}
}

class TriStateSettingValueState(
private val settings: Settings,
val key: String,
val defaultValue: Boolean? = null,
) : SettingValueState<Boolean?> {

private var _value by mutableStateOf(settings.getBooleanOrNull(key))

override var value: Boolean?
set(value) {
_value = value
if (value == null) {
settings.remove(key)
} else {
settings.putBoolean(key, value)
}
}
get() = _value

override fun reset() {
value = defaultValue
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.alorma.compose.settings.storage.memory

import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import com.alorma.compose.settings.storage.base.SettingValueState

@Composable
fun rememberMemoryTriStateSettingState(defaultValue: Boolean? = null): SettingValueState<Boolean?> {
return remember { InMemoryTriStateSettingValueState(defaultValue) }
}

internal class InMemoryTriStateSettingValueState(private val defaultValue: Boolean?) :
SettingValueState<Boolean?> {
override var value: Boolean? by mutableStateOf(defaultValue)
override fun reset() {
value = defaultValue
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,29 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.Role
import com.alorma.compose.settings.storage.base.SettingValueState
import com.alorma.compose.settings.storage.base.getValue
import com.alorma.compose.settings.storage.base.setValue
import com.alorma.compose.settings.ui.internal.SettingsTileScaffold

@Composable
fun SettingsCheckbox(
state: Boolean,
title: @Composable () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
state: SettingValueState<Boolean>,
icon: @Composable (() -> Unit)? = null,
title: @Composable () -> Unit,
subtitle: @Composable (() -> Unit)? = null,
checkboxColors: CheckboxColors = CheckboxDefaults.colors(),
onCheckedChange: (Boolean) -> Unit = {},
) {
var storageValue by state
val update: (Boolean) -> Unit = { boolean ->
storageValue = boolean
onCheckedChange(storageValue)
}
val update: (Boolean) -> Unit = { boolean -> onCheckedChange(boolean) }
Surface {
Row(
modifier = modifier
.fillMaxWidth()
.toggleable(
enabled = enabled,
value = storageValue,
role = Role.Checkbox,
onValueChange = { update(!storageValue) },
value = state,
role = Role.Switch,
onValueChange = { update(!state) },
),
verticalAlignment = Alignment.CenterVertically,
) {
Expand All @@ -52,7 +45,7 @@ fun SettingsCheckbox(
action = {
Checkbox(
enabled = enabled,
checked = storageValue,
checked = state,
onCheckedChange = update,
colors = checkboxColors,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.alorma.compose.settings.ui

import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.selection.triStateToggleable
import androidx.compose.material3.CheckboxColors
import androidx.compose.material3.CheckboxDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.TriStateCheckbox
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.state.ToggleableState
import com.alorma.compose.settings.ui.internal.SettingsTileScaffold

@Composable
fun SettingsTriStateCheckbox(
state: Boolean?,
title: @Composable () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
icon: @Composable (() -> Unit)? = null,
subtitle: @Composable (() -> Unit)? = null,
checkboxColors: CheckboxColors = CheckboxDefaults.colors(),
onCheckedChange: (Boolean?) -> Unit = {},
) {
val update: () -> Unit = { onCheckedChange(state?.not() ?: true) }
Surface {
Row(
modifier = modifier
.fillMaxWidth()
.triStateToggleable(
state = mapNullableBooleanToToggleableState(state),
onClick = update,
enabled = enabled,
role = Role.Checkbox,
),
verticalAlignment = Alignment.CenterVertically,
) {
SettingsTileScaffold(
enabled = enabled,
title = title,
subtitle = subtitle,
icon = icon,
action = {
TriStateCheckbox(
enabled = enabled,
state = mapNullableBooleanToToggleableState(state),
onClick = update,
colors = checkboxColors,
)
},
)
}
}
}

private fun mapNullableBooleanToToggleableState(state: Boolean?) = when (state) {
true -> ToggleableState.On
false -> ToggleableState.Off
null -> ToggleableState.Indeterminate
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.alorma.compose.settings.sample.shared

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.HorizontalDivider
Expand All @@ -11,12 +10,15 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.alorma.compose.settings.storage.base.getValue
import com.alorma.compose.settings.storage.base.setValue
import com.alorma.compose.settings.storage.disk.rememberBooleanSettingState
import com.alorma.compose.settings.storage.disk.rememberIntSettingState
import com.alorma.compose.settings.storage.memory.rememberMemoryTriStateSettingState
import com.alorma.compose.settings.ui.SettingsCheckbox
import com.alorma.compose.settings.ui.SettingsMenuLink
import com.alorma.compose.settings.ui.SettingsSwitch
import com.alorma.compose.settings.ui.SettingsTriStateCheckbox
import com.russhwolf.settings.Settings

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun SettingsScreen(
settings: Settings,
Expand All @@ -34,6 +36,7 @@ fun SettingsScreen(
title = { Text(text = "Dark mode") },
onCheckedChange = onDarkThemeChange,
)

HorizontalDivider()

var intState by rememberIntSettingState(
Expand All @@ -51,6 +54,42 @@ fun SettingsScreen(
},
onClick = { intState += 1 }
)

HorizontalDivider()

var checkState by rememberBooleanSettingState(
key = "checkbox",
defaultValue = true,
settings = settings,
)
SettingsCheckbox(
state = checkState,
title = { Text(text = "Logger enabled") },
onCheckedChange = { newState -> checkState = newState },
subtitle = {
if (checkState) {
Text(text = "All your data belongs to us!")
} else {
Text(text = "Don't worry, we won't track you")
}
},
)

HorizontalDivider()

var checkTriState by rememberMemoryTriStateSettingState(defaultValue = null)
SettingsTriStateCheckbox(
state = checkTriState,
title = { Text(text = "Online status") },
onCheckedChange = { newState -> checkTriState = newState },
subtitle = {
when (checkTriState) {
true -> Text(text = "You are connected!")
false -> Text(text = "Probably out of the office")
null -> Text(text = "I'm confused")
}
},
)
}
}
}

0 comments on commit d75bbe3

Please sign in to comment.