mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-11 06:44:00 +00:00
Merge pull request #2702
This commit is contained in:
@@ -111,6 +111,7 @@
|
||||
<string name="window_corner_radius_description">When you swipe up to open Recents, the current app follows your finger, shrinking into a card. Use this slider to adjust the corner radius of the card when it’s nearly full-screen such that it matches the corners of your screen.</string>
|
||||
<string name="system_icons">System Icons</string>
|
||||
<string name="accent_color">Accent Color</string>
|
||||
<string name="invalid_color">Invalid Color</string>
|
||||
<string name="done">Done</string>
|
||||
<string name="loading">Loading…</string>
|
||||
<string name="system">System</string>
|
||||
@@ -180,6 +181,16 @@
|
||||
<string name="dynamic">Dynamic</string>
|
||||
<string name="all_apps_search_market_message">Search for More Apps</string>
|
||||
<string name="show_app_search_bar">Show Search Bar</string>
|
||||
<string name="rgb">RGB</string>
|
||||
<string name="rgb_red">Red</string>
|
||||
<string name="rgb_green">Green</string>
|
||||
<string name="rgb_blue">Blue</string>
|
||||
<string name="hsb">HSB</string>
|
||||
<string name="hsb_hue">Hue</string>
|
||||
<string name="hsb_saturation">Saturation</string>
|
||||
<string name="hsb_brightness">Brightness</string>
|
||||
<string name="hex">Hex</string>
|
||||
<string name="color_sliders">Sliders</string>
|
||||
|
||||
<string name="lawnchair_bug_report">Lawnchair Bug Report</string>
|
||||
<string name="crash_report_notif_title">%1$s Crashed</string>
|
||||
|
||||
@@ -27,7 +27,6 @@ import app.lawnchair.preferences.getAdapter
|
||||
import app.lawnchair.preferences.preferenceManager
|
||||
import app.lawnchair.preferences2.asState
|
||||
import app.lawnchair.preferences2.preferenceManager2
|
||||
import app.lawnchair.ui.preferences.components.AccentColorPreference
|
||||
import app.lawnchair.ui.preferences.components.ExpandAndShrink
|
||||
import app.lawnchair.ui.preferences.components.FontPreference
|
||||
import app.lawnchair.ui.preferences.components.IconShapePreference
|
||||
@@ -38,6 +37,7 @@ import app.lawnchair.ui.preferences.components.PreferenceLayout
|
||||
import app.lawnchair.ui.preferences.components.SliderPreference
|
||||
import app.lawnchair.ui.preferences.components.SwitchPreference
|
||||
import app.lawnchair.ui.preferences.components.ThemePreference
|
||||
import app.lawnchair.ui.preferences.components.colorpreference.ColorPreference
|
||||
import app.lawnchair.ui.preferences.components.notificationDotsEnabled
|
||||
import app.lawnchair.ui.preferences.components.notificationServiceEnabled
|
||||
import com.android.launcher3.R
|
||||
@@ -110,7 +110,10 @@ fun GeneralPreferences() {
|
||||
}
|
||||
PreferenceGroup(heading = stringResource(id = R.string.colors)) {
|
||||
ThemePreference()
|
||||
AccentColorPreference()
|
||||
ColorPreference(
|
||||
preference = prefs2.accentColor,
|
||||
label = stringResource(id = R.string.accent_color),
|
||||
)
|
||||
}
|
||||
val wrapAdaptiveIcons = prefs.wrapAdaptiveIcons.getAdapter()
|
||||
PreferenceGroup(
|
||||
|
||||
@@ -30,6 +30,7 @@ import app.lawnchair.backup.ui.createBackupGraph
|
||||
import app.lawnchair.backup.ui.restoreBackupGraph
|
||||
import app.lawnchair.ui.preferences.about.aboutGraph
|
||||
import app.lawnchair.ui.preferences.components.SystemUi
|
||||
import app.lawnchair.ui.preferences.components.colorpreference.colorSelectionGraph
|
||||
import app.lawnchair.ui.util.ProvideBottomSheetHandler
|
||||
import app.lawnchair.util.ProvideLifecycleState
|
||||
import com.google.accompanist.navigation.animation.AnimatedNavHost
|
||||
@@ -45,6 +46,7 @@ object Routes {
|
||||
const val FOLDERS = "folders"
|
||||
const val QUICKSTEP = "quickstep"
|
||||
const val FONT_SELECTION = "fontSelection"
|
||||
const val COLOR_SELECTION = "colorSelection"
|
||||
const val DEBUG_MENU = "debugMenu"
|
||||
const val SELECT_ICON = "selectIcon"
|
||||
const val ICON_PICKER = "iconPicker"
|
||||
@@ -97,6 +99,7 @@ fun Preferences(interactor: PreferenceInteractor = viewModel<PreferenceViewModel
|
||||
quickstepGraph(route = subRoute(Routes.QUICKSTEP))
|
||||
aboutGraph(route = subRoute(Routes.ABOUT))
|
||||
fontSelectionGraph(route = subRoute(Routes.FONT_SELECTION))
|
||||
colorSelectionGraph(route = subRoute(Routes.COLOR_SELECTION))
|
||||
debugMenuGraph(route = subRoute(Routes.DEBUG_MENU))
|
||||
selectIconGraph(route = subRoute(Routes.SELECT_ICON))
|
||||
iconPickerGraph(route = subRoute(Routes.ICON_PICKER))
|
||||
|
||||
@@ -39,6 +39,7 @@ fun PreferenceLayout(
|
||||
scrollState: ScrollState? = rememberScrollState(),
|
||||
label: String,
|
||||
actions: @Composable RowScope.() -> Unit = {},
|
||||
bottomBar: @Composable () -> Unit = { BottomSpacer() },
|
||||
backArrowVisible: Boolean = true,
|
||||
content: @Composable ColumnScope.() -> Unit
|
||||
) {
|
||||
@@ -47,6 +48,7 @@ fun PreferenceLayout(
|
||||
floating = rememberFloatingState(scrollState),
|
||||
label = label,
|
||||
actions = actions,
|
||||
bottomBar = bottomBar,
|
||||
) {
|
||||
PreferenceColumn(
|
||||
verticalArrangement = verticalArrangement,
|
||||
|
||||
@@ -15,6 +15,7 @@ fun PreferenceScaffold(
|
||||
floating: State<Boolean> = remember { mutableStateOf(false) },
|
||||
label: String,
|
||||
actions: @Composable RowScope.() -> Unit = {},
|
||||
bottomBar: @Composable () -> Unit = { BottomSpacer() },
|
||||
content: @Composable (PaddingValues) -> Unit
|
||||
) {
|
||||
Scaffold(
|
||||
@@ -26,7 +27,7 @@ fun PreferenceScaffold(
|
||||
actions = actions,
|
||||
)
|
||||
},
|
||||
bottomBar = { BottomSpacer() },
|
||||
bottomBar = bottomBar,
|
||||
contentPadding = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal).asPaddingValues()
|
||||
) {
|
||||
content(it)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package app.lawnchair.ui.preferences.components.colorpreference
|
||||
|
||||
import android.graphics.Color.colorToHSV
|
||||
import android.graphics.Color.HSVToColor
|
||||
import android.graphics.Color.parseColor
|
||||
|
||||
fun intColorToHsvColorArray(color: Int) =
|
||||
FloatArray(size = 3).apply { colorToHSV(color, this) }
|
||||
|
||||
fun hsvValuesToIntColor(hue: Float, saturation: Float, brightness: Float): Int =
|
||||
HSVToColor(floatArrayOf(hue, saturation, brightness))
|
||||
|
||||
fun intColorToColorString(color: Int) =
|
||||
String.format("#%06X", 0xFFFFFF and color).removePrefix("#")
|
||||
|
||||
fun colorStringToIntColor(colorString: String): Int? =
|
||||
try {
|
||||
parseColor("#${colorString.removePrefix("#")}")
|
||||
} catch (e: IllegalArgumentException) {
|
||||
null
|
||||
}
|
||||
@@ -1,12 +1,6 @@
|
||||
package app.lawnchair.ui.preferences.components
|
||||
package app.lawnchair.ui.preferences.components.colorpreference
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.lawnchair.preferences.getAdapter
|
||||
import app.lawnchair.preferences2.preferenceManager2
|
||||
import app.lawnchair.theme.color.ColorOption
|
||||
import app.lawnchair.ui.preferences.components.colorpreference.ColorPreference
|
||||
import com.android.launcher3.R
|
||||
|
||||
val staticColors = listOf(
|
||||
ColorOption.CustomColor(0xFFF32020),
|
||||
@@ -23,17 +17,7 @@ val staticColors = listOf(
|
||||
ColorOption.CustomColor(0xFF67818E)
|
||||
).map(ColorOption::colorPreferenceEntry)
|
||||
|
||||
|
||||
val dynamicColors = listOf(ColorOption.SystemAccent, ColorOption.WallpaperPrimary)
|
||||
.filter(ColorOption::isSupported)
|
||||
.map(ColorOption::colorPreferenceEntry)
|
||||
|
||||
@Composable
|
||||
fun AccentColorPreference() {
|
||||
val adapter = preferenceManager2().accentColor.getAdapter()
|
||||
ColorPreference(
|
||||
adapter = adapter,
|
||||
label = stringResource(id = R.string.accent_color),
|
||||
dynamicEntries = dynamicColors,
|
||||
staticEntries = staticColors,
|
||||
)
|
||||
}
|
||||
@@ -17,165 +17,36 @@
|
||||
package app.lawnchair.ui.preferences.components.colorpreference
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.RadioButton
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import app.lawnchair.preferences.PreferenceAdapter
|
||||
import app.lawnchair.preferences.getAdapter
|
||||
import app.lawnchair.theme.color.ColorOption
|
||||
import app.lawnchair.ui.AlertBottomSheetContent
|
||||
import app.lawnchair.ui.preferences.components.Chip
|
||||
import app.lawnchair.ui.preferences.components.PreferenceDivider
|
||||
import app.lawnchair.ui.preferences.components.PreferenceTemplate
|
||||
import app.lawnchair.ui.theme.lightenColor
|
||||
import app.lawnchair.ui.util.bottomSheetHandler
|
||||
import com.android.launcher3.R
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import com.google.accompanist.pager.HorizontalPager
|
||||
import com.google.accompanist.pager.rememberPagerState
|
||||
import kotlinx.coroutines.launch
|
||||
import app.lawnchair.ui.preferences.LocalNavController
|
||||
import app.lawnchair.ui.preferences.components.*
|
||||
import com.patrykmichalik.opto.domain.Preference
|
||||
|
||||
@OptIn(ExperimentalPagerApi::class)
|
||||
/**
|
||||
* A a custom implementation of [PreferenceTemplate] for [ColorOption] preferences.
|
||||
*
|
||||
* @see colorSelectionGraph
|
||||
* @see ColorSelection
|
||||
*/
|
||||
@Composable
|
||||
fun ColorPreference(
|
||||
adapter: PreferenceAdapter<ColorOption>,
|
||||
preference: Preference<ColorOption, String, *>,
|
||||
label: String,
|
||||
dynamicEntries: List<ColorPreferenceEntry<ColorOption>>,
|
||||
staticEntries: List<ColorPreferenceEntry<ColorOption>>,
|
||||
) {
|
||||
var selectedColor by adapter
|
||||
val selectedEntry = dynamicEntries.firstOrNull { it.value == selectedColor } ?: staticEntries.firstOrNull { it.value == selectedColor }
|
||||
val defaultTabIndex = if (dynamicEntries.any { it.value == selectedColor }) 0 else 1
|
||||
val description = selectedEntry?.label?.invoke()
|
||||
val bottomSheetHandler = bottomSheetHandler
|
||||
var bottomSheetShown by remember { mutableStateOf(false) }
|
||||
|
||||
val adapter: PreferenceAdapter<ColorOption> = preference.getAdapter()
|
||||
val navController = LocalNavController.current
|
||||
PreferenceTemplate(
|
||||
title = { Text(text = label) },
|
||||
endWidget = { ColorDot(color = MaterialTheme.colorScheme.primary) },
|
||||
modifier = Modifier.clickable { bottomSheetShown = true },
|
||||
endWidget = { ColorDot(Color(adapter.state.value.colorPreferenceEntry.lightColor())) },
|
||||
description = {
|
||||
if (description != null) {
|
||||
Text(text = description)
|
||||
}
|
||||
Text(text = adapter.state.value.colorPreferenceEntry.label())
|
||||
},
|
||||
modifier = Modifier.clickable { navController.navigate(route = "/colorSelection/${preference.key}/") },
|
||||
)
|
||||
|
||||
if (bottomSheetShown) {
|
||||
bottomSheetHandler.onDismiss { bottomSheetShown = false }
|
||||
bottomSheetHandler.show {
|
||||
val pagerState = rememberPagerState(defaultTabIndex)
|
||||
val scope = rememberCoroutineScope()
|
||||
val scrollToPage = { page: Int -> scope.launch { pagerState.animateScrollToPage(page) } }
|
||||
AlertBottomSheetContent(
|
||||
title = { Text(text = label) },
|
||||
buttons = {
|
||||
Button(onClick = { bottomSheetHandler.hide() }) {
|
||||
Text(text = stringResource(id = R.string.done))
|
||||
}
|
||||
}
|
||||
) {
|
||||
Column {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
) {
|
||||
Chip(
|
||||
label = stringResource(id = R.string.dynamic),
|
||||
onClick = { scrollToPage(0) },
|
||||
currentOffset = pagerState.currentPage + pagerState.currentPageOffset,
|
||||
page = 0,
|
||||
)
|
||||
Chip(
|
||||
label = stringResource(id = R.string.presets),
|
||||
onClick = { scrollToPage(1) },
|
||||
currentOffset = pagerState.currentPage + pagerState.currentPageOffset,
|
||||
page = 1,
|
||||
)
|
||||
}
|
||||
HorizontalPager(
|
||||
count = 2,
|
||||
state = pagerState,
|
||||
verticalAlignment = Alignment.Top,
|
||||
modifier = Modifier.pagerHeight(
|
||||
dynamicCount = dynamicEntries.size,
|
||||
staticCount = staticEntries.size,
|
||||
),
|
||||
) { page ->
|
||||
when (page) {
|
||||
0 -> {
|
||||
PresetsList(
|
||||
dynamicEntries = dynamicEntries,
|
||||
adapter = adapter,
|
||||
)
|
||||
}
|
||||
1 -> {
|
||||
SwatchGrid(
|
||||
entries = staticEntries,
|
||||
modifier = Modifier.padding(
|
||||
start = 16.dp,
|
||||
top = 20.dp,
|
||||
end = 16.dp,
|
||||
bottom = 16.dp,
|
||||
),
|
||||
onSwatchClick = { selectedColor = it },
|
||||
isSwatchSelected = { it == selectedColor },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PresetsList(
|
||||
dynamicEntries: List<ColorPreferenceEntry<ColorOption>>,
|
||||
adapter: PreferenceAdapter<ColorOption>,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight(),
|
||||
contentAlignment = Alignment.TopStart
|
||||
) {
|
||||
Column(modifier = Modifier.padding(top = 16.dp)) {
|
||||
dynamicEntries.mapIndexed { index, entry ->
|
||||
key(entry) {
|
||||
if (index > 0) {
|
||||
PreferenceDivider(startIndent = 40.dp)
|
||||
}
|
||||
PreferenceTemplate(
|
||||
title = { Text(text = entry.label()) },
|
||||
verticalPadding = 12.dp,
|
||||
modifier = Modifier.clickable { adapter.onChange(entry.value) },
|
||||
startWidget = {
|
||||
RadioButton(
|
||||
selected = entry.value == adapter.state.value,
|
||||
onClick = null
|
||||
)
|
||||
ColorDot(
|
||||
entry = entry,
|
||||
modifier = Modifier.padding(start = 16.dp)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open class ColorPreferenceEntry<T>(
|
||||
val value: T,
|
||||
val label: @Composable () -> String,
|
||||
val lightColor: @Composable () -> Int,
|
||||
val darkColor: @Composable () -> Int = { lightenColor(lightColor()) },
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
package app.lawnchair.ui.preferences.components.colorpreference
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import app.lawnchair.ui.theme.lightenColor
|
||||
|
||||
open class ColorPreferenceEntry<T>(
|
||||
val value: T,
|
||||
val label: @Composable () -> String,
|
||||
val lightColor: @Composable () -> Int,
|
||||
val darkColor: @Composable () -> Int = { lightenColor(lightColor()) },
|
||||
)
|
||||
@@ -0,0 +1,168 @@
|
||||
package app.lawnchair.ui.preferences.components.colorpreference
|
||||
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.navArgument
|
||||
import app.lawnchair.preferences.getAdapter
|
||||
import app.lawnchair.preferences2.preferenceManager2
|
||||
import app.lawnchair.theme.color.ColorOption
|
||||
import app.lawnchair.ui.preferences.components.*
|
||||
import app.lawnchair.ui.preferences.components.colorpreference.pickers.CustomColorPicker
|
||||
import app.lawnchair.ui.preferences.components.colorpreference.pickers.PresetsList
|
||||
import app.lawnchair.ui.preferences.components.colorpreference.pickers.SwatchGrid
|
||||
import app.lawnchair.ui.preferences.preferenceGraph
|
||||
import com.android.launcher3.R
|
||||
import com.google.accompanist.navigation.animation.composable
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import com.google.accompanist.pager.HorizontalPager
|
||||
import com.google.accompanist.pager.rememberPagerState
|
||||
import com.patrykmichalik.opto.domain.Preference
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
fun NavGraphBuilder.colorSelectionGraph(route: String) {
|
||||
preferenceGraph(route, {}) { subRoute ->
|
||||
composable(
|
||||
route = subRoute("{prefKey}"),
|
||||
arguments = listOf(
|
||||
navArgument("prefKey") { type = NavType.StringType },
|
||||
),
|
||||
) { backStackEntry ->
|
||||
|
||||
val args = backStackEntry.arguments!!
|
||||
val prefKey = args.getString("prefKey")!!
|
||||
val preferenceManager2 = preferenceManager2()
|
||||
val pref = when (prefKey) {
|
||||
preferenceManager2.accentColor.key.name -> preferenceManager2.accentColor
|
||||
else -> return@composable
|
||||
}
|
||||
val label = when (prefKey) {
|
||||
preferenceManager2.accentColor.key.name -> stringResource(id = R.string.accent_color)
|
||||
else -> return@composable
|
||||
}
|
||||
ColorSelection(label = label, preference = pref)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPagerApi::class)
|
||||
@Composable
|
||||
fun ColorSelection(
|
||||
label: String,
|
||||
preference: Preference<ColorOption, String, *>,
|
||||
dynamicEntries: List<ColorPreferenceEntry<ColorOption>> = dynamicColors,
|
||||
staticEntries: List<ColorPreferenceEntry<ColorOption>> = staticColors,
|
||||
) {
|
||||
|
||||
val adapter = preference.getAdapter()
|
||||
val appliedColor by adapter
|
||||
val appliedEntry = dynamicEntries.firstOrNull { it.value == appliedColor }
|
||||
?: staticEntries.firstOrNull { it.value == appliedColor }
|
||||
val selectedColor = remember { mutableStateOf(appliedColor) }
|
||||
val defaultTabIndex = when {
|
||||
dynamicEntries.any { it.value == appliedColor } -> 0
|
||||
appliedEntry?.value is ColorOption.CustomColor -> 2
|
||||
else -> 1
|
||||
}
|
||||
|
||||
PreferenceLayout(
|
||||
label = label,
|
||||
bottomBar = {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
onClick = {
|
||||
adapter.onChange(newValue = selectedColor.value)
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.apply_grid))
|
||||
}
|
||||
BottomSpacer()
|
||||
}
|
||||
},
|
||||
) {
|
||||
|
||||
val pagerState = rememberPagerState(defaultTabIndex)
|
||||
val scope = rememberCoroutineScope()
|
||||
val scrollToPage =
|
||||
{ page: Int -> scope.launch { pagerState.animateScrollToPage(page) } }
|
||||
|
||||
Column {
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
) {
|
||||
Chip(
|
||||
label = stringResource(id = R.string.dynamic),
|
||||
onClick = { scrollToPage(0) },
|
||||
currentOffset = pagerState.currentPage + pagerState.currentPageOffset,
|
||||
page = 0,
|
||||
)
|
||||
Chip(
|
||||
label = stringResource(id = R.string.presets),
|
||||
onClick = { scrollToPage(1) },
|
||||
currentOffset = pagerState.currentPage + pagerState.currentPageOffset,
|
||||
page = 1,
|
||||
)
|
||||
Chip(
|
||||
label = stringResource(id = R.string.custom),
|
||||
onClick = { scrollToPage(2) },
|
||||
currentOffset = pagerState.currentPage + pagerState.currentPageOffset,
|
||||
page = 2,
|
||||
)
|
||||
}
|
||||
HorizontalPager(
|
||||
count = 3,
|
||||
state = pagerState,
|
||||
verticalAlignment = Alignment.Top,
|
||||
modifier = Modifier.animateContentSize(),
|
||||
) { page ->
|
||||
when (page) {
|
||||
0 -> {
|
||||
PresetsList(
|
||||
dynamicEntries = dynamicEntries,
|
||||
onPresetClick = { selectedColor.value = it },
|
||||
isPresetSelected = { it == selectedColor.value },
|
||||
)
|
||||
}
|
||||
1 -> {
|
||||
SwatchGrid(
|
||||
modifier = Modifier.padding(top = 12.dp),
|
||||
contentModifier = Modifier.padding(
|
||||
start = 16.dp,
|
||||
top = 20.dp,
|
||||
end = 16.dp,
|
||||
bottom = 16.dp,
|
||||
),
|
||||
entries = staticEntries,
|
||||
onSwatchClick = { selectedColor.value = it },
|
||||
isSwatchSelected = { it == selectedColor.value },
|
||||
)
|
||||
}
|
||||
2 -> {
|
||||
CustomColorPicker(
|
||||
selectedColorOption = selectedColor.value,
|
||||
onSelect = { selectedColor.value = it },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package app.lawnchair.ui.preferences.components.colorpreference
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.ContentAlpha
|
||||
import androidx.compose.material.LocalContentAlpha
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.util.toRange
|
||||
import app.lawnchair.ui.preferences.components.PreferenceTemplate
|
||||
import app.lawnchair.ui.preferences.components.getSteps
|
||||
import app.lawnchair.ui.preferences.components.snapSliderValue
|
||||
import com.android.launcher3.R
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Composable
|
||||
fun RgbColorSlider(
|
||||
label: String,
|
||||
colorStart: Color,
|
||||
colorEnd: Color,
|
||||
value: Int,
|
||||
onValueChange: (Float) -> Unit,
|
||||
) {
|
||||
|
||||
val step = 0f
|
||||
val rgbRange = 0f..255f
|
||||
|
||||
PreferenceTemplate(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(bottom = 12.dp),
|
||||
title = {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp),
|
||||
) {
|
||||
Text(text = label)
|
||||
CompositionLocalProvider(
|
||||
LocalContentAlpha provides ContentAlpha.medium,
|
||||
LocalContentColor provides androidx.compose.material.MaterialTheme.colors.onBackground,
|
||||
) {
|
||||
val valueText = snapSliderValue(rgbRange.start, value.toFloat(), step)
|
||||
.roundToInt().toString()
|
||||
Text(text = valueText)
|
||||
}
|
||||
}
|
||||
},
|
||||
description = {
|
||||
Row(
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.requiredSize(6.dp)
|
||||
.clip(CircleShape)
|
||||
.background(colorStart),
|
||||
)
|
||||
Slider(
|
||||
value = value.toFloat(),
|
||||
onValueChange = onValueChange,
|
||||
valueRange = rgbRange,
|
||||
steps = getSteps(rgbRange, step),
|
||||
modifier = Modifier
|
||||
.height(24.dp)
|
||||
.weight(1f)
|
||||
.padding(horizontal = 3.dp),
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.requiredSize(6.dp)
|
||||
.clip(CircleShape)
|
||||
.background(colorEnd),
|
||||
)
|
||||
}
|
||||
},
|
||||
applyPaddings = false,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HsbColorSlider(
|
||||
type: HsbSliderType,
|
||||
value: Float,
|
||||
onValueChange: (Float) -> Unit,
|
||||
) {
|
||||
|
||||
val step = 0f
|
||||
|
||||
val range = when (type) {
|
||||
HsbSliderType.HUE -> 0f..359f
|
||||
else -> 0f..1f
|
||||
}
|
||||
|
||||
val showAsPercentage = when (type) {
|
||||
HsbSliderType.HUE -> false
|
||||
else -> true
|
||||
}
|
||||
|
||||
val label = when (type) {
|
||||
HsbSliderType.HUE -> stringResource(id = R.string.hsb_hue)
|
||||
HsbSliderType.SATURATION -> stringResource(id = R.string.hsb_saturation)
|
||||
HsbSliderType.BRIGHTNESS -> stringResource(id = R.string.hsb_brightness)
|
||||
}
|
||||
|
||||
PreferenceTemplate(
|
||||
modifier = Modifier.padding(horizontal = 8.dp),
|
||||
title = {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 6.dp)
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp),
|
||||
) {
|
||||
Text(text = label)
|
||||
CompositionLocalProvider(
|
||||
LocalContentAlpha provides ContentAlpha.medium,
|
||||
LocalContentColor provides androidx.compose.material.MaterialTheme.colors.onBackground,
|
||||
) {
|
||||
val valueText = snapSliderValue(range.start, value, step)
|
||||
Text(
|
||||
text = if (showAsPercentage) stringResource(
|
||||
id = R.string.n_percent,
|
||||
(valueText * 100).roundToInt(),
|
||||
) else "${valueText.roundToInt()}°",
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
description = {
|
||||
Column(
|
||||
modifier = Modifier.padding(top = 4.dp, bottom = 12.dp),
|
||||
) {
|
||||
if (type == HsbSliderType.HUE) {
|
||||
val brushColors = arrayListOf<Color>()
|
||||
val stepSize = 6
|
||||
repeat((range.endInclusive - range.start).toInt() / stepSize) {
|
||||
val newColor = Color.hsv(
|
||||
hue = it * stepSize.toFloat(),
|
||||
saturation = 1f,
|
||||
value = 1f,
|
||||
)
|
||||
brushColors.add(newColor)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 8.dp)
|
||||
.padding(horizontal = 6.dp)
|
||||
.requiredHeight(24.dp)
|
||||
.clip(RoundedCornerShape(6.dp))
|
||||
.background(brush = Brush.horizontalGradient(brushColors)),
|
||||
)
|
||||
}
|
||||
Slider(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
onValueChangeFinished = { },
|
||||
valueRange = range,
|
||||
steps = getSteps(range, step),
|
||||
colors = SliderDefaults.colors(),
|
||||
modifier = Modifier
|
||||
.height(24.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
},
|
||||
applyPaddings = false,
|
||||
)
|
||||
}
|
||||
|
||||
enum class HsbSliderType {
|
||||
HUE, SATURATION, BRIGHTNESS
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package app.lawnchair.ui.preferences.components.colorpreference
|
||||
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.LayoutModifier
|
||||
import androidx.compose.ui.layout.Measurable
|
||||
import androidx.compose.ui.layout.MeasureResult
|
||||
import androidx.compose.ui.layout.MeasureScope
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.unit.constrainHeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import java.util.*
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
fun Modifier.pagerHeight(
|
||||
dynamicCount: Int,
|
||||
staticCount: Int
|
||||
) = this.then(
|
||||
PagerHeightModifier(
|
||||
dynamicCount = dynamicCount,
|
||||
staticCount = staticCount
|
||||
)
|
||||
)
|
||||
|
||||
class PagerHeightModifier(
|
||||
private val dynamicCount: Int,
|
||||
private val staticCount: Int
|
||||
) : LayoutModifier {
|
||||
|
||||
override fun MeasureScope.measure(
|
||||
measurable: Measurable,
|
||||
constraints: Constraints
|
||||
): MeasureResult {
|
||||
val dynamicItemHeight = 54.dp.roundToPx()
|
||||
val dynamicListHeight = 16.dp.roundToPx() +
|
||||
dynamicItemHeight * dynamicCount +
|
||||
1.dp.roundToPx() * (dynamicCount - 1)
|
||||
|
||||
val columnCount = SwatchGridDefaults.ColumnCount
|
||||
val rowCount = (staticCount - 1) / columnCount + 1
|
||||
|
||||
val width = constraints.maxWidth
|
||||
val horizontalPadding = 16.dp.roundToPx() * 2
|
||||
val gutterSizePx = SwatchGridDefaults.GutterSize.roundToPx()
|
||||
val totalGutterWidth = gutterSizePx * (columnCount - 1)
|
||||
val availableWidth = width - horizontalPadding - totalGutterWidth
|
||||
val swatchMaxWidth = SwatchGridDefaults.SwatchMaxWidth.roundToPx()
|
||||
val swatchWidth = min(availableWidth / columnCount, swatchMaxWidth)
|
||||
|
||||
val swatchGridVerticalPadding = 20.dp.roundToPx() + 16.dp.roundToPx()
|
||||
val swatchGridInnerHeight = swatchWidth * rowCount + gutterSizePx * (rowCount - 1)
|
||||
val swatchGridHeight = swatchGridInnerHeight + swatchGridVerticalPadding
|
||||
|
||||
val height = constraints.constrainHeight(max(dynamicListHeight, swatchGridHeight))
|
||||
|
||||
val placeable = measurable.measure(constraints.copy(maxHeight = height))
|
||||
return layout(width, constraints.constrainHeight(swatchGridHeight)) {
|
||||
placeable.place(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return Objects.hash(dynamicCount, staticCount)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is PagerHeightModifier
|
||||
&& dynamicCount == other.dynamicCount
|
||||
&& staticCount == other.staticCount
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
package app.lawnchair.ui.preferences.components.colorpreference.pickers
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.graphics.blue
|
||||
import androidx.core.graphics.green
|
||||
import androidx.core.graphics.red
|
||||
import app.lawnchair.theme.color.ColorOption
|
||||
import app.lawnchair.ui.preferences.components.Chip
|
||||
import app.lawnchair.ui.preferences.components.DividerColumn
|
||||
import app.lawnchair.ui.preferences.components.PreferenceGroup
|
||||
import app.lawnchair.ui.preferences.components.colorpreference.*
|
||||
import com.android.launcher3.R
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import com.google.accompanist.pager.HorizontalPager
|
||||
import com.google.accompanist.pager.rememberPagerState
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Unlike [PresetsList] & [SwatchGrid], This Composable allows the user to select a fully custom [ColorOption] using HEX, HSB & RGB values.
|
||||
*
|
||||
* @see HexColorPicker
|
||||
* @see HsvColorPicker
|
||||
* @see RgbColorPicker
|
||||
*/
|
||||
@OptIn(ExperimentalPagerApi::class)
|
||||
@Composable
|
||||
fun CustomColorPicker(
|
||||
modifier: Modifier = Modifier,
|
||||
selectedColorOption: ColorOption,
|
||||
onSelect: (ColorOption) -> Unit,
|
||||
) {
|
||||
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
val selectedColor = selectedColorOption.colorPreferenceEntry.lightColor()
|
||||
val selectedColorCompose = Color(selectedColor)
|
||||
|
||||
val textFieldValue = remember {
|
||||
mutableStateOf(
|
||||
TextFieldValue(
|
||||
text = intColorToColorString(color = selectedColor),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Column(modifier = modifier) {
|
||||
|
||||
PreferenceGroup(heading = stringResource(id = R.string.hex)) {
|
||||
|
||||
Row(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.requiredSize(48.dp)
|
||||
.clip(CircleShape)
|
||||
.background(selectedColorCompose),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.requiredWidth(16.dp))
|
||||
|
||||
HexColorPicker(
|
||||
textFieldValue = textFieldValue.value,
|
||||
onTextFieldValueChange = { newValue ->
|
||||
val newText = newValue.text.removePrefix("#").take(6).uppercase()
|
||||
textFieldValue.value = newValue.copy(text = newText)
|
||||
val newColor = colorStringToIntColor(colorString = newText)
|
||||
if (newColor != null) {
|
||||
onSelect(ColorOption.CustomColor(newColor))
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
val pagerState = rememberPagerState(0)
|
||||
val scope = rememberCoroutineScope()
|
||||
val scrollToPage =
|
||||
{ page: Int -> scope.launch { pagerState.animateScrollToPage(page) } }
|
||||
|
||||
PreferenceGroup(
|
||||
heading = stringResource(id = R.string.color_sliders),
|
||||
) {
|
||||
|
||||
Column {
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(top = 12.dp),
|
||||
) {
|
||||
Chip(
|
||||
label = stringResource(id = R.string.hsb),
|
||||
onClick = { scrollToPage(0) },
|
||||
currentOffset = pagerState.currentPage + pagerState.currentPageOffset,
|
||||
page = 0,
|
||||
)
|
||||
Chip(
|
||||
label = stringResource(id = R.string.rgb),
|
||||
onClick = { scrollToPage(1) },
|
||||
currentOffset = pagerState.currentPage + pagerState.currentPageOffset,
|
||||
page = 1,
|
||||
)
|
||||
}
|
||||
|
||||
HorizontalPager(
|
||||
modifier = Modifier.animateContentSize(),
|
||||
count = 2,
|
||||
state = pagerState,
|
||||
verticalAlignment = Alignment.Top,
|
||||
) { page ->
|
||||
when (page) {
|
||||
0 -> {
|
||||
HsvColorPicker(
|
||||
selectedColor = selectedColor,
|
||||
onSelectedColorChange = {
|
||||
textFieldValue.value =
|
||||
textFieldValue.value.copy(
|
||||
text = intColorToColorString(
|
||||
selectedColor,
|
||||
),
|
||||
)
|
||||
},
|
||||
onSliderValuesChange = { newColor ->
|
||||
focusManager.clearFocus()
|
||||
onSelect(newColor)
|
||||
}
|
||||
)
|
||||
}
|
||||
1 -> {
|
||||
RgbColorPicker(
|
||||
selectedColor = selectedColor,
|
||||
onSelectedColorChange = {
|
||||
textFieldValue.value =
|
||||
textFieldValue.value.copy(
|
||||
text = intColorToColorString(selectedColor),
|
||||
)
|
||||
},
|
||||
onSliderValuesChange = { newColor ->
|
||||
focusManager.clearFocus()
|
||||
onSelect(newColor)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HexColorPicker(
|
||||
textFieldValue: TextFieldValue,
|
||||
onTextFieldValueChange: (TextFieldValue) -> Unit,
|
||||
) {
|
||||
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
val invalidString = colorStringToIntColor(textFieldValue.text) == null
|
||||
|
||||
OutlinedTextField(
|
||||
textStyle = LocalTextStyle.current.copy(
|
||||
fontSize = 18.sp,
|
||||
textAlign = TextAlign.Start,
|
||||
),
|
||||
isError = invalidString,
|
||||
value = textFieldValue,
|
||||
onValueChange = onTextFieldValueChange,
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
capitalization = KeyboardCapitalization.Characters,
|
||||
autoCorrect = false,
|
||||
imeAction = ImeAction.Done,
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = {
|
||||
focusManager.clearFocus()
|
||||
},
|
||||
),
|
||||
trailingIcon = {
|
||||
Crossfade(targetState = invalidString) {
|
||||
if (it) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_warning),
|
||||
contentDescription = stringResource(id = R.string.invalid_color),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HsvColorPicker(
|
||||
selectedColor: Int,
|
||||
onSelectedColorChange: () -> Unit,
|
||||
onSliderValuesChange: (ColorOption.CustomColor) -> Unit,
|
||||
) {
|
||||
|
||||
val hue = remember { mutableStateOf(intColorToHsvColorArray(selectedColor)[0]) }
|
||||
val saturation = remember { mutableStateOf(intColorToHsvColorArray(selectedColor)[1]) }
|
||||
val brightness = remember { mutableStateOf(intColorToHsvColorArray(selectedColor)[2]) }
|
||||
|
||||
DividerColumn {
|
||||
|
||||
HsbColorSlider(
|
||||
type = HsbSliderType.HUE,
|
||||
value = hue.value,
|
||||
onValueChange = { newValue ->
|
||||
hue.value = newValue
|
||||
},
|
||||
)
|
||||
HsbColorSlider(
|
||||
type = HsbSliderType.SATURATION,
|
||||
value = saturation.value,
|
||||
onValueChange = { newValue ->
|
||||
saturation.value = newValue
|
||||
},
|
||||
)
|
||||
HsbColorSlider(
|
||||
type = HsbSliderType.BRIGHTNESS,
|
||||
value = brightness.value,
|
||||
onValueChange = { newValue ->
|
||||
brightness.value = newValue
|
||||
},
|
||||
)
|
||||
|
||||
LaunchedEffect(key1 = selectedColor) {
|
||||
val hsv = intColorToHsvColorArray(selectedColor)
|
||||
hue.value = hsv[0]
|
||||
saturation.value = hsv[1]
|
||||
brightness.value = hsv[2]
|
||||
onSelectedColorChange()
|
||||
}
|
||||
|
||||
LaunchedEffect(
|
||||
key1 = hue.value,
|
||||
key2 = saturation.value,
|
||||
key3 = brightness.value,
|
||||
) {
|
||||
onSliderValuesChange(
|
||||
ColorOption.CustomColor(
|
||||
hsvValuesToIntColor(
|
||||
hue = hue.value,
|
||||
saturation = saturation.value,
|
||||
brightness = brightness.value,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RgbColorPicker(
|
||||
selectedColor: Int,
|
||||
selectedColorCompose: Color = Color(selectedColor),
|
||||
onSelectedColorChange: () -> Unit,
|
||||
onSliderValuesChange: (ColorOption.CustomColor) -> Unit,
|
||||
) {
|
||||
|
||||
val red = remember { mutableStateOf(selectedColor.red) }
|
||||
val green = remember { mutableStateOf(selectedColor.green) }
|
||||
val blue = remember { mutableStateOf(selectedColor.blue) }
|
||||
|
||||
DividerColumn {
|
||||
|
||||
RgbColorSlider(
|
||||
label = stringResource(id = R.string.rgb_red),
|
||||
value = red.value,
|
||||
colorStart = selectedColorCompose.copy(red = 0f),
|
||||
colorEnd = selectedColorCompose.copy(red = 1f),
|
||||
onValueChange = { newValue ->
|
||||
red.value = newValue.toInt()
|
||||
},
|
||||
)
|
||||
RgbColorSlider(
|
||||
label = stringResource(id = R.string.rgb_green),
|
||||
value = green.value,
|
||||
colorStart = selectedColorCompose.copy(green = 0f),
|
||||
colorEnd = selectedColorCompose.copy(green = 1f),
|
||||
onValueChange = { newValue ->
|
||||
green.value = newValue.toInt()
|
||||
},
|
||||
)
|
||||
RgbColorSlider(
|
||||
label = stringResource(id = R.string.rgb_blue),
|
||||
value = blue.value,
|
||||
colorStart = selectedColorCompose.copy(blue = 0f),
|
||||
colorEnd = selectedColorCompose.copy(blue = 1f),
|
||||
onValueChange = { newValue ->
|
||||
blue.value = newValue.toInt()
|
||||
},
|
||||
)
|
||||
|
||||
LaunchedEffect(key1 = selectedColor) {
|
||||
red.value = selectedColor.red
|
||||
green.value = selectedColor.green
|
||||
blue.value = selectedColor.blue
|
||||
onSelectedColorChange()
|
||||
}
|
||||
|
||||
LaunchedEffect(
|
||||
key1 = red.value,
|
||||
key2 = green.value,
|
||||
key3 = blue.value,
|
||||
) {
|
||||
onSliderValuesChange(
|
||||
ColorOption.CustomColor(
|
||||
android.graphics.Color.argb(
|
||||
255,
|
||||
red.value,
|
||||
green.value,
|
||||
blue.value,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package app.lawnchair.ui.preferences.components.colorpreference.pickers
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.lawnchair.theme.color.ColorOption
|
||||
import app.lawnchair.ui.preferences.components.PreferenceDivider
|
||||
import app.lawnchair.ui.preferences.components.PreferenceGroup
|
||||
import app.lawnchair.ui.preferences.components.PreferenceTemplate
|
||||
import app.lawnchair.ui.preferences.components.colorpreference.ColorDot
|
||||
import app.lawnchair.ui.preferences.components.colorpreference.ColorPreferenceEntry
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun PresetsList(
|
||||
dynamicEntries: List<ColorPreferenceEntry<ColorOption>>,
|
||||
onPresetClick: (ColorOption) -> Unit,
|
||||
isPresetSelected: (ColorOption) -> Boolean,
|
||||
) {
|
||||
|
||||
PreferenceGroup(
|
||||
modifier = Modifier.padding(top = 12.dp),
|
||||
showDividers = false,
|
||||
) {
|
||||
|
||||
dynamicEntries.mapIndexed { index, entry ->
|
||||
key(entry) {
|
||||
if (index > 0) {
|
||||
PreferenceDivider(startIndent = 40.dp)
|
||||
}
|
||||
PreferenceTemplate(
|
||||
title = { Text(text = entry.label()) },
|
||||
verticalPadding = 12.dp,
|
||||
modifier = Modifier.clickable { onPresetClick(entry.value) },
|
||||
startWidget = {
|
||||
RadioButton(
|
||||
selected = isPresetSelected(entry.value),
|
||||
onClick = null,
|
||||
)
|
||||
ColorDot(
|
||||
entry = entry,
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package app.lawnchair.ui.preferences.components.colorpreference
|
||||
package app.lawnchair.ui.preferences.components.colorpreference.pickers
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.background
|
||||
@@ -15,6 +15,8 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.lawnchair.ui.preferences.components.PreferenceGroup
|
||||
import app.lawnchair.ui.preferences.components.colorpreference.ColorPreferenceEntry
|
||||
|
||||
object SwatchGridDefaults {
|
||||
val GutterSize = 12.dp
|
||||
@@ -24,41 +26,47 @@ object SwatchGridDefaults {
|
||||
|
||||
@Composable
|
||||
fun <T> SwatchGrid(
|
||||
modifier: Modifier = Modifier,
|
||||
contentModifier: Modifier = Modifier,
|
||||
entries: List<ColorPreferenceEntry<T>>,
|
||||
onSwatchClick: (T) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
isSwatchSelected: (T) -> Boolean
|
||||
) {
|
||||
val columnCount = SwatchGridDefaults.ColumnCount
|
||||
val rowCount = (entries.size - 1) / columnCount + 1
|
||||
val gutter = SwatchGridDefaults.GutterSize
|
||||
|
||||
Column(modifier = modifier) {
|
||||
for (rowNo in 1..rowCount) {
|
||||
val firstIndex = (rowNo - 1) * columnCount
|
||||
val lastIndex = firstIndex + columnCount - 1
|
||||
val indices = firstIndex..lastIndex
|
||||
PreferenceGroup(
|
||||
modifier = modifier,
|
||||
showDividers = false,
|
||||
) {
|
||||
Column(modifier = contentModifier) {
|
||||
for (rowNo in 1..rowCount) {
|
||||
val firstIndex = (rowNo - 1) * columnCount
|
||||
val lastIndex = firstIndex + columnCount - 1
|
||||
val indices = firstIndex..lastIndex
|
||||
|
||||
Row {
|
||||
entries.slice(indices).forEachIndexed { index, colorOption ->
|
||||
Box(
|
||||
modifier = Modifier.weight(1f),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
ColorSwatch(
|
||||
entry = colorOption,
|
||||
onClick = { onSwatchClick(colorOption.value) },
|
||||
modifier = Modifier.widthIn(0.dp, SwatchGridDefaults.SwatchMaxWidth),
|
||||
selected = isSwatchSelected(colorOption.value)
|
||||
)
|
||||
}
|
||||
if (index != columnCount - 1) {
|
||||
Spacer(modifier = Modifier.width(gutter))
|
||||
Row {
|
||||
entries.slice(indices).forEachIndexed { index, colorOption ->
|
||||
Box(
|
||||
modifier = Modifier.weight(1f),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
ColorSwatch(
|
||||
entry = colorOption,
|
||||
onClick = { onSwatchClick(colorOption.value) },
|
||||
modifier = Modifier.widthIn(0.dp, SwatchGridDefaults.SwatchMaxWidth),
|
||||
selected = isSwatchSelected(colorOption.value),
|
||||
)
|
||||
}
|
||||
if (index != columnCount - 1) {
|
||||
Spacer(modifier = Modifier.width(gutter))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rowNo != rowCount) {
|
||||
Spacer(modifier = Modifier.height(gutter))
|
||||
if (rowNo != rowCount) {
|
||||
Spacer(modifier = Modifier.height(gutter))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user