(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 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.IconShape
import de.mm20.launcher2.preferences.ui.CardStyle import de.mm20.launcher2.preferences.ui.CardStyle
import de.mm20.launcher2.preferences.ui.GridSettings 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.LocalCardStyle
import de.mm20.launcher2.ui.locals.LocalFavoritesEnabled import de.mm20.launcher2.ui.locals.LocalFavoritesEnabled
import de.mm20.launcher2.ui.locals.LocalGridSettings 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.FavoritesWidget
import de.mm20.launcher2.widgets.WidgetRepository import de.mm20.launcher2.widgets.WidgetRepository
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
@ -48,10 +50,6 @@ fun ProvideSettings(
LocalCardStyle provides cardStyle, LocalCardStyle provides cardStyle,
LocalFavoritesEnabled provides favoritesEnabled, LocalFavoritesEnabled provides favoritesEnabled,
LocalGridSettings provides gridSettings, LocalGridSettings provides gridSettings,
LocalTransparencyScheme provides TransparencyScheme(
background = cardStyle.opacity * 0.85f,
surface = cardStyle.opacity,
)
) { ) {
ProvideIconShape(iconShape) { ProvideIconShape(iconShape) {
content() content()

View File

@ -40,7 +40,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel 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.R
import de.mm20.launcher2.ui.component.BottomSheetDialog import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.LargeMessage import de.mm20.launcher2.ui.component.LargeMessage

View File

@ -5,9 +5,8 @@ import android.net.Uri
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.preferences.ColorsDescriptor
import de.mm20.launcher2.preferences.ui.UiSettings 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.ThemeRepository
import de.mm20.launcher2.themes.fromLegacyJson import de.mm20.launcher2.themes.fromLegacyJson
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -58,9 +57,9 @@ class ImportThemeSheetVM : ViewModel(), KoinComponent {
} }
private fun importTheme(colors: Colors, apply: Boolean) { private fun importTheme(colors: Colors, apply: Boolean) {
themeRepository.createColors(colors) themeRepository.colors.create(colors)
if (apply) { 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 package de.mm20.launcher2.ui.component
import androidx.compose.foundation.BorderStroke 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.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier 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.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape 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 androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import de.mm20.launcher2.ui.locals.LocalCardStyle import de.mm20.launcher2.ui.locals.LocalCardStyle
import de.mm20.launcher2.ui.theme.transparency.LocalTransparencyScheme import de.mm20.launcher2.ui.theme.transparency.transparency
@Composable @Composable
fun LauncherCard( fun LauncherCard(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
elevation: Dp = 2.dp, elevation: Dp = 2.dp,
backgroundOpacity: Float = LocalTransparencyScheme.current.surface, backgroundOpacity: Float = MaterialTheme.transparency.surface,
shape: Shape = MaterialTheme.shapes.medium, 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 } border: BorderStroke? = LocalCardStyle.current.borderWidth.takeIf { it > 0 }
?.let { BorderStroke(it.dp, MaterialTheme.colorScheme.surface) }, ?.let { BorderStroke(it.dp, MaterialTheme.colorScheme.surface) },
content: @Composable () -> Unit = {} content: @Composable () -> Unit = {}
@ -49,149 +39,3 @@ fun LauncherCard(
tonalElevation = elevation, 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.preferences.SearchBarStyle
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.layout.BottomReversed import de.mm20.launcher2.ui.layout.BottomReversed
import de.mm20.launcher2.ui.theme.transparency.LocalTransparencyScheme import de.mm20.launcher2.ui.theme.transparency.transparency
@Composable @Composable
fun SearchBar( fun SearchBar(
@ -102,7 +102,7 @@ fun SearchBar(
} }
}) { }) {
when { when {
it == SearchBarLevel.Active -> LocalTransparencyScheme.current.surface it == SearchBarLevel.Active -> MaterialTheme.transparency.surface
style != SearchBarStyle.Transparent -> 1f style != SearchBarStyle.Transparent -> 1f
it == SearchBarLevel.Resting -> 0f it == SearchBarLevel.Resting -> 0f
else -> 1f 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.SearchVM
import de.mm20.launcher2.ui.launcher.search.filters.KeyboardFilterBar import de.mm20.launcher2.ui.launcher.search.filters.KeyboardFilterBar
import de.mm20.launcher2.ui.launcher.searchbar.LauncherSearchBar 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.hazeEffect
import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.hazeSource
import dev.chrisbanes.haze.rememberHazeState import dev.chrisbanes.haze.rememberHazeState
@ -1416,7 +1416,7 @@ internal fun LauncherScaffold(
.homePageAnimation( .homePageAnimation(
state, state,
if (config.homeComponent.drawBackground) { if (config.homeComponent.drawBackground) {
config.backgroundColor.copy(alpha = LocalTransparencyScheme.current.background) config.backgroundColor.copy(alpha = MaterialTheme.transparency.background)
} else { } else {
Color.Transparent Color.Transparent
} }
@ -1516,7 +1516,7 @@ internal fun LauncherScaffold(
blurRadius = 4.dp blurRadius = 4.dp
} }
.background( .background(
MaterialTheme.colorScheme.surfaceContainer.copy(alpha = LocalTransparencyScheme.current.background) MaterialTheme.colorScheme.surfaceContainer.copy(alpha = MaterialTheme.transparency.background)
) )
.statusBarsPadding() .statusBarsPadding()
) )
@ -1536,7 +1536,7 @@ internal fun LauncherScaffold(
blurRadius = 4.dp blurRadius = 4.dp
} }
.background( .background(
MaterialTheme.colorScheme.surfaceContainer.copy(alpha = LocalTransparencyScheme.current.background) MaterialTheme.colorScheme.surfaceContainer.copy(alpha = MaterialTheme.transparency.background)
) )
.navigationBarsPadding() .navigationBarsPadding()
) )
@ -1583,7 +1583,7 @@ private fun SecondaryPage(
.fillMaxSize() .fillMaxSize()
.secondaryPageAnimation( .secondaryPageAnimation(
state, state,
config.backgroundColor.copy(alpha = LocalTransparencyScheme.current.background), config.backgroundColor.copy(alpha = MaterialTheme.transparency.background),
) )
val composable = composables[component] 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.launcher.sheets.LocalBottomSheetManager
import de.mm20.launcher2.ui.locals.LocalCardStyle import de.mm20.launcher2.ui.locals.LocalCardStyle
import de.mm20.launcher2.ui.locals.LocalGridSettings import de.mm20.launcher2.ui.locals.LocalGridSettings
import de.mm20.launcher2.ui.theme.transparency.LocalTransparencyScheme import de.mm20.launcher2.ui.theme.transparency.transparency
@Composable @Composable
fun SearchColumn( fun SearchColumn(
@ -370,7 +370,7 @@ fun LazyListScope.SingleResult(
vertical = 4.dp, vertical = 4.dp,
), ),
color = if (highlight) MaterialTheme.colorScheme.secondaryContainer color = if (highlight) MaterialTheme.colorScheme.secondaryContainer
else MaterialTheme.colorScheme.surface.copy(LocalTransparencyScheme.current.surface) else MaterialTheme.colorScheme.surface.copy(MaterialTheme.transparency.surface)
) { ) {
content() 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.LocalGridSettings
import de.mm20.launcher2.ui.locals.LocalWindowSize import de.mm20.launcher2.ui.locals.LocalWindowSize
import de.mm20.launcher2.ui.overlays.Overlay import de.mm20.launcher2.ui.overlays.Overlay
import de.mm20.launcher2.ui.theme.transparency.transparency
import kotlin.math.pow import kotlin.math.pow
@ -266,7 +267,7 @@ fun ItemPopup(origin: IntRect, searchable: Searchable, onDismissRequest: () -> U
) { ) {
LauncherCard( LauncherCard(
elevation = 8.dp * animationProgress.value, elevation = 8.dp * animationProgress.value,
backgroundOpacity = 1f, backgroundOpacity = MaterialTheme.transparency.elevatedSurface,
modifier = Modifier modifier = Modifier
.placeOverlay( .placeOverlay(
origin.translate( origin.translate(

View File

@ -13,7 +13,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import de.mm20.launcher2.search.SavableSearchable import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.ui.ktx.withCorners 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 import kotlin.math.ceil
fun <T : SavableSearchable> LazyListScope.GridResults( fun <T : SavableSearchable> LazyListScope.GridResults(
@ -39,7 +39,7 @@ fun <T : SavableSearchable> LazyListScope.GridResults(
bottom = if (!reverse && isBottom) 8.dp else 0.dp, bottom = if (!reverse && isBottom) 8.dp else 0.dp,
) )
.background( .background(
MaterialTheme.colorScheme.surface.copy(alpha = LocalTransparencyScheme.current.surface), MaterialTheme.colorScheme.surface.copy(alpha = MaterialTheme.transparency.surface),
MaterialTheme.shapes.medium.withCorners( MaterialTheme.shapes.medium.withCorners(
topStart = isTop, topStart = isTop,
topEnd = isTop, topEnd = isTop,
@ -72,7 +72,7 @@ fun <T : SavableSearchable> LazyListScope.GridResults(
bottom = if (!reverse && isLast) 8.dp else 0.dp, bottom = if (!reverse && isLast) 8.dp else 0.dp,
) )
.background( .background(
MaterialTheme.colorScheme.surface.copy(alpha = LocalTransparencyScheme.current.surface), MaterialTheme.colorScheme.surface.copy(alpha = MaterialTheme.transparency.surface),
MaterialTheme.shapes.medium.withCorners( MaterialTheme.shapes.medium.withCorners(
topStart = isFirst && !reverse || isLast && reverse, topStart = isFirst && !reverse || isLast && reverse,
topEnd = 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, bottom = if (!reverse) 8.dp else 0.dp,
) )
.background( .background(
MaterialTheme.colorScheme.surface.copy(alpha = LocalTransparencyScheme.current.surface), MaterialTheme.colorScheme.surface.copy(alpha = MaterialTheme.transparency.surface),
MaterialTheme.shapes.medium.withCorners( MaterialTheme.shapes.medium.withCorners(
topStart = isTop, topStart = isTop,
topEnd = isTop, topEnd = isTop,

View File

@ -1,6 +1,5 @@
package de.mm20.launcher2.ui.launcher.search.common.list 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.animateDp
import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.updateTransition 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.layout.padding
import androidx.compose.foundation.lazy.LazyItemScope import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -22,10 +20,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow import androidx.compose.ui.draw.shadow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import de.mm20.launcher2.search.SavableSearchable 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.ktx.animateShapeAsState
import de.mm20.launcher2.ui.layout.BottomReversed 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( fun <T : SavableSearchable> LazyListScope.ListResults(
key: String, key: String,
@ -101,12 +98,13 @@ fun LazyItemScope.ListItemSurface(
content: @Composable ColumnScope.() -> Unit, content: @Composable ColumnScope.() -> Unit,
) { ) {
val transition = updateTransition(isExpanded) val transition = updateTransition(isExpanded)
val elevation by transition.animateDp {
if (it) 2.dp else 0.dp
}
val backgroundAlpha by transition.animateFloat { 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 { val padding by transition.animateDp {
if (it) 8.dp else 1.dp 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.component.Banner
import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid
import de.mm20.launcher2.ui.layout.BottomReversed 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( fun LazyListScope.SearchFavorites(
favorites: List<SavableSearchable>, favorites: List<SavableSearchable>,
@ -47,7 +47,7 @@ fun LazyListScope.SearchFavorites(
) )
.background( .background(
MaterialTheme.colorScheme.surface.copy( MaterialTheme.colorScheme.surface.copy(
LocalTransparencyScheme.current.surface MaterialTheme.transparency.surface
), ),
MaterialTheme.shapes.medium 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.PluginRepository
import de.mm20.launcher2.plugin.PluginType import de.mm20.launcher2.plugin.PluginType
import de.mm20.launcher2.search.calendar.CalendarListType 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.R
import de.mm20.launcher2.ui.base.LocalAppWidgetHost import de.mm20.launcher2.ui.base.LocalAppWidgetHost
import de.mm20.launcher2.ui.component.BottomSheetDialog 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.WeatherWidget
import de.mm20.launcher2.widgets.Widget import de.mm20.launcher2.widgets.Widget
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import org.koin.androidx.compose.get
import org.koin.compose.koinInject import org.koin.compose.koinInject
import java.time.ZonedDateTime import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter 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.music.MusicWidget
import de.mm20.launcher2.ui.launcher.widgets.notes.NotesWidget import de.mm20.launcher2.ui.launcher.widgets.notes.NotesWidget
import de.mm20.launcher2.ui.launcher.widgets.weather.WeatherWidget 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.AppWidget
import de.mm20.launcher2.widgets.CalendarWidget import de.mm20.launcher2.widgets.CalendarWidget
import de.mm20.launcher2.widgets.FavoritesWidget import de.mm20.launcher2.widgets.FavoritesWidget
@ -73,7 +73,7 @@ fun WidgetItem(
} else null } else null
val backgroundOpacity by animateFloatAsState( 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", 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.EnterHomeTransitionParams
import de.mm20.launcher2.ui.launcher.transitions.HandleEnterHomeTransition import de.mm20.launcher2.ui.launcher.transitions.HandleEnterHomeTransition
import de.mm20.launcher2.ui.locals.LocalWindowSize 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 de.mm20.launcher2.widgets.MusicWidget
import kotlin.math.min import kotlin.math.min
@ -352,7 +352,7 @@ fun MusicWidget(widget: MusicWidget) {
) { ) {
FilledTonalIconButton( FilledTonalIconButton(
colors = IconButtonDefaults.filledTonalIconButtonColors( colors = IconButtonDefaults.filledTonalIconButtonColors(
containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = LocalTransparencyScheme.current.surface), containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = MaterialTheme.transparency.surface),
), ),
onClick = { viewModel.togglePause() }, 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.Tooltip
import de.mm20.launcher2.ui.component.weather.AnimatedWeatherIcon import de.mm20.launcher2.ui.component.weather.AnimatedWeatherIcon
import de.mm20.launcher2.ui.component.weather.WeatherIcon 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.DailyForecast
import de.mm20.launcher2.weather.Forecast import de.mm20.launcher2.weather.Forecast
import de.mm20.launcher2.widgets.WeatherWidget import de.mm20.launcher2.widgets.WeatherWidget
@ -187,7 +187,7 @@ fun WeatherWidget(widget: WeatherWidget) {
val currentDayForecasts by viewModel.currentDayForecasts val currentDayForecasts by viewModel.currentDayForecasts
Surface( Surface(
color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = LocalTransparencyScheme.current.surface), color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = MaterialTheme.transparency.surface),
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Column( Column(
@ -299,7 +299,7 @@ fun CurrentWeather(forecast: Forecast, imperialUnits: Boolean) {
topEnd = CornerSize(0), topEnd = CornerSize(0),
bottomEnd = 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(
text = "${forecast.provider} (${ 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.shapes.ShapeSchemesSettingsScreen
import de.mm20.launcher2.ui.settings.tags.TagsSettingsScreen import de.mm20.launcher2.ui.settings.tags.TagsSettingsScreen
import de.mm20.launcher2.ui.settings.tasks.TasksIntegrationSettingsScreen 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.UnitConverterHelpSettingsScreen
import de.mm20.launcher2.ui.settings.unitconverter.UnitConverterSettingsScreen import de.mm20.launcher2.ui.settings.unitconverter.UnitConverterSettingsScreen
import de.mm20.launcher2.ui.settings.weather.WeatherIntegrationSettingsScreen import de.mm20.launcher2.ui.settings.weather.WeatherIntegrationSettingsScreen
@ -206,6 +210,14 @@ class SettingsActivity : BaseActivity() {
} ?: return@composable } ?: return@composable
ShapeSchemeSettingsScreen(id) ShapeSchemeSettingsScreen(id)
} }
composable<TransparencySchemesSettingsRoute> {
TransparencySchemesSettingsScreen()
}
composable<TransparencySchemeSettingsRoute> {
val route: TransparencySchemeSettingsRoute = it.toRoute()
?: return@composable
TransparencySchemeSettingsScreen(UUID.fromString(route.id))
}
composable("settings/appearance/cards") { composable("settings/appearance/cards") {
CardsSettingsScreen() 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.ArrowCircleDown
import androidx.compose.material.icons.rounded.ArrowCircleUp import androidx.compose.material.icons.rounded.ArrowCircleUp
import androidx.compose.material.icons.rounded.CropSquare 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.Palette
import androidx.compose.material.icons.rounded.TextFields import androidx.compose.material.icons.rounded.TextFields
import androidx.compose.material3.Text 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.PreferenceScreen
import de.mm20.launcher2.ui.component.preferences.value import de.mm20.launcher2.ui.component.preferences.value
import de.mm20.launcher2.ui.locals.LocalNavController import de.mm20.launcher2.ui.locals.LocalNavController
import de.mm20.launcher2.ui.settings.transparencies.TransparencySchemesSettingsRoute
import de.mm20.launcher2.ui.theme.getTypography import de.mm20.launcher2.ui.theme.getTypography
@Composable @Composable
@ -36,6 +38,7 @@ fun AppearanceSettingsScreen() {
val navController = LocalNavController.current val navController = LocalNavController.current
val colorThemeName by viewModel.colorThemeName.collectAsStateWithLifecycle(null) val colorThemeName by viewModel.colorThemeName.collectAsStateWithLifecycle(null)
val shapeThemeName by viewModel.shapeThemeName.collectAsStateWithLifecycle(null) val shapeThemeName by viewModel.shapeThemeName.collectAsStateWithLifecycle(null)
val transparencyThemeName by viewModel.transparencyThemeName.collectAsStateWithLifecycle(null)
val compatModeColors by viewModel.compatModeColors.collectAsState() val compatModeColors by viewModel.compatModeColors.collectAsState()
val importLauncher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { val importLauncher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {
@ -101,6 +104,14 @@ fun AppearanceSettingsScreen() {
}, },
icon = Icons.Rounded.CropSquare, icon = Icons.Rounded.CropSquare,
) )
Preference(
title = stringResource(id = R.string.preference_screen_transparencies),
summary = transparencyThemeName,
onClick = {
navController?.navigate(TransparencySchemesSettingsRoute)
},
icon = Icons.Rounded.Opacity,
)
Preference( Preference(
title = stringResource(R.string.preference_cards), title = stringResource(R.string.preference_cards),

View File

@ -25,15 +25,22 @@ class AppearanceSettingsScreenVM : ViewModel(), KoinComponent {
uiSettings.setColorScheme(colorScheme) uiSettings.setColorScheme(colorScheme)
} }
val colorThemeName = uiSettings.colors.flatMapLatest { val colorThemeName = uiSettings.colorsId.flatMapLatest {
themeRepository.getColorsOrDefault(it) themeRepository.colors.getOrDefault(it)
}.map { }.map {
it.name it.name
} }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val shapeThemeName = uiSettings.shapes.flatMapLatest { val shapeThemeName = uiSettings.shapesId.flatMapLatest {
themeRepository.getShapesOrDefault(it) themeRepository.shapes.getOrDefault(it)
}.map {
it.name
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val transparencyThemeName = uiSettings.transparenciesId.flatMapLatest {
themeRepository.transparencies.getOrDefault(it)
}.map { }.map {
it.name 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.Icons
import androidx.compose.material.icons.rounded.CropSquare import androidx.compose.material.icons.rounded.CropSquare
import androidx.compose.material.icons.rounded.KeyboardArrowDown 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.Palette
import androidx.compose.material.icons.rounded.Save import androidx.compose.material.icons.rounded.Save
import androidx.compose.material.icons.rounded.Share import androidx.compose.material.icons.rounded.Share
@ -50,10 +51,11 @@ fun ExportThemeSettingsScreen() {
val colorSchemes by viewModel.colorSchemes.collectAsState(emptyList()) val colorSchemes by viewModel.colorSchemes.collectAsState(emptyList())
val shapeThemes by viewModel.shapeSchemes.collectAsState(emptyList()) val shapeThemes by viewModel.shapeSchemes.collectAsState(emptyList())
val transparencySchemes by viewModel.transparencySchemes.collectAsState(emptyList())
val isValidSelection by remember { val isValidSelection by remember {
derivedStateOf { 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) 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 { item {

View File

@ -3,7 +3,6 @@ package de.mm20.launcher2.ui.settings.appearance
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.util.Log
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@ -11,11 +10,11 @@ import androidx.core.content.FileProvider
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.ktx.tryStartActivity import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.themes.Colors import de.mm20.launcher2.themes.colors.Colors
import de.mm20.launcher2.themes.Shapes import de.mm20.launcher2.themes.shapes.Shapes
import de.mm20.launcher2.themes.ThemeBundle import de.mm20.launcher2.themes.ThemeBundle
import de.mm20.launcher2.themes.ThemeRepository 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.Dispatchers
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -28,8 +27,9 @@ class ExportThemeSettingsScreenVM: ViewModel(), KoinComponent {
private val themeRepository: ThemeRepository by inject() private val themeRepository: ThemeRepository by inject()
val colorSchemes = themeRepository.getAllColors().map { it.filter { !it.builtIn } } val colorSchemes = themeRepository.colors.getAll().map { it.filter { !it.builtIn } }
val shapeSchemes = themeRepository.getAllShapes().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 themeName by mutableStateOf("")
var themeAuthor by mutableStateOf("") var themeAuthor by mutableStateOf("")
@ -55,12 +55,21 @@ class ExportThemeSettingsScreenVM: ViewModel(), KoinComponent {
shapeScheme = scheme 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 { private fun getThemeBundle(): ThemeBundle {
return ThemeBundle( return ThemeBundle(
name = themeName, name = themeName,
author = themeAuthor.takeIf { it.isNotBlank() }, author = themeAuthor.takeIf { it.isNotBlank() },
colors = colorScheme, colors = colorScheme,
shapes = shapeScheme, 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.ErrorOutline
import androidx.compose.material.icons.rounded.LightMode import androidx.compose.material.icons.rounded.LightMode
import androidx.compose.material.icons.rounded.MoreVert 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.Palette
import androidx.compose.material.icons.rounded.Star import androidx.compose.material.icons.rounded.Star
import androidx.compose.material3.Button import androidx.compose.material3.Button
@ -35,6 +36,7 @@ import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.SingleChoiceSegmentedButtonRow import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf 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.PreferenceScreen
import de.mm20.launcher2.ui.component.preferences.SwitchPreference import de.mm20.launcher2.ui.component.preferences.SwitchPreference
import de.mm20.launcher2.ui.locals.LocalDarkTheme 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.darkColorSchemeOf
import de.mm20.launcher2.ui.theme.colorscheme.lightColorSchemeOf import de.mm20.launcher2.ui.theme.colorscheme.lightColorSchemeOf
import de.mm20.launcher2.ui.theme.shapes.shapesOf 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.coroutines.launch
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -118,10 +124,15 @@ fun ImportThemeSettingsScreen(
} ?: MaterialTheme.colorScheme, } ?: MaterialTheme.colorScheme,
shapes = themeBundle.shapes?.let { shapesOf(it) } ?: MaterialTheme.shapes, shapes = themeBundle.shapes?.let { shapesOf(it) } ?: MaterialTheme.shapes,
) { ) {
ThemePreview( val transparencies = themeBundle.transparencies?.let { transparencySchemeOf(it) } ?: MaterialTheme.transparency
darkMode = darkModePreview, CompositionLocalProvider(
onDarkModeChanged = { darkModePreview = it } LocalTransparencyScheme provides transparencies
) ) {
ThemePreview(
darkMode = darkModePreview,
onDarkModeChanged = { darkModePreview = it }
)
}
} }
} }
item { item {
@ -156,6 +167,21 @@ fun ImportThemeSettingsScreen(
} else null, } 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) { if (viewModel.colorsExists || viewModel.shapesExists) {
Banner( Banner(
modifier = Modifier modifier = Modifier
@ -211,8 +237,13 @@ private fun ThemePreview(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clip(MaterialTheme.shapes.medium) .clip(MaterialTheme.shapes.medium)
.checkerboard(
MaterialTheme.colorScheme.primary,
MaterialTheme.colorScheme.onPrimaryContainer,
12.dp,
)
.background( .background(
MaterialTheme.colorScheme.surfaceContainer, MaterialTheme.colorScheme.surfaceContainer.copy(alpha = MaterialTheme.transparency.background),
MaterialTheme.shapes.medium MaterialTheme.shapes.medium
) )
.innerShadow( .innerShadow(
@ -225,7 +256,7 @@ private fun ThemePreview(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(top = 12.dp, start = 12.dp, end = 12.dp), .padding(top = 12.dp, start = 12.dp, end = 12.dp),
level = SearchBarLevel.Active, level = SearchBarLevel.Raised,
value = "", value = "",
onValueChange = {}, onValueChange = {},
readOnly = true, readOnly = true,
@ -239,7 +270,10 @@ private fun ThemePreview(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(top = 12.dp, start = 12.dp, end = 12.dp) .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) .padding(12.dp)
) { ) {
Row( Row(

View File

@ -8,8 +8,6 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.crashreporter.CrashReporter 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.preferences.ui.UiSettings
import de.mm20.launcher2.themes.ThemeBundle import de.mm20.launcher2.themes.ThemeBundle
import de.mm20.launcher2.themes.ThemeRepository import de.mm20.launcher2.themes.ThemeRepository
@ -19,9 +17,8 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject 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 themeRepository by inject<ThemeRepository>()
private val uiSettings by inject<UiSettings>() private val uiSettings by inject<UiSettings>()
@ -35,6 +32,9 @@ class ImportThemeSettingsScreenVM: ViewModel(), KoinComponent {
var shapesExists by mutableStateOf(false) var shapesExists by mutableStateOf(false)
private set private set
var transparenciesExists by mutableStateOf(false)
private set
var loading by mutableStateOf(false) var loading by mutableStateOf(false)
private set private set
@ -54,11 +54,16 @@ class ImportThemeSettingsScreenVM: ViewModel(), KoinComponent {
val text = it.readText() val text = it.readText()
val theme = ThemeBundle.fromJson(text) val theme = ThemeBundle.fromJson(text)
if (theme != null) { if (theme != null) {
val colors = theme.colors?.id?.let { themeRepository.getColors(it) }?.first() val colors =
val shapes = theme.shapes?.id?.let { themeRepository.getShapes(it) }?.first() 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 colorsExists = colors != null
shapesExists = shapes != null shapesExists = shapes != null
transparenciesExists = transparencies != null
themeBundle = theme themeBundle = theme
loading = false loading = false
} else { } else {
@ -77,30 +82,42 @@ class ImportThemeSettingsScreenVM: ViewModel(), KoinComponent {
val colors = themeBundle.colors val colors = themeBundle.colors
val shapes = themeBundle.shapes val shapes = themeBundle.shapes
val transparencies = themeBundle.transparencies
val colorsExist = this.colorsExists val colorsExist = this.colorsExists
val shapesExist = this.shapesExists val shapesExist = this.shapesExists
val transparenciesExist = this.transparenciesExists
loading = true loading = true
return viewModelScope.launch { return viewModelScope.launch {
if (colors != null) { if (colors != null) {
if (colorsExist) { if (colorsExist) {
themeRepository.updateColors(colors) themeRepository.colors.update(colors)
} else { } else {
themeRepository.createColors(colors) themeRepository.colors.create(colors)
} }
if (applyTheme) { if (applyTheme) {
uiSettings.setColors(ColorsDescriptor.Custom(colors.id.toString())) uiSettings.setColorsId(colors.id)
} }
} }
if (shapes != null) { if (shapes != null) {
if (shapesExist) { if (shapesExist) {
themeRepository.updateShapes(shapes) themeRepository.shapes.update(shapes)
} else { } else {
themeRepository.createShapes(shapes) themeRepository.shapes.create(shapes)
} }
if (applyTheme) { 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 loading = false

View File

@ -1,32 +1,19 @@
package de.mm20.launcher2.ui.settings.calendarsearch 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.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.CheckboxDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.calendar.providers.CalendarList 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.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.R
import de.mm20.launcher2.ui.component.Banner
import de.mm20.launcher2.ui.component.preferences.CheckboxPreference import de.mm20.launcher2.ui.component.preferences.CheckboxPreference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen import de.mm20.launcher2.ui.component.preferences.PreferenceScreen

View File

@ -47,16 +47,6 @@ fun CardsSettingsScreen() {
} }
item { item {
PreferenceCategory { 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( SliderPreference(
title = stringResource(R.string.preference_cards_stroke_width), title = stringResource(R.string.preference_cards_stroke_width),
icon = Icons.Rounded.LineWeight, icon = Icons.Rounded.LineWeight,

View File

@ -28,7 +28,6 @@ import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.FilterChip import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -51,7 +50,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.lifecycle.compose.collectAsStateWithLifecycle 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.ColorLayer
import de.mm20.launcher2.icons.StaticLauncherIcon import de.mm20.launcher2.icons.StaticLauncherIcon
import de.mm20.launcher2.icons.TintedIconLayer import de.mm20.launcher2.icons.TintedIconLayer
import de.mm20.launcher2.themes.DefaultDarkColorScheme import de.mm20.launcher2.themes.colors.DefaultDarkColorScheme
import de.mm20.launcher2.themes.DefaultLightColorScheme import de.mm20.launcher2.themes.colors.DefaultLightColorScheme
import de.mm20.launcher2.themes.merge import de.mm20.launcher2.themes.colors.merge
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.Banner import de.mm20.launcher2.ui.component.Banner
import de.mm20.launcher2.ui.component.ShapedLauncherIcon import de.mm20.launcher2.ui.component.ShapedLauncherIcon

View File

@ -1,8 +1,5 @@
package de.mm20.launcher2.ui.settings.colorscheme 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.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box 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.Add
import androidx.compose.material.icons.rounded.ContentCopy import androidx.compose.material.icons.rounded.ContentCopy
import androidx.compose.material.icons.rounded.Delete 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.Edit
import androidx.compose.material.icons.rounded.MoreVert import androidx.compose.material.icons.rounded.MoreVert
import androidx.compose.material.icons.rounded.RadioButtonChecked 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.AlertDialog
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -45,9 +40,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel 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.R
import de.mm20.launcher2.ui.common.ImportThemeSheet
import de.mm20.launcher2.ui.component.preferences.Preference import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen 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.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.ktx.tryStartActivity import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.preferences.ColorsDescriptor
import de.mm20.launcher2.preferences.ui.UiSettings import de.mm20.launcher2.preferences.ui.UiSettings
import de.mm20.launcher2.themes.BlackAndWhiteThemeId import de.mm20.launcher2.themes.BlackAndWhiteThemeId
import de.mm20.launcher2.themes.DefaultThemeId 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.HighContrastThemeId
import de.mm20.launcher2.themes.ThemeRepository import de.mm20.launcher2.themes.ThemeRepository
import de.mm20.launcher2.themes.toLegacyJson import de.mm20.launcher2.themes.toLegacyJson
@ -30,39 +29,27 @@ class ColorSchemesSettingsScreenVM : ViewModel(), KoinComponent {
private val themeRepository: ThemeRepository by inject() private val themeRepository: ThemeRepository by inject()
private val uiSettings: UiSettings by inject() private val uiSettings: UiSettings by inject()
val selectedColors = uiSettings.colors.map { val selectedColors = uiSettings.colorsId
when(it) { val colors: Flow<List<Colors>> = themeRepository.colors.getAll()
ColorsDescriptor.Default -> DefaultThemeId
ColorsDescriptor.HighContrast -> HighContrastThemeId
ColorsDescriptor.BlackAndWhite -> BlackAndWhiteThemeId
is ColorsDescriptor.Custom -> UUID.fromString(it.id)
}
}
val colors: Flow<List<Colors>> = themeRepository.getAllColors()
fun getTheme(id: UUID): Flow<Colors?> { fun getTheme(id: UUID): Flow<Colors?> {
return themeRepository.getColors(id) return themeRepository.colors.get(id)
} }
fun updateTheme(colors: Colors) { fun updateTheme(colors: Colors) {
themeRepository.updateColors(colors) themeRepository.colors.update(colors)
} }
fun selectTheme(colors: Colors) { fun selectTheme(colors: Colors) {
uiSettings.setColors(when(colors.id) { uiSettings.setColorsId(colors.id)
DefaultThemeId -> ColorsDescriptor.Default
HighContrastThemeId -> ColorsDescriptor.HighContrast
BlackAndWhiteThemeId -> ColorsDescriptor.BlackAndWhite
else -> ColorsDescriptor.Custom(colors.id.toString())
})
} }
fun duplicate(colors: Colors) { fun duplicate(colors: Colors) {
themeRepository.createColors(colors.copy(id = UUID.randomUUID())) themeRepository.colors.create(colors.copy(id = UUID.randomUUID()))
} }
fun delete(colors: Colors) { fun delete(colors: Colors) {
themeRepository.deleteColors(colors) themeRepository.colors.delete(colors)
} }
fun exportTheme(context: Context, colors: Colors) { fun exportTheme(context: Context, colors: Colors) {
@ -85,10 +72,10 @@ class ColorSchemesSettingsScreenVM : ViewModel(), KoinComponent {
} }
fun createNew(context: Context) { fun createNew(context: Context) {
themeRepository.createColors( themeRepository.colors.create(
Colors( Colors(
id = UUID.randomUUID(), 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.animation.shrinkVertically
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth 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.Color
import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.res.stringResource 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.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import de.mm20.launcher2.themes.get
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.BottomSheetDialog 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.HctColorPicker
import de.mm20.launcher2.ui.component.colorpicker.rememberHctColorPickerState import de.mm20.launcher2.ui.component.colorpicker.rememberHctColorPickerState
import de.mm20.launcher2.ui.component.preferences.SwitchPreference 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.Canvas
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row 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.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState 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.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import de.mm20.launcher2.themes.ColorRef import de.mm20.launcher2.themes.colors.ColorRef
import de.mm20.launcher2.themes.CorePaletteColor import de.mm20.launcher2.themes.colors.CorePaletteColor
import de.mm20.launcher2.themes.FullCorePalette import de.mm20.launcher2.themes.colors.FullCorePalette
import de.mm20.launcher2.themes.StaticColor import de.mm20.launcher2.themes.colors.StaticColor
import de.mm20.launcher2.themes.atTone import de.mm20.launcher2.themes.colors.atTone
import de.mm20.launcher2.themes.get import de.mm20.launcher2.themes.colors.get
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.BottomSheetDialog 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.HctColorPicker
import de.mm20.launcher2.ui.component.colorpicker.rememberHctColorPickerState import de.mm20.launcher2.ui.component.colorpicker.rememberHctColorPickerState
import de.mm20.launcher2.ui.ktx.hct import de.mm20.launcher2.ui.ktx.hct
import hct.Hct import hct.Hct
import kotlin.math.roundToInt import kotlin.math.roundToInt
import de.mm20.launcher2.themes.Color as ThemeColor import de.mm20.launcher2.themes.colors.Color as ThemeColor
@Composable @Composable
fun ThemeColorPreference( 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.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size 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.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons 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.Info
import androidx.compose.material.icons.rounded.Settings import androidx.compose.material.icons.rounded.Settings
import androidx.compose.material.icons.rounded.Verified import androidx.compose.material.icons.rounded.Verified
import androidx.compose.material3.CheckboxDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -40,12 +36,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -57,15 +50,12 @@ import coil.compose.AsyncImage
import de.mm20.launcher2.crashreporter.CrashReporter import de.mm20.launcher2.crashreporter.CrashReporter
import de.mm20.launcher2.ktx.sendWithBackgroundPermission import de.mm20.launcher2.ktx.sendWithBackgroundPermission
import de.mm20.launcher2.plugin.PluginState import de.mm20.launcher2.plugin.PluginState
import de.mm20.launcher2.themes.atTone
import de.mm20.launcher2.ui.R 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.GuardedPreference
import de.mm20.launcher2.ui.component.preferences.Preference import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceWithSwitch import de.mm20.launcher2.ui.component.preferences.PreferenceWithSwitch
import de.mm20.launcher2.ui.component.preferences.SwitchPreference import de.mm20.launcher2.ui.component.preferences.SwitchPreference
import de.mm20.launcher2.ui.locals.LocalDarkTheme
import de.mm20.launcher2.ui.locals.LocalNavController import de.mm20.launcher2.ui.locals.LocalNavController
@Composable @Composable

View File

@ -66,7 +66,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.icons.CutCorner import de.mm20.launcher2.icons.CutCorner
import de.mm20.launcher2.icons.RoundedCornerAlt 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.R
import de.mm20.launcher2.ui.component.BottomSheetDialog import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory 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 java.util.UUID
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import de.mm20.launcher2.themes.Shape as ThemeShape import de.mm20.launcher2.themes.shapes.Shape as ThemeShape
@Composable @Composable
fun ShapeSchemeSettingsScreen(themeId: UUID) { fun ShapeSchemeSettingsScreen(themeId: UUID) {
@ -284,7 +284,7 @@ fun ShapeSchemeSettingsScreen(themeId: UUID) {
} }
} }
item { item {
PreferenceCategory(title = "Large") { PreferenceCategory {
ShapePreview( ShapePreview(
previewShapes = previewShapes, previewShapes = previewShapes,
) { ) {

View File

@ -35,8 +35,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.themes.CornerStyle import de.mm20.launcher2.themes.shapes.CornerStyle
import de.mm20.launcher2.themes.Shapes import de.mm20.launcher2.themes.shapes.Shapes
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.preferences.Preference import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
@ -49,7 +49,7 @@ fun ShapeSchemesSettingsScreen() {
val navController = LocalNavController.current val navController = LocalNavController.current
val context = LocalContext.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()) val themes by viewModel.shapes.collectAsStateWithLifecycle(emptyList())
var deleteShapes by remember { mutableStateOf<Shapes?>(null) } 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 android.content.Context
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import de.mm20.launcher2.preferences.ShapesDescriptor
import de.mm20.launcher2.preferences.ui.UiSettings import de.mm20.launcher2.preferences.ui.UiSettings
import de.mm20.launcher2.themes.CutShapesId import de.mm20.launcher2.themes.shapes.Shapes
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.ThemeRepository import de.mm20.launcher2.themes.ThemeRepository
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
import java.util.UUID import java.util.UUID
@ -23,45 +17,34 @@ class ShapeSchemesSettingsScreenVM : ViewModel(), KoinComponent {
private val themeRepository: ThemeRepository by inject() private val themeRepository: ThemeRepository by inject()
private val uiSettings: UiSettings by inject() private val uiSettings: UiSettings by inject()
val selectedShapes = uiSettings.shapes.map { val selectedShapesId = uiSettings.shapesId
when(it) { val shapes: Flow<List<Shapes>> = themeRepository.shapes.getAll()
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()
fun getShapes(id: UUID): Flow<Shapes?> { fun getShapes(id: UUID): Flow<Shapes?> {
return themeRepository.getShapes(id) return themeRepository.shapes.get(id)
} }
fun updateShapes(shapes: Shapes) { fun updateShapes(shapes: Shapes) {
themeRepository.updateShapes(shapes) themeRepository.shapes.update(shapes)
} }
fun selectShapes(shapes: Shapes) { fun selectShapes(shapes: Shapes) {
uiSettings.setShapes(when(shapes.id) { uiSettings.setShapesId(shapes.id)
DefaultThemeId -> ShapesDescriptor.Default
else -> ShapesDescriptor.Custom(shapes.id.toString())
})
} }
fun duplicate(shapes: Shapes) { fun duplicate(shapes: Shapes) {
themeRepository.createShapes(shapes.copy(id = UUID.randomUUID())) themeRepository.shapes.create(shapes.copy(id = UUID.randomUUID()))
} }
fun delete(shapes: Shapes) { fun delete(shapes: Shapes) {
themeRepository.deleteShapes(shapes) themeRepository.shapes.delete(shapes)
} }
fun createNew(context: Context) { fun createNew(context: Context) {
themeRepository.createShapes( themeRepository.shapes.create(
Shapes( Shapes(
id = UUID.randomUUID(), 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 android.content.Context
import androidx.compose.foundation.isSystemInDarkTheme 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.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalContext 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.Font
import de.mm20.launcher2.preferences.SurfaceShape
import de.mm20.launcher2.preferences.ui.UiSettings import de.mm20.launcher2.preferences.ui.UiSettings
import de.mm20.launcher2.themes.ThemeRepository import de.mm20.launcher2.themes.ThemeRepository
import de.mm20.launcher2.ui.locals.LocalDarkTheme import de.mm20.launcher2.ui.locals.LocalDarkTheme
import de.mm20.launcher2.ui.theme.colorscheme.* import de.mm20.launcher2.ui.theme.colorscheme.*
import de.mm20.launcher2.ui.theme.shapes.shapesOf 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.DefaultTypography
import de.mm20.launcher2.ui.theme.typography.getDeviceDefaultTypography import de.mm20.launcher2.ui.theme.typography.getDeviceDefaultTypography
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import org.koin.compose.koinInject import org.koin.compose.koinInject
import de.mm20.launcher2.preferences.ColorScheme as ColorSchemePref import de.mm20.launcher2.preferences.ColorScheme as ColorSchemePref
@ -35,14 +30,20 @@ fun LauncherTheme(
val themeRepository: ThemeRepository = koinInject() val themeRepository: ThemeRepository = koinInject()
val themeColors by remember { val themeColors by remember {
uiSettings.colors.flatMapLatest { uiSettings.colorsId.flatMapLatest {
themeRepository.getColorsOrDefault(it) themeRepository.colors.getOrDefault(it)
} }
}.collectAsState(null) }.collectAsState(null)
val themeShapes by remember { val themeShapes by remember {
uiSettings.shapes.flatMapLatest { uiSettings.shapesId.flatMapLatest {
themeRepository.getShapesOrDefault(it) themeRepository.shapes.getOrDefault(it)
}
}.collectAsState(null)
val themeTransparencies by remember {
uiSettings.transparenciesId.flatMapLatest {
themeRepository.transparencies.getOrDefault(it)
} }
}.collectAsState(null) }.collectAsState(null)
@ -52,7 +53,7 @@ fun LauncherTheme(
val darkTheme = val darkTheme =
colorSchemePref == ColorSchemePref.Dark || colorSchemePref == ColorSchemePref.System && isSystemInDarkTheme() colorSchemePref == ColorSchemePref.Dark || colorSchemePref == ColorSchemePref.System && isSystemInDarkTheme()
if (themeColors == null || themeShapes == null) { if (themeColors == null || themeShapes == null || themeTransparencies == null) {
return return
} }
@ -64,6 +65,7 @@ fun LauncherTheme(
val shapes = shapesOf(themeShapes!!) val shapes = shapesOf(themeShapes!!)
val transparencyScheme = transparencySchemeOf(themeTransparencies!!)
val font by remember { uiSettings.font }.collectAsState( val font by remember { uiSettings.font }.collectAsState(
@ -75,7 +77,8 @@ fun LauncherTheme(
} }
CompositionLocalProvider( CompositionLocalProvider(
LocalDarkTheme provides darkTheme LocalDarkTheme provides darkTheme,
LocalTransparencyScheme provides transparencyScheme,
) { ) {
MaterialExpressiveTheme( MaterialExpressiveTheme(
colorScheme = colorScheme, colorScheme = colorScheme,

View File

@ -1,5 +1,6 @@
package de.mm20.launcher2.ui.theme.colorscheme package de.mm20.launcher2.ui.theme.colorscheme
import android.R
import android.os.Build import android.os.Build
import androidx.compose.material3.ColorScheme import androidx.compose.material3.ColorScheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -11,14 +12,14 @@ import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import de.mm20.launcher2.preferences.ui.UiSettings import de.mm20.launcher2.preferences.ui.UiSettings
import de.mm20.launcher2.themes.CorePalette import de.mm20.launcher2.themes.colors.CorePalette
import de.mm20.launcher2.themes.DefaultDarkColorScheme import de.mm20.launcher2.themes.colors.DefaultDarkColorScheme
import de.mm20.launcher2.themes.DefaultLightColorScheme import de.mm20.launcher2.themes.colors.DefaultLightColorScheme
import de.mm20.launcher2.themes.FullColorScheme import de.mm20.launcher2.themes.colors.FullColorScheme
import de.mm20.launcher2.themes.PartialCorePalette import de.mm20.launcher2.themes.colors.PartialCorePalette
import de.mm20.launcher2.themes.Colors as ThemeColors import de.mm20.launcher2.themes.colors.Colors as ThemeColors
import de.mm20.launcher2.themes.get import de.mm20.launcher2.themes.colors.get
import de.mm20.launcher2.themes.merge import de.mm20.launcher2.themes.colors.merge
import de.mm20.launcher2.ui.locals.LocalWallpaperColors import de.mm20.launcher2.ui.locals.LocalWallpaperColors
import org.koin.compose.koinInject import org.koin.compose.koinInject
@ -88,11 +89,11 @@ fun systemCorePalette(): CorePalette<Int> {
if (Build.VERSION.SDK_INT >= 31 && !compatModeColors) { if (Build.VERSION.SDK_INT >= 31 && !compatModeColors) {
val context = LocalContext.current val context = LocalContext.current
return CorePalette( return CorePalette(
primary = ContextCompat.getColor(context, android.R.color.system_accent1_500), primary = ContextCompat.getColor(context, R.color.system_accent1_500),
secondary = ContextCompat.getColor(context, android.R.color.system_accent2_500), secondary = ContextCompat.getColor(context, R.color.system_accent2_500),
tertiary = ContextCompat.getColor(context, android.R.color.system_accent3_500), tertiary = ContextCompat.getColor(context, R.color.system_accent3_500),
neutral = ContextCompat.getColor(context, android.R.color.system_neutral1_500), neutral = ContextCompat.getColor(context, R.color.system_neutral1_500),
neutralVariant = ContextCompat.getColor(context, android.R.color.system_neutral2_500), neutralVariant = ContextCompat.getColor(context, R.color.system_neutral2_500),
error = 0xFFB3261E.toInt(), error = 0xFFB3261E.toInt(),
) )
} }

View File

@ -8,9 +8,9 @@ import androidx.compose.material3.Shapes
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import de.mm20.launcher2.themes.CornerStyle import de.mm20.launcher2.themes.shapes.CornerStyle
import de.mm20.launcher2.themes.Shape as ThemeShape import de.mm20.launcher2.themes.shapes.Shape as ThemeShape
import de.mm20.launcher2.themes.Shapes as ThemeShapes import de.mm20.launcher2.themes.shapes.Shapes as ThemeShapes
@Composable @Composable
fun shapesOf(shapes: ThemeShapes): Shapes { fun shapesOf(shapes: ThemeShapes): Shapes {

View File

@ -1,10 +1,31 @@
package de.mm20.launcher2.ui.theme.transparency 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.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( data class TransparencyScheme(
val background: Float, val background: Float = 0.85f,
val surface: Float, 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_extra_round">Extra round</string>
<string name="preference_shapes_rect">Rectangular</string> <string name="preference_shapes_rect">Rectangular</string>
<string name="preference_shapes_base">Base shape</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">Font</string>
<string name="preference_font_system">System default</string> <string name="preference_font_system">System default</string>
<string name="preference_screen_about">About</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="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_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="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="confirmation_delete_transparencies_scheme">Do you really want to delete the transparency scheme %1$s?</string>
<string name="new_shapes_name">New shapes</string> <string name="new_theme_name">(untitled)</string>
<string name="theme_color_scheme_system_default">Use system default</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_autogenerate">From primary color</string>
<string name="theme_color_scheme_palette_color">Palette</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 android.content.Context
import de.mm20.launcher2.search.SearchFilters import de.mm20.launcher2.search.SearchFilters
import de.mm20.launcher2.serialization.UUIDSerializer
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonNames import java.util.UUID
@Serializable @Serializable
data class LauncherSettingsData internal constructor( data class LauncherSettingsData internal constructor(
val schemaVersion: Int = 5, val schemaVersion: Int = 5,
val uiColorScheme: ColorScheme = ColorScheme.System, val uiColorScheme: ColorScheme = ColorScheme.System,
@JsonNames("uiTheme") @Serializable(with = UUIDSerializer::class)
val uiColors: ColorsDescriptor = ColorsDescriptor.Default, val uiColorsId: UUID = UUID(0L, 0L),
val uiShapes: ShapesDescriptor = ShapesDescriptor.Default, @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 uiCompatModeColors: Boolean = false,
val uiFont: Font = Font.Outfit, val uiFont: Font = Font.Outfit,
@ -205,53 +209,6 @@ enum class Font {
System, 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 { internal enum class ClockWidgetStyleEnum {
Digital1, Digital1,
Digital2, Digital2,

View File

@ -8,10 +8,9 @@ import de.mm20.launcher2.preferences.ScreenOrientation
import de.mm20.launcher2.preferences.SearchBarColors import de.mm20.launcher2.preferences.SearchBarColors
import de.mm20.launcher2.preferences.SearchBarStyle import de.mm20.launcher2.preferences.SearchBarStyle
import de.mm20.launcher2.preferences.SystemBarColors 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.distinctUntilChanged
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import java.util.UUID
data class CardStyle( data class CardStyle(
val opacity: Float = 1f, val opacity: Float = 1f,
@ -286,25 +285,36 @@ class UiSettings internal constructor(
} }
val colors val colorsId
get() = launcherDataStore.data.map { get() = launcherDataStore.data.map {
it.uiColors it.uiColorsId
}.distinctUntilChanged() }.distinctUntilChanged()
fun setColors(colors: ColorsDescriptor) { fun setColorsId(colorsId: UUID) {
launcherDataStore.update { launcherDataStore.update {
it.copy(uiColors = colors) it.copy(uiColorsId = colorsId)
} }
} }
val shapes val shapesId
get() = launcherDataStore.data.map { get() = launcherDataStore.data.map {
it.uiShapes it.uiShapesId
}.distinctUntilChanged() }.distinctUntilChanged()
fun setShapes(shapes: ShapesDescriptor) { fun setShapesId(shapesId: UUID) {
launcherDataStore.update { 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.SearchActionEntity
import de.mm20.launcher2.database.entities.ColorsEntity import de.mm20.launcher2.database.entities.ColorsEntity
import de.mm20.launcher2.database.entities.ShapesEntity 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.entities.WidgetEntity
import de.mm20.launcher2.database.migrations.Migration_10_11 import de.mm20.launcher2.database.migrations.Migration_10_11
import de.mm20.launcher2.database.migrations.Migration_11_12 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_25_26
import de.mm20.launcher2.database.migrations.Migration_26_27 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_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_6_7
import de.mm20.launcher2.database.migrations.Migration_7_8 import de.mm20.launcher2.database.migrations.Migration_7_8
import de.mm20.launcher2.database.migrations.Migration_8_9 import de.mm20.launcher2.database.migrations.Migration_8_9
@ -59,7 +61,8 @@ import java.util.UUID
ColorsEntity::class, ColorsEntity::class,
PluginEntity::class, PluginEntity::class,
ShapesEntity::class, ShapesEntity::class,
], version = 28, exportSchema = true TransparenciesEntity::class,
], version = 29, exportSchema = true
) )
@TypeConverters(ComponentNameConverter::class) @TypeConverters(ComponentNameConverter::class)
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
@ -160,6 +163,7 @@ abstract class AppDatabase : RoomDatabase() {
Migration_25_26(), Migration_25_26(),
Migration_26_27(), Migration_26_27(),
Migration_27_28(), Migration_27_28(),
Migration_28_29(),
).build() ).build()
if (_instance == null) _instance = instance if (_instance == null) _instance = instance
return instance return instance

View File

@ -6,6 +6,7 @@ import androidx.room.Query
import androidx.room.Update import androidx.room.Update
import de.mm20.launcher2.database.entities.ColorsEntity import de.mm20.launcher2.database.entities.ColorsEntity
import de.mm20.launcher2.database.entities.ShapesEntity import de.mm20.launcher2.database.entities.ShapesEntity
import de.mm20.launcher2.database.entities.TransparenciesEntity
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import java.util.UUID import java.util.UUID
@ -17,39 +18,60 @@ interface ThemeDao {
@Query("SELECT * FROM Shapes") @Query("SELECT * FROM Shapes")
fun getAllShapes(): Flow<List<ShapesEntity>> fun getAllShapes(): Flow<List<ShapesEntity>>
@Query("SELECT * FROM Transparencies")
fun getAllTransparencies(): Flow<List<TransparenciesEntity>>
@Query("SELECT * FROM Theme WHERE id = :id LIMIT 1") @Query("SELECT * FROM Theme WHERE id = :id LIMIT 1")
fun getColors(id: UUID): Flow<ColorsEntity?> fun getColors(id: UUID): Flow<ColorsEntity?>
@Query("SELECT * FROM Shapes WHERE id = :id LIMIT 1") @Query("SELECT * FROM Shapes WHERE id = :id LIMIT 1")
fun getShapes(id: UUID): Flow<ShapesEntity?> fun getShapes(id: UUID): Flow<ShapesEntity?>
@Query("SELECT * FROM Transparencies WHERE id = :id LIMIT 1")
fun getTransparencies(id: UUID): Flow<TransparenciesEntity?>
@Insert @Insert
suspend fun insertColors(colors: ColorsEntity) suspend fun insertColors(colors: ColorsEntity)
@Insert @Insert
suspend fun insertShapes(shapes: ShapesEntity) suspend fun insertShapes(shapes: ShapesEntity)
@Insert
suspend fun insertTransparencies(transparencies: TransparenciesEntity)
@Update @Update
suspend fun updateColors(colors: ColorsEntity) suspend fun updateColors(colors: ColorsEntity)
@Update @Update
suspend fun updateShapes(shapes: ShapesEntity) suspend fun updateShapes(shapes: ShapesEntity)
@Update
suspend fun updateTransparencies(transparencies: TransparenciesEntity)
@Query("DELETE FROM Theme WHERE id = :id") @Query("DELETE FROM Theme WHERE id = :id")
suspend fun deleteColors(id: UUID) suspend fun deleteColors(id: UUID)
@Query("DELETE FROM Shapes WHERE id = :id") @Query("DELETE FROM Shapes WHERE id = :id")
suspend fun deleteShapes(id: UUID) suspend fun deleteShapes(id: UUID)
@Query("DELETE FROM Transparencies WHERE id = :id")
suspend fun deleteTransparencies(id: UUID)
@Query("DELETE FROM Theme") @Query("DELETE FROM Theme")
suspend fun deleteAllColors() suspend fun deleteAllColors()
@Query("DELETE FROM Shapes") @Query("DELETE FROM Shapes")
suspend fun deleteAllShapes() suspend fun deleteAllShapes()
@Query("DELETE FROM Transparencies")
suspend fun deleteAllTransparencies()
@Insert @Insert
fun insertAllColors(colors: List<ColorsEntity>) fun insertAllColors(colors: List<ColorsEntity>)
@Insert @Insert
fun insertAllShapes(shapes: List<ShapesEntity>) 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 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 import java.util.UUID
val DefaultThemeId = UUID(0L, 0L) val DefaultThemeId = UUID(0L, 0L)
val HighContrastThemeId = UUID(0L, 2L) val HighContrastThemeId = UUID(0L, 2L)
val BlackAndWhiteThemeId = UUID(0L, 1L) val BlackAndWhiteThemeId = UUID(0L, 1L)
val ExtraRoundShapesId = UUID(0L, 1L) val ExtraRoundShapesId = UUID(0L, 1L)
val CutShapesId = UUID(0L, 2L) val CutShapesId = UUID(0L, 2L)
val RectShapesId = UUID(0L, 3L) val RectShapesId = UUID(0L, 3L)
val DefaultLightColorScheme = ColorScheme<Color>( val SemiTransparentId = UUID(0L, 1L)
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,5 +1,9 @@
package de.mm20.launcher2.themes 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.KSerializer
import kotlinx.serialization.SerializationException import kotlinx.serialization.SerializationException
import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveKind

View File

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

View File

@ -2,12 +2,17 @@ package de.mm20.launcher2.themes
import android.util.Log import android.util.Log
import de.mm20.launcher2.crashreporter.CrashReporter import de.mm20.launcher2.crashreporter.CrashReporter
import de.mm20.launcher2.serialization.Json import de.mm20.launcher2.themes.colors.Color
import kotlinx.coroutines.Dispatchers import de.mm20.launcher2.themes.colors.ColorScheme
import kotlinx.coroutines.withContext 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.Serializable
import kotlinx.serialization.SerializationException import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.contentOrNull import kotlinx.serialization.json.contentOrNull
@ -22,6 +27,7 @@ data class ThemeBundle(
val author: String? = null, val author: String? = null,
val colors: Colors? = null, val colors: Colors? = null,
val shapes: Shapes? = null, val shapes: Shapes? = null,
val transparencies: Transparencies? = null,
/** /**
* The file version, always 2 for the new theme format. * 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.backup.Backupable
import de.mm20.launcher2.crashreporter.CrashReporter import de.mm20.launcher2.crashreporter.CrashReporter
import de.mm20.launcher2.database.AppDatabase import de.mm20.launcher2.database.AppDatabase
import de.mm20.launcher2.preferences.ColorsDescriptor import de.mm20.launcher2.themes.colors.Colors
import de.mm20.launcher2.preferences.ShapesDescriptor import de.mm20.launcher2.themes.colors.ColorsRepository
import kotlinx.coroutines.CoroutineScope import de.mm20.launcher2.themes.shapes.ShapesRepository
import de.mm20.launcher2.themes.transparencies.TransparenciesRepository
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first 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.coroutines.withContext
import kotlinx.serialization.SerializationException import kotlinx.serialization.SerializationException
import java.io.File import java.io.File
import java.util.UUID
class ThemeRepository( class ThemeRepository(
private val context: Context, private val context: Context,
private val database: AppDatabase, private val database: AppDatabase,
) : Backupable { ) : 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) { override suspend fun backup(toDir: File) = withContext(Dispatchers.IO) {
val dao = database.themeDao() val dao = database.themeDao()
@ -257,5 +56,4 @@ class ThemeRepository(
dao.insertAllColors(colors.map { it.toEntity() }) 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.database.entities.ColorsEntity
import de.mm20.launcher2.serialization.ColorIntAsHexSerializer 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.database.entities.ShapesEntity
import de.mm20.launcher2.serialization.UUIDSerializer import de.mm20.launcher2.serialization.UUIDSerializer
import de.mm20.launcher2.themes.ShapeSerializer
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import java.util.UUID 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" androidx-emojipicker = "1.5.0"
accompanist = "0.36.0" accompanist = "0.36.0"
haze = "1.6.3" haze = "1.6.4"
coil = "2.7.0" coil = "2.7.0"
koin = "4.0.4" koin = "4.0.4"
retrofit = "2.11.0" retrofit = "2.11.0"