(feat) custom typography schemes
This commit is contained in:
@@ -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
|
||||
}
|
||||
+125
@@ -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,
|
||||
)
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user