diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/settings/SettingsContent.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/settings/SettingsContent.kt deleted file mode 100644 index 8924ac59e..000000000 --- a/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/settings/SettingsContent.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2023 Allan Wang - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.pitchedapps.frost.compose.settings - -import androidx.compose.material3.Checkbox -import androidx.compose.material3.Switch -import androidx.compose.runtime.Composable - -fun SettingsContentScope.checkbox(state: SettingState): SettingsContent = - object : SettingsContent { - - override fun onClick() { - state.value = !state.value - } - - @Composable - override fun compose() { - Checkbox( - enabled = state.enabled, - checked = state.value, - onCheckedChange = { state.value = it }, - ) - } - } - -fun SettingsContentScope.switch( - state: SettingState, -): SettingsContent = - object : SettingsContent { - - override fun onClick() { - state.value = !state.value - } - - @Composable - override fun compose() { - Switch( - enabled = state.enabled, - checked = state.value, - onCheckedChange = { state.value = it }, - ) - } - } diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/settings/SettingsContentScope.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/settings/SettingsContentScope.kt deleted file mode 100644 index 8e4b92b06..000000000 --- a/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/settings/SettingsContentScope.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2023 Allan Wang - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.pitchedapps.frost.compose.settings - -import androidx.compose.foundation.layout.LayoutScopeMarker -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue - -@LayoutScopeMarker -interface SettingsContentScope { - @Composable - fun T.rememberSetting( - enabler: T.() -> Boolean = { true }, - getter: T.() -> R, - setter: T.(R) -> Unit - ): SettingState { - return remember(this) { - object : SettingState { - override val enabled: Boolean - get() = enabler() - - override var value: R - get() = getter() - set(value) { - setter(value) - } - } - } - } - - @Composable - fun MutableState.asSettingState(): SettingState { - return remember(this) { - object : SettingState { - override val enabled: Boolean = true - override var value: T by this@asSettingState - } - } - } -} - -interface SettingsContent { - - fun onClick() - - @Composable fun compose() -} diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/settings/SettingsDsl.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/settings/SettingsDsl.kt new file mode 100644 index 000000000..eb8889f36 --- /dev/null +++ b/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/settings/SettingsDsl.kt @@ -0,0 +1,79 @@ +/* + * Copyright 2023 Allan Wang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.pitchedapps.frost.compose.settings + +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.vector.ImageVector + +@DslMarker annotation class SettingsDslMarker + +@SettingsDslMarker +interface SettingsDsl { + /** Entry point to avoid cluttering the global namespace. */ + companion object : SettingsDsl +} + +/** Dsl for creating individual entries in a list */ +@SettingsDslMarker +interface SettingsListDsl { + + /** + * Sub list with group title + * + * TODO support collapsed and/or shown? + */ + // fun group(title: String, enabled: Boolean = true, action: SettingsListDsl.() -> Unit) + + /** Generic item without content */ + fun item( + title: String, + enabled: Boolean = true, + icon: ImageVector? = null, + description: String? = null, + onClick: (() -> Unit)? = null + ) + + /** Long, non clickable content */ + fun description(text: String, icon: ImageVector? = null) + + fun checkbox( + title: String, + enabled: Boolean = true, + icon: ImageVector? = null, + description: String? = null, + checked: Boolean, + onCheckedChanged: (Boolean) -> Unit, + ) + + fun switch( + title: String, + enabled: Boolean = true, + icon: ImageVector? = null, + description: String? = null, + checked: Boolean, + onCheckedChanged: (Boolean) -> Unit, + ) + + fun custom( + title: String, + enabled: Boolean = true, + icon: ImageVector? = null, + description: String? = null, + onClick: (() -> Unit)? = null, + content: @Composable () -> Unit, + ) +} diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/settings/SettingsListDsl.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/settings/SettingsListDsl.kt new file mode 100644 index 000000000..62a54a279 --- /dev/null +++ b/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/settings/SettingsListDsl.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2023 Allan Wang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.pitchedapps.frost.compose.settings + +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.MaterialTheme +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 androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview + +@Composable +fun SettingsListDsl(modifier: Modifier = Modifier, content: SettingsListDsl.() -> Unit) { + val items = SettingsDsl.settingsListDsl(content) + + LazyColumn(modifier = modifier) { items(items) { compose -> compose() } } +} + +@Preview +@Composable +fun SettingsListDslPreview() { + + data class Model( + val check1: Boolean = false, + val switch1: Boolean = false, + val switch2: Boolean = false, + ) + + var state by remember { mutableStateOf(Model()) } + + MaterialTheme { + SettingsListDsl { + checkbox( + title = "Check 1", + checked = state.check1, + onCheckedChanged = { state = state.copy(check1 = it) }, + ) + checkbox( + title = "Check 1", + description = "Linked again", + checked = state.check1, + onCheckedChanged = { state = state.copy(check1 = it) }, + ) + switch( + title = "Switch 1", + checked = state.switch1, + onCheckedChanged = { state = state.copy(switch1 = it) }, + ) + switch( + title = "Switch 2", + enabled = state.switch1, + description = "Enabled by switch 1", + checked = state.switch2, + onCheckedChanged = { state = state.copy(switch2 = it) }, + ) + } + } +} diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/settings/SettingsListItem.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/settings/SettingsListItem.kt index 7ef81e271..8fa729669 100644 --- a/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/settings/SettingsListItem.kt +++ b/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/settings/SettingsListItem.kt @@ -18,16 +18,22 @@ package com.pitchedapps.frost.compose.settings import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.size +import androidx.compose.material.ContentAlpha +import androidx.compose.material.LocalContentAlpha import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Person +import androidx.compose.material3.Checkbox import androidx.compose.material3.Icon import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text 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 androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -38,60 +44,54 @@ import com.pitchedapps.frost.ext.thenIf @Composable fun SettingsListItem( modifier: Modifier = Modifier, - icon: ImageVector? = null, title: String, + enabled: Boolean = true, + icon: ImageVector? = null, description: String? = null, - content: (@Composable SettingsContentScope.() -> SettingsContent)? = null + onClick: (() -> Unit)? = null, + content: (@Composable () -> Unit)? = null ) { - val settingsContent = content?.invoke(SettingsContentScopeImpl) - + val alpha = if (enabled) LocalContentAlpha.current else ContentAlpha.disabled ListItem( modifier = - modifier.thenIf(settingsContent != null) { - Modifier.clickable( - onClick = settingsContent!!::onClick, - ) + modifier.thenIf(onClick != null) { + Modifier.clickable(enabled = enabled) { onClick?.invoke() } }, leadingContent = icon.optionalCompose { Icon( - modifier = Modifier.size(24.dp), + modifier = Modifier.size(24.dp).alpha(alpha), imageVector = it, contentDescription = null, ) }, - headlineContent = { Text(text = title) }, - supportingContent = description.optionalCompose { Text(text = it) }, - trailingContent = - settingsContent.takeIf { it !is SettingsContentClickOnly }.optionalCompose { it.compose() }, + headlineContent = { Text(modifier = Modifier.alpha(alpha), text = title) }, + supportingContent = + description.optionalCompose { + Text( + modifier = Modifier.alpha(alpha), + text = it, + ) + }, + trailingContent = content, ) } @Preview @Composable private fun SettingsListItemPreview() { - val state = remember { mutableStateOf(false) } + var state by remember { mutableStateOf(false) } + MaterialTheme { SettingsListItem( icon = Icons.Outlined.Person, title = "Test Title", description = "Test Description", ) { - checkbox(state.asSettingState()) + Checkbox( + checked = state, + onCheckedChange = { state = it }, + ) } } } - -private object SettingsContentScopeImpl : SettingsContentScope - -private class SettingsContentClickOnly(private val action: () -> Unit) : SettingsContent { - - override fun onClick() { - action() - } - - @Composable override fun compose() = Unit -} - -fun SettingsContentScope.click(action: () -> Unit): SettingsContent = - SettingsContentClickOnly(action) diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/settings/SettingsListItemDsl.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/settings/SettingsListItemDsl.kt new file mode 100644 index 000000000..69378f01b --- /dev/null +++ b/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/settings/SettingsListItemDsl.kt @@ -0,0 +1,114 @@ +/* + * Copyright 2023 Allan Wang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.pitchedapps.frost.compose.settings + +import androidx.compose.material3.Checkbox +import androidx.compose.material3.Switch +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.vector.ImageVector + +fun SettingsDsl.settingsListDsl(content: SettingsListDsl.() -> Unit): List<@Composable () -> Unit> { + val data = SettingsListDslData() + data.content() + return data.items +} + +private class SettingsListDslData : SettingsListDsl { + val items: MutableList<@Composable () -> Unit> = mutableListOf() + + private fun addCompose(content: @Composable () -> Unit) { + items.add(content) + } + + override fun item( + title: String, + enabled: Boolean, + icon: ImageVector?, + description: String?, + onClick: (() -> Unit)? + ) { + addCompose {} + } + + override fun description(text: String, icon: ImageVector?) {} + + override fun checkbox( + title: String, + enabled: Boolean, + icon: ImageVector?, + description: String?, + checked: Boolean, + onCheckedChanged: (Boolean) -> Unit + ) { + custom( + icon = icon, + title = title, + enabled = enabled, + description = description, + onClick = { onCheckedChanged(!checked) }, + ) { + Checkbox( + enabled = enabled, + checked = checked, + onCheckedChange = onCheckedChanged, + ) + } + } + + override fun switch( + title: String, + enabled: Boolean, + icon: ImageVector?, + description: String?, + checked: Boolean, + onCheckedChanged: (Boolean) -> Unit + ) { + custom( + icon = icon, + title = title, + enabled = enabled, + description = description, + onClick = { onCheckedChanged(!checked) }, + ) { + Switch( + enabled = enabled, + checked = checked, + onCheckedChange = onCheckedChanged, + ) + } + } + + override fun custom( + title: String, + enabled: Boolean, + icon: ImageVector?, + description: String?, + onClick: (() -> Unit)?, + content: @Composable () -> Unit + ) { + addCompose { + SettingsListItem( + icon = icon, + title = title, + enabled = enabled, + description = description, + onClick = onClick, + content = content, + ) + } + } +} diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/settings/SettingsScreen.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/settings/SettingsScreen.kt deleted file mode 100644 index 58655254d..000000000 --- a/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/settings/SettingsScreen.kt +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2023 Allan Wang - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.pitchedapps.frost.compose.settings - -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.material3.MaterialTheme -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 androidx.compose.ui.tooling.preview.Preview - -@Composable fun SettingsScreen() {} - -@Preview -@Composable -fun SettingsScreenPreview() { - - data class Model( - val check1: Boolean = false, - val switch1: Boolean = false, - val switch2: Boolean = false, - ) - - var state by remember { mutableStateOf(Model()) } - - val composables: List<@Composable () -> Unit> = remember { - listOf( - { - SettingsListItem( - title = "Check 1", - ) { - checkbox( - state.rememberSetting( - getter = { state.check1 }, - setter = { state = state.copy(check1 = it) }, - ), - ) - } - }, - { - SettingsListItem( - title = "Check 1", - description = "Linked again", - ) { - checkbox( - state.rememberSetting( - getter = { state.check1 }, - setter = { state = state.copy(check1 = it) }, - ), - ) - } - }, - { - SettingsListItem( - title = "Switch 1", - ) { - switch( - state.rememberSetting( - getter = { state.switch1 }, - setter = { state = state.copy(switch1 = it) }, - ), - ) - } - }, - { - SettingsListItem( - title = "Switch 2", - description = "Enabled by switch 1", - ) { - switch( - state.rememberSetting( - enabler = { state.switch1 }, - getter = { state.switch2 }, - setter = { state = state.copy(switch2 = it) }, - ), - ) - } - }, - ) - } - - MaterialTheme { LazyColumn { items(composables) { compose -> compose() } } } -}