(feat) custom typography schemes
This commit is contained in:
parent
4bf9ee8b0c
commit
da8416a58c
@ -84,6 +84,10 @@ import de.mm20.launcher2.ui.settings.transparencies.TransparencySchemeSettingsRo
|
||||
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.typography.TypographiesSettingsRoute
|
||||
import de.mm20.launcher2.ui.settings.typography.TypographiesSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.typography.TypographySettingsRoute
|
||||
import de.mm20.launcher2.ui.settings.typography.TypographySettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.unitconverter.UnitConverterHelpSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.unitconverter.UnitConverterSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.weather.WeatherIntegrationSettingsScreen
|
||||
@ -218,6 +222,14 @@ class SettingsActivity : BaseActivity() {
|
||||
?: return@composable
|
||||
TransparencySchemeSettingsScreen(UUID.fromString(route.id))
|
||||
}
|
||||
composable<TypographiesSettingsRoute> {
|
||||
TypographiesSettingsScreen()
|
||||
}
|
||||
composable<TypographySettingsRoute> {
|
||||
val route: TypographySettingsRoute = it.toRoute()
|
||||
?: return@composable
|
||||
TypographySettingsScreen(UUID.fromString(route.id))
|
||||
}
|
||||
composable("settings/appearance/cards") {
|
||||
CardsSettingsScreen()
|
||||
}
|
||||
|
||||
@ -29,14 +29,15 @@ import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||
import de.mm20.launcher2.ui.component.preferences.value
|
||||
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||
import de.mm20.launcher2.ui.settings.transparencies.TransparencySchemesSettingsRoute
|
||||
import de.mm20.launcher2.ui.settings.typography.TypographiesSettingsRoute
|
||||
import de.mm20.launcher2.ui.theme.getTypography
|
||||
|
||||
@Composable
|
||||
fun AppearanceSettingsScreen() {
|
||||
val viewModel: AppearanceSettingsScreenVM = viewModel()
|
||||
val context = LocalContext.current
|
||||
val navController = LocalNavController.current
|
||||
val colorThemeName by viewModel.colorThemeName.collectAsStateWithLifecycle(null)
|
||||
val typographyThemeName by viewModel.typographyThemeName.collectAsStateWithLifecycle(null)
|
||||
val shapeThemeName by viewModel.shapeThemeName.collectAsStateWithLifecycle(null)
|
||||
val transparencyThemeName by viewModel.transparencyThemeName.collectAsStateWithLifecycle(null)
|
||||
val compatModeColors by viewModel.compatModeColors.collectAsState()
|
||||
@ -77,22 +78,11 @@ fun AppearanceSettingsScreen() {
|
||||
},
|
||||
icon = Icons.Rounded.Palette,
|
||||
)
|
||||
val font by viewModel.font.collectAsState()
|
||||
ListPreference(
|
||||
title = stringResource(R.string.preference_font),
|
||||
items = listOf(
|
||||
"Outfit" to Font.Outfit,
|
||||
stringResource(R.string.preference_font_system) to Font.System,
|
||||
),
|
||||
value = font,
|
||||
onValueChanged = {
|
||||
if (it != null) viewModel.setFont(it)
|
||||
},
|
||||
itemLabel = {
|
||||
val typography = remember(it.value) {
|
||||
getTypography(context, it.value)
|
||||
}
|
||||
Text(it.first, style = typography.titleMedium)
|
||||
Preference(
|
||||
title = stringResource(id = R.string.preference_screen_typography),
|
||||
summary = typographyThemeName,
|
||||
onClick = {
|
||||
navController?.navigate(TypographiesSettingsRoute)
|
||||
},
|
||||
icon = Icons.Rounded.TextFields,
|
||||
)
|
||||
|
||||
@ -39,6 +39,13 @@ class AppearanceSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
}
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
|
||||
|
||||
val typographyThemeName = uiSettings.typographyId.flatMapLatest {
|
||||
themeRepository.typographies.getOrDefault(it)
|
||||
}.map {
|
||||
it.name
|
||||
}
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
|
||||
|
||||
val transparencyThemeName = uiSettings.transparenciesId.flatMapLatest {
|
||||
themeRepository.transparencies.getOrDefault(it)
|
||||
}.map {
|
||||
|
||||
@ -65,6 +65,7 @@ import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
|
||||
import de.mm20.launcher2.ui.locals.LocalDarkTheme
|
||||
import de.mm20.launcher2.ui.settings.transparencies.checkerboard
|
||||
import de.mm20.launcher2.ui.settings.typography.PreviewTexts
|
||||
import de.mm20.launcher2.ui.theme.colorscheme.darkColorSchemeOf
|
||||
import de.mm20.launcher2.ui.theme.colorscheme.lightColorSchemeOf
|
||||
import de.mm20.launcher2.ui.theme.shapes.shapesOf
|
||||
@ -233,6 +234,7 @@ private fun ThemePreview(
|
||||
darkMode: Boolean,
|
||||
onDarkModeChanged: (Boolean) -> Unit,
|
||||
) {
|
||||
val previewTexts = PreviewTexts()
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@ -291,17 +293,17 @@ private fun ThemePreview(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
"Title",
|
||||
previewTexts.Medium1,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
Text(
|
||||
"Subtitle",
|
||||
previewTexts.Medium2,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = MaterialTheme.colorScheme.secondary
|
||||
)
|
||||
Text(
|
||||
"Body",
|
||||
previewTexts.TwoLines,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
@ -316,7 +318,7 @@ private fun ThemePreview(
|
||||
) {
|
||||
FilterChip(
|
||||
selected = true,
|
||||
label = { Text("Chip") },
|
||||
label = { Text(previewTexts.Short1) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
Icons.Rounded.Star, null,
|
||||
@ -327,7 +329,7 @@ private fun ThemePreview(
|
||||
)
|
||||
FilterChip(
|
||||
selected = false,
|
||||
label = { Text("Chip") },
|
||||
label = { Text(previewTexts.Short2) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
Icons.Rounded.Star, null,
|
||||
@ -348,8 +350,8 @@ private fun ThemePreview(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End),
|
||||
) {
|
||||
Button(onClick = {}) { Text("Button") }
|
||||
OutlinedButton(onClick = {}) { Text("Button") }
|
||||
Button(onClick = {}) { Text(previewTexts.Medium1) }
|
||||
OutlinedButton(onClick = {}) { Text(previewTexts.Medium2) }
|
||||
}
|
||||
}
|
||||
Box(
|
||||
|
||||
@ -18,7 +18,6 @@ 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.material.icons.rounded.Share
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
@ -61,6 +60,8 @@ fun ColorSchemesSettingsScreen() {
|
||||
|
||||
var deleteColors by remember { mutableStateOf<Colors?>(null) }
|
||||
|
||||
val (builtin, user) = themes.partition { it.builtIn }
|
||||
|
||||
PreferenceScreen(
|
||||
title = stringResource(R.string.preference_screen_colors),
|
||||
topBarActions = {
|
||||
@ -71,7 +72,7 @@ fun ColorSchemesSettingsScreen() {
|
||||
) {
|
||||
item {
|
||||
PreferenceCategory {
|
||||
for (theme in themes) {
|
||||
for (theme in builtin) {
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
Preference(
|
||||
icon = if (theme.id == selectedTheme) Icons.Rounded.RadioButtonChecked else Icons.Rounded.RadioButtonUnchecked,
|
||||
@ -90,18 +91,6 @@ fun ColorSchemesSettingsScreen() {
|
||||
expanded = showMenu,
|
||||
onDismissRequest = { showMenu = false }
|
||||
) {
|
||||
if (!theme.builtIn) {
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(Icons.Rounded.Edit, null)
|
||||
},
|
||||
text = { Text(stringResource(R.string.edit)) },
|
||||
onClick = {
|
||||
navController?.navigate("settings/appearance/colors/${theme.id}")
|
||||
showMenu = false
|
||||
}
|
||||
)
|
||||
}
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(Icons.Rounded.ContentCopy, null)
|
||||
@ -112,14 +101,56 @@ fun ColorSchemesSettingsScreen() {
|
||||
showMenu = false
|
||||
}
|
||||
)
|
||||
if (!theme.builtIn) {
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
viewModel.selectTheme(theme)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (user.isNotEmpty()) {
|
||||
item {
|
||||
PreferenceCategory {
|
||||
for (theme in user) {
|
||||
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,
|
||||
) {
|
||||
ColorSchemePreview(theme)
|
||||
IconButton(
|
||||
modifier = Modifier.padding(start = 12.dp),
|
||||
onClick = { showMenu = true }) {
|
||||
Icon(Icons.Rounded.MoreVert, null)
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = showMenu,
|
||||
onDismissRequest = { showMenu = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(Icons.Rounded.Share, null)
|
||||
Icon(Icons.Rounded.Edit, null)
|
||||
},
|
||||
text = { Text(stringResource(R.string.menu_share)) },
|
||||
text = { Text(stringResource(R.string.edit)) },
|
||||
onClick = {
|
||||
viewModel.exportTheme(context, theme)
|
||||
navController?.navigate("settings/appearance/colors/${theme.id}")
|
||||
showMenu = false
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(Icons.Rounded.ContentCopy, null)
|
||||
},
|
||||
text = { Text(stringResource(R.string.duplicate)) },
|
||||
onClick = {
|
||||
viewModel.duplicate(theme)
|
||||
showMenu = false
|
||||
}
|
||||
)
|
||||
@ -135,12 +166,12 @@ fun ColorSchemesSettingsScreen() {
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
viewModel.selectTheme(theme)
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
viewModel.selectTheme(theme)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,6 +54,8 @@ fun ShapeSchemesSettingsScreen() {
|
||||
|
||||
var deleteShapes by remember { mutableStateOf<Shapes?>(null) }
|
||||
|
||||
val (builtin, user) = themes.partition { it.builtIn }
|
||||
|
||||
PreferenceScreen(
|
||||
title = stringResource(R.string.preference_screen_shapes),
|
||||
topBarActions = {
|
||||
@ -64,7 +66,7 @@ fun ShapeSchemesSettingsScreen() {
|
||||
) {
|
||||
item {
|
||||
PreferenceCategory {
|
||||
for (theme in themes) {
|
||||
for (theme in builtin) {
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
Preference(
|
||||
icon = if (theme.id == selectedTheme) Icons.Rounded.RadioButtonChecked else Icons.Rounded.RadioButtonUnchecked,
|
||||
@ -83,18 +85,6 @@ fun ShapeSchemesSettingsScreen() {
|
||||
expanded = showMenu,
|
||||
onDismissRequest = { showMenu = false }
|
||||
) {
|
||||
if (!theme.builtIn) {
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(Icons.Rounded.Edit, null)
|
||||
},
|
||||
text = { Text(stringResource(R.string.edit)) },
|
||||
onClick = {
|
||||
navController?.navigate("settings/appearance/shapes/${theme.id}")
|
||||
showMenu = false
|
||||
}
|
||||
)
|
||||
}
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(Icons.Rounded.ContentCopy, null)
|
||||
@ -105,7 +95,58 @@ fun ShapeSchemesSettingsScreen() {
|
||||
showMenu = false
|
||||
}
|
||||
)
|
||||
if (!theme.builtIn) {
|
||||
}
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
viewModel.selectShapes(theme)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (user.isNotEmpty()) {
|
||||
item {
|
||||
PreferenceCategory {
|
||||
for (theme in user) {
|
||||
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,
|
||||
) {
|
||||
ShapesPreview(theme)
|
||||
IconButton(
|
||||
modifier = Modifier.padding(start = 12.dp),
|
||||
onClick = { showMenu = true }) {
|
||||
Icon(Icons.Rounded.MoreVert, null)
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = showMenu,
|
||||
onDismissRequest = { showMenu = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(Icons.Rounded.Edit, null)
|
||||
},
|
||||
text = { Text(stringResource(R.string.edit)) },
|
||||
onClick = {
|
||||
navController?.navigate("settings/appearance/shapes/${theme.id}")
|
||||
showMenu = false
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(Icons.Rounded.ContentCopy, null)
|
||||
},
|
||||
text = { Text(stringResource(R.string.duplicate)) },
|
||||
onClick = {
|
||||
viewModel.duplicate(theme)
|
||||
showMenu = false
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(Icons.Rounded.Delete, null)
|
||||
@ -118,12 +159,12 @@ fun ShapeSchemesSettingsScreen() {
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
viewModel.selectShapes(theme)
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
viewModel.selectShapes(theme)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,6 @@ 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
|
||||
@ -33,8 +32,6 @@ 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
|
||||
@ -65,6 +62,8 @@ fun TransparencySchemesSettingsScreen() {
|
||||
|
||||
var deleteTransparencies by remember { mutableStateOf<Transparencies?>(null) }
|
||||
|
||||
val (builtin, user) = themes.partition { it.builtIn }
|
||||
|
||||
val wallpaperColors = wallpaperColorsAsState().value
|
||||
|
||||
PreferenceScreen(
|
||||
@ -77,7 +76,7 @@ fun TransparencySchemesSettingsScreen() {
|
||||
) {
|
||||
item {
|
||||
PreferenceCategory {
|
||||
for (theme in themes) {
|
||||
for (theme in builtin) {
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
Preference(
|
||||
icon = if (theme.id == selectedTheme) Icons.Rounded.RadioButtonChecked else Icons.Rounded.RadioButtonUnchecked,
|
||||
@ -96,7 +95,48 @@ fun TransparencySchemesSettingsScreen() {
|
||||
expanded = showMenu,
|
||||
onDismissRequest = { showMenu = false }
|
||||
) {
|
||||
if (!theme.builtIn) {
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(Icons.Rounded.ContentCopy, null)
|
||||
},
|
||||
text = { Text(stringResource(R.string.duplicate)) },
|
||||
onClick = {
|
||||
viewModel.duplicate(theme)
|
||||
showMenu = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
viewModel.selectTransparencies(theme)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (user.isNotEmpty()) {
|
||||
item {
|
||||
PreferenceCategory {
|
||||
for (theme in user) {
|
||||
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 }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(Icons.Rounded.Edit, null)
|
||||
@ -109,18 +149,16 @@ fun TransparencySchemesSettingsScreen() {
|
||||
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.ContentCopy, null)
|
||||
},
|
||||
text = { Text(stringResource(R.string.duplicate)) },
|
||||
onClick = {
|
||||
viewModel.duplicate(theme)
|
||||
showMenu = false
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(Icons.Rounded.Delete, null)
|
||||
@ -133,12 +171,12 @@ fun TransparencySchemesSettingsScreen() {
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
viewModel.selectTransparencies(theme)
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
viewModel.selectTransparencies(theme)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -193,13 +231,19 @@ private fun TransparenciesPreview(wallpaperColors: WallpaperColors, theme: Trans
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.surfaceContainer.copy(alpha = transparencies.background), MaterialTheme.shapes.extraSmall)
|
||||
.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)
|
||||
.background(
|
||||
MaterialTheme.colorScheme.surface.copy(alpha = transparencies.surface),
|
||||
MaterialTheme.shapes.extraSmall
|
||||
)
|
||||
.height(24.dp)
|
||||
.width(48.dp)
|
||||
)
|
||||
|
||||
@ -0,0 +1,88 @@
|
||||
package de.mm20.launcher2.ui.settings.typography
|
||||
|
||||
import android.icu.util.ULocale
|
||||
|
||||
/**
|
||||
* Preview texts to demonstrate typography settings in the users dominant script.
|
||||
* Preview texts should be similar in length, and they should be language-neutral, and not use
|
||||
* letters that are used only in some languages.
|
||||
*/
|
||||
interface PreviewTexts {
|
||||
/**
|
||||
* Preview for the font. Should be a character that is representative for a font.
|
||||
*/
|
||||
val ExtraShort: String
|
||||
|
||||
/**
|
||||
* A short, 3-letter preview.
|
||||
*/
|
||||
val Short1: String
|
||||
|
||||
/**
|
||||
* An alternative to [Short1], also 3 letters.
|
||||
*/
|
||||
val Short2: String
|
||||
/**
|
||||
* A medium-length preview, 5-6 letters.
|
||||
*/
|
||||
val Medium1: String
|
||||
/**
|
||||
* An alternative to [Medium1], also 5-6 letters.
|
||||
*/
|
||||
val Medium2: String
|
||||
/**
|
||||
* A longer preview, to demonstrate line height.
|
||||
* Should contain letters and numbers, and a newline.
|
||||
*/
|
||||
val TwoLines: String
|
||||
|
||||
companion object {
|
||||
operator fun invoke(): PreviewTexts {
|
||||
val script = ULocale.addLikelySubtags(ULocale.getDefault()).script
|
||||
return forScript(script)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the appropriate [PreviewTexts] implementation based on the script.
|
||||
* Defaults to [LatinPreviewTexts] if the script is not recognized.
|
||||
* @param script the ISO-15924 script code
|
||||
*/
|
||||
fun forScript(script: String): PreviewTexts {
|
||||
return when (script) {
|
||||
"Latn" -> LatinPreviewTexts
|
||||
"Cyrl" -> CyrillicPreviewTexts
|
||||
"Grek" -> GreekPreviewTexts
|
||||
else -> LatinPreviewTexts
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object LatinPreviewTexts: PreviewTexts {
|
||||
override val ExtraShort: String = "Aa"
|
||||
override val Short1: String = "Abc"
|
||||
override val Short2: String = "Deg"
|
||||
override val Medium1: String = "Abcdeg"
|
||||
override val Medium2: String = "Hilmno"
|
||||
override val TwoLines: String = "Abcdeghilm Nop\nRst 123456890"
|
||||
}
|
||||
|
||||
object CyrillicPreviewTexts: PreviewTexts {
|
||||
override val ExtraShort: String = "Аа"
|
||||
override val Short1: String = "Абв"
|
||||
override val Short2: String = "Где"
|
||||
override val Medium1: String = "Абвгде"
|
||||
override val Medium2: String = "Жзиклм"
|
||||
override val TwoLines: String = "Абвгдежзик Лмн\nОпр 1234567890"
|
||||
}
|
||||
|
||||
object GreekPreviewTexts: PreviewTexts {
|
||||
override val ExtraShort: String = "Αα"
|
||||
override val Short1: String = "Αβγ"
|
||||
override val Short2: String = "Δεζ"
|
||||
override val Medium1: String = "Αβγδεζ"
|
||||
override val Medium2: String = "Ηθικλμ"
|
||||
override val TwoLines: String = "Αβγδεζηθ Ικλμ\nΝξο 1234567890"
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,253 @@
|
||||
package de.mm20.launcher2.ui.settings.typography
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
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.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
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.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.typography.Typography
|
||||
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.typography.typographyOf
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data object TypographiesSettingsRoute
|
||||
|
||||
@Composable
|
||||
fun TypographiesSettingsScreen() {
|
||||
val viewModel: TypographySettingsScreenVM = viewModel()
|
||||
val navController = LocalNavController.current
|
||||
val context = LocalContext.current
|
||||
|
||||
val selectedTheme by viewModel.selectedTypography.collectAsStateWithLifecycle(null)
|
||||
val themes by viewModel.typography.collectAsStateWithLifecycle(emptyList())
|
||||
|
||||
var deleteTypography by remember { mutableStateOf<Typography?>(null) }
|
||||
|
||||
val (builtin, user) = themes.partition { it.builtIn }
|
||||
|
||||
|
||||
PreferenceScreen(
|
||||
title = stringResource(R.string.preference_screen_typography),
|
||||
topBarActions = {
|
||||
IconButton(onClick = { viewModel.createNew(context) }) {
|
||||
Icon(Icons.Rounded.Add, null)
|
||||
}
|
||||
},
|
||||
) {
|
||||
item {
|
||||
PreferenceCategory {
|
||||
for (theme in builtin) {
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
val typo = typographyOf(theme)
|
||||
Preference(
|
||||
icon = {
|
||||
Icon(
|
||||
if (theme.id == selectedTheme) Icons.Rounded.RadioButtonChecked else Icons.Rounded.RadioButtonUnchecked,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
text = theme.name,
|
||||
maxLines = 1,
|
||||
style = typo.titleMedium
|
||||
)
|
||||
},
|
||||
controls = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
TypographyPreview(typo)
|
||||
IconButton(
|
||||
modifier = Modifier.padding(start = 12.dp),
|
||||
onClick = { showMenu = true }) {
|
||||
Icon(Icons.Rounded.MoreVert, null)
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = showMenu,
|
||||
onDismissRequest = { showMenu = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(Icons.Rounded.ContentCopy, null)
|
||||
},
|
||||
text = { Text(stringResource(R.string.duplicate)) },
|
||||
onClick = {
|
||||
viewModel.duplicate(theme)
|
||||
showMenu = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
viewModel.selectTypography(theme)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (user.isNotEmpty()) {
|
||||
item {
|
||||
PreferenceCategory {
|
||||
for (theme in user) {
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
val typo = typographyOf(theme)
|
||||
Preference(
|
||||
icon = {
|
||||
Icon(
|
||||
if (theme.id == selectedTheme) Icons.Rounded.RadioButtonChecked else Icons.Rounded.RadioButtonUnchecked,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
text = theme.name,
|
||||
maxLines = 1,
|
||||
style = typo.titleMedium
|
||||
)
|
||||
},
|
||||
controls = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
TypographyPreview(typo)
|
||||
IconButton(
|
||||
modifier = Modifier.padding(start = 12.dp),
|
||||
onClick = { showMenu = true }) {
|
||||
Icon(Icons.Rounded.MoreVert, null)
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = showMenu,
|
||||
onDismissRequest = { showMenu = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(Icons.Rounded.Edit, null)
|
||||
},
|
||||
text = { Text(stringResource(R.string.edit)) },
|
||||
onClick = {
|
||||
navController?.navigate(
|
||||
TypographySettingsRoute(theme.id.toString())
|
||||
)
|
||||
showMenu = false
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(Icons.Rounded.ContentCopy, null)
|
||||
},
|
||||
text = { Text(stringResource(R.string.duplicate)) },
|
||||
onClick = {
|
||||
viewModel.duplicate(theme)
|
||||
showMenu = false
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(Icons.Rounded.Delete, null)
|
||||
},
|
||||
text = { Text(stringResource(R.string.menu_delete)) },
|
||||
onClick = {
|
||||
deleteTypography = theme
|
||||
showMenu = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
viewModel.selectTypography(theme)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (deleteTypography != null) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { deleteTypography = null },
|
||||
text = {
|
||||
Text(
|
||||
stringResource(
|
||||
R.string.confirmation_delete_transparencies_scheme,
|
||||
deleteTypography!!.name
|
||||
)
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
viewModel.delete(deleteTypography!!)
|
||||
deleteTypography = null
|
||||
}
|
||||
) {
|
||||
Text(stringResource(android.R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = { deleteTypography = null }
|
||||
) {
|
||||
Text(stringResource(android.R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TypographyPreview(typography: androidx.compose.material3.Typography) {
|
||||
val previewTexts = PreviewTexts()
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(vertical = 12.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = previewTexts.Short1,
|
||||
style = typography.titleSmall,
|
||||
)
|
||||
Text(
|
||||
text = previewTexts.Short2,
|
||||
style = typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,50 @@
|
||||
package de.mm20.launcher2.ui.settings.typography
|
||||
|
||||
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.typography.Typography
|
||||
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 TypographySettingsScreenVM : ViewModel(), KoinComponent {
|
||||
|
||||
private val themeRepository: ThemeRepository by inject()
|
||||
private val uiSettings: UiSettings by inject()
|
||||
|
||||
val selectedTypography = uiSettings.typographyId
|
||||
val typography: Flow<List<Typography>> = themeRepository.typographies.getAll()
|
||||
|
||||
fun getTypography(id: UUID): Flow<Typography?> {
|
||||
return themeRepository.typographies.get(id)
|
||||
}
|
||||
|
||||
fun updateTypography(typography: Typography) {
|
||||
themeRepository.typographies.update(typography)
|
||||
}
|
||||
|
||||
fun selectTypography(typography: Typography) {
|
||||
uiSettings.setTypographyId(typography.id)
|
||||
}
|
||||
|
||||
fun duplicate(typography: Typography) {
|
||||
themeRepository.typographies.create(typography.copy(id = UUID.randomUUID()))
|
||||
}
|
||||
|
||||
fun delete(typography: Typography) {
|
||||
themeRepository.typographies.delete(typography)
|
||||
}
|
||||
|
||||
fun createNew(context: Context) {
|
||||
themeRepository.typographies.create(
|
||||
Typography(
|
||||
id = UUID.randomUUID(),
|
||||
name = context.getString(R.string.new_theme_name)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -15,6 +15,7 @@ import de.mm20.launcher2.ui.theme.transparency.LocalTransparencyScheme
|
||||
import de.mm20.launcher2.ui.theme.transparency.transparencySchemeOf
|
||||
import de.mm20.launcher2.ui.theme.typography.DefaultTypography
|
||||
import de.mm20.launcher2.ui.theme.typography.getDeviceDefaultTypography
|
||||
import de.mm20.launcher2.ui.theme.typography.typographyOf
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import org.koin.compose.koinInject
|
||||
import de.mm20.launcher2.preferences.ColorScheme as ColorSchemePref
|
||||
@ -24,8 +25,6 @@ import de.mm20.launcher2.preferences.ColorScheme as ColorSchemePref
|
||||
fun LauncherTheme(
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
|
||||
val context = LocalContext.current
|
||||
val uiSettings: UiSettings = koinInject()
|
||||
val themeRepository: ThemeRepository = koinInject()
|
||||
|
||||
@ -41,6 +40,12 @@ fun LauncherTheme(
|
||||
}
|
||||
}.collectAsState(null)
|
||||
|
||||
val themeTypography by remember {
|
||||
uiSettings.typographyId.flatMapLatest {
|
||||
themeRepository.typographies.getOrDefault(it)
|
||||
}
|
||||
}.collectAsState(null)
|
||||
|
||||
val themeTransparencies by remember {
|
||||
uiSettings.transparenciesId.flatMapLatest {
|
||||
themeRepository.transparencies.getOrDefault(it)
|
||||
@ -53,7 +58,7 @@ fun LauncherTheme(
|
||||
val darkTheme =
|
||||
colorSchemePref == ColorSchemePref.Dark || colorSchemePref == ColorSchemePref.System && isSystemInDarkTheme()
|
||||
|
||||
if (themeColors == null || themeShapes == null || themeTransparencies == null) {
|
||||
if (themeColors == null || themeShapes == null || themeTransparencies == null || themeTypography == null) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -64,6 +69,7 @@ fun LauncherTheme(
|
||||
}
|
||||
|
||||
val shapes = shapesOf(themeShapes!!)
|
||||
val typography = typographyOf(themeTypography!!)
|
||||
|
||||
val transparencyScheme = transparencySchemeOf(themeTransparencies!!)
|
||||
|
||||
@ -72,10 +78,6 @@ fun LauncherTheme(
|
||||
Font.Outfit
|
||||
)
|
||||
|
||||
val typography = remember(font) {
|
||||
getTypography(context, font)
|
||||
}
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalDarkTheme provides darkTheme,
|
||||
LocalTransparencyScheme provides transparencyScheme,
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package de.mm20.launcher2.ui.theme.colorscheme
|
||||
|
||||
import android.R
|
||||
import android.os.Build
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
@ -89,11 +88,11 @@ fun systemCorePalette(): CorePalette<Int> {
|
||||
if (Build.VERSION.SDK_INT >= 31 && !compatModeColors) {
|
||||
val context = LocalContext.current
|
||||
return CorePalette(
|
||||
primary = ContextCompat.getColor(context, R.color.system_accent1_500),
|
||||
secondary = ContextCompat.getColor(context, R.color.system_accent2_500),
|
||||
tertiary = ContextCompat.getColor(context, R.color.system_accent3_500),
|
||||
neutral = ContextCompat.getColor(context, R.color.system_neutral1_500),
|
||||
neutralVariant = ContextCompat.getColor(context, R.color.system_neutral2_500),
|
||||
primary = ContextCompat.getColor(context, android.R.color.system_accent1_500),
|
||||
secondary = ContextCompat.getColor(context, android.R.color.system_accent2_500),
|
||||
tertiary = ContextCompat.getColor(context, android.R.color.system_accent3_500),
|
||||
neutral = ContextCompat.getColor(context, android.R.color.system_neutral1_500),
|
||||
neutralVariant = ContextCompat.getColor(context, android.R.color.system_neutral2_500),
|
||||
error = 0xFFB3261E.toInt(),
|
||||
)
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ fun makeTypography(
|
||||
headlineSmallEmphasized = baseTypography.headlineSmall.copy(fontFamily = headlineFamily, fontWeight = FontWeight.Bold),
|
||||
titleLarge = baseTypography.titleLarge.copy(fontFamily = headlineFamily, fontWeight = FontWeight.SemiBold),
|
||||
titleLargeEmphasized = baseTypography.titleLargeEmphasized.copy(fontFamily = headlineFamily, fontWeight = FontWeight.Bold),
|
||||
titleMedium = baseTypography.titleMedium.copy(fontFamily = headlineFamily),
|
||||
titleMedium = baseTypography.titleMedium.copy(fontFamily = headlineFamily, fontWeight = FontWeight.SemiBold),
|
||||
titleMediumEmphasized = baseTypography.titleMediumEmphasized.copy(fontFamily = headlineFamily),
|
||||
titleSmall = baseTypography.titleSmall.copy(fontFamily = headlineFamily, fontWeight = FontWeight.SemiBold),
|
||||
titleSmallEmphasized = baseTypography.titleSmallEmphasized.copy(fontFamily = headlineFamily, fontWeight = FontWeight.Bold),
|
||||
|
||||
@ -0,0 +1,327 @@
|
||||
package de.mm20.launcher2.ui.theme.typography
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.em
|
||||
import androidx.compose.ui.unit.sp
|
||||
import de.mm20.launcher2.themes.typography.DefaultEmphasizedTextStyles
|
||||
import de.mm20.launcher2.themes.typography.DefaultTextStyles
|
||||
import de.mm20.launcher2.ui.theme.typography.fontfamily.Outfit
|
||||
import de.mm20.launcher2.ui.theme.typography.fontfamily.getDeviceBodyFontFamily
|
||||
import de.mm20.launcher2.ui.theme.typography.fontfamily.getDeviceHeadlineFontFamily
|
||||
import de.mm20.launcher2.themes.typography.FontFamily as ThemeFontFamily
|
||||
import de.mm20.launcher2.themes.typography.FontWeight as ThemeFontWeight
|
||||
import de.mm20.launcher2.themes.typography.TextStyle as ThemeTextStyle
|
||||
import de.mm20.launcher2.themes.typography.Typography as ThemeTypography
|
||||
|
||||
@Composable
|
||||
fun typographyOf(typography: ThemeTypography): Typography {
|
||||
val context = LocalContext.current
|
||||
return remember(context, typography) {
|
||||
val base = Typography()
|
||||
|
||||
val fonts = getFontFamilies(context, typography.fonts)
|
||||
|
||||
base.copy(
|
||||
displayLarge = textStyleOf(
|
||||
typography.styles.displayLarge,
|
||||
DefaultTextStyles.displayLarge!!,
|
||||
base.displayLarge,
|
||||
fonts,
|
||||
),
|
||||
displayLargeEmphasized = emphasizedTextStyleOf(
|
||||
typography.emphasizedStyles.displayLarge,
|
||||
typography.styles.displayLarge,
|
||||
DefaultEmphasizedTextStyles.displayLarge!!,
|
||||
DefaultTextStyles.displayLarge!!,
|
||||
base.displayLargeEmphasized,
|
||||
fonts,
|
||||
),
|
||||
displayMedium = textStyleOf(
|
||||
typography.styles.displayMedium,
|
||||
DefaultTextStyles.displayMedium!!,
|
||||
base.displayMedium,
|
||||
fonts,
|
||||
),
|
||||
displayMediumEmphasized = emphasizedTextStyleOf(
|
||||
typography.emphasizedStyles.displayMedium,
|
||||
typography.styles.displayMedium,
|
||||
DefaultEmphasizedTextStyles.displayMedium!!,
|
||||
DefaultTextStyles.displayMedium!!,
|
||||
base.displayMediumEmphasized,
|
||||
fonts,
|
||||
),
|
||||
displaySmall = textStyleOf(
|
||||
typography.styles.displaySmall,
|
||||
DefaultTextStyles.displaySmall!!,
|
||||
base.displaySmall,
|
||||
fonts,
|
||||
),
|
||||
displaySmallEmphasized = emphasizedTextStyleOf(
|
||||
typography.emphasizedStyles.displaySmall,
|
||||
typography.styles.displaySmall,
|
||||
DefaultEmphasizedTextStyles.displaySmall!!,
|
||||
DefaultTextStyles.displaySmall!!,
|
||||
base.displaySmallEmphasized,
|
||||
fonts,
|
||||
),
|
||||
headlineLarge = textStyleOf(
|
||||
typography.styles.headlineLarge,
|
||||
DefaultTextStyles.headlineLarge!!,
|
||||
base.headlineLarge,
|
||||
fonts,
|
||||
),
|
||||
headlineLargeEmphasized = emphasizedTextStyleOf(
|
||||
typography.emphasizedStyles.headlineLarge,
|
||||
typography.styles.headlineLarge,
|
||||
DefaultEmphasizedTextStyles.headlineLarge!!,
|
||||
DefaultTextStyles.headlineLarge!!,
|
||||
base.headlineLargeEmphasized,
|
||||
fonts,
|
||||
),
|
||||
headlineMedium = textStyleOf(
|
||||
typography.styles.headlineMedium,
|
||||
DefaultTextStyles.headlineMedium!!,
|
||||
base.headlineMedium,
|
||||
fonts,
|
||||
),
|
||||
headlineMediumEmphasized = emphasizedTextStyleOf(
|
||||
typography.emphasizedStyles.headlineMedium,
|
||||
typography.styles.headlineMedium,
|
||||
DefaultEmphasizedTextStyles.headlineMedium!!,
|
||||
DefaultTextStyles.headlineMedium!!,
|
||||
base.headlineMediumEmphasized,
|
||||
fonts,
|
||||
),
|
||||
headlineSmall = textStyleOf(
|
||||
typography.styles.headlineSmall,
|
||||
DefaultTextStyles.headlineSmall!!,
|
||||
base.headlineSmall,
|
||||
fonts,
|
||||
),
|
||||
headlineSmallEmphasized = emphasizedTextStyleOf(
|
||||
typography.emphasizedStyles.headlineSmall,
|
||||
typography.styles.headlineSmall,
|
||||
DefaultEmphasizedTextStyles.headlineSmall!!,
|
||||
DefaultTextStyles.headlineSmall!!,
|
||||
base.headlineSmallEmphasized,
|
||||
fonts,
|
||||
),
|
||||
titleLarge = textStyleOf(
|
||||
typography.styles.titleLarge,
|
||||
DefaultTextStyles.titleLarge!!,
|
||||
base.titleLarge,
|
||||
fonts,
|
||||
),
|
||||
titleLargeEmphasized = emphasizedTextStyleOf(
|
||||
typography.emphasizedStyles.titleLarge,
|
||||
typography.styles.titleLarge,
|
||||
DefaultEmphasizedTextStyles.titleLarge!!,
|
||||
DefaultTextStyles.titleLarge!!,
|
||||
base.titleLargeEmphasized,
|
||||
fonts,
|
||||
),
|
||||
titleMedium = textStyleOf(
|
||||
typography.styles.titleMedium,
|
||||
DefaultTextStyles.titleMedium!!,
|
||||
base.titleMedium,
|
||||
fonts,
|
||||
),
|
||||
titleMediumEmphasized = emphasizedTextStyleOf(
|
||||
typography.emphasizedStyles.titleMedium,
|
||||
typography.styles.titleMedium,
|
||||
DefaultEmphasizedTextStyles.titleMedium!!,
|
||||
DefaultTextStyles.titleMedium!!,
|
||||
base.titleMediumEmphasized,
|
||||
fonts,
|
||||
),
|
||||
titleSmall = textStyleOf(
|
||||
typography.styles.titleSmall,
|
||||
DefaultTextStyles.titleSmall!!,
|
||||
base.titleSmall,
|
||||
fonts,
|
||||
),
|
||||
titleSmallEmphasized = emphasizedTextStyleOf(
|
||||
typography.emphasizedStyles.titleSmall,
|
||||
typography.styles.titleSmall,
|
||||
DefaultEmphasizedTextStyles.titleSmall!!,
|
||||
DefaultTextStyles.titleSmall!!,
|
||||
base.titleSmallEmphasized,
|
||||
fonts,
|
||||
),
|
||||
bodyLarge = textStyleOf(
|
||||
typography.styles.bodyLarge,
|
||||
DefaultTextStyles.bodyLarge!!,
|
||||
base.bodyLarge,
|
||||
fonts,
|
||||
),
|
||||
bodyLargeEmphasized = emphasizedTextStyleOf(
|
||||
typography.emphasizedStyles.bodyLarge,
|
||||
typography.styles.bodyLarge,
|
||||
DefaultEmphasizedTextStyles.bodyLarge!!,
|
||||
DefaultTextStyles.bodyLarge!!,
|
||||
base.bodyLargeEmphasized,
|
||||
fonts,
|
||||
),
|
||||
bodyMedium = textStyleOf(
|
||||
typography.styles.bodyMedium,
|
||||
DefaultTextStyles.bodyMedium!!,
|
||||
base.bodyMedium,
|
||||
fonts,
|
||||
),
|
||||
bodyMediumEmphasized = emphasizedTextStyleOf(
|
||||
typography.emphasizedStyles.bodyMedium,
|
||||
typography.styles.bodyMedium,
|
||||
DefaultEmphasizedTextStyles.bodyMedium!!,
|
||||
DefaultTextStyles.bodyMedium!!,
|
||||
base.bodyMediumEmphasized,
|
||||
fonts,
|
||||
),
|
||||
bodySmall = textStyleOf(
|
||||
typography.styles.bodySmall,
|
||||
DefaultTextStyles.bodySmall!!,
|
||||
base.bodySmall,
|
||||
fonts,
|
||||
),
|
||||
bodySmallEmphasized = emphasizedTextStyleOf(
|
||||
typography.emphasizedStyles.bodySmall,
|
||||
typography.styles.bodySmall,
|
||||
DefaultEmphasizedTextStyles.bodySmall!!,
|
||||
DefaultTextStyles.bodySmall!!,
|
||||
base.bodySmallEmphasized,
|
||||
fonts,
|
||||
),
|
||||
labelLarge = textStyleOf(
|
||||
typography.styles.labelLarge,
|
||||
DefaultTextStyles.labelLarge!!,
|
||||
base.labelLarge,
|
||||
fonts,
|
||||
),
|
||||
labelLargeEmphasized = emphasizedTextStyleOf(
|
||||
typography.emphasizedStyles.labelLarge,
|
||||
typography.styles.labelLarge,
|
||||
DefaultEmphasizedTextStyles.labelLarge!!,
|
||||
DefaultTextStyles.labelLarge!!,
|
||||
base.labelLargeEmphasized,
|
||||
fonts,
|
||||
),
|
||||
labelMedium = textStyleOf(
|
||||
typography.styles.labelMedium,
|
||||
DefaultTextStyles.labelMedium!!,
|
||||
base.labelMedium,
|
||||
fonts,
|
||||
),
|
||||
labelMediumEmphasized = emphasizedTextStyleOf(
|
||||
typography.emphasizedStyles.labelMedium,
|
||||
typography.styles.labelMedium,
|
||||
DefaultEmphasizedTextStyles.labelMedium!!,
|
||||
DefaultTextStyles.labelMedium!!,
|
||||
base.labelMediumEmphasized,
|
||||
fonts,
|
||||
),
|
||||
labelSmall = textStyleOf(
|
||||
typography.styles.labelSmall,
|
||||
DefaultTextStyles.labelSmall!!,
|
||||
base.labelSmall,
|
||||
fonts,
|
||||
),
|
||||
labelSmallEmphasized = emphasizedTextStyleOf(
|
||||
typography.emphasizedStyles.labelSmall,
|
||||
typography.styles.labelSmall,
|
||||
DefaultEmphasizedTextStyles.labelSmall!!,
|
||||
DefaultTextStyles.labelSmall!!,
|
||||
base.labelSmallEmphasized,
|
||||
fonts,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun textStyleOf(
|
||||
style: ThemeTextStyle<ThemeFontWeight.Absolute?>?,
|
||||
fallback: ThemeTextStyle<ThemeFontWeight.Absolute?>,
|
||||
base: TextStyle,
|
||||
fonts: Map<String, FontFamily?>,
|
||||
): TextStyle {
|
||||
return base.copy(
|
||||
fontFamily = (style?.fontFamily ?: fallback.fontFamily)?.let { fonts[it] }
|
||||
?: base.fontFamily,
|
||||
fontWeight = (style?.fontWeight?.weight
|
||||
?: fallback.fontWeight?.weight)?.let { FontWeight(it) } ?: base.fontWeight,
|
||||
fontSize = (style?.fontSize ?: fallback.fontSize)?.sp ?: base.fontSize,
|
||||
lineHeight = (style?.lineHeight ?: fallback.lineHeight)?.em ?: base.lineHeight,
|
||||
letterSpacing = (style?.letterSpacing ?: fallback.letterSpacing)?.em ?: base.letterSpacing,
|
||||
)
|
||||
}
|
||||
|
||||
private fun emphasizedTextStyleOf(
|
||||
style: ThemeTextStyle<ThemeFontWeight?>?,
|
||||
parent: ThemeTextStyle<ThemeFontWeight.Absolute?>?,
|
||||
fallback: ThemeTextStyle<ThemeFontWeight?>,
|
||||
fallbackParent: ThemeTextStyle<ThemeFontWeight.Absolute?>,
|
||||
base: TextStyle,
|
||||
fonts: Map<String, FontFamily?>,
|
||||
): TextStyle {
|
||||
val weight: ThemeFontWeight? = style?.fontWeight ?: fallback.fontWeight
|
||||
val parentWeight = parent?.fontWeight ?: fallbackParent.fontWeight
|
||||
|
||||
val fontWeight = when (weight) {
|
||||
is ThemeFontWeight.Absolute -> FontWeight(weight.weight)
|
||||
is ThemeFontWeight.Relative if (parentWeight != null) -> {
|
||||
FontWeight(parentWeight.weight + weight.relativeWeight)
|
||||
}
|
||||
|
||||
else -> base.fontWeight
|
||||
}
|
||||
|
||||
return base.copy(
|
||||
fontFamily = (style?.fontFamily
|
||||
?: parent?.fontFamily
|
||||
?: fallback.fontFamily
|
||||
?: fallbackParent.fontFamily)
|
||||
?.let { fonts[it] }
|
||||
?: base.fontFamily,
|
||||
fontWeight = fontWeight,
|
||||
fontSize = (style?.fontSize ?: parent?.fontSize ?: fallback.fontSize
|
||||
?: fallbackParent.fontSize)?.sp ?: base.fontSize,
|
||||
lineHeight = (style?.lineHeight ?: parent?.lineHeight ?: fallback.lineHeight
|
||||
?: fallbackParent.lineHeight)?.em
|
||||
?: base.lineHeight,
|
||||
letterSpacing = (style?.letterSpacing ?: parent?.letterSpacing
|
||||
?: fallback.letterSpacing ?: fallbackParent.letterSpacing)?.em ?: base.letterSpacing,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getFontFamilies(
|
||||
context: Context,
|
||||
fonts: Map<String, ThemeFontFamily?>
|
||||
): Map<String, FontFamily?> {
|
||||
val distinct = fonts.values.distinct()
|
||||
|
||||
val map: Map<ThemeFontFamily?, FontFamily> = distinct.associateWith {
|
||||
fontFamilyOf(context, it)
|
||||
}
|
||||
|
||||
return fonts.keys.associateWith { map[fonts[it]] }
|
||||
}
|
||||
|
||||
fun fontFamilyOf(
|
||||
context: Context,
|
||||
fontFamily: ThemeFontFamily?
|
||||
): FontFamily {
|
||||
return when (fontFamily) {
|
||||
is ThemeFontFamily.LauncherDefault -> Outfit
|
||||
is ThemeFontFamily.DeviceHeadline -> getDeviceHeadlineFontFamily(context)
|
||||
is ThemeFontFamily.DeviceBody -> getDeviceBodyFontFamily(context)
|
||||
is ThemeFontFamily.SansSerif -> FontFamily.SansSerif
|
||||
is ThemeFontFamily.Serif -> FontFamily.Serif
|
||||
is ThemeFontFamily.Monospace -> FontFamily.Monospace
|
||||
else -> FontFamily.Default
|
||||
}
|
||||
}
|
||||
@ -41,4 +41,41 @@ fun getDeviceHeadlineFontFamily(context: Context): FontFamily {
|
||||
|
||||
return FontFamily.SansSerif
|
||||
|
||||
}
|
||||
|
||||
fun getDeviceBodyFontFamily(context: Context): FontFamily {
|
||||
val configResId = context.resources
|
||||
.getIdentifier("config_bodyFontFamily", "string", "android")
|
||||
|
||||
if (configResId != 0) {
|
||||
val fontFamily = context.resources.getString(configResId)
|
||||
|
||||
if (fontFamily.isBlank()) return FontFamily.SansSerif
|
||||
|
||||
return try {
|
||||
FontFamily(
|
||||
Font(DeviceFontFamilyName(fontFamily), weight = FontWeight.Thin, style = FontStyle.Normal),
|
||||
Font(DeviceFontFamilyName(fontFamily), weight = FontWeight.ExtraLight, style = FontStyle.Normal),
|
||||
Font(DeviceFontFamilyName(fontFamily), weight = FontWeight.Light, style = FontStyle.Normal),
|
||||
Font(DeviceFontFamilyName(fontFamily), weight = FontWeight.Normal, style = FontStyle.Normal),
|
||||
Font(DeviceFontFamilyName(fontFamily), weight = FontWeight.Medium, style = FontStyle.Normal),
|
||||
Font(DeviceFontFamilyName(fontFamily), weight = FontWeight.SemiBold, style = FontStyle.Normal),
|
||||
Font(DeviceFontFamilyName(fontFamily), weight = FontWeight.Bold, style = FontStyle.Normal),
|
||||
Font(DeviceFontFamilyName(fontFamily), weight = FontWeight.ExtraBold, style = FontStyle.Normal),
|
||||
Font(DeviceFontFamilyName(fontFamily), weight = FontWeight.Black, style = FontStyle.Normal),
|
||||
Font(DeviceFontFamilyName(fontFamily), weight = FontWeight.Thin, style = FontStyle.Italic),
|
||||
Font(DeviceFontFamilyName(fontFamily), weight = FontWeight.ExtraLight, style = FontStyle.Italic),
|
||||
Font(DeviceFontFamilyName(fontFamily), weight = FontWeight.Light, style = FontStyle.Italic),
|
||||
Font(DeviceFontFamilyName(fontFamily), weight = FontWeight.Normal, style = FontStyle.Italic),
|
||||
Font(DeviceFontFamilyName(fontFamily), weight = FontWeight.Medium, style = FontStyle.Italic),
|
||||
Font(DeviceFontFamilyName(fontFamily), weight = FontWeight.SemiBold, style = FontStyle.Italic),
|
||||
Font(DeviceFontFamilyName(fontFamily), weight = FontWeight.Bold, style = FontStyle.Italic),
|
||||
Font(DeviceFontFamilyName(fontFamily), weight = FontWeight.ExtraBold, style = FontStyle.Italic),
|
||||
Font(DeviceFontFamilyName(fontFamily), weight = FontWeight.Black, style = FontStyle.Italic),
|
||||
)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
FontFamily.SansSerif
|
||||
}
|
||||
}
|
||||
return FontFamily.SansSerif
|
||||
}
|
||||
@ -1834,4 +1834,69 @@ val _RoundedCornerAlt = materialIcon("Icons.Rounded.RoundedCornerAlt") {
|
||||
}
|
||||
|
||||
val Icons.Rounded.RoundedCornerAlt
|
||||
get() = _RoundedCornerAlt
|
||||
get() = _RoundedCornerAlt
|
||||
|
||||
private val _LetterSpacing2 = materialIcon("Icons.Rounded.LetterSpacing2") {
|
||||
materialPath {
|
||||
moveTo(6.7f, 21.5125f)
|
||||
quadToRelative(-0.275f, 0.275f, -0.7f, 0.275f)
|
||||
quadToRelative(-0.425f, 0f, -0.7f, -0.275f)
|
||||
lineToRelative(-2.6f, -2.6f)
|
||||
quadToRelative(-0.3f, -0.3f, -0.3f, -0.7f)
|
||||
quadToRelative(0f, -0.4f, 0.3f, -0.7f)
|
||||
lineToRelative(2.6f, -2.6f)
|
||||
quadToRelative(0.275f, -0.275f, 0.6875f, -0.275f)
|
||||
quadToRelative(0.4125f, 0f, 0.7125f, 0.275f)
|
||||
quadToRelative(0.3f, 0.3f, 0.3f, 0.7125f)
|
||||
quadToRelative(0f, 0.4125f, -0.3f, 0.7125f)
|
||||
lineToRelative(-0.875f, 0.875f)
|
||||
horizontalLineToRelative(12.35f)
|
||||
lineToRelative(-0.9f, -0.9f)
|
||||
quadTo(17f, 16.0375f, 17f, 15.625f)
|
||||
quadToRelative(0f, -0.4125f, 0.3f, -0.7125f)
|
||||
quadToRelative(0.275f, -0.275f, 0.7f, -0.275f)
|
||||
quadToRelative(0.425f, 0f, 0.7f, 0.275f)
|
||||
lineToRelative(2.6f, 2.6f)
|
||||
quadToRelative(0.3f, 0.3f, 0.3f, 0.7f)
|
||||
quadToRelative(0f, 0.4f, -0.3f, 0.7f)
|
||||
lineToRelative(-2.6f, 2.6f)
|
||||
quadToRelative(-0.275f, 0.275f, -0.6875f, 0.275f)
|
||||
quadToRelative(-0.4125f, 0f, -0.7125f, -0.275f)
|
||||
quadToRelative(-0.3f, -0.3f, -0.3f, -0.7125f)
|
||||
quadToRelative(0f, -0.4125f, 0.3f, -0.7125f)
|
||||
lineToRelative(0.875f, -0.875f)
|
||||
horizontalLineTo(5.825f)
|
||||
lineToRelative(0.9f, 0.9f)
|
||||
quadTo(7f, 20.3875f, 7f, 20.8f)
|
||||
quadTo(7f, 21.2125f, 6.7f, 21.5125f)
|
||||
close()
|
||||
moveToRelative(0.65f, -9.5f)
|
||||
lineToRelative(3.425f, -9.2f)
|
||||
quadTo(10.875f, 2.5375f, 11.1125f, 2.375f)
|
||||
quadTo(11.35f, 2.2125f, 11.65f, 2.2125f)
|
||||
horizontalLineToRelative(0.7f)
|
||||
quadToRelative(0.3f, 0f, 0.5375f, 0.1625f)
|
||||
quadToRelative(0.2375f, 0.1625f, 0.3375f, 0.4375f)
|
||||
lineToRelative(3.425f, 9.225f)
|
||||
quadToRelative(0.15f, 0.425f, -0.1f, 0.8f)
|
||||
quadToRelative(-0.25f, 0.375f, -0.7f, 0.375f)
|
||||
quadToRelative(-0.275f, 0f, -0.5125f, -0.1625f)
|
||||
quadTo(15.1f, 12.8875f, 15f, 12.6125f)
|
||||
lineToRelative(-0.75f, -2.2f)
|
||||
horizontalLineTo(9.8f)
|
||||
lineTo(9f, 12.6375f)
|
||||
quadToRelative(-0.1f, 0.275f, -0.325f, 0.425f)
|
||||
quadToRelative(-0.225f, 0.15f, -0.5f, 0.15f)
|
||||
quadToRelative(-0.475f, 0f, -0.7375f, -0.3875f)
|
||||
quadTo(7.175f, 12.4375f, 7.35f, 12.0125f)
|
||||
close()
|
||||
moveToRelative(3f, -3.2f)
|
||||
horizontalLineToRelative(3.3f)
|
||||
lineToRelative(-1.6f, -4.55f)
|
||||
horizontalLineToRelative(-0.1f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.LetterSpacing2
|
||||
get() = _LetterSpacing2
|
||||
@ -439,6 +439,7 @@
|
||||
<string name="preference_shapes_extra_round">Extra round</string>
|
||||
<string name="preference_shapes_rect">Rectangular</string>
|
||||
<string name="preference_shapes_base">Base shape</string>
|
||||
<string name="preference_screen_typography">Typography</string>
|
||||
<string name="preference_screen_transparencies">Transparency</string>
|
||||
<string name="preference_transparencies_default">Default</string>
|
||||
<string name="preference_transparencies_semi_transparent">Semi-transparent</string>
|
||||
@ -824,6 +825,7 @@
|
||||
<string name="note_widget_file_write_error_description">The note could not be written to the linked file. Possibly, it has been moved or deleted. A copy has been saved to the launcher\'s internal storage.</string>
|
||||
<string name="confirmation_delete_color_scheme">Do you really want to delete the color scheme %1$s\?</string>
|
||||
<string name="confirmation_delete_shapes_scheme">Do you really want to delete the shape scheme %1$s?</string>
|
||||
<string name="confirmation_delete_typography_scheme">Do you really want to delete the typography scheme %1$s?</string>
|
||||
<string name="confirmation_delete_transparencies_scheme">Do you really want to delete the transparency scheme %1$s?</string>
|
||||
<string name="new_theme_name">(untitled)</string>
|
||||
<string name="theme_color_scheme_system_default">Use system default</string>
|
||||
|
||||
@ -18,6 +18,8 @@ data class LauncherSettingsData internal constructor(
|
||||
val uiShapesId: UUID = UUID(0L, 0L),
|
||||
@Serializable(with = UUIDSerializer::class)
|
||||
val uiTransparenciesId: UUID = UUID(0L, 0L),
|
||||
@Serializable(with = UUIDSerializer::class)
|
||||
val uiTypographyId: UUID = UUID(0L, 0L),
|
||||
|
||||
val uiCompatModeColors: Boolean = false,
|
||||
val uiFont: Font = Font.Outfit,
|
||||
|
||||
@ -318,6 +318,17 @@ class UiSettings internal constructor(
|
||||
}
|
||||
}
|
||||
|
||||
val typographyId
|
||||
get() = launcherDataStore.data.map {
|
||||
it.uiTypographyId
|
||||
}.distinctUntilChanged()
|
||||
|
||||
fun setTypographyId(typographyId: UUID) {
|
||||
launcherDataStore.update {
|
||||
it.copy(uiTypographyId = typographyId)
|
||||
}
|
||||
}
|
||||
|
||||
val font
|
||||
get() = launcherDataStore.data.map {
|
||||
it.uiFont
|
||||
|
||||
@ -10,6 +10,7 @@ import androidx.room.TypeConverters
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import de.mm20.launcher2.database.daos.PluginDao
|
||||
import de.mm20.launcher2.database.daos.ThemeDao
|
||||
import de.mm20.launcher2.database.entities.ColorsEntity
|
||||
import de.mm20.launcher2.database.entities.CurrencyEntity
|
||||
import de.mm20.launcher2.database.entities.CustomAttributeEntity
|
||||
import de.mm20.launcher2.database.entities.ForecastEntity
|
||||
@ -18,9 +19,9 @@ import de.mm20.launcher2.database.entities.IconPackEntity
|
||||
import de.mm20.launcher2.database.entities.PluginEntity
|
||||
import de.mm20.launcher2.database.entities.SavedSearchableEntity
|
||||
import de.mm20.launcher2.database.entities.SearchActionEntity
|
||||
import de.mm20.launcher2.database.entities.ColorsEntity
|
||||
import de.mm20.launcher2.database.entities.ShapesEntity
|
||||
import de.mm20.launcher2.database.entities.TransparenciesEntity
|
||||
import de.mm20.launcher2.database.entities.TypographyEntity
|
||||
import de.mm20.launcher2.database.entities.WidgetEntity
|
||||
import de.mm20.launcher2.database.migrations.Migration_10_11
|
||||
import de.mm20.launcher2.database.migrations.Migration_11_12
|
||||
@ -41,6 +42,7 @@ import de.mm20.launcher2.database.migrations.Migration_25_26
|
||||
import de.mm20.launcher2.database.migrations.Migration_26_27
|
||||
import de.mm20.launcher2.database.migrations.Migration_27_28
|
||||
import de.mm20.launcher2.database.migrations.Migration_28_29
|
||||
import de.mm20.launcher2.database.migrations.Migration_29_30
|
||||
import de.mm20.launcher2.database.migrations.Migration_6_7
|
||||
import de.mm20.launcher2.database.migrations.Migration_7_8
|
||||
import de.mm20.launcher2.database.migrations.Migration_8_9
|
||||
@ -62,7 +64,8 @@ import java.util.UUID
|
||||
PluginEntity::class,
|
||||
ShapesEntity::class,
|
||||
TransparenciesEntity::class,
|
||||
], version = 29, exportSchema = true
|
||||
TypographyEntity::class,
|
||||
], version = 30, exportSchema = true
|
||||
)
|
||||
@TypeConverters(ComponentNameConverter::class)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
@ -164,6 +167,7 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
Migration_26_27(),
|
||||
Migration_27_28(),
|
||||
Migration_28_29(),
|
||||
Migration_29_30(),
|
||||
).build()
|
||||
if (_instance == null) _instance = instance
|
||||
return instance
|
||||
|
||||
@ -7,6 +7,7 @@ import androidx.room.Update
|
||||
import de.mm20.launcher2.database.entities.ColorsEntity
|
||||
import de.mm20.launcher2.database.entities.ShapesEntity
|
||||
import de.mm20.launcher2.database.entities.TransparenciesEntity
|
||||
import de.mm20.launcher2.database.entities.TypographyEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import java.util.UUID
|
||||
|
||||
@ -21,6 +22,9 @@ interface ThemeDao {
|
||||
@Query("SELECT * FROM Transparencies")
|
||||
fun getAllTransparencies(): Flow<List<TransparenciesEntity>>
|
||||
|
||||
@Query("SELECT * FROM Typography")
|
||||
fun getAllTypographies(): Flow<List<TypographyEntity>>
|
||||
|
||||
@Query("SELECT * FROM Theme WHERE id = :id LIMIT 1")
|
||||
fun getColors(id: UUID): Flow<ColorsEntity?>
|
||||
|
||||
@ -30,6 +34,9 @@ interface ThemeDao {
|
||||
@Query("SELECT * FROM Transparencies WHERE id = :id LIMIT 1")
|
||||
fun getTransparencies(id: UUID): Flow<TransparenciesEntity?>
|
||||
|
||||
@Query("SELECT * FROM Typography WHERE id = :id LIMIT 1")
|
||||
fun getTypography(id: UUID): Flow<TypographyEntity?>
|
||||
|
||||
@Insert
|
||||
suspend fun insertColors(colors: ColorsEntity)
|
||||
|
||||
@ -39,6 +46,9 @@ interface ThemeDao {
|
||||
@Insert
|
||||
suspend fun insertTransparencies(transparencies: TransparenciesEntity)
|
||||
|
||||
@Insert
|
||||
suspend fun insertTypography(typography: TypographyEntity)
|
||||
|
||||
@Update
|
||||
suspend fun updateColors(colors: ColorsEntity)
|
||||
|
||||
@ -48,6 +58,9 @@ interface ThemeDao {
|
||||
@Update
|
||||
suspend fun updateTransparencies(transparencies: TransparenciesEntity)
|
||||
|
||||
@Update
|
||||
suspend fun updateTypography(typography: TypographyEntity)
|
||||
|
||||
@Query("DELETE FROM Theme WHERE id = :id")
|
||||
suspend fun deleteColors(id: UUID)
|
||||
|
||||
@ -57,6 +70,9 @@ interface ThemeDao {
|
||||
@Query("DELETE FROM Transparencies WHERE id = :id")
|
||||
suspend fun deleteTransparencies(id: UUID)
|
||||
|
||||
@Query("DELETE FROM Typography WHERE id = :id")
|
||||
suspend fun deleteTypography(id: UUID)
|
||||
|
||||
@Query("DELETE FROM Theme")
|
||||
suspend fun deleteAllColors()
|
||||
|
||||
@ -66,6 +82,9 @@ interface ThemeDao {
|
||||
@Query("DELETE FROM Transparencies")
|
||||
suspend fun deleteAllTransparencies()
|
||||
|
||||
@Query("DELETE FROM Typography")
|
||||
suspend fun deleteAllTypographies()
|
||||
|
||||
@Insert
|
||||
fun insertAllColors(colors: List<ColorsEntity>)
|
||||
|
||||
@ -74,4 +93,7 @@ interface ThemeDao {
|
||||
|
||||
@Insert
|
||||
fun insertAllTransparencies(transparencies: List<TransparenciesEntity>)
|
||||
|
||||
@Insert
|
||||
fun insertAllTypographies(typography: List<TypographyEntity>)
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
package de.mm20.launcher2.database.entities
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import java.util.UUID
|
||||
|
||||
@Entity(tableName = "Typography")
|
||||
data class TypographyEntity(
|
||||
@PrimaryKey val id: UUID,
|
||||
val name: String,
|
||||
|
||||
val fonts: String? = null,
|
||||
val displayLarge: String? = null,
|
||||
val displayMedium: String? = null,
|
||||
val displaySmall: String? = null,
|
||||
val headlineLarge: String? = null,
|
||||
val headlineMedium: String? = null,
|
||||
val headlineSmall: String? = null,
|
||||
val titleLarge: String? = null,
|
||||
val titleMedium: String? = null,
|
||||
val titleSmall: String? = null,
|
||||
val bodyLarge: String? = null,
|
||||
val bodyMedium: String? = null,
|
||||
val bodySmall: String? = null,
|
||||
val labelLarge: String? = null,
|
||||
val labelMedium: String? = null,
|
||||
val labelSmall: String? = null,
|
||||
val emphasizedDisplayLarge: String? = null,
|
||||
val emphasizedDisplayMedium: String? = null,
|
||||
val emphasizedDisplaySmall: String? = null,
|
||||
val emphasizedHeadlineLarge: String? = null,
|
||||
val emphasizedHeadlineMedium: String? = null,
|
||||
val emphasizedHeadlineSmall: String? = null,
|
||||
val emphasizedTitleLarge: String? = null,
|
||||
val emphasizedTitleMedium: String? = null,
|
||||
val emphasizedTitleSmall: String? = null,
|
||||
val emphasizedBodyLarge: String? = null,
|
||||
val emphasizedBodyMedium: String? = null,
|
||||
val emphasizedBodySmall: String? = null,
|
||||
val emphasizedLabelLarge: String? = null,
|
||||
val emphasizedLabelMedium: String? = null,
|
||||
val emphasizedLabelSmall: String? = null,
|
||||
)
|
||||
@ -0,0 +1,50 @@
|
||||
package de.mm20.launcher2.database.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.SQLiteConnection
|
||||
import androidx.sqlite.execSQL
|
||||
|
||||
class Migration_29_30 : Migration(29, 30) {
|
||||
|
||||
override fun migrate(connection: SQLiteConnection) {
|
||||
connection.execSQL(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS `Typography` (
|
||||
`id` BLOB NOT NULL PRIMARY KEY,
|
||||
`name` TEXT NOT NULL,
|
||||
`fonts` TEXT,
|
||||
`displayLarge` TEXT,
|
||||
`displayMedium` TEXT,
|
||||
`displaySmall` TEXT,
|
||||
`headlineLarge` TEXT,
|
||||
`headlineMedium` TEXT,
|
||||
`headlineSmall` TEXT,
|
||||
`titleLarge` TEXT,
|
||||
`titleMedium` TEXT,
|
||||
`titleSmall` TEXT,
|
||||
`bodyLarge` TEXT,
|
||||
`bodyMedium` TEXT,
|
||||
`bodySmall` TEXT,
|
||||
`labelLarge` TEXT,
|
||||
`labelMedium` TEXT,
|
||||
`labelSmall` TEXT,
|
||||
`emphasizedDisplayLarge` TEXT,
|
||||
`emphasizedDisplayMedium` TEXT,
|
||||
`emphasizedDisplaySmall` TEXT,
|
||||
`emphasizedHeadlineLarge` TEXT,
|
||||
`emphasizedHeadlineMedium` TEXT,
|
||||
`emphasizedHeadlineSmall` TEXT,
|
||||
`emphasizedTitleLarge` TEXT,
|
||||
`emphasizedTitleMedium` TEXT,
|
||||
`emphasizedTitleSmall` TEXT,
|
||||
`emphasizedBodyLarge` TEXT,
|
||||
`emphasizedBodyMedium` TEXT,
|
||||
`emphasizedBodySmall` TEXT,
|
||||
`emphasizedLabelLarge` TEXT,
|
||||
`emphasizedLabelMedium` TEXT,
|
||||
`emphasizedLabelSmall` TEXT
|
||||
)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,5 @@
|
||||
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
|
||||
|
||||
|
||||
@ -17,4 +12,8 @@ val ExtraRoundShapesId = UUID(0L, 1L)
|
||||
val CutShapesId = UUID(0L, 2L)
|
||||
val RectShapesId = UUID(0L, 3L)
|
||||
|
||||
val SemiTransparentId = UUID(0L, 1L)
|
||||
val SemiTransparentId = UUID(0L, 1L)
|
||||
|
||||
val SystemFontId = UUID(0L, 1L)
|
||||
val MonospaceId = UUID(0L, 2L)
|
||||
val SerifId = UUID(0L, 3L)
|
||||
@ -3,6 +3,7 @@ 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 de.mm20.launcher2.themes.typography.FontWeight
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
@ -26,8 +27,9 @@ val ThemeJson = Json {
|
||||
coerceInputValues = true
|
||||
}
|
||||
|
||||
internal object ColorSerializer: KSerializer<Color> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ColorSerializer", PrimitiveKind.STRING)
|
||||
internal object ColorSerializer : KSerializer<Color> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
PrimitiveSerialDescriptor("ColorSerializer", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(
|
||||
encoder: Encoder,
|
||||
@ -42,8 +44,9 @@ internal object ColorSerializer: KSerializer<Color> {
|
||||
}
|
||||
}
|
||||
|
||||
internal object ShapeSerializer: KSerializer<Shape> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ShapeSerializer", PrimitiveKind.STRING)
|
||||
internal object ShapeSerializer : KSerializer<Shape> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
PrimitiveSerialDescriptor("ShapeSerializer", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(
|
||||
encoder: Encoder,
|
||||
@ -55,4 +58,34 @@ internal object ShapeSerializer: KSerializer<Shape> {
|
||||
override fun deserialize(decoder: Decoder): Shape {
|
||||
return Shape.fromString(decoder.decodeString())!!
|
||||
}
|
||||
}
|
||||
|
||||
internal object FontWeightSerializer : KSerializer<FontWeight?> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
PrimitiveSerialDescriptor("FontWeightSerializer", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(
|
||||
encoder: Encoder,
|
||||
value: FontWeight?
|
||||
) {
|
||||
if (value is FontWeight.Absolute) {
|
||||
encoder.encodeString(value.weight.toString())
|
||||
} else if (value is FontWeight.Relative) {
|
||||
encoder.encodeString(
|
||||
(if (value.relativeWeight >= 0) "+" else "-") + value.relativeWeight.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): FontWeight? {
|
||||
val str = decoder.decodeString()
|
||||
|
||||
if (str.isBlank()) return null
|
||||
|
||||
return when {
|
||||
str.startsWith("+") -> FontWeight.Relative(str.substring(1).toInt())
|
||||
str.startsWith("-") -> FontWeight.Relative(-str.substring(1).toInt())
|
||||
else -> FontWeight.Absolute(str.toInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,7 @@ import de.mm20.launcher2.themes.colors.Colors
|
||||
import de.mm20.launcher2.themes.colors.ColorsRepository
|
||||
import de.mm20.launcher2.themes.shapes.ShapesRepository
|
||||
import de.mm20.launcher2.themes.transparencies.TransparenciesRepository
|
||||
import de.mm20.launcher2.themes.typography.TypographyRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.withContext
|
||||
@ -21,6 +22,7 @@ class ThemeRepository(
|
||||
val colors = ColorsRepository(context, database)
|
||||
val shapes = ShapesRepository(context, database)
|
||||
val transparencies = TransparenciesRepository(context, database)
|
||||
val typographies = TypographyRepository(context, database)
|
||||
|
||||
|
||||
override suspend fun backup(toDir: File) = withContext(Dispatchers.IO) {
|
||||
|
||||
@ -0,0 +1,127 @@
|
||||
package de.mm20.launcher2.themes.typography
|
||||
|
||||
val DefaultTextStyles = TextStyles(
|
||||
displayLarge = TextStyle(
|
||||
fontFamily = "brand",
|
||||
fontSize = 57,
|
||||
fontWeight = FontWeight.Absolute(500),
|
||||
lineHeight = 64,
|
||||
letterSpacing = -0.25f
|
||||
),
|
||||
displayMedium = TextStyle(
|
||||
fontFamily = "brand",
|
||||
fontSize = 45,
|
||||
fontWeight = FontWeight.Absolute(500),
|
||||
lineHeight = 52,
|
||||
letterSpacing = 0f
|
||||
),
|
||||
displaySmall = TextStyle(
|
||||
fontFamily = "brand",
|
||||
fontSize = 36,
|
||||
fontWeight = FontWeight.Absolute(500),
|
||||
lineHeight = 44,
|
||||
letterSpacing = 0f
|
||||
),
|
||||
headlineLarge = TextStyle(
|
||||
fontFamily = "brand",
|
||||
fontSize = 32,
|
||||
fontWeight = FontWeight.Absolute(600),
|
||||
lineHeight = 40,
|
||||
letterSpacing = 0f
|
||||
),
|
||||
headlineMedium = TextStyle(
|
||||
fontFamily = "brand",
|
||||
fontSize = 28,
|
||||
fontWeight = FontWeight.Absolute(600),
|
||||
lineHeight = 36,
|
||||
letterSpacing = 0f
|
||||
),
|
||||
headlineSmall = TextStyle(
|
||||
fontFamily = "brand",
|
||||
fontSize = 24,
|
||||
fontWeight = FontWeight.Absolute(600),
|
||||
lineHeight = 32,
|
||||
letterSpacing = 0f
|
||||
),
|
||||
titleLarge = TextStyle(
|
||||
fontFamily = "brand",
|
||||
fontSize = 22,
|
||||
fontWeight = FontWeight.Absolute(600),
|
||||
lineHeight = 28,
|
||||
letterSpacing = 0f
|
||||
),
|
||||
titleMedium = TextStyle(
|
||||
fontFamily = "brand",
|
||||
fontSize = 16,
|
||||
fontWeight = FontWeight.Absolute(600),
|
||||
lineHeight = 24,
|
||||
letterSpacing = 0.15f
|
||||
),
|
||||
titleSmall = TextStyle(
|
||||
fontFamily = "brand",
|
||||
fontSize = 14,
|
||||
fontWeight = FontWeight.Absolute(600),
|
||||
lineHeight = 20,
|
||||
letterSpacing = 0.1f
|
||||
),
|
||||
bodyLarge = TextStyle(
|
||||
fontFamily = "plain",
|
||||
fontSize = 16,
|
||||
fontWeight = FontWeight.Absolute(400),
|
||||
lineHeight = 24,
|
||||
letterSpacing = 0.5f
|
||||
),
|
||||
bodyMedium = TextStyle(
|
||||
fontFamily = "plain",
|
||||
fontSize = 14,
|
||||
fontWeight = FontWeight.Absolute(400),
|
||||
lineHeight = 20,
|
||||
letterSpacing = 0.25f
|
||||
),
|
||||
bodySmall = TextStyle(
|
||||
fontFamily = "plain",
|
||||
fontSize = 12,
|
||||
fontWeight = FontWeight.Absolute(400),
|
||||
lineHeight = 16,
|
||||
letterSpacing = 0.4f
|
||||
),
|
||||
labelLarge = TextStyle(
|
||||
fontFamily = "brand",
|
||||
fontSize = 14,
|
||||
fontWeight = FontWeight.Absolute(500),
|
||||
lineHeight = 20,
|
||||
letterSpacing = 0.1f
|
||||
),
|
||||
labelMedium = TextStyle(
|
||||
fontFamily = "brand",
|
||||
fontSize = 12,
|
||||
fontWeight = FontWeight.Absolute(500),
|
||||
lineHeight = 16,
|
||||
letterSpacing = 0.5f
|
||||
),
|
||||
labelSmall = TextStyle(
|
||||
fontFamily = "brand",
|
||||
fontSize = 11,
|
||||
fontWeight = FontWeight.Absolute(500),
|
||||
lineHeight = 16,
|
||||
letterSpacing = 0.5f
|
||||
)
|
||||
)
|
||||
|
||||
val DefaultEmphasizedTextStyles = TextStyles(
|
||||
displayLarge = TextStyle(fontWeight = FontWeight.Relative(100)),
|
||||
displayMedium = TextStyle(fontWeight = FontWeight.Relative(100)),
|
||||
displaySmall = TextStyle(fontWeight = FontWeight.Relative(100)),
|
||||
headlineLarge = TextStyle(fontWeight = FontWeight.Relative(100)),
|
||||
headlineMedium = TextStyle(fontWeight = FontWeight.Relative(100)),
|
||||
headlineSmall = TextStyle(fontWeight = FontWeight.Relative(100)),
|
||||
titleLarge = TextStyle(fontWeight = FontWeight.Relative(100)),
|
||||
titleMedium = TextStyle(fontWeight = FontWeight.Relative(100)),
|
||||
titleSmall = TextStyle(fontWeight = FontWeight.Relative(100)),
|
||||
bodyLarge = TextStyle(fontWeight = FontWeight.Relative(100)),
|
||||
bodyMedium = TextStyle(fontWeight = FontWeight.Relative(100)),
|
||||
bodySmall = TextStyle(fontWeight = FontWeight.Relative(100)),
|
||||
labelLarge = TextStyle(fontWeight = FontWeight.Relative(200)),
|
||||
labelMedium = TextStyle(fontWeight = FontWeight.Relative(200)),
|
||||
labelSmall = TextStyle(fontWeight = FontWeight.Relative(200))
|
||||
)
|
||||
@ -0,0 +1,45 @@
|
||||
package de.mm20.launcher2.themes.typography
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.fonts.SystemFonts
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.core.graphics.TypefaceCompat
|
||||
|
||||
data class FontList(
|
||||
val builtIn: List<FontFamily>,
|
||||
val generic: List<FontFamily>,
|
||||
val deviceDefault: List<FontFamily>,
|
||||
val system: List<FontFamily>,
|
||||
)
|
||||
|
||||
class FontManager(
|
||||
private val context: Context
|
||||
) {
|
||||
fun getInstalledFonts(): FontList {
|
||||
val deviceHeadlineResId = context.resources
|
||||
.getIdentifier("config_headlineFontFamily", "string", "android")
|
||||
val deviceBodyResId = context.resources
|
||||
.getIdentifier("config_bodyFontFamily", "string", "android")
|
||||
|
||||
val deviceHeadlineExists = deviceHeadlineResId != 0 &&
|
||||
context.resources.getString(deviceHeadlineResId).isNotBlank()
|
||||
|
||||
val deviceBodyExists = deviceBodyResId != 0 &&
|
||||
context.resources.getString(deviceBodyResId).isNotBlank()
|
||||
return FontList(
|
||||
builtIn = listOf(FontFamily.LauncherDefault),
|
||||
generic = listOf(
|
||||
FontFamily.SansSerif,
|
||||
FontFamily.Serif,
|
||||
FontFamily.Monospace,
|
||||
),
|
||||
deviceDefault = listOfNotNull(
|
||||
if (deviceHeadlineExists) FontFamily.DeviceHeadline else null,
|
||||
if (deviceBodyExists) FontFamily.DeviceBody else null,
|
||||
),
|
||||
system = emptyList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,228 @@
|
||||
package de.mm20.launcher2.themes.typography
|
||||
|
||||
import de.mm20.launcher2.database.entities.TypographyEntity
|
||||
import de.mm20.launcher2.serialization.UUIDSerializer
|
||||
import de.mm20.launcher2.themes.FontWeightSerializer
|
||||
import de.mm20.launcher2.themes.ThemeJson
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.UUID
|
||||
|
||||
@Serializable
|
||||
data class Typography(
|
||||
@Serializable(with = UUIDSerializer::class) val id: UUID = UUID.randomUUID(),
|
||||
val builtIn: Boolean = false,
|
||||
val name: String,
|
||||
|
||||
/**
|
||||
* Map of font families used in this typography.
|
||||
* `null` refers to the default font (sans-serif).
|
||||
*/
|
||||
val fonts: Map<String, FontFamily?> = mapOf("brand" to null, "plain" to null),
|
||||
|
||||
val styles: TextStyles<@Contextual FontWeight.Absolute?> = TextStyles(),
|
||||
val emphasizedStyles: TextStyles<FontWeight?> = TextStyles(),
|
||||
) {
|
||||
internal constructor(entity: TypographyEntity) : this(
|
||||
id = entity.id,
|
||||
builtIn = false,
|
||||
name = entity.name,
|
||||
fonts = entity.fonts?.let { ThemeJson.decodeFromString(it) } ?: mapOf(
|
||||
"brand" to null,
|
||||
"plain" to null
|
||||
),
|
||||
styles = TextStyles(
|
||||
bodySmall = TextStyle.fromString(entity.bodySmall),
|
||||
bodyMedium = TextStyle.fromString(entity.bodyMedium),
|
||||
bodyLarge = TextStyle.fromString(entity.bodyLarge),
|
||||
labelSmall = TextStyle.fromString(entity.labelSmall),
|
||||
labelMedium = TextStyle.fromString(entity.labelMedium),
|
||||
labelLarge = TextStyle.fromString(entity.labelLarge),
|
||||
titleSmall = TextStyle.fromString(entity.titleSmall),
|
||||
titleMedium = TextStyle.fromString(entity.titleMedium),
|
||||
titleLarge = TextStyle.fromString(entity.titleLarge),
|
||||
headlineSmall = TextStyle.fromString(entity.headlineSmall),
|
||||
headlineMedium = TextStyle.fromString(entity.headlineMedium),
|
||||
headlineLarge = TextStyle.fromString(entity.headlineLarge),
|
||||
displaySmall = TextStyle.fromString(entity.displaySmall),
|
||||
displayMedium = TextStyle.fromString(entity.displayMedium),
|
||||
displayLarge = TextStyle.fromString(entity.displayLarge)
|
||||
),
|
||||
emphasizedStyles = TextStyles(
|
||||
bodySmall = TextStyle.fromString(entity.emphasizedBodySmall),
|
||||
bodyMedium = TextStyle.fromString(entity.emphasizedBodyMedium),
|
||||
bodyLarge = TextStyle.fromString(entity.emphasizedBodyLarge),
|
||||
labelSmall = TextStyle.fromString(entity.emphasizedLabelSmall),
|
||||
labelMedium = TextStyle.fromString(entity.emphasizedLabelMedium),
|
||||
labelLarge = TextStyle.fromString(entity.emphasizedLabelLarge),
|
||||
titleSmall = TextStyle.fromString(entity.emphasizedTitleSmall),
|
||||
titleMedium = TextStyle.fromString(entity.emphasizedTitleMedium),
|
||||
titleLarge = TextStyle.fromString(entity.emphasizedTitleLarge),
|
||||
headlineSmall = TextStyle.fromString(entity.emphasizedHeadlineSmall),
|
||||
headlineMedium = TextStyle.fromString(entity.emphasizedHeadlineMedium),
|
||||
headlineLarge = TextStyle.fromString(entity.emphasizedHeadlineLarge),
|
||||
displaySmall = TextStyle.fromString(entity.emphasizedDisplaySmall),
|
||||
displayMedium = TextStyle.fromString(entity.emphasizedDisplayMedium),
|
||||
displayLarge = TextStyle.fromString(entity.emphasizedDisplayLarge)
|
||||
)
|
||||
)
|
||||
|
||||
internal fun toEntity(): TypographyEntity {
|
||||
return TypographyEntity(
|
||||
id = id,
|
||||
name = name,
|
||||
fonts = ThemeJson.encodeToString(fonts),
|
||||
displayLarge = styles.displayLarge?.toString(),
|
||||
displayMedium = styles.displayMedium?.toString(),
|
||||
displaySmall = styles.displaySmall?.toString(),
|
||||
headlineLarge = styles.headlineLarge?.toString(),
|
||||
headlineMedium = styles.headlineMedium?.toString(),
|
||||
headlineSmall = styles.headlineSmall?.toString(),
|
||||
titleLarge = styles.titleLarge?.toString(),
|
||||
titleMedium = styles.titleMedium?.toString(),
|
||||
titleSmall = styles.titleSmall?.toString(),
|
||||
bodyLarge = styles.bodyLarge?.toString(),
|
||||
bodyMedium = styles.bodyMedium?.toString(),
|
||||
bodySmall = styles.bodySmall?.toString(),
|
||||
labelLarge = styles.labelLarge?.toString(),
|
||||
labelMedium = styles.labelMedium?.toString(),
|
||||
labelSmall = styles.labelSmall?.toString(),
|
||||
emphasizedDisplayLarge = emphasizedStyles.displayLarge?.toString(),
|
||||
emphasizedDisplayMedium = emphasizedStyles.displayMedium?.toString(),
|
||||
emphasizedDisplaySmall = emphasizedStyles.displaySmall?.toString(),
|
||||
emphasizedHeadlineLarge = emphasizedStyles.headlineLarge?.toString(),
|
||||
emphasizedHeadlineMedium = emphasizedStyles.headlineMedium?.toString(),
|
||||
emphasizedHeadlineSmall = emphasizedStyles.headlineSmall?.toString(),
|
||||
emphasizedTitleLarge = emphasizedStyles.titleLarge?.toString(),
|
||||
emphasizedTitleMedium = emphasizedStyles.titleMedium?.toString(),
|
||||
emphasizedTitleSmall = emphasizedStyles.titleSmall?.toString(),
|
||||
emphasizedBodyLarge = emphasizedStyles.bodyLarge?.toString(),
|
||||
emphasizedBodyMedium = emphasizedStyles.bodyMedium?.toString(),
|
||||
emphasizedBodySmall = emphasizedStyles.bodySmall?.toString(),
|
||||
emphasizedLabelLarge = emphasizedStyles.labelLarge?.toString(),
|
||||
emphasizedLabelMedium = emphasizedStyles.labelMedium?.toString(),
|
||||
emphasizedLabelSmall = emphasizedStyles.labelSmall?.toString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class TextStyles<out W : FontWeight?>(
|
||||
val bodySmall: TextStyle<W>? = null,
|
||||
val bodyMedium: TextStyle<W>? = null,
|
||||
val bodyLarge: TextStyle<W>? = null,
|
||||
val labelSmall: TextStyle<W>? = null,
|
||||
val labelMedium: TextStyle<W>? = null,
|
||||
val labelLarge: TextStyle<W>? = null,
|
||||
val titleSmall: TextStyle<W>? = null,
|
||||
val titleMedium: TextStyle<W>? = null,
|
||||
val titleLarge: TextStyle<W>? = null,
|
||||
val headlineSmall: TextStyle<W>? = null,
|
||||
val headlineMedium: TextStyle<W>? = null,
|
||||
val headlineLarge: TextStyle<W>? = null,
|
||||
val displaySmall: TextStyle<W>? = null,
|
||||
val displayMedium: TextStyle<W>? = null,
|
||||
val displayLarge: TextStyle<W>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class TextStyle<out W : FontWeight?>(
|
||||
/**
|
||||
* Index of the font family to use for this text style.
|
||||
*/
|
||||
val fontFamily: String? = null,
|
||||
/**
|
||||
* The font size in sp.
|
||||
*/
|
||||
val fontSize: Int? = null,
|
||||
/**
|
||||
* The font weight, e.g. 400 for normal, 700 for bold.
|
||||
*/
|
||||
@Contextual val fontWeight: W? = null,
|
||||
/**
|
||||
* The line height in em.
|
||||
*/
|
||||
val lineHeight: Float? = null,
|
||||
/**
|
||||
* The letter spacing in em.
|
||||
*/
|
||||
val letterSpacing: Float? = null,
|
||||
) {
|
||||
/**
|
||||
* Secondary constructor that uses absolute units for line height and letter spacing.
|
||||
*/
|
||||
constructor(
|
||||
fontFamily: String?,
|
||||
fontSize: Int,
|
||||
fontWeight: W?,
|
||||
lineHeight: Int?,
|
||||
letterSpacing: Float?,
|
||||
) : this(
|
||||
fontFamily = fontFamily,
|
||||
fontSize = fontSize,
|
||||
fontWeight = fontWeight,
|
||||
lineHeight = lineHeight?.toFloat()?.div(fontSize),
|
||||
letterSpacing = letterSpacing?.div(fontSize)
|
||||
)
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
return ThemeJson.encodeToString<TextStyle<FontWeight?>>(this)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun <T : FontWeight> fromString(string: String?): TextStyle<T>? {
|
||||
if (string.isNullOrEmpty()) return null
|
||||
return ThemeJson.decodeFromString<TextStyle<FontWeight>>(string) as TextStyle<T>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed interface FontFamily {
|
||||
@Serializable
|
||||
@SerialName("launcher_default")
|
||||
data object LauncherDefault : FontFamily
|
||||
|
||||
@Serializable
|
||||
@SerialName("device_headline")
|
||||
data object DeviceHeadline : FontFamily
|
||||
|
||||
@Serializable
|
||||
@SerialName("device_body")
|
||||
data object DeviceBody : FontFamily
|
||||
|
||||
@Serializable
|
||||
@SerialName("sans-serif")
|
||||
data object SansSerif : FontFamily
|
||||
|
||||
@Serializable
|
||||
@SerialName("serif")
|
||||
data object Serif : FontFamily
|
||||
|
||||
@Serializable
|
||||
@SerialName("monospace")
|
||||
data object Monospace : FontFamily
|
||||
|
||||
@Serializable
|
||||
@SerialName("system")
|
||||
data class System(val name: String) : FontFamily
|
||||
}
|
||||
|
||||
@Serializable(with = FontWeightSerializer::class)
|
||||
sealed interface FontWeight {
|
||||
/**
|
||||
* Absolute font weight, e.g. 400 for normal, 700 for bold.
|
||||
*/
|
||||
@JvmInline
|
||||
value class Absolute(val weight: Int) : FontWeight
|
||||
|
||||
/**
|
||||
* Relative font weight, in relation to another font style.
|
||||
* This is used for emphasized styles, i.e. if the base style has a weight of 400,
|
||||
* the emphasized style with a relative weight of 100 will have a weight of 500,
|
||||
*/
|
||||
@JvmInline
|
||||
value class Relative(val relativeWeight: Int) : FontWeight
|
||||
}
|
||||
@ -0,0 +1,125 @@
|
||||
package de.mm20.launcher2.themes.typography
|
||||
|
||||
import android.content.Context
|
||||
import de.mm20.launcher2.database.AppDatabase
|
||||
import de.mm20.launcher2.themes.DefaultThemeId
|
||||
import de.mm20.launcher2.themes.MonospaceId
|
||||
import de.mm20.launcher2.themes.R
|
||||
import de.mm20.launcher2.themes.SerifId
|
||||
import de.mm20.launcher2.themes.SystemFontId
|
||||
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 TypographyRepository(
|
||||
private val context: Context,
|
||||
private val database: AppDatabase,
|
||||
) {
|
||||
private val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
|
||||
fun getAll(): Flow<List<Typography>> {
|
||||
return database.themeDao().getAllTypographies().map {
|
||||
getBuiltIn() + it.map { Typography(it) }
|
||||
}
|
||||
}
|
||||
|
||||
fun get(id: UUID): Flow<Typography?> {
|
||||
if (id == DefaultThemeId) return flowOf(default)
|
||||
if (id == SystemFontId) return flowOf(systemFont)
|
||||
if (id == SerifId) return flowOf(serif)
|
||||
if (id == MonospaceId) return flowOf(monospace)
|
||||
return database.themeDao().getTypography(id).map { it?.let { Typography(it) } }
|
||||
}
|
||||
|
||||
fun create(typography: Typography) {
|
||||
scope.launch {
|
||||
database.themeDao().insertTypography(typography.toEntity())
|
||||
}
|
||||
}
|
||||
|
||||
fun update(typography: Typography) {
|
||||
scope.launch {
|
||||
database.themeDao().updateTypography(typography.toEntity())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun delete(typography: Typography) {
|
||||
scope.launch {
|
||||
database.themeDao().deleteTypography(typography.id)
|
||||
}
|
||||
}
|
||||
|
||||
fun getOrDefault(id: UUID?): Flow<Typography> {
|
||||
if (id == null) return flowOf(default)
|
||||
return get(id).map { it ?: default }
|
||||
}
|
||||
|
||||
private fun getBuiltIn(): List<Typography> {
|
||||
return listOf(
|
||||
default,
|
||||
systemFont,
|
||||
serif,
|
||||
monospace,
|
||||
)
|
||||
}
|
||||
|
||||
private val default: Typography
|
||||
get() = Typography(
|
||||
id = DefaultThemeId,
|
||||
builtIn = true,
|
||||
name = "Outfit",
|
||||
fonts = mapOf(
|
||||
"brand" to FontFamily.LauncherDefault,
|
||||
"plain" to null,
|
||||
),
|
||||
styles = DefaultTextStyles,
|
||||
emphasizedStyles = DefaultEmphasizedTextStyles,
|
||||
)
|
||||
|
||||
|
||||
private val systemFont: Typography
|
||||
get() = Typography(
|
||||
id = SystemFontId,
|
||||
builtIn = true,
|
||||
name = context.getString(R.string.preference_font_system),
|
||||
fonts = mapOf(
|
||||
"brand" to FontFamily.DeviceHeadline,
|
||||
"plain" to FontFamily.DeviceBody,
|
||||
),
|
||||
styles = DefaultTextStyles,
|
||||
emphasizedStyles = DefaultEmphasizedTextStyles,
|
||||
)
|
||||
|
||||
private val serif: Typography
|
||||
get() = Typography(
|
||||
id = SerifId,
|
||||
builtIn = true,
|
||||
name = "Serif",
|
||||
fonts = mapOf(
|
||||
"brand" to FontFamily.Serif,
|
||||
"plain" to FontFamily.Serif,
|
||||
),
|
||||
styles = DefaultTextStyles,
|
||||
emphasizedStyles = DefaultEmphasizedTextStyles,
|
||||
)
|
||||
|
||||
private val monospace: Typography
|
||||
get() = Typography(
|
||||
id = MonospaceId,
|
||||
builtIn = true,
|
||||
name = "Monospace",
|
||||
fonts = mapOf(
|
||||
"brand" to FontFamily.Monospace,
|
||||
"plain" to FontFamily.Monospace,
|
||||
),
|
||||
styles = DefaultTextStyles,
|
||||
emphasizedStyles = DefaultEmphasizedTextStyles,
|
||||
)
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user