(feat) transparency schemes

This commit is contained in:
MM20
2025-06-14 17:17:07 +02:00
parent 68874f1d87
commit 167be6e34d
61 changed files with 1608 additions and 904 deletions
@@ -1,6 +1,10 @@
package de.mm20.launcher2.ui.base
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import de.mm20.launcher2.preferences.IconShape
import de.mm20.launcher2.preferences.ui.CardStyle
import de.mm20.launcher2.preferences.ui.GridSettings
@@ -9,8 +13,6 @@ import de.mm20.launcher2.ui.component.ProvideIconShape
import de.mm20.launcher2.ui.locals.LocalCardStyle
import de.mm20.launcher2.ui.locals.LocalFavoritesEnabled
import de.mm20.launcher2.ui.locals.LocalGridSettings
import de.mm20.launcher2.ui.theme.transparency.LocalTransparencyScheme
import de.mm20.launcher2.ui.theme.transparency.TransparencyScheme
import de.mm20.launcher2.widgets.FavoritesWidget
import de.mm20.launcher2.widgets.WidgetRepository
import kotlinx.coroutines.flow.combine
@@ -48,10 +50,6 @@ fun ProvideSettings(
LocalCardStyle provides cardStyle,
LocalFavoritesEnabled provides favoritesEnabled,
LocalGridSettings provides gridSettings,
LocalTransparencyScheme provides TransparencyScheme(
background = cardStyle.opacity * 0.85f,
surface = cardStyle.opacity,
)
) {
ProvideIconShape(iconShape) {
content()
@@ -40,7 +40,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.themes.Colors
import de.mm20.launcher2.themes.colors.Colors
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.LargeMessage
@@ -5,9 +5,8 @@ import android.net.Uri
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.preferences.ColorsDescriptor
import de.mm20.launcher2.preferences.ui.UiSettings
import de.mm20.launcher2.themes.Colors
import de.mm20.launcher2.themes.colors.Colors
import de.mm20.launcher2.themes.ThemeRepository
import de.mm20.launcher2.themes.fromLegacyJson
import kotlinx.coroutines.Dispatchers
@@ -58,9 +57,9 @@ class ImportThemeSheetVM : ViewModel(), KoinComponent {
}
private fun importTheme(colors: Colors, apply: Boolean) {
themeRepository.createColors(colors)
themeRepository.colors.create(colors)
if (apply) {
uiSettings.setColors(ColorsDescriptor.Custom(colors.id.toString()))
uiSettings.setColorsId(colors.id)
}
}
}
@@ -1,39 +1,29 @@
package de.mm20.launcher2.ui.component
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.material3.LocalAbsoluteTonalElevation
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.drawOutline
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.withTransform
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.ui.locals.LocalCardStyle
import de.mm20.launcher2.ui.theme.transparency.LocalTransparencyScheme
import de.mm20.launcher2.ui.theme.transparency.transparency
@Composable
fun LauncherCard(
modifier: Modifier = Modifier,
elevation: Dp = 2.dp,
backgroundOpacity: Float = LocalTransparencyScheme.current.surface,
backgroundOpacity: Float = MaterialTheme.transparency.surface,
shape: Shape = MaterialTheme.shapes.medium,
color: Color = MaterialTheme.colorScheme.surface.copy(alpha = backgroundOpacity.coerceIn(0f, 1f)),
color: Color = MaterialTheme.colorScheme.surface.copy(
alpha = backgroundOpacity.coerceIn(
0f,
1f
)
),
border: BorderStroke? = LocalCardStyle.current.borderWidth.takeIf { it > 0 }
?.let { BorderStroke(it.dp, MaterialTheme.colorScheme.surface) },
content: @Composable () -> Unit = {}
@@ -48,150 +38,4 @@ fun LauncherCard(
shadowElevation = if (backgroundOpacity == 1f) elevation else 0.dp,
tonalElevation = elevation,
)
}
@Composable
fun PartialLauncherCard(
modifier: Modifier = Modifier,
isTop: Boolean = false,
isBottom: Boolean = false,
elevation: Dp = 2.dp,
backgroundOpacity: Float = LocalTransparencyScheme.current.surface,
content: @Composable () -> Unit
) {
if (isTop && isBottom) {
LauncherCard(modifier = modifier, content = content)
} else if (!isTop && !isBottom) {
CardMiddlePiece(modifier = modifier, elevation = elevation, content = content)
} else {
CardEndPiece(
modifier = modifier,
isTop = isTop,
isBottom = isBottom,
elevation = elevation,
backgroundOpacity = backgroundOpacity,
content = content
)
}
}
@Composable
private fun CardMiddlePiece(
modifier: Modifier,
elevation: Dp,
backgroundOpacity: Float = LocalTransparencyScheme.current.surface,
content: @Composable () -> Unit
) {
val borderWidth = LocalCardStyle.current.borderWidth.dp
val borderColor = MaterialTheme.colorScheme.surface
val absoluteElevation = LocalAbsoluteTonalElevation.current + elevation
Box(
modifier = modifier
.shadow(if (backgroundOpacity < 1f) 0.dp else elevation, RectangleShape, true)
.background(
if (backgroundOpacity == 1f) {
MaterialTheme.colorScheme.surfaceColorAtElevation(absoluteElevation)
} else {
MaterialTheme.colorScheme.surface.copy(
alpha = backgroundOpacity.coerceIn(
0f,
1f
)
)
}
)
.drawBehind {
if (borderWidth == 0.dp) return@drawBehind
val border = borderWidth.toPx()
drawRect(
color = borderColor,
topLeft = Offset.Zero,
size = size.copy(width = border)
)
drawRect(
color = borderColor,
topLeft = Offset(size.width - border, 0f),
size = size.copy(width = border)
)
},
) {
CompositionLocalProvider(
LocalContentColor provides MaterialTheme.colorScheme.onSurface,
LocalAbsoluteTonalElevation provides absoluteElevation,
) {
content()
}
}
}
@Composable
private fun CardEndPiece(
modifier: Modifier = Modifier,
isTop: Boolean,
isBottom: Boolean,
elevation: Dp,
backgroundOpacity: Float,
content: @Composable () -> Unit,
) {
val shape = when {
isTop -> MaterialTheme.shapes.medium.copy(
bottomEnd = CornerSize(0),
bottomStart = CornerSize(0),
)
isBottom -> MaterialTheme.shapes.medium.copy(
topEnd = CornerSize(0),
topStart = CornerSize(0),
)
else -> RectangleShape
}
val borderWidth = LocalCardStyle.current.borderWidth.dp
val borderColor = MaterialTheme.colorScheme.surface
val absoluteElevation = LocalAbsoluteTonalElevation.current + elevation
Box(
modifier = modifier
.shadow(if (backgroundOpacity < 1f) 0.dp else elevation, shape, true)
.background(
if (backgroundOpacity == 1f) {
MaterialTheme.colorScheme.surfaceColorAtElevation(absoluteElevation)
} else {
MaterialTheme.colorScheme.surface.copy(
alpha = backgroundOpacity.coerceIn(
0f,
1f
)
)
}
)
.drawWithCache {
val border = borderWidth.toPx()
val outline = shape.createOutline(
size.copy(height = size.height + border),
layoutDirection,
Density(density, fontScale)
)
onDrawBehind {
if (borderWidth == 0.dp) return@onDrawBehind
withTransform({
translate(0f, if (isBottom) -border else 0f)
}) {
drawOutline(
outline,
borderColor,
style = Stroke(width = border * 2)
)
}
}
},
) {
CompositionLocalProvider(
LocalContentColor provides MaterialTheme.colorScheme.onSurface,
LocalAbsoluteTonalElevation provides absoluteElevation,
) {
content()
}
}
}
@@ -45,7 +45,7 @@ import androidx.compose.ui.unit.dp
import de.mm20.launcher2.preferences.SearchBarStyle
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.layout.BottomReversed
import de.mm20.launcher2.ui.theme.transparency.LocalTransparencyScheme
import de.mm20.launcher2.ui.theme.transparency.transparency
@Composable
fun SearchBar(
@@ -102,7 +102,7 @@ fun SearchBar(
}
}) {
when {
it == SearchBarLevel.Active -> LocalTransparencyScheme.current.surface
it == SearchBarLevel.Active -> MaterialTheme.transparency.surface
style != SearchBarStyle.Transparent -> 1f
it == SearchBarLevel.Resting -> 0f
else -> 1f
@@ -100,7 +100,7 @@ import de.mm20.launcher2.ui.launcher.helper.WallpaperBlur
import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.search.filters.KeyboardFilterBar
import de.mm20.launcher2.ui.launcher.searchbar.LauncherSearchBar
import de.mm20.launcher2.ui.theme.transparency.LocalTransparencyScheme
import de.mm20.launcher2.ui.theme.transparency.transparency
import dev.chrisbanes.haze.hazeEffect
import dev.chrisbanes.haze.hazeSource
import dev.chrisbanes.haze.rememberHazeState
@@ -1416,7 +1416,7 @@ internal fun LauncherScaffold(
.homePageAnimation(
state,
if (config.homeComponent.drawBackground) {
config.backgroundColor.copy(alpha = LocalTransparencyScheme.current.background)
config.backgroundColor.copy(alpha = MaterialTheme.transparency.background)
} else {
Color.Transparent
}
@@ -1516,7 +1516,7 @@ internal fun LauncherScaffold(
blurRadius = 4.dp
}
.background(
MaterialTheme.colorScheme.surfaceContainer.copy(alpha = LocalTransparencyScheme.current.background)
MaterialTheme.colorScheme.surfaceContainer.copy(alpha = MaterialTheme.transparency.background)
)
.statusBarsPadding()
)
@@ -1536,7 +1536,7 @@ internal fun LauncherScaffold(
blurRadius = 4.dp
}
.background(
MaterialTheme.colorScheme.surfaceContainer.copy(alpha = LocalTransparencyScheme.current.background)
MaterialTheme.colorScheme.surfaceContainer.copy(alpha = MaterialTheme.transparency.background)
)
.navigationBarsPadding()
)
@@ -1583,7 +1583,7 @@ private fun SecondaryPage(
.fillMaxSize()
.secondaryPageAnimation(
state,
config.backgroundColor.copy(alpha = LocalTransparencyScheme.current.background),
config.backgroundColor.copy(alpha = MaterialTheme.transparency.background),
)
val composable = composables[component]
@@ -53,7 +53,7 @@ import de.mm20.launcher2.ui.launcher.sheets.HiddenItemsSheet
import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager
import de.mm20.launcher2.ui.locals.LocalCardStyle
import de.mm20.launcher2.ui.locals.LocalGridSettings
import de.mm20.launcher2.ui.theme.transparency.LocalTransparencyScheme
import de.mm20.launcher2.ui.theme.transparency.transparency
@Composable
fun SearchColumn(
@@ -370,7 +370,7 @@ fun LazyListScope.SingleResult(
vertical = 4.dp,
),
color = if (highlight) MaterialTheme.colorScheme.secondaryContainer
else MaterialTheme.colorScheme.surface.copy(LocalTransparencyScheme.current.surface)
else MaterialTheme.colorScheme.surface.copy(MaterialTheme.transparency.surface)
) {
content()
}
@@ -78,6 +78,7 @@ import de.mm20.launcher2.ui.launcher.transitions.HandleEnterHomeTransition
import de.mm20.launcher2.ui.locals.LocalGridSettings
import de.mm20.launcher2.ui.locals.LocalWindowSize
import de.mm20.launcher2.ui.overlays.Overlay
import de.mm20.launcher2.ui.theme.transparency.transparency
import kotlin.math.pow
@@ -266,7 +267,7 @@ fun ItemPopup(origin: IntRect, searchable: Searchable, onDismissRequest: () -> U
) {
LauncherCard(
elevation = 8.dp * animationProgress.value,
backgroundOpacity = 1f,
backgroundOpacity = MaterialTheme.transparency.elevatedSurface,
modifier = Modifier
.placeOverlay(
origin.translate(
@@ -13,7 +13,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.ui.ktx.withCorners
import de.mm20.launcher2.ui.theme.transparency.LocalTransparencyScheme
import de.mm20.launcher2.ui.theme.transparency.transparency
import kotlin.math.ceil
fun <T : SavableSearchable> LazyListScope.GridResults(
@@ -39,7 +39,7 @@ fun <T : SavableSearchable> LazyListScope.GridResults(
bottom = if (!reverse && isBottom) 8.dp else 0.dp,
)
.background(
MaterialTheme.colorScheme.surface.copy(alpha = LocalTransparencyScheme.current.surface),
MaterialTheme.colorScheme.surface.copy(alpha = MaterialTheme.transparency.surface),
MaterialTheme.shapes.medium.withCorners(
topStart = isTop,
topEnd = isTop,
@@ -72,7 +72,7 @@ fun <T : SavableSearchable> LazyListScope.GridResults(
bottom = if (!reverse && isLast) 8.dp else 0.dp,
)
.background(
MaterialTheme.colorScheme.surface.copy(alpha = LocalTransparencyScheme.current.surface),
MaterialTheme.colorScheme.surface.copy(alpha = MaterialTheme.transparency.surface),
MaterialTheme.shapes.medium.withCorners(
topStart = isFirst && !reverse || isLast && reverse,
topEnd = isFirst && !reverse || isLast && reverse,
@@ -120,7 +120,7 @@ fun <T : SavableSearchable> LazyListScope.GridResults(
bottom = if (!reverse) 8.dp else 0.dp,
)
.background(
MaterialTheme.colorScheme.surface.copy(alpha = LocalTransparencyScheme.current.surface),
MaterialTheme.colorScheme.surface.copy(alpha = MaterialTheme.transparency.surface),
MaterialTheme.shapes.medium.withCorners(
topStart = isTop,
topEnd = isTop,
@@ -1,6 +1,5 @@
package de.mm20.launcher2.ui.launcher.search.common.list
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.updateTransition
@@ -12,7 +11,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
@@ -22,10 +20,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.ui.ktx.animateCorners
import de.mm20.launcher2.ui.ktx.animateShapeAsState
import de.mm20.launcher2.ui.layout.BottomReversed
import de.mm20.launcher2.ui.theme.transparency.LocalTransparencyScheme
import de.mm20.launcher2.ui.theme.transparency.transparency
fun <T : SavableSearchable> LazyListScope.ListResults(
key: String,
@@ -101,12 +98,13 @@ fun LazyItemScope.ListItemSurface(
content: @Composable ColumnScope.() -> Unit,
) {
val transition = updateTransition(isExpanded)
val elevation by transition.animateDp {
if (it) 2.dp else 0.dp
}
val backgroundAlpha by transition.animateFloat {
if (it) 1f else LocalTransparencyScheme.current.surface
if (it) MaterialTheme.transparency.elevatedSurface else MaterialTheme.transparency.surface
}
val elevation by transition.animateDp {
if (it && backgroundAlpha == 1f) 2.dp else 0.dp
}
val padding by transition.animateDp {
if (it) 8.dp else 1.dp
@@ -22,7 +22,7 @@ import de.mm20.launcher2.ui.common.FavoritesTagSelector
import de.mm20.launcher2.ui.component.Banner
import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid
import de.mm20.launcher2.ui.layout.BottomReversed
import de.mm20.launcher2.ui.theme.transparency.LocalTransparencyScheme
import de.mm20.launcher2.ui.theme.transparency.transparency
fun LazyListScope.SearchFavorites(
favorites: List<SavableSearchable>,
@@ -47,7 +47,7 @@ fun LazyListScope.SearchFavorites(
)
.background(
MaterialTheme.colorScheme.surface.copy(
LocalTransparencyScheme.current.surface
MaterialTheme.transparency.surface
),
MaterialTheme.shapes.medium
)
@@ -72,7 +72,7 @@ import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.plugin.PluginRepository
import de.mm20.launcher2.plugin.PluginType
import de.mm20.launcher2.search.calendar.CalendarListType
import de.mm20.launcher2.themes.atTone
import de.mm20.launcher2.themes.colors.atTone
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.base.LocalAppWidgetHost
import de.mm20.launcher2.ui.component.BottomSheetDialog
@@ -95,7 +95,6 @@ import de.mm20.launcher2.widgets.NotesWidget
import de.mm20.launcher2.widgets.WeatherWidget
import de.mm20.launcher2.widgets.Widget
import kotlinx.coroutines.flow.map
import org.koin.androidx.compose.get
import org.koin.compose.koinInject
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
@@ -41,7 +41,7 @@ import de.mm20.launcher2.ui.launcher.widgets.favorites.FavoritesWidget
import de.mm20.launcher2.ui.launcher.widgets.music.MusicWidget
import de.mm20.launcher2.ui.launcher.widgets.notes.NotesWidget
import de.mm20.launcher2.ui.launcher.widgets.weather.WeatherWidget
import de.mm20.launcher2.ui.theme.transparency.LocalTransparencyScheme
import de.mm20.launcher2.ui.theme.transparency.transparency
import de.mm20.launcher2.widgets.AppWidget
import de.mm20.launcher2.widgets.CalendarWidget
import de.mm20.launcher2.widgets.FavoritesWidget
@@ -73,7 +73,7 @@ fun WidgetItem(
} else null
val backgroundOpacity by animateFloatAsState(
if (widget is AppWidget && !widget.config.background && !editMode) 0f else LocalTransparencyScheme.current.surface,
if (widget is AppWidget && !widget.config.background && !editMode) 0f else MaterialTheme.transparency.surface,
label = "widgetCardBackgroundOpacity",
)
@@ -87,7 +87,7 @@ import de.mm20.launcher2.ui.ktx.conditional
import de.mm20.launcher2.ui.launcher.transitions.EnterHomeTransitionParams
import de.mm20.launcher2.ui.launcher.transitions.HandleEnterHomeTransition
import de.mm20.launcher2.ui.locals.LocalWindowSize
import de.mm20.launcher2.ui.theme.transparency.LocalTransparencyScheme
import de.mm20.launcher2.ui.theme.transparency.transparency
import de.mm20.launcher2.widgets.MusicWidget
import kotlin.math.min
@@ -352,7 +352,7 @@ fun MusicWidget(widget: MusicWidget) {
) {
FilledTonalIconButton(
colors = IconButtonDefaults.filledTonalIconButtonColors(
containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = LocalTransparencyScheme.current.surface),
containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = MaterialTheme.transparency.surface),
),
onClick = { viewModel.togglePause() },
) {
@@ -86,7 +86,7 @@ import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.component.Tooltip
import de.mm20.launcher2.ui.component.weather.AnimatedWeatherIcon
import de.mm20.launcher2.ui.component.weather.WeatherIcon
import de.mm20.launcher2.ui.theme.transparency.LocalTransparencyScheme
import de.mm20.launcher2.ui.theme.transparency.transparency
import de.mm20.launcher2.weather.DailyForecast
import de.mm20.launcher2.weather.Forecast
import de.mm20.launcher2.widgets.WeatherWidget
@@ -187,7 +187,7 @@ fun WeatherWidget(widget: WeatherWidget) {
val currentDayForecasts by viewModel.currentDayForecasts
Surface(
color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = LocalTransparencyScheme.current.surface),
color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = MaterialTheme.transparency.surface),
modifier = Modifier.fillMaxWidth()
) {
Column(
@@ -299,7 +299,7 @@ fun CurrentWeather(forecast: Forecast, imperialUnits: Boolean) {
topEnd = CornerSize(0),
bottomEnd = CornerSize(0)
),
color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = LocalTransparencyScheme.current.surface),
color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = MaterialTheme.transparency.surface),
) {
Text(
text = "${forecast.provider} (${
@@ -80,6 +80,10 @@ import de.mm20.launcher2.ui.settings.shapes.ShapeSchemeSettingsScreen
import de.mm20.launcher2.ui.settings.shapes.ShapeSchemesSettingsScreen
import de.mm20.launcher2.ui.settings.tags.TagsSettingsScreen
import de.mm20.launcher2.ui.settings.tasks.TasksIntegrationSettingsScreen
import de.mm20.launcher2.ui.settings.transparencies.TransparencySchemeSettingsRoute
import de.mm20.launcher2.ui.settings.transparencies.TransparencySchemeSettingsScreen
import de.mm20.launcher2.ui.settings.transparencies.TransparencySchemesSettingsRoute
import de.mm20.launcher2.ui.settings.transparencies.TransparencySchemesSettingsScreen
import de.mm20.launcher2.ui.settings.unitconverter.UnitConverterHelpSettingsScreen
import de.mm20.launcher2.ui.settings.unitconverter.UnitConverterSettingsScreen
import de.mm20.launcher2.ui.settings.weather.WeatherIntegrationSettingsScreen
@@ -206,6 +210,14 @@ class SettingsActivity : BaseActivity() {
} ?: return@composable
ShapeSchemeSettingsScreen(id)
}
composable<TransparencySchemesSettingsRoute> {
TransparencySchemesSettingsScreen()
}
composable<TransparencySchemeSettingsRoute> {
val route: TransparencySchemeSettingsRoute = it.toRoute()
?: return@composable
TransparencySchemeSettingsScreen(UUID.fromString(route.id))
}
composable("settings/appearance/cards") {
CardsSettingsScreen()
}
@@ -6,6 +6,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowCircleDown
import androidx.compose.material.icons.rounded.ArrowCircleUp
import androidx.compose.material.icons.rounded.CropSquare
import androidx.compose.material.icons.rounded.Opacity
import androidx.compose.material.icons.rounded.Palette
import androidx.compose.material.icons.rounded.TextFields
import androidx.compose.material3.Text
@@ -27,6 +28,7 @@ import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
import de.mm20.launcher2.ui.component.preferences.value
import de.mm20.launcher2.ui.locals.LocalNavController
import de.mm20.launcher2.ui.settings.transparencies.TransparencySchemesSettingsRoute
import de.mm20.launcher2.ui.theme.getTypography
@Composable
@@ -36,6 +38,7 @@ fun AppearanceSettingsScreen() {
val navController = LocalNavController.current
val colorThemeName by viewModel.colorThemeName.collectAsStateWithLifecycle(null)
val shapeThemeName by viewModel.shapeThemeName.collectAsStateWithLifecycle(null)
val transparencyThemeName by viewModel.transparencyThemeName.collectAsStateWithLifecycle(null)
val compatModeColors by viewModel.compatModeColors.collectAsState()
val importLauncher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {
@@ -101,6 +104,14 @@ fun AppearanceSettingsScreen() {
},
icon = Icons.Rounded.CropSquare,
)
Preference(
title = stringResource(id = R.string.preference_screen_transparencies),
summary = transparencyThemeName,
onClick = {
navController?.navigate(TransparencySchemesSettingsRoute)
},
icon = Icons.Rounded.Opacity,
)
Preference(
title = stringResource(R.string.preference_cards),
@@ -25,15 +25,22 @@ class AppearanceSettingsScreenVM : ViewModel(), KoinComponent {
uiSettings.setColorScheme(colorScheme)
}
val colorThemeName = uiSettings.colors.flatMapLatest {
themeRepository.getColorsOrDefault(it)
}.map {
it.name
}
val colorThemeName = uiSettings.colorsId.flatMapLatest {
themeRepository.colors.getOrDefault(it)
}.map {
it.name
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val shapeThemeName = uiSettings.shapes.flatMapLatest {
themeRepository.getShapesOrDefault(it)
val shapeThemeName = uiSettings.shapesId.flatMapLatest {
themeRepository.shapes.getOrDefault(it)
}.map {
it.name
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val transparencyThemeName = uiSettings.transparenciesId.flatMapLatest {
themeRepository.transparencies.getOrDefault(it)
}.map {
it.name
}
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.CropSquare
import androidx.compose.material.icons.rounded.KeyboardArrowDown
import androidx.compose.material.icons.rounded.Opacity
import androidx.compose.material.icons.rounded.Palette
import androidx.compose.material.icons.rounded.Save
import androidx.compose.material.icons.rounded.Share
@@ -50,10 +51,11 @@ fun ExportThemeSettingsScreen() {
val colorSchemes by viewModel.colorSchemes.collectAsState(emptyList())
val shapeThemes by viewModel.shapeSchemes.collectAsState(emptyList())
val transparencySchemes by viewModel.transparencySchemes.collectAsState(emptyList())
val isValidSelection by remember {
derivedStateOf {
viewModel.colorScheme != null || viewModel.shapeScheme != null
viewModel.colorScheme != null || viewModel.shapeScheme != null || viewModel.transparencyScheme != null
}
}
@@ -111,6 +113,17 @@ fun ExportThemeSettingsScreen() {
viewModel.setShapeScheme(newValue)
}
)
ListPreference(
stringResource(R.string.preference_screen_transparencies),
icon = Icons.Rounded.Opacity,
value = viewModel.transparencyScheme,
items = listOf(stringResource(R.string.no_selection) to null) + transparencySchemes.map {
it.name to it
},
onValueChanged = { newValue ->
viewModel.setTransparencyScheme(newValue)
}
)
}
}
item {
@@ -3,7 +3,6 @@ package de.mm20.launcher2.ui.settings.appearance
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.Log
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
@@ -11,11 +10,11 @@ import androidx.core.content.FileProvider
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.themes.Colors
import de.mm20.launcher2.themes.Shapes
import de.mm20.launcher2.themes.colors.Colors
import de.mm20.launcher2.themes.shapes.Shapes
import de.mm20.launcher2.themes.ThemeBundle
import de.mm20.launcher2.themes.ThemeRepository
import de.mm20.launcher2.themes.toLegacyJson
import de.mm20.launcher2.themes.transparencies.Transparencies
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
@@ -28,8 +27,9 @@ class ExportThemeSettingsScreenVM: ViewModel(), KoinComponent {
private val themeRepository: ThemeRepository by inject()
val colorSchemes = themeRepository.getAllColors().map { it.filter { !it.builtIn } }
val shapeSchemes = themeRepository.getAllShapes().map { it.filter { !it.builtIn } }
val colorSchemes = themeRepository.colors.getAll().map { it.filter { !it.builtIn } }
val shapeSchemes = themeRepository.shapes.getAll().map { it.filter { !it.builtIn } }
val transparencySchemes = themeRepository.transparencies.getAll().map { it.filter { !it.builtIn } }
var themeName by mutableStateOf("")
var themeAuthor by mutableStateOf("")
@@ -55,12 +55,21 @@ class ExportThemeSettingsScreenVM: ViewModel(), KoinComponent {
shapeScheme = scheme
}
var transparencyScheme by mutableStateOf<Transparencies?>(null)
@JvmName("_setTransparencyScheme")
private set
fun setTransparencyScheme(scheme: Transparencies?) {
if (themeName.isBlank() && scheme != null) themeName = scheme.name
transparencyScheme = scheme
}
private fun getThemeBundle(): ThemeBundle {
return ThemeBundle(
name = themeName,
author = themeAuthor.takeIf { it.isNotBlank() },
colors = colorScheme,
shapes = shapeScheme,
transparencies = transparencyScheme,
)
}
@@ -19,6 +19,7 @@ import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.ErrorOutline
import androidx.compose.material.icons.rounded.LightMode
import androidx.compose.material.icons.rounded.MoreVert
import androidx.compose.material.icons.rounded.Opacity
import androidx.compose.material.icons.rounded.Palette
import androidx.compose.material.icons.rounded.Star
import androidx.compose.material3.Button
@@ -35,6 +36,7 @@ import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -62,9 +64,13 @@ import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
import de.mm20.launcher2.ui.locals.LocalDarkTheme
import de.mm20.launcher2.ui.settings.transparencies.checkerboard
import de.mm20.launcher2.ui.theme.colorscheme.darkColorSchemeOf
import de.mm20.launcher2.ui.theme.colorscheme.lightColorSchemeOf
import de.mm20.launcher2.ui.theme.shapes.shapesOf
import de.mm20.launcher2.ui.theme.transparency.LocalTransparencyScheme
import de.mm20.launcher2.ui.theme.transparency.transparency
import de.mm20.launcher2.ui.theme.transparency.transparencySchemeOf
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
@@ -118,10 +124,15 @@ fun ImportThemeSettingsScreen(
} ?: MaterialTheme.colorScheme,
shapes = themeBundle.shapes?.let { shapesOf(it) } ?: MaterialTheme.shapes,
) {
ThemePreview(
darkMode = darkModePreview,
onDarkModeChanged = { darkModePreview = it }
)
val transparencies = themeBundle.transparencies?.let { transparencySchemeOf(it) } ?: MaterialTheme.transparency
CompositionLocalProvider(
LocalTransparencyScheme provides transparencies
) {
ThemePreview(
darkMode = darkModePreview,
onDarkModeChanged = { darkModePreview = it }
)
}
}
}
item {
@@ -156,6 +167,21 @@ fun ImportThemeSettingsScreen(
} else null,
)
}
if (themeBundle.transparencies != null) {
Preference(
icon = Icons.Rounded.Opacity,
title = stringResource(R.string.preference_screen_transparencies),
summary = themeBundle.transparencies?.name,
controls = if (viewModel.transparenciesExists) {
{
Icon(
Icons.Rounded.ChangeCircle,
stringResource(R.string.import_theme_exists)
)
}
} else null,
)
}
if (viewModel.colorsExists || viewModel.shapesExists) {
Banner(
modifier = Modifier
@@ -211,8 +237,13 @@ private fun ThemePreview(
modifier = Modifier
.fillMaxWidth()
.clip(MaterialTheme.shapes.medium)
.checkerboard(
MaterialTheme.colorScheme.primary,
MaterialTheme.colorScheme.onPrimaryContainer,
12.dp,
)
.background(
MaterialTheme.colorScheme.surfaceContainer,
MaterialTheme.colorScheme.surfaceContainer.copy(alpha = MaterialTheme.transparency.background),
MaterialTheme.shapes.medium
)
.innerShadow(
@@ -225,7 +256,7 @@ private fun ThemePreview(
modifier = Modifier
.fillMaxWidth()
.padding(top = 12.dp, start = 12.dp, end = 12.dp),
level = SearchBarLevel.Active,
level = SearchBarLevel.Raised,
value = "",
onValueChange = {},
readOnly = true,
@@ -239,7 +270,10 @@ private fun ThemePreview(
modifier = Modifier
.fillMaxWidth()
.padding(top = 12.dp, start = 12.dp, end = 12.dp)
.background(MaterialTheme.colorScheme.surface, MaterialTheme.shapes.medium)
.background(
MaterialTheme.colorScheme.surface.copy(alpha = MaterialTheme.transparency.surface),
MaterialTheme.shapes.medium
)
.padding(12.dp)
) {
Row(
@@ -8,8 +8,6 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.crashreporter.CrashReporter
import de.mm20.launcher2.preferences.ColorsDescriptor
import de.mm20.launcher2.preferences.ShapesDescriptor
import de.mm20.launcher2.preferences.ui.UiSettings
import de.mm20.launcher2.themes.ThemeBundle
import de.mm20.launcher2.themes.ThemeRepository
@@ -19,9 +17,8 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import kotlin.getValue
class ImportThemeSettingsScreenVM: ViewModel(), KoinComponent {
class ImportThemeSettingsScreenVM : ViewModel(), KoinComponent {
private val themeRepository by inject<ThemeRepository>()
private val uiSettings by inject<UiSettings>()
@@ -35,6 +32,9 @@ class ImportThemeSettingsScreenVM: ViewModel(), KoinComponent {
var shapesExists by mutableStateOf(false)
private set
var transparenciesExists by mutableStateOf(false)
private set
var loading by mutableStateOf(false)
private set
@@ -54,11 +54,16 @@ class ImportThemeSettingsScreenVM: ViewModel(), KoinComponent {
val text = it.readText()
val theme = ThemeBundle.fromJson(text)
if (theme != null) {
val colors = theme.colors?.id?.let { themeRepository.getColors(it) }?.first()
val shapes = theme.shapes?.id?.let { themeRepository.getShapes(it) }?.first()
val colors =
theme.colors?.id?.let { themeRepository.colors.get(it) }?.first()
val shapes =
theme.shapes?.id?.let { themeRepository.shapes.get(it) }?.first()
val transparencies =
theme.transparencies?.id?.let { themeRepository.transparencies.get(it) }?.first()
colorsExists = colors != null
shapesExists = shapes != null
transparenciesExists = transparencies != null
themeBundle = theme
loading = false
} else {
@@ -77,30 +82,42 @@ class ImportThemeSettingsScreenVM: ViewModel(), KoinComponent {
val colors = themeBundle.colors
val shapes = themeBundle.shapes
val transparencies = themeBundle.transparencies
val colorsExist = this.colorsExists
val shapesExist = this.shapesExists
val transparenciesExist = this.transparenciesExists
loading = true
return viewModelScope.launch {
if (colors != null) {
if (colorsExist) {
themeRepository.updateColors(colors)
themeRepository.colors.update(colors)
} else {
themeRepository.createColors(colors)
themeRepository.colors.create(colors)
}
if (applyTheme) {
uiSettings.setColors(ColorsDescriptor.Custom(colors.id.toString()))
uiSettings.setColorsId(colors.id)
}
}
if (shapes != null) {
if (shapesExist) {
themeRepository.updateShapes(shapes)
themeRepository.shapes.update(shapes)
} else {
themeRepository.createShapes(shapes)
themeRepository.shapes.create(shapes)
}
if (applyTheme) {
uiSettings.setShapes(ShapesDescriptor.Custom(shapes.id.toString()))
uiSettings.setShapesId(shapes.id)
}
}
if (transparencies != null) {
if (transparenciesExist) {
themeRepository.transparencies.update(transparencies)
} else {
themeRepository.transparencies.create(transparencies)
}
if (applyTheme) {
uiSettings.setTransparenciesId(transparencies.id)
}
}
loading = false
@@ -1,32 +1,19 @@
package de.mm20.launcher2.ui.settings.calendarsearch
import android.app.PendingIntent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ErrorOutline
import androidx.compose.material3.CheckboxDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.calendar.providers.CalendarList
import de.mm20.launcher2.crashreporter.CrashReporter
import de.mm20.launcher2.ktx.sendWithBackgroundPermission
import de.mm20.launcher2.plugin.PluginState
import de.mm20.launcher2.themes.atTone
import de.mm20.launcher2.themes.colors.atTone
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.Banner
import de.mm20.launcher2.ui.component.preferences.CheckboxPreference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
@@ -47,16 +47,6 @@ fun CardsSettingsScreen() {
}
item {
PreferenceCategory {
SliderPreference(
title = stringResource(R.string.preference_cards_opacity),
icon = Icons.Rounded.Opacity,
value = cardStyle.opacity,
min = 0f,
max = 1f,
onValueChanged = {
viewModel.setOpacity(it)
}
)
SliderPreference(
title = stringResource(R.string.preference_cards_stroke_width),
icon = Icons.Rounded.LineWeight,
@@ -28,7 +28,6 @@ import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@@ -51,7 +50,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -60,9 +58,9 @@ import de.mm20.launcher2.badges.Badge
import de.mm20.launcher2.icons.ColorLayer
import de.mm20.launcher2.icons.StaticLauncherIcon
import de.mm20.launcher2.icons.TintedIconLayer
import de.mm20.launcher2.themes.DefaultDarkColorScheme
import de.mm20.launcher2.themes.DefaultLightColorScheme
import de.mm20.launcher2.themes.merge
import de.mm20.launcher2.themes.colors.DefaultDarkColorScheme
import de.mm20.launcher2.themes.colors.DefaultLightColorScheme
import de.mm20.launcher2.themes.colors.merge
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.Banner
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
@@ -1,8 +1,5 @@
package de.mm20.launcher2.ui.settings.colorscheme
import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
@@ -17,7 +14,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Add
import androidx.compose.material.icons.rounded.ContentCopy
import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material.icons.rounded.Download
import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.MoreVert
import androidx.compose.material.icons.rounded.RadioButtonChecked
@@ -26,7 +22,6 @@ import androidx.compose.material.icons.rounded.Share
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@@ -45,9 +40,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.themes.Colors
import de.mm20.launcher2.themes.colors.Colors
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.common.ImportThemeSheet
import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
@@ -6,11 +6,10 @@ import androidx.core.content.FileProvider
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.preferences.ColorsDescriptor
import de.mm20.launcher2.preferences.ui.UiSettings
import de.mm20.launcher2.themes.BlackAndWhiteThemeId
import de.mm20.launcher2.themes.DefaultThemeId
import de.mm20.launcher2.themes.Colors
import de.mm20.launcher2.themes.colors.Colors
import de.mm20.launcher2.themes.HighContrastThemeId
import de.mm20.launcher2.themes.ThemeRepository
import de.mm20.launcher2.themes.toLegacyJson
@@ -30,39 +29,27 @@ class ColorSchemesSettingsScreenVM : ViewModel(), KoinComponent {
private val themeRepository: ThemeRepository by inject()
private val uiSettings: UiSettings by inject()
val selectedColors = uiSettings.colors.map {
when(it) {
ColorsDescriptor.Default -> DefaultThemeId
ColorsDescriptor.HighContrast -> HighContrastThemeId
ColorsDescriptor.BlackAndWhite -> BlackAndWhiteThemeId
is ColorsDescriptor.Custom -> UUID.fromString(it.id)
}
}
val colors: Flow<List<Colors>> = themeRepository.getAllColors()
val selectedColors = uiSettings.colorsId
val colors: Flow<List<Colors>> = themeRepository.colors.getAll()
fun getTheme(id: UUID): Flow<Colors?> {
return themeRepository.getColors(id)
return themeRepository.colors.get(id)
}
fun updateTheme(colors: Colors) {
themeRepository.updateColors(colors)
themeRepository.colors.update(colors)
}
fun selectTheme(colors: Colors) {
uiSettings.setColors(when(colors.id) {
DefaultThemeId -> ColorsDescriptor.Default
HighContrastThemeId -> ColorsDescriptor.HighContrast
BlackAndWhiteThemeId -> ColorsDescriptor.BlackAndWhite
else -> ColorsDescriptor.Custom(colors.id.toString())
})
uiSettings.setColorsId(colors.id)
}
fun duplicate(colors: Colors) {
themeRepository.createColors(colors.copy(id = UUID.randomUUID()))
themeRepository.colors.create(colors.copy(id = UUID.randomUUID()))
}
fun delete(colors: Colors) {
themeRepository.deleteColors(colors)
themeRepository.colors.delete(colors)
}
fun exportTheme(context: Context, colors: Colors) {
@@ -85,10 +72,10 @@ class ColorSchemesSettingsScreenVM : ViewModel(), KoinComponent {
}
fun createNew(context: Context) {
themeRepository.createColors(
themeRepository.colors.create(
Colors(
id = UUID.randomUUID(),
name = context.getString(R.string.new_color_scheme_name)
name = context.getString(R.string.new_theme_name)
)
)
}
@@ -5,7 +5,6 @@ import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
@@ -33,14 +32,11 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.themes.get
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.Tooltip
import de.mm20.launcher2.ui.component.colorpicker.HctColorPicker
import de.mm20.launcher2.ui.component.colorpicker.rememberHctColorPickerState
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
@@ -4,7 +4,6 @@ import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -12,7 +11,6 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
@@ -51,21 +49,20 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.themes.ColorRef
import de.mm20.launcher2.themes.CorePaletteColor
import de.mm20.launcher2.themes.FullCorePalette
import de.mm20.launcher2.themes.StaticColor
import de.mm20.launcher2.themes.atTone
import de.mm20.launcher2.themes.get
import de.mm20.launcher2.themes.colors.ColorRef
import de.mm20.launcher2.themes.colors.CorePaletteColor
import de.mm20.launcher2.themes.colors.FullCorePalette
import de.mm20.launcher2.themes.colors.StaticColor
import de.mm20.launcher2.themes.colors.atTone
import de.mm20.launcher2.themes.colors.get
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.Tooltip
import de.mm20.launcher2.ui.component.colorpicker.HctColorPicker
import de.mm20.launcher2.ui.component.colorpicker.rememberHctColorPickerState
import de.mm20.launcher2.ui.ktx.hct
import hct.Hct
import kotlin.math.roundToInt
import de.mm20.launcher2.themes.Color as ThemeColor
import de.mm20.launcher2.themes.colors.Color as ThemeColor
@Composable
fun ThemeColorPreference(
@@ -14,8 +14,6 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
@@ -26,11 +24,9 @@ import androidx.compose.material.icons.rounded.Error
import androidx.compose.material.icons.rounded.Info
import androidx.compose.material.icons.rounded.Settings
import androidx.compose.material.icons.rounded.Verified
import androidx.compose.material3.CheckboxDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
@@ -40,12 +36,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
@@ -57,15 +50,12 @@ import coil.compose.AsyncImage
import de.mm20.launcher2.crashreporter.CrashReporter
import de.mm20.launcher2.ktx.sendWithBackgroundPermission
import de.mm20.launcher2.plugin.PluginState
import de.mm20.launcher2.themes.atTone
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.preferences.CheckboxPreference
import de.mm20.launcher2.ui.component.preferences.GuardedPreference
import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceWithSwitch
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
import de.mm20.launcher2.ui.locals.LocalDarkTheme
import de.mm20.launcher2.ui.locals.LocalNavController
@Composable
@@ -66,7 +66,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.icons.CutCorner
import de.mm20.launcher2.icons.RoundedCornerAlt
import de.mm20.launcher2.themes.CornerStyle
import de.mm20.launcher2.themes.shapes.CornerStyle
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
@@ -76,7 +76,7 @@ import de.mm20.launcher2.ui.theme.shapes.shapesOf
import java.util.UUID
import kotlin.math.max
import kotlin.math.min
import de.mm20.launcher2.themes.Shape as ThemeShape
import de.mm20.launcher2.themes.shapes.Shape as ThemeShape
@Composable
fun ShapeSchemeSettingsScreen(themeId: UUID) {
@@ -284,7 +284,7 @@ fun ShapeSchemeSettingsScreen(themeId: UUID) {
}
}
item {
PreferenceCategory(title = "Large") {
PreferenceCategory {
ShapePreview(
previewShapes = previewShapes,
) {
@@ -35,8 +35,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.themes.CornerStyle
import de.mm20.launcher2.themes.Shapes
import de.mm20.launcher2.themes.shapes.CornerStyle
import de.mm20.launcher2.themes.shapes.Shapes
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
@@ -49,7 +49,7 @@ fun ShapeSchemesSettingsScreen() {
val navController = LocalNavController.current
val context = LocalContext.current
val selectedTheme by viewModel.selectedShapes.collectAsStateWithLifecycle(null)
val selectedTheme by viewModel.selectedShapesId.collectAsStateWithLifecycle(null)
val themes by viewModel.shapes.collectAsStateWithLifecycle(emptyList())
var deleteShapes by remember { mutableStateOf<Shapes?>(null) }
@@ -2,17 +2,11 @@ package de.mm20.launcher2.ui.settings.shapes
import android.content.Context
import androidx.lifecycle.ViewModel
import de.mm20.launcher2.preferences.ShapesDescriptor
import de.mm20.launcher2.preferences.ui.UiSettings
import de.mm20.launcher2.themes.CutShapesId
import de.mm20.launcher2.themes.DefaultThemeId
import de.mm20.launcher2.themes.ExtraRoundShapesId
import de.mm20.launcher2.themes.RectShapesId
import de.mm20.launcher2.themes.Shapes
import de.mm20.launcher2.themes.shapes.Shapes
import de.mm20.launcher2.themes.ThemeRepository
import de.mm20.launcher2.ui.R
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import java.util.UUID
@@ -23,45 +17,34 @@ class ShapeSchemesSettingsScreenVM : ViewModel(), KoinComponent {
private val themeRepository: ThemeRepository by inject()
private val uiSettings: UiSettings by inject()
val selectedShapes = uiSettings.shapes.map {
when(it) {
ShapesDescriptor.Default -> DefaultThemeId
ShapesDescriptor.Cut -> CutShapesId
ShapesDescriptor.ExtraRound -> ExtraRoundShapesId
ShapesDescriptor.Rect -> RectShapesId
is ShapesDescriptor.Custom -> UUID.fromString(it.id)
}
}
val shapes: Flow<List<Shapes>> = themeRepository.getAllShapes()
val selectedShapesId = uiSettings.shapesId
val shapes: Flow<List<Shapes>> = themeRepository.shapes.getAll()
fun getShapes(id: UUID): Flow<Shapes?> {
return themeRepository.getShapes(id)
return themeRepository.shapes.get(id)
}
fun updateShapes(shapes: Shapes) {
themeRepository.updateShapes(shapes)
themeRepository.shapes.update(shapes)
}
fun selectShapes(shapes: Shapes) {
uiSettings.setShapes(when(shapes.id) {
DefaultThemeId -> ShapesDescriptor.Default
else -> ShapesDescriptor.Custom(shapes.id.toString())
})
uiSettings.setShapesId(shapes.id)
}
fun duplicate(shapes: Shapes) {
themeRepository.createShapes(shapes.copy(id = UUID.randomUUID()))
themeRepository.shapes.create(shapes.copy(id = UUID.randomUUID()))
}
fun delete(shapes: Shapes) {
themeRepository.deleteShapes(shapes)
themeRepository.shapes.delete(shapes)
}
fun createNew(context: Context) {
themeRepository.createShapes(
themeRepository.shapes.create(
Shapes(
id = UUID.randomUUID(),
name = context.getString(R.string.new_shapes_name)
name = context.getString(R.string.new_theme_name)
)
)
}
@@ -0,0 +1,36 @@
package de.mm20.launcher2.ui.settings.transparencies
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.ktx.ceilToInt
fun Modifier.checkerboard(
color1: Color = Color.LightGray,
color2: Color = Color.DarkGray,
tileSize: Dp = 16.dp
): Modifier {
return drawBehind {
val tileSizePx = with(density) { tileSize.toPx() }
val hTiles = (size.width / tileSizePx).ceilToInt()
val vTiles = (size.height / tileSizePx).ceilToInt()
for (i in 0 until hTiles) {
for (j in 0 until vTiles) {
val color = if ((i + j) % 2 == 0) color1 else color2
drawRect(
color = color,
topLeft = Offset(
x = i * tileSizePx,
y = j * tileSizePx
),
size = Size(tileSizePx + 1, tileSizePx + 1)
)
}
}
}
}
@@ -0,0 +1,304 @@
package de.mm20.launcher2.ui.settings.transparencies
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.RestartAlt
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalAbsoluteTonalElevation
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Slider
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.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.themes.transparencies.Transparencies
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.LocalIconShape
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
import de.mm20.launcher2.ui.component.surfaceColorAtElevation
import de.mm20.launcher2.ui.locals.LocalGridSettings
import de.mm20.launcher2.ui.theme.transparency.transparencySchemeOf
import de.mm20.launcher2.ui.theme.wallpaperColorsAsState
import kotlinx.serialization.Serializable
import java.text.DecimalFormat
import java.util.UUID
import kotlin.math.floor
import kotlin.math.log
import kotlin.math.round
@Serializable
data class TransparencySchemeSettingsRoute(
val id: String
)
@Composable
fun TransparencySchemeSettingsScreen(themeId: UUID) {
val viewModel: TransparencySchemesSettingsScreenVM = viewModel()
val context = LocalContext.current
val wallpaperColors by wallpaperColorsAsState()
val theme by remember(
viewModel,
themeId
) { viewModel.getTransparencies(themeId) }.collectAsStateWithLifecycle(null)
var editName by remember { mutableStateOf(false) }
if (editName) {
var name by remember(theme) { mutableStateOf(theme?.name ?: "") }
AlertDialog(
onDismissRequest = { editName = false },
text = {
OutlinedTextField(
value = name,
onValueChange = { name = it },
singleLine = true
)
},
confirmButton = {
Button(
onClick = {
viewModel.updateTransparencies(theme!!.copy(name = name))
editName = false
}
) {
Text(stringResource(R.string.save))
}
}
)
}
PreferenceScreen(
title = {
Text(
theme?.name ?: "",
modifier = Modifier.clickable {
editName = true
},
)
},
helpUrl = "https://kvaesitso.mm20.de/docs/user-guide/customization/color-schemes",
) {
if (theme == null) return@PreferenceScreen
item {
PreferenceCategory {
TransparenciesPreview(theme!!)
TransparencyPreference(
title = "Background",
value = theme!!.background,
defaultValue = 0.85f,
onValueChange = { viewModel.updateTransparencies(theme!!.copy(background = it)) }
)
TransparencyPreference(
title = "Surface",
value = theme!!.surface,
defaultValue = 1f,
onValueChange = { viewModel.updateTransparencies(theme!!.copy(surface = it)) }
)
TransparencyPreference(
title = "Elevated Surface",
value = theme!!.elevatedSurface,
defaultValue = 1f,
onValueChange = { viewModel.updateTransparencies(theme!!.copy(elevatedSurface = it)) }
)
}
}
}
}
@Composable
private fun TransparenciesPreview(
theme: Transparencies,
) {
val transparencyScheme = transparencySchemeOf(theme)
Box {
Box(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.clip(MaterialTheme.shapes.extraSmall)
.checkerboard(
MaterialTheme.colorScheme.primary,
MaterialTheme.colorScheme.onPrimaryContainer,
12.dp,
)
) {
Box(
modifier = Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth()
.height(160.dp)
.background(
MaterialTheme.colorScheme.surfaceContainer.copy(transparencyScheme.background),
MaterialTheme.shapes.large.copy(
bottomStart = CornerSize(0f),
bottomEnd = CornerSize(0f),
)
),
) {
val xs = MaterialTheme.shapes.extraSmall
Row(
modifier = Modifier
.fillMaxSize()
.padding(top = 48.dp, start = 12.dp, end = 12.dp, bottom = 2.dp)
.background(
MaterialTheme.colorScheme.surfaceContainer.copy(transparencyScheme.surface),
MaterialTheme.shapes.medium.copy(
bottomStart = xs.bottomStart,
bottomEnd = xs.bottomStart,
),
)
.padding(12.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
repeat(LocalGridSettings.current.columnCount) {
Column(
modifier = Modifier
.weight(1f)
.padding(vertical = 12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Box(
modifier = Modifier
.size(LocalGridSettings.current.iconSize.dp)
.background(
MaterialTheme.colorScheme.primaryContainer,
LocalIconShape.current,
)
)
}
}
}
}
}
val elevatedSurface = MaterialTheme.colorScheme
.surfaceColorAtElevation(8.dp + LocalAbsoluteTonalElevation.current)
.copy(alpha = transparencyScheme.elevatedSurface)
Box(
modifier = Modifier
.padding(end = 24.dp)
.shadow(
if (transparencyScheme.elevatedSurface < 1f) 0.dp else 8.dp,
MaterialTheme.shapes.medium,
clip = true
)
.background(elevatedSurface)
.align(Alignment.CenterEnd)
.size(144.dp),
)
}
}
@Composable
private fun TransparencyPreference(
title: String,
value: Float?,
defaultValue: Float,
onValueChange: (Float?) -> Unit,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clip(MaterialTheme.shapes.extraSmall)
.background(MaterialTheme.colorScheme.surface)
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Box(
modifier = Modifier
.padding(end = 20.dp)
.size(48.dp)
.clip(MaterialTheme.shapes.small)
.checkerboard(
MaterialTheme.colorScheme.primary,
MaterialTheme.colorScheme.onPrimaryContainer,
12.dp,
)
.padding(8.dp)
.background(
MaterialTheme.colorScheme.surface.copy(alpha = value ?: defaultValue),
MaterialTheme.shapes.extraSmall
),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.size(48.dp)
)
}
Column(
modifier = Modifier.weight(1f)
) {
Text(
title,
style = MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace),
textAlign = TextAlign.Center,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Slider(
modifier = Modifier.weight(1f),
value = value ?: defaultValue,
onValueChange = {
val roundedValue = round(it * 100) / 100
onValueChange(roundedValue)
},
valueRange = 0f..1f,
)
val format = remember { DecimalFormat().apply {
maximumFractionDigits = 2
minimumFractionDigits = 0
} }
Text(
text = format.format(value ?: defaultValue),
style = MaterialTheme.typography.labelSmall,
textAlign = TextAlign.Center,
modifier = Modifier.padding(start = 8.dp).widthIn(min = 48.dp),
)
}
}
}
}
@@ -0,0 +1,218 @@
package de.mm20.launcher2.ui.settings.transparencies
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Add
import androidx.compose.material.icons.rounded.ContentCopy
import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.MoreVert
import androidx.compose.material.icons.rounded.RadioButtonChecked
import androidx.compose.material.icons.rounded.RadioButtonUnchecked
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.surfaceColorAtElevation
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.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.themes.transparencies.Transparencies
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
import de.mm20.launcher2.ui.locals.LocalNavController
import de.mm20.launcher2.ui.theme.WallpaperColors
import de.mm20.launcher2.ui.theme.transparency.transparencySchemeOf
import de.mm20.launcher2.ui.theme.wallpaperColorsAsState
import kotlinx.serialization.Serializable
@Serializable
data object TransparencySchemesSettingsRoute
@Composable
fun TransparencySchemesSettingsScreen() {
val viewModel: TransparencySchemesSettingsScreenVM = viewModel()
val navController = LocalNavController.current
val context = LocalContext.current
val selectedTheme by viewModel.selectedTransparencies.collectAsStateWithLifecycle(null)
val themes by viewModel.transparencies.collectAsStateWithLifecycle(emptyList())
var deleteTransparencies by remember { mutableStateOf<Transparencies?>(null) }
val wallpaperColors = wallpaperColorsAsState().value
PreferenceScreen(
title = stringResource(R.string.preference_screen_transparencies),
topBarActions = {
IconButton(onClick = { viewModel.createNew(context) }) {
Icon(Icons.Rounded.Add, null)
}
},
) {
item {
PreferenceCategory {
for (theme in themes) {
var showMenu by remember { mutableStateOf(false) }
Preference(
icon = if (theme.id == selectedTheme) Icons.Rounded.RadioButtonChecked else Icons.Rounded.RadioButtonUnchecked,
title = theme.name,
controls = {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
TransparenciesPreview(wallpaperColors, theme)
IconButton(
modifier = Modifier.padding(start = 12.dp),
onClick = { showMenu = true }) {
Icon(Icons.Rounded.MoreVert, null)
}
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false }
) {
if (!theme.builtIn) {
DropdownMenuItem(
leadingIcon = {
Icon(Icons.Rounded.Edit, null)
},
text = { Text(stringResource(R.string.edit)) },
onClick = {
navController?.navigate(
TransparencySchemeSettingsRoute(theme.id.toString())
)
showMenu = false
}
)
}
DropdownMenuItem(
leadingIcon = {
Icon(Icons.Rounded.ContentCopy, null)
},
text = { Text(stringResource(R.string.duplicate)) },
onClick = {
viewModel.duplicate(theme)
showMenu = false
}
)
if (!theme.builtIn) {
DropdownMenuItem(
leadingIcon = {
Icon(Icons.Rounded.Delete, null)
},
text = { Text(stringResource(R.string.menu_delete)) },
onClick = {
deleteTransparencies = theme
showMenu = false
}
)
}
}
}
},
onClick = {
viewModel.selectTransparencies(theme)
}
)
}
}
}
}
if (deleteTransparencies != null) {
AlertDialog(
onDismissRequest = { deleteTransparencies = null },
text = {
Text(
stringResource(
R.string.confirmation_delete_transparencies_scheme,
deleteTransparencies!!.name
)
)
},
confirmButton = {
TextButton(
onClick = {
viewModel.delete(deleteTransparencies!!)
deleteTransparencies = null
}
) {
Text(stringResource(android.R.string.ok))
}
},
dismissButton = {
TextButton(
onClick = { deleteTransparencies = null }
) {
Text(stringResource(android.R.string.cancel))
}
}
)
}
}
@Composable
private fun TransparenciesPreview(wallpaperColors: WallpaperColors, theme: Transparencies) {
val transparencies = transparencySchemeOf(theme)
Box(
modifier = Modifier
.clip(MaterialTheme.shapes.extraSmall)
.checkerboard(
MaterialTheme.colorScheme.primary,
MaterialTheme.colorScheme.onPrimaryContainer,
12.dp,
)
.height(40.dp)
.width(56.dp),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.background(MaterialTheme.colorScheme.surfaceContainer.copy(alpha = transparencies.background), MaterialTheme.shapes.extraSmall)
.height(40.dp)
.width(56.dp)
)
Box(
modifier = Modifier
.background(MaterialTheme.colorScheme.surface.copy(alpha = transparencies.surface), MaterialTheme.shapes.extraSmall)
.height(24.dp)
.width(48.dp)
)
Box(
modifier = Modifier
.height(32.dp)
.width(36.dp)
.shadow(
if (transparencies.elevatedSurface < 1f) 0.dp else 8.dp,
shape = MaterialTheme.shapes.extraSmall,
clip = true,
)
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(8.dp))
)
}
}
@@ -0,0 +1,50 @@
package de.mm20.launcher2.ui.settings.transparencies
import android.content.Context
import androidx.lifecycle.ViewModel
import de.mm20.launcher2.preferences.ui.UiSettings
import de.mm20.launcher2.themes.ThemeRepository
import de.mm20.launcher2.themes.transparencies.Transparencies
import de.mm20.launcher2.ui.R
import kotlinx.coroutines.flow.Flow
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import java.util.UUID
class TransparencySchemesSettingsScreenVM : ViewModel(), KoinComponent {
private val themeRepository: ThemeRepository by inject()
private val uiSettings: UiSettings by inject()
val selectedTransparencies = uiSettings.transparenciesId
val transparencies: Flow<List<Transparencies>> = themeRepository.transparencies.getAll()
fun getTransparencies(id: UUID): Flow<Transparencies?> {
return themeRepository.transparencies.get(id)
}
fun updateTransparencies(transparencies: Transparencies) {
themeRepository.transparencies.update(transparencies)
}
fun selectTransparencies(transparencies: Transparencies) {
uiSettings.setTransparenciesId(transparencies.id)
}
fun duplicate(transparencies: Transparencies) {
themeRepository.transparencies.create(transparencies.copy(id = UUID.randomUUID()))
}
fun delete(transparencies: Transparencies) {
themeRepository.transparencies.delete(transparencies)
}
fun createNew(context: Context) {
themeRepository.transparencies.create(
Transparencies(
id = UUID.randomUUID(),
name = context.getString(R.string.new_theme_name)
)
)
}
}
@@ -2,25 +2,20 @@ package de.mm20.launcher2.ui.theme
import android.content.Context
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.CutCornerShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.coerceAtMost
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.preferences.Font
import de.mm20.launcher2.preferences.SurfaceShape
import de.mm20.launcher2.preferences.ui.UiSettings
import de.mm20.launcher2.themes.ThemeRepository
import de.mm20.launcher2.ui.locals.LocalDarkTheme
import de.mm20.launcher2.ui.theme.colorscheme.*
import de.mm20.launcher2.ui.theme.shapes.shapesOf
import de.mm20.launcher2.ui.theme.transparency.LocalTransparencyScheme
import de.mm20.launcher2.ui.theme.transparency.transparencySchemeOf
import de.mm20.launcher2.ui.theme.typography.DefaultTypography
import de.mm20.launcher2.ui.theme.typography.getDeviceDefaultTypography
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import org.koin.compose.koinInject
import de.mm20.launcher2.preferences.ColorScheme as ColorSchemePref
@@ -35,14 +30,20 @@ fun LauncherTheme(
val themeRepository: ThemeRepository = koinInject()
val themeColors by remember {
uiSettings.colors.flatMapLatest {
themeRepository.getColorsOrDefault(it)
uiSettings.colorsId.flatMapLatest {
themeRepository.colors.getOrDefault(it)
}
}.collectAsState(null)
val themeShapes by remember {
uiSettings.shapes.flatMapLatest {
themeRepository.getShapesOrDefault(it)
uiSettings.shapesId.flatMapLatest {
themeRepository.shapes.getOrDefault(it)
}
}.collectAsState(null)
val themeTransparencies by remember {
uiSettings.transparenciesId.flatMapLatest {
themeRepository.transparencies.getOrDefault(it)
}
}.collectAsState(null)
@@ -52,7 +53,7 @@ fun LauncherTheme(
val darkTheme =
colorSchemePref == ColorSchemePref.Dark || colorSchemePref == ColorSchemePref.System && isSystemInDarkTheme()
if (themeColors == null || themeShapes == null) {
if (themeColors == null || themeShapes == null || themeTransparencies == null) {
return
}
@@ -64,6 +65,7 @@ fun LauncherTheme(
val shapes = shapesOf(themeShapes!!)
val transparencyScheme = transparencySchemeOf(themeTransparencies!!)
val font by remember { uiSettings.font }.collectAsState(
@@ -75,7 +77,8 @@ fun LauncherTheme(
}
CompositionLocalProvider(
LocalDarkTheme provides darkTheme
LocalDarkTheme provides darkTheme,
LocalTransparencyScheme provides transparencyScheme,
) {
MaterialExpressiveTheme(
colorScheme = colorScheme,
@@ -1,5 +1,6 @@
package de.mm20.launcher2.ui.theme.colorscheme
import android.R
import android.os.Build
import androidx.compose.material3.ColorScheme
import androidx.compose.runtime.Composable
@@ -11,14 +12,14 @@ import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.core.content.ContextCompat
import de.mm20.launcher2.preferences.ui.UiSettings
import de.mm20.launcher2.themes.CorePalette
import de.mm20.launcher2.themes.DefaultDarkColorScheme
import de.mm20.launcher2.themes.DefaultLightColorScheme
import de.mm20.launcher2.themes.FullColorScheme
import de.mm20.launcher2.themes.PartialCorePalette
import de.mm20.launcher2.themes.Colors as ThemeColors
import de.mm20.launcher2.themes.get
import de.mm20.launcher2.themes.merge
import de.mm20.launcher2.themes.colors.CorePalette
import de.mm20.launcher2.themes.colors.DefaultDarkColorScheme
import de.mm20.launcher2.themes.colors.DefaultLightColorScheme
import de.mm20.launcher2.themes.colors.FullColorScheme
import de.mm20.launcher2.themes.colors.PartialCorePalette
import de.mm20.launcher2.themes.colors.Colors as ThemeColors
import de.mm20.launcher2.themes.colors.get
import de.mm20.launcher2.themes.colors.merge
import de.mm20.launcher2.ui.locals.LocalWallpaperColors
import org.koin.compose.koinInject
@@ -88,11 +89,11 @@ fun systemCorePalette(): CorePalette<Int> {
if (Build.VERSION.SDK_INT >= 31 && !compatModeColors) {
val context = LocalContext.current
return CorePalette(
primary = ContextCompat.getColor(context, android.R.color.system_accent1_500),
secondary = ContextCompat.getColor(context, android.R.color.system_accent2_500),
tertiary = ContextCompat.getColor(context, android.R.color.system_accent3_500),
neutral = ContextCompat.getColor(context, android.R.color.system_neutral1_500),
neutralVariant = ContextCompat.getColor(context, android.R.color.system_neutral2_500),
primary = ContextCompat.getColor(context, R.color.system_accent1_500),
secondary = ContextCompat.getColor(context, R.color.system_accent2_500),
tertiary = ContextCompat.getColor(context, R.color.system_accent3_500),
neutral = ContextCompat.getColor(context, R.color.system_neutral1_500),
neutralVariant = ContextCompat.getColor(context, R.color.system_neutral2_500),
error = 0xFFB3261E.toInt(),
)
}
@@ -8,9 +8,9 @@ import androidx.compose.material3.Shapes
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.themes.CornerStyle
import de.mm20.launcher2.themes.Shape as ThemeShape
import de.mm20.launcher2.themes.Shapes as ThemeShapes
import de.mm20.launcher2.themes.shapes.CornerStyle
import de.mm20.launcher2.themes.shapes.Shape as ThemeShape
import de.mm20.launcher2.themes.shapes.Shapes as ThemeShapes
@Composable
fun shapesOf(shapes: ThemeShapes): Shapes {
@@ -1,10 +1,31 @@
package de.mm20.launcher2.ui.theme.transparency
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember
import de.mm20.launcher2.themes.transparencies.Transparencies
@Composable
fun transparencySchemeOf(transparencies: Transparencies): TransparencyScheme {
return remember(transparencies) {
TransparencyScheme(
background = transparencies.background ?: 0.85f,
surface = transparencies.surface ?: 1f,
elevatedSurface = transparencies.elevatedSurface ?: 1f,
)
}
}
data class TransparencyScheme(
val background: Float,
val surface: Float,
val background: Float = 0.85f,
val surface: Float = 1f,
val elevatedSurface: Float = 1f,
)
val LocalTransparencyScheme = compositionLocalOf { TransparencyScheme(0.85f, 1f) }
val LocalTransparencyScheme = compositionLocalOf { TransparencyScheme(background = 0.85f, surface = 1f, elevatedSurface = 1f) }
val MaterialTheme.transparency
@Composable
get() = LocalTransparencyScheme.current