(feat) transparency schemes

This commit is contained in:
MM20 2025-06-14 17:17:07 +02:00
parent 68874f1d87
commit 167be6e34d
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
61 changed files with 1608 additions and 904 deletions

View File

@ -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()

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -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()
}
}
}

View File

@ -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

View File

@ -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]

View File

@ -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()
}

View File

@ -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(

View File

@ -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,

View File

@ -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

View File

@ -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
)

View File

@ -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

View File

@ -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",
)

View File

@ -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() },
) {

View File

@ -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} (${

View File

@ -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()
}

View File

@ -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),

View File

@ -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
}

View File

@ -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 {

View File

@ -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,
)
}

View File

@ -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(

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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)
)
)
}

View File

@ -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

View File

@ -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(

View File

@ -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

View File

@ -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,
) {

View File

@ -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) }

View File

@ -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)
)
)
}

View File

@ -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)
)
}
}
}
}

View File

@ -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),
)
}
}
}
}

View File

@ -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))
)
}
}

View File

@ -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)
)
)
}
}

View File

@ -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,

View File

@ -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(),
)
}

View File

@ -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 {

View File

@ -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

View File

@ -439,6 +439,9 @@
<string name="preference_shapes_extra_round">Extra round</string>
<string name="preference_shapes_rect">Rectangular</string>
<string name="preference_shapes_base">Base shape</string>
<string name="preference_screen_transparencies">Transparency</string>
<string name="preference_transparencies_default">Default</string>
<string name="preference_transparencies_semi_transparent">Semi-transparent</string>
<string name="preference_font">Font</string>
<string name="preference_font_system">System default</string>
<string name="preference_screen_about">About</string>
@ -821,8 +824,8 @@
<string name="note_widget_file_write_error_description">The note could not be written to the linked file. Possibly, it has been moved or deleted. A copy has been saved to the launcher\'s internal storage.</string>
<string name="confirmation_delete_color_scheme">Do you really want to delete the color scheme %1$s\?</string>
<string name="confirmation_delete_shapes_scheme">Do you really want to delete the shape scheme %1$s?</string>
<string name="new_color_scheme_name">New color scheme</string>
<string name="new_shapes_name">New shapes</string>
<string name="confirmation_delete_transparencies_scheme">Do you really want to delete the transparency scheme %1$s?</string>
<string name="new_theme_name">(untitled)</string>
<string name="theme_color_scheme_system_default">Use system default</string>
<string name="theme_color_scheme_autogenerate">From primary color</string>
<string name="theme_color_scheme_palette_color">Palette</string>

View File

@ -2,18 +2,22 @@ package de.mm20.launcher2.preferences
import android.content.Context
import de.mm20.launcher2.search.SearchFilters
import de.mm20.launcher2.serialization.UUIDSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonNames
import java.util.UUID
@Serializable
data class LauncherSettingsData internal constructor(
val schemaVersion: Int = 5,
val uiColorScheme: ColorScheme = ColorScheme.System,
@JsonNames("uiTheme")
val uiColors: ColorsDescriptor = ColorsDescriptor.Default,
val uiShapes: ShapesDescriptor = ShapesDescriptor.Default,
@Serializable(with = UUIDSerializer::class)
val uiColorsId: UUID = UUID(0L, 0L),
@Serializable(with = UUIDSerializer::class)
val uiShapesId: UUID = UUID(0L, 0L),
@Serializable(with = UUIDSerializer::class)
val uiTransparenciesId: UUID = UUID(0L, 0L),
val uiCompatModeColors: Boolean = false,
val uiFont: Font = Font.Outfit,
@ -205,53 +209,6 @@ enum class Font {
System,
}
@Serializable
sealed interface ColorsDescriptor {
@Serializable
@SerialName("default")
data object Default : ColorsDescriptor
@Serializable
@SerialName("high_contrast")
data object HighContrast : ColorsDescriptor
@Serializable
@SerialName("bw")
data object BlackAndWhite : ColorsDescriptor
@Serializable
@SerialName("custom")
data class Custom(
val id: String,
) : ColorsDescriptor
}
@Serializable
sealed interface ShapesDescriptor {
@Serializable
@SerialName("default")
data object Default : ShapesDescriptor
@Serializable
@SerialName("cut")
data object Cut : ShapesDescriptor
@Serializable
@SerialName("extra_round")
data object ExtraRound : ShapesDescriptor
@Serializable
@SerialName("rect")
data object Rect : ShapesDescriptor
@Serializable
@SerialName("custom")
data class Custom(
val id: String,
) : ShapesDescriptor
}
internal enum class ClockWidgetStyleEnum {
Digital1,
Digital2,

View File

@ -8,10 +8,9 @@ import de.mm20.launcher2.preferences.ScreenOrientation
import de.mm20.launcher2.preferences.SearchBarColors
import de.mm20.launcher2.preferences.SearchBarStyle
import de.mm20.launcher2.preferences.SystemBarColors
import de.mm20.launcher2.preferences.ColorsDescriptor
import de.mm20.launcher2.preferences.ShapesDescriptor
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import java.util.UUID
data class CardStyle(
val opacity: Float = 1f,
@ -286,25 +285,36 @@ class UiSettings internal constructor(
}
val colors
val colorsId
get() = launcherDataStore.data.map {
it.uiColors
it.uiColorsId
}.distinctUntilChanged()
fun setColors(colors: ColorsDescriptor) {
fun setColorsId(colorsId: UUID) {
launcherDataStore.update {
it.copy(uiColors = colors)
it.copy(uiColorsId = colorsId)
}
}
val shapes
val shapesId
get() = launcherDataStore.data.map {
it.uiShapes
it.uiShapesId
}.distinctUntilChanged()
fun setShapes(shapes: ShapesDescriptor) {
fun setShapesId(shapesId: UUID) {
launcherDataStore.update {
it.copy(uiShapes = shapes)
it.copy(uiShapesId = shapesId)
}
}
val transparenciesId
get() = launcherDataStore.data.map {
it.uiTransparenciesId
}.distinctUntilChanged()
fun setTransparenciesId(transparenciesId: UUID) {
launcherDataStore.update {
it.copy(uiTransparenciesId = transparenciesId)
}
}

View File

@ -20,6 +20,7 @@ import de.mm20.launcher2.database.entities.SavedSearchableEntity
import de.mm20.launcher2.database.entities.SearchActionEntity
import de.mm20.launcher2.database.entities.ColorsEntity
import de.mm20.launcher2.database.entities.ShapesEntity
import de.mm20.launcher2.database.entities.TransparenciesEntity
import de.mm20.launcher2.database.entities.WidgetEntity
import de.mm20.launcher2.database.migrations.Migration_10_11
import de.mm20.launcher2.database.migrations.Migration_11_12
@ -39,6 +40,7 @@ import de.mm20.launcher2.database.migrations.Migration_24_25
import de.mm20.launcher2.database.migrations.Migration_25_26
import de.mm20.launcher2.database.migrations.Migration_26_27
import de.mm20.launcher2.database.migrations.Migration_27_28
import de.mm20.launcher2.database.migrations.Migration_28_29
import de.mm20.launcher2.database.migrations.Migration_6_7
import de.mm20.launcher2.database.migrations.Migration_7_8
import de.mm20.launcher2.database.migrations.Migration_8_9
@ -59,7 +61,8 @@ import java.util.UUID
ColorsEntity::class,
PluginEntity::class,
ShapesEntity::class,
], version = 28, exportSchema = true
TransparenciesEntity::class,
], version = 29, exportSchema = true
)
@TypeConverters(ComponentNameConverter::class)
abstract class AppDatabase : RoomDatabase() {
@ -160,6 +163,7 @@ abstract class AppDatabase : RoomDatabase() {
Migration_25_26(),
Migration_26_27(),
Migration_27_28(),
Migration_28_29(),
).build()
if (_instance == null) _instance = instance
return instance

View File

@ -6,6 +6,7 @@ import androidx.room.Query
import androidx.room.Update
import de.mm20.launcher2.database.entities.ColorsEntity
import de.mm20.launcher2.database.entities.ShapesEntity
import de.mm20.launcher2.database.entities.TransparenciesEntity
import kotlinx.coroutines.flow.Flow
import java.util.UUID
@ -17,39 +18,60 @@ interface ThemeDao {
@Query("SELECT * FROM Shapes")
fun getAllShapes(): Flow<List<ShapesEntity>>
@Query("SELECT * FROM Transparencies")
fun getAllTransparencies(): Flow<List<TransparenciesEntity>>
@Query("SELECT * FROM Theme WHERE id = :id LIMIT 1")
fun getColors(id: UUID): Flow<ColorsEntity?>
@Query("SELECT * FROM Shapes WHERE id = :id LIMIT 1")
fun getShapes(id: UUID): Flow<ShapesEntity?>
@Query("SELECT * FROM Transparencies WHERE id = :id LIMIT 1")
fun getTransparencies(id: UUID): Flow<TransparenciesEntity?>
@Insert
suspend fun insertColors(colors: ColorsEntity)
@Insert
suspend fun insertShapes(shapes: ShapesEntity)
@Insert
suspend fun insertTransparencies(transparencies: TransparenciesEntity)
@Update
suspend fun updateColors(colors: ColorsEntity)
@Update
suspend fun updateShapes(shapes: ShapesEntity)
@Update
suspend fun updateTransparencies(transparencies: TransparenciesEntity)
@Query("DELETE FROM Theme WHERE id = :id")
suspend fun deleteColors(id: UUID)
@Query("DELETE FROM Shapes WHERE id = :id")
suspend fun deleteShapes(id: UUID)
@Query("DELETE FROM Transparencies WHERE id = :id")
suspend fun deleteTransparencies(id: UUID)
@Query("DELETE FROM Theme")
suspend fun deleteAllColors()
@Query("DELETE FROM Shapes")
suspend fun deleteAllShapes()
@Query("DELETE FROM Transparencies")
suspend fun deleteAllTransparencies()
@Insert
fun insertAllColors(colors: List<ColorsEntity>)
@Insert
fun insertAllShapes(shapes: List<ShapesEntity>)
@Insert
fun insertAllTransparencies(transparencies: List<TransparenciesEntity>)
}

View File

@ -0,0 +1,15 @@
package de.mm20.launcher2.database.entities
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.util.UUID
@Entity(tableName = "Transparencies")
data class TransparenciesEntity(
@PrimaryKey val id: UUID,
val name: String,
val background: Float?,
val surface: Float?,
val elevatedSurface: Float?,
)

View File

@ -0,0 +1,22 @@
package de.mm20.launcher2.database.migrations
import androidx.room.migration.Migration
import androidx.sqlite.SQLiteConnection
import androidx.sqlite.execSQL
class Migration_28_29: Migration(28, 29) {
override fun migrate(connection: SQLiteConnection) {
connection.execSQL(
"""
CREATE TABLE IF NOT EXISTS `Transparencies` (
`id` BLOB NOT NULL PRIMARY KEY,
`name` TEXT NOT NULL,
`background` REAL,
`surface` REAL,
`elevatedSurface` REAL
)
""".trimIndent()
)
}
}

View File

@ -1,245 +1,20 @@
package de.mm20.launcher2.themes
import de.mm20.launcher2.themes.colors.Color
import de.mm20.launcher2.themes.colors.ColorRef
import de.mm20.launcher2.themes.colors.ColorScheme
import de.mm20.launcher2.themes.colors.CorePaletteColor
import de.mm20.launcher2.themes.colors.StaticColor
import java.util.UUID
val DefaultThemeId = UUID(0L, 0L)
val HighContrastThemeId = UUID(0L, 2L)
val BlackAndWhiteThemeId = UUID(0L, 1L)
val ExtraRoundShapesId = UUID(0L, 1L)
val CutShapesId = UUID(0L, 2L)
val RectShapesId = UUID(0L, 3L)
val DefaultLightColorScheme = ColorScheme<Color>(
primary = ColorRef(CorePaletteColor.Primary, 40),
onPrimary = ColorRef(CorePaletteColor.Primary, 100),
primaryContainer = ColorRef(CorePaletteColor.Primary, 90),
onPrimaryContainer = ColorRef(CorePaletteColor.Primary, 30),
secondary = ColorRef(CorePaletteColor.Secondary, 40),
onSecondary = ColorRef(CorePaletteColor.Secondary, 100),
secondaryContainer = ColorRef(CorePaletteColor.Secondary, 90),
onSecondaryContainer = ColorRef(CorePaletteColor.Secondary, 30),
tertiary = ColorRef(CorePaletteColor.Tertiary, 40),
onTertiary = ColorRef(CorePaletteColor.Tertiary, 100),
tertiaryContainer = ColorRef(CorePaletteColor.Tertiary, 90),
onTertiaryContainer = ColorRef(CorePaletteColor.Tertiary, 30),
error = ColorRef(CorePaletteColor.Error, 40),
onError = ColorRef(CorePaletteColor.Error, 100),
errorContainer = ColorRef(CorePaletteColor.Error, 90),
onErrorContainer = ColorRef(CorePaletteColor.Error, 30),
surfaceDim = ColorRef(CorePaletteColor.Neutral, 87),
surface = ColorRef(CorePaletteColor.Neutral, 98),
surfaceBright = ColorRef(CorePaletteColor.Neutral, 98),
surfaceContainerLowest = ColorRef(CorePaletteColor.Neutral, 100),
surfaceContainerLow = ColorRef(CorePaletteColor.Neutral, 96),
surfaceContainer = ColorRef(CorePaletteColor.Neutral, 94),
surfaceContainerHigh = ColorRef(CorePaletteColor.Neutral, 92),
surfaceContainerHighest = ColorRef(CorePaletteColor.Neutral, 90),
onSurface = ColorRef(CorePaletteColor.Neutral, 10),
onSurfaceVariant = ColorRef(CorePaletteColor.NeutralVariant, 30),
outline = ColorRef(CorePaletteColor.NeutralVariant, 50),
outlineVariant = ColorRef(CorePaletteColor.NeutralVariant, 80),
inverseSurface = ColorRef(CorePaletteColor.Neutral, 20),
inverseOnSurface = ColorRef(CorePaletteColor.Neutral, 95),
inversePrimary = ColorRef(CorePaletteColor.Primary, 80),
surfaceVariant = ColorRef(CorePaletteColor.NeutralVariant, 90),
surfaceTint = ColorRef(CorePaletteColor.Primary, 40),
background = ColorRef(CorePaletteColor.Neutral, 98),
onBackground = ColorRef(CorePaletteColor.Neutral, 10),
scrim = ColorRef(CorePaletteColor.Neutral, 0),
)
val DefaultDarkColorScheme = ColorScheme<Color>(
primary = ColorRef(CorePaletteColor.Primary, 80),
onPrimary = ColorRef(CorePaletteColor.Primary, 20),
primaryContainer = ColorRef(CorePaletteColor.Primary, 30),
onPrimaryContainer = ColorRef(CorePaletteColor.Primary, 90),
secondary = ColorRef(CorePaletteColor.Secondary, 80),
onSecondary = ColorRef(CorePaletteColor.Secondary, 20),
secondaryContainer = ColorRef(CorePaletteColor.Secondary, 30),
onSecondaryContainer = ColorRef(CorePaletteColor.Secondary, 90),
tertiary = ColorRef(CorePaletteColor.Tertiary, 80),
onTertiary = ColorRef(CorePaletteColor.Tertiary, 20),
tertiaryContainer = ColorRef(CorePaletteColor.Tertiary, 30),
onTertiaryContainer = ColorRef(CorePaletteColor.Tertiary, 90),
error = ColorRef(CorePaletteColor.Error, 80),
onError = ColorRef(CorePaletteColor.Error, 20),
errorContainer = ColorRef(CorePaletteColor.Error, 30),
onErrorContainer = ColorRef(CorePaletteColor.Error, 90),
surfaceDim = ColorRef(CorePaletteColor.Neutral, 6),
surface = ColorRef(CorePaletteColor.Neutral, 6),
surfaceBright = ColorRef(CorePaletteColor.Neutral, 24),
surfaceContainerLowest = ColorRef(CorePaletteColor.Neutral, 4),
surfaceContainerLow = ColorRef(CorePaletteColor.Neutral, 10),
surfaceContainer = ColorRef(CorePaletteColor.Neutral, 12),
surfaceContainerHigh = ColorRef(CorePaletteColor.Neutral, 17),
surfaceContainerHighest = ColorRef(CorePaletteColor.Neutral, 22),
onSurface = ColorRef(CorePaletteColor.Neutral, 90),
onSurfaceVariant = ColorRef(CorePaletteColor.NeutralVariant, 80),
outline = ColorRef(CorePaletteColor.NeutralVariant, 60),
outlineVariant = ColorRef(CorePaletteColor.NeutralVariant, 30),
inverseSurface = ColorRef(CorePaletteColor.Neutral, 90),
inverseOnSurface = ColorRef(CorePaletteColor.Neutral, 20),
inversePrimary = ColorRef(CorePaletteColor.Primary, 40),
surfaceVariant = ColorRef(CorePaletteColor.NeutralVariant, 30),
surfaceTint = ColorRef(CorePaletteColor.Primary, 80),
background = ColorRef(CorePaletteColor.Neutral, 6),
onBackground = ColorRef(CorePaletteColor.Neutral, 90),
scrim = ColorRef(CorePaletteColor.Neutral, 0),
)
val HighContrastLightColorScheme = ColorScheme<Color>(
primary = ColorRef(CorePaletteColor.Primary, 20),
onPrimary = ColorRef(CorePaletteColor.Primary, 100),
primaryContainer = ColorRef(CorePaletteColor.Primary, 30),
onPrimaryContainer = ColorRef(CorePaletteColor.Primary, 100),
secondary = ColorRef(CorePaletteColor.Secondary, 20),
onSecondary = ColorRef(CorePaletteColor.Secondary, 100),
secondaryContainer = ColorRef(CorePaletteColor.Secondary, 30),
onSecondaryContainer = ColorRef(CorePaletteColor.Secondary, 100),
tertiary = ColorRef(CorePaletteColor.Tertiary, 20),
onTertiary = ColorRef(CorePaletteColor.Tertiary, 100),
tertiaryContainer = ColorRef(CorePaletteColor.Tertiary, 30),
onTertiaryContainer = ColorRef(CorePaletteColor.Tertiary, 100),
error = ColorRef(CorePaletteColor.Error, 20),
onError = ColorRef(CorePaletteColor.Error, 100),
errorContainer = ColorRef(CorePaletteColor.Error, 30),
onErrorContainer = ColorRef(CorePaletteColor.Error, 100),
surfaceDim = ColorRef(CorePaletteColor.Neutral, 87),
surface = ColorRef(CorePaletteColor.Neutral, 98),
surfaceBright = ColorRef(CorePaletteColor.Neutral, 98),
surfaceContainerLowest = ColorRef(CorePaletteColor.Neutral, 100),
surfaceContainerLow = ColorRef(CorePaletteColor.Neutral, 96),
surfaceContainer = ColorRef(CorePaletteColor.Neutral, 94),
surfaceContainerHigh = ColorRef(CorePaletteColor.Neutral, 92),
surfaceContainerHighest = ColorRef(CorePaletteColor.Neutral, 90),
onSurface = ColorRef(CorePaletteColor.Neutral, 0),
onSurfaceVariant = ColorRef(CorePaletteColor.NeutralVariant, 0),
outline = ColorRef(CorePaletteColor.NeutralVariant, 20),
outlineVariant = ColorRef(CorePaletteColor.NeutralVariant, 30),
inverseSurface = ColorRef(CorePaletteColor.Neutral, 20),
inverseOnSurface = ColorRef(CorePaletteColor.Neutral, 100),
inversePrimary = ColorRef(CorePaletteColor.Primary, 80),
surfaceVariant = ColorRef(CorePaletteColor.NeutralVariant, 90),
surfaceTint = ColorRef(CorePaletteColor.Primary, 20),
background = ColorRef(CorePaletteColor.Neutral, 98),
onBackground = ColorRef(CorePaletteColor.Neutral, 0),
scrim = ColorRef(CorePaletteColor.Neutral, 0),
)
val HighContrastDarkColorScheme = ColorScheme<Color>(
primary = ColorRef(CorePaletteColor.Primary, 95),
onPrimary = ColorRef(CorePaletteColor.Primary, 0),
primaryContainer = ColorRef(CorePaletteColor.Primary, 80),
onPrimaryContainer = ColorRef(CorePaletteColor.Primary, 0),
secondary = ColorRef(CorePaletteColor.Secondary, 95),
onSecondary = ColorRef(CorePaletteColor.Secondary, 0),
secondaryContainer = ColorRef(CorePaletteColor.Secondary, 80),
onSecondaryContainer = ColorRef(CorePaletteColor.Secondary, 0),
tertiary = ColorRef(CorePaletteColor.Tertiary, 95),
onTertiary = ColorRef(CorePaletteColor.Tertiary, 0),
tertiaryContainer = ColorRef(CorePaletteColor.Tertiary, 80),
onTertiaryContainer = ColorRef(CorePaletteColor.Tertiary, 0),
error = ColorRef(CorePaletteColor.Error, 95),
onError = ColorRef(CorePaletteColor.Error, 0),
errorContainer = ColorRef(CorePaletteColor.Error, 80),
onErrorContainer = ColorRef(CorePaletteColor.Error, 0),
surfaceDim = ColorRef(CorePaletteColor.Neutral, 6),
surface = ColorRef(CorePaletteColor.Neutral, 6),
surfaceBright = ColorRef(CorePaletteColor.Neutral, 24),
surfaceContainerLowest = ColorRef(CorePaletteColor.Neutral, 4),
surfaceContainerLow = ColorRef(CorePaletteColor.Neutral, 10),
surfaceContainer = ColorRef(CorePaletteColor.Neutral, 12),
surfaceContainerHigh = ColorRef(CorePaletteColor.Neutral, 17),
surfaceContainerHighest = ColorRef(CorePaletteColor.Neutral, 22),
onSurface = ColorRef(CorePaletteColor.Neutral, 100),
onSurfaceVariant = ColorRef(CorePaletteColor.NeutralVariant, 100),
outline = ColorRef(CorePaletteColor.NeutralVariant, 95),
outlineVariant = ColorRef(CorePaletteColor.NeutralVariant, 80),
inverseSurface = ColorRef(CorePaletteColor.Neutral, 90),
inverseOnSurface = ColorRef(CorePaletteColor.Neutral, 0),
inversePrimary = ColorRef(CorePaletteColor.Primary, 20),
surfaceVariant = ColorRef(CorePaletteColor.NeutralVariant, 30),
surfaceTint = ColorRef(CorePaletteColor.Primary, 95),
background = ColorRef(CorePaletteColor.Neutral, 6),
onBackground = ColorRef(CorePaletteColor.Neutral, 100),
scrim = ColorRef(CorePaletteColor.Neutral, 0),
)
val BlackAndWhiteLightColorScheme = ColorScheme<Color?>(
primary = StaticColor(0xFF000000.toInt()),
onPrimary = StaticColor(0xFFFFFFFF.toInt()),
primaryContainer = StaticColor(0xFFFFFFFF.toInt()),
onPrimaryContainer = StaticColor(0xFF000000.toInt()),
inversePrimary = StaticColor(0xFFFFFFFF.toInt()),
secondary = StaticColor(0xFF000000.toInt()),
onSecondary = StaticColor(0xFFFFFFFF.toInt()),
secondaryContainer = StaticColor(0xFFFFFFFF.toInt()),
onSecondaryContainer = StaticColor(0xFF000000.toInt()),
tertiary = StaticColor(0xFF000000.toInt()),
onTertiary = StaticColor(0xFFFFFFFF.toInt()),
tertiaryContainer = StaticColor(0xFFFFFFFF.toInt()),
onTertiaryContainer = StaticColor(0xFF000000.toInt()),
background = StaticColor(0xFFFFFFFF.toInt()),
onBackground = StaticColor(0xFF000000.toInt()),
surface = StaticColor(0xFFFFFFFF.toInt()),
onSurface = StaticColor(0xFF000000.toInt()),
surfaceVariant = StaticColor(0xFFFFFFFF.toInt()),
onSurfaceVariant = StaticColor(0xFF000000.toInt()),
inverseSurface = StaticColor(0xFF000000.toInt()),
inverseOnSurface = StaticColor(0xFFFFFFFF.toInt()),
error = null,
onError = null,
errorContainer = null,
onErrorContainer = null,
outline = StaticColor(0xFF000000.toInt()),
surfaceTint = StaticColor(0xFFFFFFFF.toInt()),
outlineVariant = StaticColor(0xFF000000.toInt()),
scrim = StaticColor(0xFF000000.toInt()),
surfaceDim = StaticColor(0xFFFFFFFF.toInt()),
surfaceBright = StaticColor(0xFFFFFFFF.toInt()),
surfaceContainer = StaticColor(0xFFFFFFFF.toInt()),
surfaceContainerHigh = StaticColor(0xFFFFFFFF.toInt()),
surfaceContainerHighest = StaticColor(0xFFFFFFFF.toInt()),
surfaceContainerLow = StaticColor(0xFFFFFFFF.toInt()),
surfaceContainerLowest = StaticColor(0xFFFFFFFF.toInt()),
)
val BlackAndWhiteDarkColorScheme = ColorScheme<Color?>(
primary = StaticColor(0xFFFFFFFF.toInt()),
onPrimary = StaticColor(0xFF000000.toInt()),
primaryContainer = StaticColor(0xFF000000.toInt()),
onPrimaryContainer = StaticColor(0xFFFFFFFF.toInt()),
inversePrimary = StaticColor(0xFF000000.toInt()),
secondary = StaticColor(0xFFFFFFFF.toInt()),
onSecondary = StaticColor(0xFF000000.toInt()),
secondaryContainer = StaticColor(0xFF000000.toInt()),
onSecondaryContainer = StaticColor(0xFFFFFFFF.toInt()),
tertiary = StaticColor(0xFFFFFFFF.toInt()),
onTertiary = StaticColor(0xFF000000.toInt()),
tertiaryContainer = StaticColor(0xFF000000.toInt()),
onTertiaryContainer = StaticColor(0xFFFFFFFF.toInt()),
background = StaticColor(0xFF000000.toInt()),
onBackground = StaticColor(0xFFFFFFFF.toInt()),
surface = StaticColor(0xFF000000.toInt()),
onSurface = StaticColor(0xFFFFFFFF.toInt()),
surfaceVariant = StaticColor(0xFF000000.toInt()),
onSurfaceVariant = StaticColor(0xFFFFFFFF.toInt()),
inverseSurface = StaticColor(0xFFFFFFFF.toInt()),
inverseOnSurface = StaticColor(0xFF000000.toInt()),
error = null,
onError = null,
errorContainer = null,
onErrorContainer = null,
outline = StaticColor(0xFFFFFFFF.toInt()),
surfaceTint = StaticColor(0xFFFFFFFF.toInt()),
outlineVariant = StaticColor(0xFFFFFFFF.toInt()),
scrim = StaticColor(0xFFFFFFFF.toInt()),
surfaceDim = StaticColor(0xFF000000.toInt()),
surfaceBright = StaticColor(0xFF000000.toInt()),
surfaceContainer = StaticColor(0xFF000000.toInt()),
surfaceContainerHigh = StaticColor(0xFF000000.toInt()),
surfaceContainerHighest = StaticColor(0xFF000000.toInt()),
surfaceContainerLow = StaticColor(0xFF000000.toInt()),
surfaceContainerLowest = StaticColor(0xFF000000.toInt()),
)
val SemiTransparentId = UUID(0L, 1L)

View File

@ -1,5 +1,9 @@
package de.mm20.launcher2.themes
import de.mm20.launcher2.themes.colors.Color
import de.mm20.launcher2.themes.colors.ColorRef
import de.mm20.launcher2.themes.colors.Colors
import de.mm20.launcher2.themes.colors.StaticColor
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationException
import kotlinx.serialization.descriptors.PrimitiveKind

View File

@ -1,5 +1,8 @@
package de.mm20.launcher2.themes
import de.mm20.launcher2.themes.colors.Color
import de.mm20.launcher2.themes.colors.StaticColor
import de.mm20.launcher2.themes.shapes.Shape
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
@ -8,7 +11,6 @@ import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic
internal val module = SerializersModule {
contextual(Color::class, ColorSerializer)

View File

@ -2,12 +2,17 @@ package de.mm20.launcher2.themes
import android.util.Log
import de.mm20.launcher2.crashreporter.CrashReporter
import de.mm20.launcher2.serialization.Json
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import de.mm20.launcher2.themes.colors.Color
import de.mm20.launcher2.themes.colors.ColorScheme
import de.mm20.launcher2.themes.colors.Colors
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.EmptyCorePalette
import de.mm20.launcher2.themes.shapes.Shapes
import de.mm20.launcher2.themes.transparencies.Transparencies
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.contentOrNull
@ -22,6 +27,7 @@ data class ThemeBundle(
val author: String? = null,
val colors: Colors? = null,
val shapes: Shapes? = null,
val transparencies: Transparencies? = null,
/**
* The file version, always 2 for the new theme format.
*/

View File

@ -4,225 +4,24 @@ import android.content.Context
import de.mm20.launcher2.backup.Backupable
import de.mm20.launcher2.crashreporter.CrashReporter
import de.mm20.launcher2.database.AppDatabase
import de.mm20.launcher2.preferences.ColorsDescriptor
import de.mm20.launcher2.preferences.ShapesDescriptor
import kotlinx.coroutines.CoroutineScope
import de.mm20.launcher2.themes.colors.Colors
import de.mm20.launcher2.themes.colors.ColorsRepository
import de.mm20.launcher2.themes.shapes.ShapesRepository
import de.mm20.launcher2.themes.transparencies.TransparenciesRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.SerializationException
import java.io.File
import java.util.UUID
class ThemeRepository(
private val context: Context,
private val database: AppDatabase,
) : Backupable {
private val scope = CoroutineScope(Dispatchers.IO + Job())
val colors = ColorsRepository(context, database)
val shapes = ShapesRepository(context, database)
val transparencies = TransparenciesRepository(context, database)
fun getAllColors(): Flow<List<Colors>> {
return database.themeDao().getAllColors().map {
getBuiltInColors() + it.map { Colors(it) }
}
}
fun getColors(id: UUID): Flow<Colors?> {
if (id == DefaultThemeId) return flowOf(getDefaultColors())
if (id == HighContrastThemeId) return flowOf(getHighContrastColors())
if (id == BlackAndWhiteThemeId) return flowOf(getBlackAndWhiteColors())
return database.themeDao().getColors(id).map { it?.let { Colors(it) } }.flowOn(Dispatchers.Default)
}
fun createColors(colors: Colors) {
scope.launch {
database.themeDao().insertColors(colors.toEntity())
}
}
fun updateColors(colors: Colors) {
scope.launch {
database.themeDao().updateColors(colors.toEntity())
}
}
fun getColorsOrDefault(theme: ColorsDescriptor?): Flow<Colors> {
return when(theme) {
is ColorsDescriptor.HighContrast -> flowOf(getHighContrastColors())
is ColorsDescriptor.BlackAndWhite -> flowOf(getBlackAndWhiteColors())
is ColorsDescriptor.Custom -> {
val id = UUID.fromString(theme.id)
getColors(id).map { it ?: getDefaultColors() }
}
else -> flowOf(getDefaultColors())
}
}
private fun getBuiltInColors(): List<Colors> {
return listOf(
getDefaultColors(),
getHighContrastColors(),
getBlackAndWhiteColors(),
)
}
private fun getDefaultColors(): Colors {
return Colors(
id = DefaultThemeId,
builtIn = true,
name = context.getString(R.string.preference_colors_default),
corePalette = EmptyCorePalette,
lightColorScheme = DefaultLightColorScheme,
darkColorScheme = DefaultDarkColorScheme,
)
}
private fun getHighContrastColors(): Colors {
return Colors(
id = HighContrastThemeId,
builtIn = true,
name = context.getString(R.string.preference_colors_high_contrast),
corePalette = EmptyCorePalette,
lightColorScheme = HighContrastLightColorScheme,
darkColorScheme = HighContrastDarkColorScheme,
)
}
private fun getBlackAndWhiteColors(): Colors {
return Colors(
id = BlackAndWhiteThemeId,
builtIn = true,
name = context.getString(R.string.preference_colors_bw),
corePalette = EmptyCorePalette,
lightColorScheme = BlackAndWhiteLightColorScheme,
darkColorScheme = BlackAndWhiteDarkColorScheme,
)
}
fun deleteColors(colors: Colors) {
scope.launch {
database.themeDao().deleteColors(colors.id)
}
}
fun getAllShapes(): Flow<List<Shapes>> {
return database.themeDao().getAllShapes().map {
getBuiltInShapes() + it.map { Shapes(it) }
}
}
fun getShapes(id: UUID): Flow<Shapes?> {
if (id == DefaultThemeId) return flowOf(getDefaultShapes())
if (id == ExtraRoundShapesId) return flowOf(getExtraRoundShapes())
if (id == RectShapesId) return flowOf(getRectShapes())
if (id == CutShapesId) return flowOf(getCutShapes())
return database.themeDao().getShapes(id).map { it?.let { Shapes(it) } }.flowOn(Dispatchers.Default)
}
fun createShapes(shapes: Shapes) {
scope.launch {
database.themeDao().insertShapes(shapes.toEntity())
}
}
fun updateShapes(shapes: Shapes) {
scope.launch {
database.themeDao().updateShapes(shapes.toEntity())
}
}
fun getShapesOrDefault(theme: ShapesDescriptor?): Flow<Shapes> {
return when(theme) {
is ShapesDescriptor.Custom -> {
val id = UUID.fromString(theme.id)
getShapes(id).map { it ?: getDefaultShapes() }
}
is ShapesDescriptor.ExtraRound -> flowOf(getExtraRoundShapes())
is ShapesDescriptor.Rect -> flowOf(getRectShapes())
is ShapesDescriptor.Cut -> flowOf(getCutShapes())
else -> flowOf(getDefaultShapes())
}
}
private fun getBuiltInShapes(): List<Shapes> {
return listOf(
getDefaultShapes(),
getExtraRoundShapes(),
getRectShapes(),
getCutShapes(),
)
}
private fun getDefaultShapes(): Shapes {
return Shapes(
id = DefaultThemeId,
builtIn = true,
name = context.getString(R.string.preference_shapes_default),
baseShape = Shape(
corners = CornerStyle.Rounded,
radii = intArrayOf(12, 12, 12, 12),
)
)
}
private fun getCutShapes(): Shapes {
return Shapes(
id = CutShapesId,
builtIn = true,
name = context.getString(R.string.preference_cards_shape_cut),
baseShape = Shape(
corners = CornerStyle.Cut,
radii = intArrayOf(12, 12, 12, 12),
)
)
}
private fun getExtraRoundShapes(): Shapes {
return Shapes(
id = ExtraRoundShapesId,
builtIn = true,
name = context.getString(R.string.preference_shapes_extra_round),
baseShape = Shape(
corners = CornerStyle.Rounded,
radii = intArrayOf(24, 24, 24, 24),
),
extraSmall = Shape(
radii = intArrayOf(4, 4, 4, 4),
),
extraLarge = Shape(
radii = intArrayOf(36, 36, 36, 36),
),
extraLargeIncreased = Shape(
radii = intArrayOf(40, 40, 40, 40),
),
extraExtraLarge = Shape(
radii = intArrayOf(56, 56, 56, 56),
)
)
}
private fun getRectShapes(): Shapes {
return Shapes(
id = RectShapesId,
builtIn = true,
name = context.getString(R.string.preference_shapes_rect),
baseShape = Shape(
corners = CornerStyle.Rounded,
radii = intArrayOf(0, 0, 0, 0),
)
)
}
fun deleteShapes(shapes: Shapes) {
scope.launch {
database.themeDao().deleteShapes(shapes.id)
}
}
override suspend fun backup(toDir: File) = withContext(Dispatchers.IO) {
val dao = database.themeDao()
@ -257,5 +56,4 @@ class ThemeRepository(
dao.insertAllColors(colors.map { it.toEntity() })
}
}
}

View File

@ -1,4 +1,4 @@
package de.mm20.launcher2.themes
package de.mm20.launcher2.themes.colors
import de.mm20.launcher2.database.entities.ColorsEntity
import de.mm20.launcher2.serialization.ColorIntAsHexSerializer

View File

@ -0,0 +1,102 @@
package de.mm20.launcher2.themes.colors
import android.content.Context
import de.mm20.launcher2.database.AppDatabase
import de.mm20.launcher2.themes.BlackAndWhiteThemeId
import de.mm20.launcher2.themes.DefaultThemeId
import de.mm20.launcher2.themes.HighContrastThemeId
import de.mm20.launcher2.themes.R
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import java.util.UUID
class ColorsRepository(
private val context: Context,
private val database: AppDatabase,
) {
private val scope = CoroutineScope(Dispatchers.IO + Job())
fun getAll(): Flow<List<Colors>> {
return database.themeDao().getAllColors().map {
getBuiltIn() + it.map { Colors(it) }
}
}
fun get(id: UUID): Flow<Colors?> {
if (id == DefaultThemeId) return flowOf(default)
if (id == HighContrastThemeId) return flowOf(highContrast)
if (id == BlackAndWhiteThemeId) return flowOf(blackAndWhite)
return database.themeDao().getColors(id).map { it?.let { Colors(it) } }
.flowOn(Dispatchers.Default)
}
fun create(colors: Colors) {
scope.launch {
database.themeDao().insertColors(colors.toEntity())
}
}
fun update(colors: Colors) {
scope.launch {
database.themeDao().updateColors(colors.toEntity())
}
}
fun delete(colors: Colors) {
scope.launch {
database.themeDao().deleteColors(colors.id)
}
}
fun getOrDefault(id: UUID?): Flow<Colors> {
if (id == null) return flowOf(default)
return get(id).map { it ?: default }
}
private fun getBuiltIn(): List<Colors> {
return listOf(
default,
highContrast,
blackAndWhite,
)
}
private val default: Colors
get() = Colors(
id = DefaultThemeId,
builtIn = true,
name = context.getString(R.string.preference_colors_default),
corePalette = EmptyCorePalette,
lightColorScheme = DefaultLightColorScheme,
darkColorScheme = DefaultDarkColorScheme,
)
private val highContrast: Colors
get() = Colors(
id = HighContrastThemeId,
builtIn = true,
name = context.getString(R.string.preference_colors_high_contrast),
corePalette = EmptyCorePalette,
lightColorScheme = HighContrastLightColorScheme,
darkColorScheme = HighContrastDarkColorScheme,
)
private val blackAndWhite: Colors
get() = Colors(
id = BlackAndWhiteThemeId,
builtIn = true,
name = context.getString(R.string.preference_colors_bw),
corePalette = EmptyCorePalette,
lightColorScheme = BlackAndWhiteLightColorScheme,
darkColorScheme = BlackAndWhiteDarkColorScheme,
)
}

View File

@ -0,0 +1,235 @@
package de.mm20.launcher2.themes.colors
val DefaultLightColorScheme = ColorScheme<Color>(
primary = ColorRef(CorePaletteColor.Primary, 40),
onPrimary = ColorRef(CorePaletteColor.Primary, 100),
primaryContainer = ColorRef(CorePaletteColor.Primary, 90),
onPrimaryContainer = ColorRef(CorePaletteColor.Primary, 30),
secondary = ColorRef(CorePaletteColor.Secondary, 40),
onSecondary = ColorRef(CorePaletteColor.Secondary, 100),
secondaryContainer = ColorRef(CorePaletteColor.Secondary, 90),
onSecondaryContainer = ColorRef(CorePaletteColor.Secondary, 30),
tertiary = ColorRef(CorePaletteColor.Tertiary, 40),
onTertiary = ColorRef(CorePaletteColor.Tertiary, 100),
tertiaryContainer = ColorRef(CorePaletteColor.Tertiary, 90),
onTertiaryContainer = ColorRef(CorePaletteColor.Tertiary, 30),
error = ColorRef(CorePaletteColor.Error, 40),
onError = ColorRef(CorePaletteColor.Error, 100),
errorContainer = ColorRef(CorePaletteColor.Error, 90),
onErrorContainer = ColorRef(CorePaletteColor.Error, 30),
surfaceDim = ColorRef(CorePaletteColor.Neutral, 87),
surface = ColorRef(CorePaletteColor.Neutral, 98),
surfaceBright = ColorRef(CorePaletteColor.Neutral, 98),
surfaceContainerLowest = ColorRef(CorePaletteColor.Neutral, 100),
surfaceContainerLow = ColorRef(CorePaletteColor.Neutral, 96),
surfaceContainer = ColorRef(CorePaletteColor.Neutral, 94),
surfaceContainerHigh = ColorRef(CorePaletteColor.Neutral, 92),
surfaceContainerHighest = ColorRef(CorePaletteColor.Neutral, 90),
onSurface = ColorRef(CorePaletteColor.Neutral, 10),
onSurfaceVariant = ColorRef(CorePaletteColor.NeutralVariant, 30),
outline = ColorRef(CorePaletteColor.NeutralVariant, 50),
outlineVariant = ColorRef(CorePaletteColor.NeutralVariant, 80),
inverseSurface = ColorRef(CorePaletteColor.Neutral, 20),
inverseOnSurface = ColorRef(CorePaletteColor.Neutral, 95),
inversePrimary = ColorRef(CorePaletteColor.Primary, 80),
surfaceVariant = ColorRef(CorePaletteColor.NeutralVariant, 90),
surfaceTint = ColorRef(CorePaletteColor.Primary, 40),
background = ColorRef(CorePaletteColor.Neutral, 98),
onBackground = ColorRef(CorePaletteColor.Neutral, 10),
scrim = ColorRef(CorePaletteColor.Neutral, 0),
)
val DefaultDarkColorScheme = ColorScheme<Color>(
primary = ColorRef(CorePaletteColor.Primary, 80),
onPrimary = ColorRef(CorePaletteColor.Primary, 20),
primaryContainer = ColorRef(CorePaletteColor.Primary, 30),
onPrimaryContainer = ColorRef(CorePaletteColor.Primary, 90),
secondary = ColorRef(CorePaletteColor.Secondary, 80),
onSecondary = ColorRef(CorePaletteColor.Secondary, 20),
secondaryContainer = ColorRef(CorePaletteColor.Secondary, 30),
onSecondaryContainer = ColorRef(CorePaletteColor.Secondary, 90),
tertiary = ColorRef(CorePaletteColor.Tertiary, 80),
onTertiary = ColorRef(CorePaletteColor.Tertiary, 20),
tertiaryContainer = ColorRef(CorePaletteColor.Tertiary, 30),
onTertiaryContainer = ColorRef(CorePaletteColor.Tertiary, 90),
error = ColorRef(CorePaletteColor.Error, 80),
onError = ColorRef(CorePaletteColor.Error, 20),
errorContainer = ColorRef(CorePaletteColor.Error, 30),
onErrorContainer = ColorRef(CorePaletteColor.Error, 90),
surfaceDim = ColorRef(CorePaletteColor.Neutral, 6),
surface = ColorRef(CorePaletteColor.Neutral, 6),
surfaceBright = ColorRef(CorePaletteColor.Neutral, 24),
surfaceContainerLowest = ColorRef(CorePaletteColor.Neutral, 4),
surfaceContainerLow = ColorRef(CorePaletteColor.Neutral, 10),
surfaceContainer = ColorRef(CorePaletteColor.Neutral, 12),
surfaceContainerHigh = ColorRef(CorePaletteColor.Neutral, 17),
surfaceContainerHighest = ColorRef(CorePaletteColor.Neutral, 22),
onSurface = ColorRef(CorePaletteColor.Neutral, 90),
onSurfaceVariant = ColorRef(CorePaletteColor.NeutralVariant, 80),
outline = ColorRef(CorePaletteColor.NeutralVariant, 60),
outlineVariant = ColorRef(CorePaletteColor.NeutralVariant, 30),
inverseSurface = ColorRef(CorePaletteColor.Neutral, 90),
inverseOnSurface = ColorRef(CorePaletteColor.Neutral, 20),
inversePrimary = ColorRef(CorePaletteColor.Primary, 40),
surfaceVariant = ColorRef(CorePaletteColor.NeutralVariant, 30),
surfaceTint = ColorRef(CorePaletteColor.Primary, 80),
background = ColorRef(CorePaletteColor.Neutral, 6),
onBackground = ColorRef(CorePaletteColor.Neutral, 90),
scrim = ColorRef(CorePaletteColor.Neutral, 0),
)
val HighContrastLightColorScheme = ColorScheme<Color>(
primary = ColorRef(CorePaletteColor.Primary, 20),
onPrimary = ColorRef(CorePaletteColor.Primary, 100),
primaryContainer = ColorRef(CorePaletteColor.Primary, 30),
onPrimaryContainer = ColorRef(CorePaletteColor.Primary, 100),
secondary = ColorRef(CorePaletteColor.Secondary, 20),
onSecondary = ColorRef(CorePaletteColor.Secondary, 100),
secondaryContainer = ColorRef(CorePaletteColor.Secondary, 30),
onSecondaryContainer = ColorRef(CorePaletteColor.Secondary, 100),
tertiary = ColorRef(CorePaletteColor.Tertiary, 20),
onTertiary = ColorRef(CorePaletteColor.Tertiary, 100),
tertiaryContainer = ColorRef(CorePaletteColor.Tertiary, 30),
onTertiaryContainer = ColorRef(CorePaletteColor.Tertiary, 100),
error = ColorRef(CorePaletteColor.Error, 20),
onError = ColorRef(CorePaletteColor.Error, 100),
errorContainer = ColorRef(CorePaletteColor.Error, 30),
onErrorContainer = ColorRef(CorePaletteColor.Error, 100),
surfaceDim = ColorRef(CorePaletteColor.Neutral, 87),
surface = ColorRef(CorePaletteColor.Neutral, 98),
surfaceBright = ColorRef(CorePaletteColor.Neutral, 98),
surfaceContainerLowest = ColorRef(CorePaletteColor.Neutral, 100),
surfaceContainerLow = ColorRef(CorePaletteColor.Neutral, 96),
surfaceContainer = ColorRef(CorePaletteColor.Neutral, 94),
surfaceContainerHigh = ColorRef(CorePaletteColor.Neutral, 92),
surfaceContainerHighest = ColorRef(CorePaletteColor.Neutral, 90),
onSurface = ColorRef(CorePaletteColor.Neutral, 0),
onSurfaceVariant = ColorRef(CorePaletteColor.NeutralVariant, 0),
outline = ColorRef(CorePaletteColor.NeutralVariant, 20),
outlineVariant = ColorRef(CorePaletteColor.NeutralVariant, 30),
inverseSurface = ColorRef(CorePaletteColor.Neutral, 20),
inverseOnSurface = ColorRef(CorePaletteColor.Neutral, 100),
inversePrimary = ColorRef(CorePaletteColor.Primary, 80),
surfaceVariant = ColorRef(CorePaletteColor.NeutralVariant, 90),
surfaceTint = ColorRef(CorePaletteColor.Primary, 20),
background = ColorRef(CorePaletteColor.Neutral, 98),
onBackground = ColorRef(CorePaletteColor.Neutral, 0),
scrim = ColorRef(CorePaletteColor.Neutral, 0),
)
val HighContrastDarkColorScheme = ColorScheme<Color>(
primary = ColorRef(CorePaletteColor.Primary, 95),
onPrimary = ColorRef(CorePaletteColor.Primary, 0),
primaryContainer = ColorRef(CorePaletteColor.Primary, 80),
onPrimaryContainer = ColorRef(CorePaletteColor.Primary, 0),
secondary = ColorRef(CorePaletteColor.Secondary, 95),
onSecondary = ColorRef(CorePaletteColor.Secondary, 0),
secondaryContainer = ColorRef(CorePaletteColor.Secondary, 80),
onSecondaryContainer = ColorRef(CorePaletteColor.Secondary, 0),
tertiary = ColorRef(CorePaletteColor.Tertiary, 95),
onTertiary = ColorRef(CorePaletteColor.Tertiary, 0),
tertiaryContainer = ColorRef(CorePaletteColor.Tertiary, 80),
onTertiaryContainer = ColorRef(CorePaletteColor.Tertiary, 0),
error = ColorRef(CorePaletteColor.Error, 95),
onError = ColorRef(CorePaletteColor.Error, 0),
errorContainer = ColorRef(CorePaletteColor.Error, 80),
onErrorContainer = ColorRef(CorePaletteColor.Error, 0),
surfaceDim = ColorRef(CorePaletteColor.Neutral, 6),
surface = ColorRef(CorePaletteColor.Neutral, 6),
surfaceBright = ColorRef(CorePaletteColor.Neutral, 24),
surfaceContainerLowest = ColorRef(CorePaletteColor.Neutral, 4),
surfaceContainerLow = ColorRef(CorePaletteColor.Neutral, 10),
surfaceContainer = ColorRef(CorePaletteColor.Neutral, 12),
surfaceContainerHigh = ColorRef(CorePaletteColor.Neutral, 17),
surfaceContainerHighest = ColorRef(CorePaletteColor.Neutral, 22),
onSurface = ColorRef(CorePaletteColor.Neutral, 100),
onSurfaceVariant = ColorRef(CorePaletteColor.NeutralVariant, 100),
outline = ColorRef(CorePaletteColor.NeutralVariant, 95),
outlineVariant = ColorRef(CorePaletteColor.NeutralVariant, 80),
inverseSurface = ColorRef(CorePaletteColor.Neutral, 90),
inverseOnSurface = ColorRef(CorePaletteColor.Neutral, 0),
inversePrimary = ColorRef(CorePaletteColor.Primary, 20),
surfaceVariant = ColorRef(CorePaletteColor.NeutralVariant, 30),
surfaceTint = ColorRef(CorePaletteColor.Primary, 95),
background = ColorRef(CorePaletteColor.Neutral, 6),
onBackground = ColorRef(CorePaletteColor.Neutral, 100),
scrim = ColorRef(CorePaletteColor.Neutral, 0),
)
val BlackAndWhiteLightColorScheme = ColorScheme<Color?>(
primary = StaticColor(0xFF000000.toInt()),
onPrimary = StaticColor(0xFFFFFFFF.toInt()),
primaryContainer = StaticColor(0xFFFFFFFF.toInt()),
onPrimaryContainer = StaticColor(0xFF000000.toInt()),
inversePrimary = StaticColor(0xFFFFFFFF.toInt()),
secondary = StaticColor(0xFF000000.toInt()),
onSecondary = StaticColor(0xFFFFFFFF.toInt()),
secondaryContainer = StaticColor(0xFFFFFFFF.toInt()),
onSecondaryContainer = StaticColor(0xFF000000.toInt()),
tertiary = StaticColor(0xFF000000.toInt()),
onTertiary = StaticColor(0xFFFFFFFF.toInt()),
tertiaryContainer = StaticColor(0xFFFFFFFF.toInt()),
onTertiaryContainer = StaticColor(0xFF000000.toInt()),
background = StaticColor(0xFFFFFFFF.toInt()),
onBackground = StaticColor(0xFF000000.toInt()),
surface = StaticColor(0xFFFFFFFF.toInt()),
onSurface = StaticColor(0xFF000000.toInt()),
surfaceVariant = StaticColor(0xFFFFFFFF.toInt()),
onSurfaceVariant = StaticColor(0xFF000000.toInt()),
inverseSurface = StaticColor(0xFF000000.toInt()),
inverseOnSurface = StaticColor(0xFFFFFFFF.toInt()),
error = null,
onError = null,
errorContainer = null,
onErrorContainer = null,
outline = StaticColor(0xFF000000.toInt()),
surfaceTint = StaticColor(0xFFFFFFFF.toInt()),
outlineVariant = StaticColor(0xFF000000.toInt()),
scrim = StaticColor(0xFF000000.toInt()),
surfaceDim = StaticColor(0xFFFFFFFF.toInt()),
surfaceBright = StaticColor(0xFFFFFFFF.toInt()),
surfaceContainer = StaticColor(0xFFFFFFFF.toInt()),
surfaceContainerHigh = StaticColor(0xFFFFFFFF.toInt()),
surfaceContainerHighest = StaticColor(0xFFFFFFFF.toInt()),
surfaceContainerLow = StaticColor(0xFFFFFFFF.toInt()),
surfaceContainerLowest = StaticColor(0xFFFFFFFF.toInt()),
)
val BlackAndWhiteDarkColorScheme = ColorScheme<Color?>(
primary = StaticColor(0xFFFFFFFF.toInt()),
onPrimary = StaticColor(0xFF000000.toInt()),
primaryContainer = StaticColor(0xFF000000.toInt()),
onPrimaryContainer = StaticColor(0xFFFFFFFF.toInt()),
inversePrimary = StaticColor(0xFF000000.toInt()),
secondary = StaticColor(0xFFFFFFFF.toInt()),
onSecondary = StaticColor(0xFF000000.toInt()),
secondaryContainer = StaticColor(0xFF000000.toInt()),
onSecondaryContainer = StaticColor(0xFFFFFFFF.toInt()),
tertiary = StaticColor(0xFFFFFFFF.toInt()),
onTertiary = StaticColor(0xFF000000.toInt()),
tertiaryContainer = StaticColor(0xFF000000.toInt()),
onTertiaryContainer = StaticColor(0xFFFFFFFF.toInt()),
background = StaticColor(0xFF000000.toInt()),
onBackground = StaticColor(0xFFFFFFFF.toInt()),
surface = StaticColor(0xFF000000.toInt()),
onSurface = StaticColor(0xFFFFFFFF.toInt()),
surfaceVariant = StaticColor(0xFF000000.toInt()),
onSurfaceVariant = StaticColor(0xFFFFFFFF.toInt()),
inverseSurface = StaticColor(0xFFFFFFFF.toInt()),
inverseOnSurface = StaticColor(0xFF000000.toInt()),
error = null,
onError = null,
errorContainer = null,
onErrorContainer = null,
outline = StaticColor(0xFFFFFFFF.toInt()),
surfaceTint = StaticColor(0xFFFFFFFF.toInt()),
outlineVariant = StaticColor(0xFFFFFFFF.toInt()),
scrim = StaticColor(0xFFFFFFFF.toInt()),
surfaceDim = StaticColor(0xFF000000.toInt()),
surfaceBright = StaticColor(0xFF000000.toInt()),
surfaceContainer = StaticColor(0xFF000000.toInt()),
surfaceContainerHigh = StaticColor(0xFF000000.toInt()),
surfaceContainerHighest = StaticColor(0xFF000000.toInt()),
surfaceContainerLow = StaticColor(0xFF000000.toInt()),
surfaceContainerLowest = StaticColor(0xFF000000.toInt()),
)

View File

@ -1,7 +1,8 @@
package de.mm20.launcher2.themes
package de.mm20.launcher2.themes.shapes
import de.mm20.launcher2.database.entities.ShapesEntity
import de.mm20.launcher2.serialization.UUIDSerializer
import de.mm20.launcher2.themes.ShapeSerializer
import kotlinx.serialization.Serializable
import java.util.UUID

View File

@ -0,0 +1,129 @@
package de.mm20.launcher2.themes.shapes
import android.content.Context
import de.mm20.launcher2.database.AppDatabase
import de.mm20.launcher2.themes.CutShapesId
import de.mm20.launcher2.themes.DefaultThemeId
import de.mm20.launcher2.themes.ExtraRoundShapesId
import de.mm20.launcher2.themes.R
import de.mm20.launcher2.themes.RectShapesId
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import java.util.UUID
class ShapesRepository(
private val context: Context,
private val database: AppDatabase,
) {
private val scope = CoroutineScope(Dispatchers.IO + Job())
fun getAll(): Flow<List<Shapes>> {
return database.themeDao().getAllShapes().map {
getBuiltIn() + it.map { Shapes(it) }
}
}
fun get(id: UUID): Flow<Shapes?> {
if (id == DefaultThemeId) return flowOf(default)
if (id == ExtraRoundShapesId) return flowOf(extraRound)
if (id == RectShapesId) return flowOf(rect)
if (id == CutShapesId) return flowOf(cut)
return database.themeDao().getShapes(id).map { it?.let { Shapes(it) } }
.flowOn(Dispatchers.Default)
}
fun create(shapes: Shapes) {
scope.launch {
database.themeDao().insertShapes(shapes.toEntity())
}
}
fun update(shapes: Shapes) {
scope.launch {
database.themeDao().updateShapes(shapes.toEntity())
}
}
fun delete(shapes: Shapes) {
scope.launch {
database.themeDao().deleteShapes(shapes.id)
}
}
fun getOrDefault(id: UUID?): Flow<Shapes> {
if (id == null) return flowOf(default)
return get(id).map { it ?: default }
}
private fun getBuiltIn(): List<Shapes> {
return listOf(
default,
extraRound,
rect,
cut,
)
}
private val default: Shapes
get() = Shapes(
id = DefaultThemeId,
builtIn = true,
name = context.getString(R.string.preference_shapes_default),
baseShape = Shape(
corners = CornerStyle.Rounded,
radii = intArrayOf(12, 12, 12, 12),
)
)
private val cut: Shapes
get() = Shapes(
id = CutShapesId,
builtIn = true,
name = context.getString(R.string.preference_cards_shape_cut),
baseShape = Shape(
corners = CornerStyle.Cut,
radii = intArrayOf(12, 12, 12, 12),
)
)
private val extraRound: Shapes
get() = Shapes(
id = ExtraRoundShapesId,
builtIn = true,
name = context.getString(R.string.preference_shapes_extra_round),
baseShape = Shape(
corners = CornerStyle.Rounded,
radii = intArrayOf(24, 24, 24, 24),
),
extraSmall = Shape(
radii = intArrayOf(4, 4, 4, 4),
),
extraLarge = Shape(
radii = intArrayOf(36, 36, 36, 36),
),
extraLargeIncreased = Shape(
radii = intArrayOf(40, 40, 40, 40),
),
extraExtraLarge = Shape(
radii = intArrayOf(56, 56, 56, 56),
)
)
private val rect: Shapes
get() = Shapes(
id = RectShapesId,
builtIn = true,
name = context.getString(R.string.preference_shapes_rect),
baseShape = Shape(
corners = CornerStyle.Rounded,
radii = intArrayOf(0, 0, 0, 0),
)
)
}

View File

@ -0,0 +1,35 @@
package de.mm20.launcher2.themes.transparencies
import de.mm20.launcher2.database.entities.TransparenciesEntity
import de.mm20.launcher2.serialization.UUIDSerializer
import kotlinx.serialization.Serializable
import java.util.UUID
@Serializable
data class Transparencies(
@Serializable(with = UUIDSerializer::class) val id: UUID = UUID.randomUUID(),
val builtIn: Boolean = false,
val name: String,
val background: Float? = null,
val surface: Float? = null,
val elevatedSurface: Float? = null,
) {
constructor(entity: TransparenciesEntity) : this(
id = entity.id,
builtIn = false,
name = entity.name,
background = entity.background,
surface = entity.surface,
elevatedSurface = entity.elevatedSurface,
)
internal fun toEntity(): TransparenciesEntity {
return TransparenciesEntity(
id = id,
name = name,
background = background,
surface = surface,
elevatedSurface = elevatedSurface,
)
}
}

View File

@ -0,0 +1,87 @@
package de.mm20.launcher2.themes.transparencies
import android.content.Context
import de.mm20.launcher2.database.AppDatabase
import de.mm20.launcher2.themes.DefaultThemeId
import de.mm20.launcher2.themes.R
import de.mm20.launcher2.themes.SemiTransparentId
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import java.util.UUID
class TransparenciesRepository(
private val context: Context,
private val database: AppDatabase,
) {
private val scope = CoroutineScope(Dispatchers.IO + Job())
fun getAll(): Flow<List<Transparencies>> {
return database.themeDao().getAllTransparencies().map {
getBuiltIn() + it.map { Transparencies(it) }
}
}
fun get(id: UUID): Flow<Transparencies?> {
if (id == DefaultThemeId) return flowOf(default)
if (id == SemiTransparentId) return flowOf(semiTransparent)
return database.themeDao().getTransparencies(id).map { it?.let { Transparencies(it) } }
}
fun create(transparencies: Transparencies) {
scope.launch {
database.themeDao().insertTransparencies(transparencies.toEntity())
}
}
fun update(transparencies: Transparencies) {
scope.launch {
database.themeDao().updateTransparencies(transparencies.toEntity())
}
}
fun delete(transparencies: Transparencies) {
scope.launch {
database.themeDao().deleteTransparencies(transparencies.id)
}
}
fun getOrDefault(id: UUID?): Flow<Transparencies> {
if (id == null) return flowOf(default)
return get(id).map { it ?: default }
}
private fun getBuiltIn(): List<Transparencies> {
return listOf(
default,
semiTransparent,
)
}
private val default: Transparencies
get() = Transparencies(
id = DefaultThemeId,
builtIn = true,
name = context.getString(R.string.preference_transparencies_default),
background = 0.85f,
surface = 1f,
elevatedSurface = 1f,
)
private val semiTransparent: Transparencies
get() = Transparencies(
id = SemiTransparentId,
builtIn = true,
name = context.getString(R.string.preference_transparencies_semi_transparent),
background = 0.40f,
surface = 0.65f,
elevatedSurface = 0.85f,
)
}

View File

@ -37,7 +37,7 @@ androidx-constraint-layout = "1.1.1"
androidx-emojipicker = "1.5.0"
accompanist = "0.36.0"
haze = "1.6.3"
haze = "1.6.4"
coil = "2.7.0"
koin = "4.0.4"
retrofit = "2.11.0"