mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-08-28 09:44:15 +00:00
changes
This commit is contained in:
+5
-3
@@ -682,7 +682,8 @@ data class User(
|
||||
override val showNtfs: Boolean,
|
||||
val sendRcptsContacts: Boolean,
|
||||
val sendRcptsSmallGroups: Boolean,
|
||||
val viewPwdHash: UserPwdHash?
|
||||
val viewPwdHash: UserPwdHash?,
|
||||
val uiThemes: ThemeModeOverrides? = null,
|
||||
): NamedChat, UserLike {
|
||||
override val displayName: String get() = profile.displayName
|
||||
override val fullName: String get() = profile.fullName
|
||||
@@ -709,6 +710,7 @@ data class User(
|
||||
sendRcptsContacts = true,
|
||||
sendRcptsSmallGroups = false,
|
||||
viewPwdHash = null,
|
||||
uiThemes = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1042,7 +1044,7 @@ data class Contact(
|
||||
val chatTs: Instant?,
|
||||
val contactGroupMemberId: Long? = null,
|
||||
val contactGrpInvSent: Boolean,
|
||||
val uiThemes: Map<String, ThemeOverrides>? = null,
|
||||
val uiThemes: ThemeModeOverrides? = null,
|
||||
): SomeChat, NamedChat {
|
||||
override val chatType get() = ChatType.Direct
|
||||
override val id get() = "@$contactId"
|
||||
@@ -1248,7 +1250,7 @@ data class GroupInfo (
|
||||
override val createdAt: Instant,
|
||||
override val updatedAt: Instant,
|
||||
val chatTs: Instant?,
|
||||
val uiThemes: Map<String, ThemeOverrides>? = null,
|
||||
val uiThemes: ThemeModeOverrides? = null,
|
||||
): SomeChat, NamedChat {
|
||||
override val chatType get() = ChatType.Group
|
||||
override val id get() = "#$groupId"
|
||||
|
||||
+14
-4
@@ -1156,7 +1156,7 @@ object ChatController {
|
||||
return false
|
||||
}
|
||||
|
||||
suspend fun apiSetChatUIThemes(rh: Long?, chatId: ChatId, themes: Map<String, ThemeOverrides>?): Boolean {
|
||||
suspend fun apiSetChatUIThemes(rh: Long?, chatId: ChatId, themes: ThemeModeOverrides?): Boolean {
|
||||
val r = sendCmd(rh, CC.ApiSetChatUIThemes(chatId, themes))
|
||||
if (r is CR.CmdOk) return true
|
||||
Log.e(TAG, "apiSetChatUIThemes bad response: ${r.responseType} ${r.details}")
|
||||
@@ -2464,7 +2464,7 @@ sealed class CC {
|
||||
class ApiSetContactAlias(val contactId: Long, val localAlias: String): CC()
|
||||
class ApiSetConnectionAlias(val connId: Long, val localAlias: String): CC()
|
||||
class ApiSetUserUIThemes(val userId: Long, val themes: Map<String, ThemeOverrides>?): CC()
|
||||
class ApiSetChatUIThemes(val chatId: String, val themes: Map<String, ThemeOverrides>?): CC()
|
||||
class ApiSetChatUIThemes(val chatId: String, val themes: ThemeModeOverrides?): CC()
|
||||
class ApiCreateMyAddress(val userId: Long): CC()
|
||||
class ApiDeleteMyAddress(val userId: Long): CC()
|
||||
class ApiShowMyAddress(val userId: Long): CC()
|
||||
@@ -2609,8 +2609,8 @@ sealed class CC {
|
||||
is ApiSetContactPrefs -> "/_set prefs @$contactId ${json.encodeToString(prefs)}"
|
||||
is ApiSetContactAlias -> "/_set alias @$contactId ${localAlias.trim()}"
|
||||
is ApiSetConnectionAlias -> "/_set alias :$connId ${localAlias.trim()}"
|
||||
is ApiSetUserUIThemes -> "/_set theme user $userId ${json.encodeToString(themes)}"
|
||||
is ApiSetChatUIThemes -> "/_set theme $chatId ${json.encodeToString(themes)}"
|
||||
is ApiSetUserUIThemes -> "/_set theme user $userId ${if (themes != null) json.encodeToString(themes) else ""}"
|
||||
is ApiSetChatUIThemes -> "/_set theme $chatId ${if (themes != null) json.encodeToString(themes) else ""}"
|
||||
is ApiCreateMyAddress -> "/_address $userId"
|
||||
is ApiDeleteMyAddress -> "/_delete_address $userId"
|
||||
is ApiShowMyAddress -> "/_show_address $userId"
|
||||
@@ -5433,7 +5433,9 @@ data class AppSettings(
|
||||
var androidCallOnLockScreen: AppSettingsLockScreenCalls? = null,
|
||||
var iosCallKitEnabled: Boolean? = null,
|
||||
var iosCallKitCallsInRecents: Boolean? = null,
|
||||
var uiProfileImageCornerRadius: Float? = null,
|
||||
var uiColorScheme: String? = null,
|
||||
var uiDarkColorScheme: String? = null,
|
||||
var uiThemes: Map<String, ThemeOverrides>? = null,
|
||||
) {
|
||||
fun prepareForExport(): AppSettings {
|
||||
@@ -5458,7 +5460,9 @@ data class AppSettings(
|
||||
if (androidCallOnLockScreen != def.androidCallOnLockScreen) { empty.androidCallOnLockScreen = androidCallOnLockScreen }
|
||||
if (iosCallKitEnabled != def.iosCallKitEnabled) { empty.iosCallKitEnabled = iosCallKitEnabled }
|
||||
if (iosCallKitCallsInRecents != def.iosCallKitCallsInRecents) { empty.iosCallKitCallsInRecents = iosCallKitCallsInRecents }
|
||||
if (uiProfileImageCornerRadius != def.uiProfileImageCornerRadius) { empty.uiProfileImageCornerRadius = uiProfileImageCornerRadius }
|
||||
if (uiColorScheme != def.uiColorScheme) { empty.uiColorScheme = uiColorScheme }
|
||||
if (uiDarkColorScheme != def.uiDarkColorScheme) { empty.uiDarkColorScheme = uiDarkColorScheme }
|
||||
if (uiThemes != def.uiThemes) { empty.uiThemes = uiThemes }
|
||||
return empty
|
||||
}
|
||||
@@ -5491,7 +5495,9 @@ data class AppSettings(
|
||||
androidCallOnLockScreen?.let { def.callOnLockScreen.set(it.toCallOnLockScreen()) }
|
||||
iosCallKitEnabled?.let { def.iosCallKitEnabled.set(it) }
|
||||
iosCallKitCallsInRecents?.let { def.iosCallKitCallsInRecents.set(it) }
|
||||
uiProfileImageCornerRadius?.let { def.profileImageCornerRadius.set(it) }
|
||||
uiColorScheme?.let { def.currentTheme.set(it) }
|
||||
uiDarkColorScheme?.let { def.systemDarkTheme.set(it) }
|
||||
uiThemes?.let { def.themeOverrides.set(it) }
|
||||
}
|
||||
|
||||
@@ -5517,7 +5523,9 @@ data class AppSettings(
|
||||
androidCallOnLockScreen = AppSettingsLockScreenCalls.SHOW,
|
||||
iosCallKitEnabled = true,
|
||||
iosCallKitCallsInRecents = false,
|
||||
uiProfileImageCornerRadius = 22.5f,
|
||||
uiColorScheme = DefaultTheme.SYSTEM.themeName,
|
||||
uiDarkColorScheme = DefaultTheme.SIMPLEX.themeName,
|
||||
uiThemes = null,
|
||||
)
|
||||
|
||||
@@ -5544,7 +5552,9 @@ data class AppSettings(
|
||||
androidCallOnLockScreen = AppSettingsLockScreenCalls.from(def.callOnLockScreen.get()),
|
||||
iosCallKitEnabled = def.iosCallKitEnabled.get(),
|
||||
iosCallKitCallsInRecents = def.iosCallKitCallsInRecents.get(),
|
||||
uiProfileImageCornerRadius = def.profileImageCornerRadius.get(),
|
||||
uiColorScheme = def.currentTheme.get() ?: DefaultTheme.SYSTEM.themeName,
|
||||
uiDarkColorScheme = def.systemDarkTheme.get() ?: DefaultTheme.SIMPLEX.themeName,
|
||||
uiThemes = def.themeOverrides.get(),
|
||||
)
|
||||
}
|
||||
|
||||
+2
-2
@@ -42,12 +42,12 @@ fun runMigrations() {
|
||||
ChatController.appPrefs.currentTheme.set(DefaultTheme.SIMPLEX.name)
|
||||
}
|
||||
lastMigration.set(117)
|
||||
} else if (lastMigration.get() < 201) {
|
||||
} else if (lastMigration.get() < 203) {
|
||||
// Making theme keys lowercase as API expects
|
||||
ChatController.appPrefs.themeOverrides.set(ChatController.appPrefs.themeOverrides.get().map { it.key.lowercase() to it.value }.toMap())
|
||||
ChatController.appPrefs.currentTheme.set(ChatController.appPrefs.currentTheme.get()?.lowercase())
|
||||
ChatController.appPrefs.systemDarkTheme.set(ChatController.appPrefs.systemDarkTheme.get()?.lowercase())
|
||||
lastMigration.set(201)
|
||||
lastMigration.set(203)
|
||||
} else {
|
||||
lastMigration.set(BuildConfigCommon.ANDROID_VERSION_CODE)
|
||||
break
|
||||
|
||||
+115
-70
@@ -23,6 +23,8 @@ enum class DefaultTheme {
|
||||
val themeName: String
|
||||
get() = name.lowercase()
|
||||
|
||||
val mode: DefaultThemeMode get() = if (this == LIGHT) DefaultThemeMode.LIGHT else DefaultThemeMode.DARK
|
||||
|
||||
// Call it only with base theme, not SYSTEM
|
||||
fun hasChangedAnyColor(colors: Colors, appColors: AppColors, appWallpaper: AppWallpaper): Boolean {
|
||||
val palette = when (this) {
|
||||
@@ -49,6 +51,12 @@ enum class DefaultTheme {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class DefaultThemeMode {
|
||||
@SerialName("light") LIGHT,
|
||||
@SerialName("dark") DARK
|
||||
}
|
||||
|
||||
@Stable
|
||||
class AppColors(
|
||||
title: Color,
|
||||
@@ -166,61 +174,12 @@ data class ThemeColors(
|
||||
val title: String? = null,
|
||||
val sentMessage: String? = null,
|
||||
val receivedMessage: String? = null,
|
||||
) {
|
||||
fun toColors(base: DefaultTheme, backgroundTheme: ThemeColors?): Colors {
|
||||
val baseColors = when (base) {
|
||||
DefaultTheme.LIGHT -> LightColorPalette
|
||||
DefaultTheme.DARK -> DarkColorPalette
|
||||
DefaultTheme.SIMPLEX -> SimplexColorPalette
|
||||
// shouldn't be here
|
||||
DefaultTheme.SYSTEM -> LightColorPalette
|
||||
}
|
||||
return baseColors.copy(
|
||||
primary = primary?.colorFromReadableHex() ?: backgroundTheme?.primary?.colorFromReadableHex() ?: baseColors.primary,
|
||||
primaryVariant = primaryVariant?.colorFromReadableHex() ?: backgroundTheme?.primaryVariant?.colorFromReadableHex() ?: baseColors.primaryVariant,
|
||||
secondary = secondary?.colorFromReadableHex() ?: backgroundTheme?.secondary?.colorFromReadableHex() ?: baseColors.secondary,
|
||||
secondaryVariant = secondaryVariant?.colorFromReadableHex() ?: backgroundTheme?.secondaryVariant?.colorFromReadableHex() ?: baseColors.secondaryVariant,
|
||||
background = background?.colorFromReadableHex() ?: backgroundTheme?.background?.colorFromReadableHex() ?: baseColors.background,
|
||||
surface = surface?.colorFromReadableHex() ?: backgroundTheme?.surface?.colorFromReadableHex() ?: baseColors.surface,
|
||||
)
|
||||
}
|
||||
|
||||
fun toAppColors(base: DefaultTheme, backgroundTheme: ThemeColors?): AppColors {
|
||||
val baseColors = when (base) {
|
||||
DefaultTheme.LIGHT -> LightColorPaletteApp
|
||||
DefaultTheme.DARK -> DarkColorPaletteApp
|
||||
DefaultTheme.SIMPLEX -> SimplexColorPaletteApp
|
||||
// shouldn't be here
|
||||
DefaultTheme.SYSTEM -> LightColorPaletteApp
|
||||
}
|
||||
return baseColors.copy(
|
||||
title = title?.colorFromReadableHex() ?: backgroundTheme?.title?.colorFromReadableHex() ?: baseColors.title,
|
||||
sentMessage = sentMessage?.colorFromReadableHex() ?: backgroundTheme?.sentMessage?.colorFromReadableHex() ?: baseColors.sentMessage,
|
||||
receivedMessage = receivedMessage?.colorFromReadableHex() ?: backgroundTheme?.receivedMessage?.colorFromReadableHex() ?: baseColors.receivedMessage,
|
||||
)
|
||||
}
|
||||
|
||||
fun withFilledColors(base: DefaultTheme, backgroundTheme: ThemeColors?): ThemeColors {
|
||||
val c = toColors(base, backgroundTheme)
|
||||
val ac = toAppColors(base, backgroundTheme)
|
||||
return ThemeColors(
|
||||
primary = c.primary.toReadableHex(),
|
||||
primaryVariant = c.primaryVariant.toReadableHex(),
|
||||
secondary = c.secondary.toReadableHex(),
|
||||
secondaryVariant = c.secondaryVariant.toReadableHex(),
|
||||
background = c.background.toReadableHex(),
|
||||
surface = c.surface.toReadableHex(),
|
||||
title = ac.title.toReadableHex(),
|
||||
sentMessage = ac.sentMessage.toReadableHex(),
|
||||
receivedMessage = ac.receivedMessage.toReadableHex(),
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ThemeWallpaper (
|
||||
val preset: String? = null,
|
||||
val scale: Float = 1f,
|
||||
val scale: Float? = null,
|
||||
val scaleType: BackgroundImageScaleType? = null,
|
||||
val background: String? = null,
|
||||
val tint: String? = null,
|
||||
@@ -232,11 +191,11 @@ data class ThemeWallpaper (
|
||||
background = background?.colorFromReadableHex(),
|
||||
tint = tint?.colorFromReadableHex(),
|
||||
type = if (preset != null) {
|
||||
BackgroundImageType.Repeated(filename = preset, scale)
|
||||
BackgroundImageType.Repeated(filename = preset, scale ?: 1f)
|
||||
} else if (imageFile != null) {
|
||||
BackgroundImageType.Static(
|
||||
filename = imageFile,
|
||||
scale,
|
||||
scale ?: 1f,
|
||||
scaleType ?: BackgroundImageScaleType.FILL
|
||||
)
|
||||
} else
|
||||
@@ -244,7 +203,8 @@ data class ThemeWallpaper (
|
||||
)
|
||||
}
|
||||
|
||||
fun withFilledWallpaperBase64(): ThemeWallpaper {
|
||||
// LALAL
|
||||
fun withFilledWallpaperBase64(perUserTheme: ThemeModeOverride?): ThemeWallpaper {
|
||||
val aw = toAppWallpaper()
|
||||
val type = aw.type
|
||||
return ThemeWallpaper(
|
||||
@@ -306,6 +266,98 @@ data class ThemeOverrides (
|
||||
val wallpaper: ThemeWallpaper = ThemeWallpaper(),
|
||||
) {
|
||||
fun withUpdatedColor(name: ThemeColor, color: String?): ThemeOverrides {
|
||||
return copy(
|
||||
colors = when (name) {
|
||||
ThemeColor.PRIMARY -> colors.copy(primary = color)
|
||||
ThemeColor.PRIMARY_VARIANT -> colors.copy(primaryVariant = color)
|
||||
ThemeColor.SECONDARY -> colors.copy(secondary = color)
|
||||
ThemeColor.SECONDARY_VARIANT -> colors.copy(secondaryVariant = color)
|
||||
ThemeColor.BACKGROUND -> colors.copy(background = color)
|
||||
ThemeColor.SURFACE -> colors.copy(surface = color)
|
||||
ThemeColor.TITLE -> colors.copy(title = color)
|
||||
ThemeColor.SENT_MESSAGE -> colors.copy(sentMessage = color)
|
||||
ThemeColor.RECEIVED_MESSAGE -> colors.copy(receivedMessage = color)
|
||||
ThemeColor.WALLPAPER_BACKGROUND -> colors.copy()
|
||||
ThemeColor.WALLPAPER_TINT -> colors.copy()
|
||||
}, wallpaper = when (name) {
|
||||
ThemeColor.WALLPAPER_BACKGROUND -> wallpaper.copy(background = color)
|
||||
ThemeColor.WALLPAPER_TINT -> wallpaper.copy(tint = color)
|
||||
else -> wallpaper.copy()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun toColors(base: DefaultTheme, perChatTheme: ThemeColors?, presetWallpaperTheme: ThemeColors?, perUserTheme: ThemeColors?): Colors {
|
||||
val baseColors = when (base) {
|
||||
DefaultTheme.LIGHT -> LightColorPalette
|
||||
DefaultTheme.DARK -> DarkColorPalette
|
||||
DefaultTheme.SIMPLEX -> SimplexColorPalette
|
||||
// shouldn't be here
|
||||
DefaultTheme.SYSTEM -> LightColorPalette
|
||||
}
|
||||
return baseColors.copy(
|
||||
primary = perChatTheme?.primary?.colorFromReadableHex() ?: presetWallpaperTheme?.primary?.colorFromReadableHex() ?: perUserTheme?.primary?.colorFromReadableHex() ?: colors.primary?.colorFromReadableHex() ?: baseColors.primary,
|
||||
primaryVariant = perChatTheme?.primaryVariant?.colorFromReadableHex() ?: presetWallpaperTheme?.primaryVariant?.colorFromReadableHex() ?: perUserTheme?.primaryVariant?.colorFromReadableHex() ?: colors.primaryVariant?.colorFromReadableHex() ?: baseColors.primaryVariant,
|
||||
secondary = perChatTheme?.secondary?.colorFromReadableHex() ?: presetWallpaperTheme?.secondary?.colorFromReadableHex() ?: perUserTheme?.secondary?.colorFromReadableHex() ?: colors.secondary?.colorFromReadableHex() ?: baseColors.secondary,
|
||||
secondaryVariant = perChatTheme?.secondaryVariant?.colorFromReadableHex() ?: presetWallpaperTheme?.secondaryVariant?.colorFromReadableHex() ?: perUserTheme?.secondaryVariant?.colorFromReadableHex() ?: colors.secondaryVariant?.colorFromReadableHex() ?: baseColors.secondaryVariant,
|
||||
background = perChatTheme?.background?.colorFromReadableHex() ?: presetWallpaperTheme?.background?.colorFromReadableHex() ?: perUserTheme?.background?.colorFromReadableHex() ?: colors.background?.colorFromReadableHex() ?: baseColors.background,
|
||||
surface = perChatTheme?.surface?.colorFromReadableHex() ?: presetWallpaperTheme?.surface?.colorFromReadableHex() ?: perUserTheme?.surface?.colorFromReadableHex() ?: colors.surface?.colorFromReadableHex() ?: baseColors.surface,
|
||||
)
|
||||
}
|
||||
|
||||
fun toAppColors(base: DefaultTheme, perChatTheme: ThemeColors?, presetWallpaperTheme: ThemeColors?, perUserTheme: ThemeColors?): AppColors {
|
||||
val baseColors = when (base) {
|
||||
DefaultTheme.LIGHT -> LightColorPaletteApp
|
||||
DefaultTheme.DARK -> DarkColorPaletteApp
|
||||
DefaultTheme.SIMPLEX -> SimplexColorPaletteApp
|
||||
// shouldn't be here
|
||||
DefaultTheme.SYSTEM -> LightColorPaletteApp
|
||||
}
|
||||
return baseColors.copy(
|
||||
title = perChatTheme?.title?.colorFromReadableHex() ?: presetWallpaperTheme?.title?.colorFromReadableHex() ?: perUserTheme?.title?.colorFromReadableHex() ?: colors.title?.colorFromReadableHex() ?: baseColors.title,
|
||||
sentMessage = perChatTheme?.sentMessage?.colorFromReadableHex() ?: presetWallpaperTheme?.sentMessage?.colorFromReadableHex() ?: perUserTheme?.sentMessage?.colorFromReadableHex() ?: colors.sentMessage?.colorFromReadableHex() ?: baseColors.sentMessage,
|
||||
receivedMessage = perChatTheme?.receivedMessage?.colorFromReadableHex() ?: presetWallpaperTheme?.receivedMessage?.colorFromReadableHex() ?: perUserTheme?.receivedMessage?.colorFromReadableHex() ?: colors.receivedMessage?.colorFromReadableHex() ?: baseColors.receivedMessage,
|
||||
)
|
||||
}
|
||||
|
||||
fun withFilledColors(base: DefaultTheme, perChatTheme: ThemeColors?, perUserTheme: ThemeColors?, appSettingsTheme: ThemeColors?): ThemeColors {
|
||||
val c = toColors(base, perChatTheme, perUserTheme, appSettingsTheme)
|
||||
val ac = toAppColors(base, perChatTheme, perUserTheme, appSettingsTheme)
|
||||
return ThemeColors(
|
||||
primary = c.primary.toReadableHex(),
|
||||
primaryVariant = c.primaryVariant.toReadableHex(),
|
||||
secondary = c.secondary.toReadableHex(),
|
||||
secondaryVariant = c.secondaryVariant.toReadableHex(),
|
||||
background = c.background.toReadableHex(),
|
||||
surface = c.surface.toReadableHex(),
|
||||
title = ac.title.toReadableHex(),
|
||||
sentMessage = ac.sentMessage.toReadableHex(),
|
||||
receivedMessage = ac.receivedMessage.toReadableHex(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ThemeModeOverrides (
|
||||
val light: ThemeModeOverride? = null,
|
||||
val dark: ThemeModeOverride? = null
|
||||
) {
|
||||
fun preferredTheme(baseTheme: DefaultTheme = CurrentColors.value.base): ThemeModeOverride? {
|
||||
return when (baseTheme) {
|
||||
DefaultTheme.LIGHT -> light
|
||||
else -> dark
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ThemeModeOverride (
|
||||
val mode: DefaultThemeMode = CurrentColors.value.base.mode,
|
||||
val colors: ThemeColors = ThemeColors(),
|
||||
val wallpaper: ThemeWallpaper? = null,
|
||||
) {
|
||||
fun withUpdatedColor(name: ThemeColor, color: String?): ThemeModeOverride {
|
||||
val wallpaper = wallpaper ?: ThemeWallpaper()
|
||||
return copy(colors = when (name) {
|
||||
ThemeColor.PRIMARY -> colors.copy(primary = color)
|
||||
ThemeColor.PRIMARY_VARIANT -> colors.copy(primaryVariant = color)
|
||||
@@ -325,20 +377,13 @@ data class ThemeOverrides (
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun Map<String, ThemeOverrides>.preferredTheme(baseTheme: DefaultTheme = CurrentColors.value.base): ThemeOverrides? {
|
||||
val idealTheme = get(baseTheme.themeName)
|
||||
if (idealTheme != null) return idealTheme
|
||||
|
||||
val light = get(DefaultTheme.LIGHT.themeName)
|
||||
val dark = get(DefaultTheme.DARK.themeName)
|
||||
val simplex = get(DefaultTheme.SIMPLEX.themeName)
|
||||
return when (baseTheme.themeName) {
|
||||
DefaultTheme.LIGHT.themeName -> dark ?: simplex
|
||||
DefaultTheme.DARK.themeName -> simplex ?: light
|
||||
DefaultTheme.SIMPLEX.themeName -> dark ?: light
|
||||
else -> null
|
||||
companion object {
|
||||
fun withFilledAppDefaults(mode: DefaultThemeMode, base: DefaultTheme): ThemeModeOverride =
|
||||
ThemeModeOverride(
|
||||
mode = mode,
|
||||
colors = ThemeOverrides().withFilledColors(base, null, null, null),
|
||||
wallpaper = ThemeWallpaper()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,7 +472,7 @@ val SimplexColorPaletteApp = AppColors(
|
||||
receivedMessage = Color(0x20B1B0B5),
|
||||
)
|
||||
|
||||
val CurrentColors: MutableStateFlow<ThemeManager.ActiveTheme> = MutableStateFlow(ThemeManager.currentColors(isInNightMode()))
|
||||
val CurrentColors: MutableStateFlow<ThemeManager.ActiveTheme> = MutableStateFlow(ThemeManager.currentColors(isInNightMode(), null, chatModel.currentUser.value?.uiThemes, appPreferences.themeOverrides.get()))
|
||||
|
||||
@Composable
|
||||
fun isInDarkTheme(): Boolean = !CurrentColors.collectAsState().value.colors.isLight
|
||||
@@ -466,7 +511,7 @@ fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) {
|
||||
LaunchedEffect(darkTheme) {
|
||||
// For preview
|
||||
if (darkTheme != null)
|
||||
CurrentColors.value = ThemeManager.currentColors(darkTheme)
|
||||
CurrentColors.value = ThemeManager.currentColors(darkTheme, null, chatModel.currentUser.value?.uiThemes, appPreferences.themeOverrides.get())
|
||||
}
|
||||
val systemDark = isSystemInDarkTheme()
|
||||
LaunchedEffect(systemDark) {
|
||||
|
||||
+56
-31
@@ -1,10 +1,12 @@
|
||||
package chat.simplex.common.ui.theme
|
||||
|
||||
import androidx.compose.material.Colors
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.platform.chatModel
|
||||
import chat.simplex.res.MR
|
||||
import chat.simplex.common.platform.platform
|
||||
import chat.simplex.common.views.helpers.*
|
||||
@@ -17,7 +19,7 @@ expect val EmojiFont: FontFamily
|
||||
object ThemeManager {
|
||||
private val appPrefs: AppPreferences = ChatController.appPrefs
|
||||
|
||||
data class ActiveTheme(val name: String, val base: DefaultTheme, val colors: Colors, val appColors: AppColors, val wallpaper: AppWallpaper)
|
||||
data class ActiveTheme(val name: String, val base: DefaultTheme, val colors: Colors, val appColors: AppColors, val wallpaper: AppWallpaper = AppWallpaper())
|
||||
|
||||
private fun systemDarkThemeColors(): Pair<Colors, DefaultTheme> = when (appPrefs.systemDarkTheme.get()) {
|
||||
DefaultTheme.DARK.themeName -> DarkColorPalette to DefaultTheme.DARK
|
||||
@@ -25,39 +27,40 @@ object ThemeManager {
|
||||
else -> SimplexColorPalette to DefaultTheme.SIMPLEX
|
||||
}
|
||||
|
||||
fun currentColors(darkForSystemTheme: Boolean, themeOverrides: Map<String, ThemeOverrides> = appPrefs.themeOverrides.get()): ActiveTheme {
|
||||
fun currentColors(darkForSystemTheme: Boolean, perChatTheme: ThemeModeOverride?, perUserTheme: ThemeModeOverrides?, appSettingsTheme: Map<String, ThemeOverrides>): ActiveTheme {
|
||||
val themeName = appPrefs.currentTheme.get()!!
|
||||
val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.themeName) {
|
||||
themeName
|
||||
} else {
|
||||
if (darkForSystemTheme) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.themeName
|
||||
}
|
||||
val theme = themeOverrides[nonSystemThemeName]
|
||||
val theme = appSettingsTheme[nonSystemThemeName]
|
||||
val modeOverrides = if (darkForSystemTheme) perUserTheme?.dark else perUserTheme?.light
|
||||
val baseTheme = when (nonSystemThemeName) {
|
||||
DefaultTheme.LIGHT.themeName -> Triple(DefaultTheme.LIGHT, LightColorPalette, LightColorPaletteApp)
|
||||
DefaultTheme.DARK.themeName -> Triple(DefaultTheme.DARK, DarkColorPalette, DarkColorPaletteApp)
|
||||
DefaultTheme.SIMPLEX.themeName -> Triple(DefaultTheme.SIMPLEX, SimplexColorPalette, SimplexColorPaletteApp)
|
||||
else -> Triple(DefaultTheme.LIGHT, LightColorPalette, LightColorPaletteApp)
|
||||
DefaultTheme.LIGHT.themeName -> ActiveTheme(DefaultTheme.LIGHT.themeName, DefaultTheme.LIGHT, LightColorPalette, LightColorPaletteApp)
|
||||
DefaultTheme.DARK.themeName -> ActiveTheme(DefaultTheme.DARK.themeName, DefaultTheme.DARK, DarkColorPalette, DarkColorPaletteApp)
|
||||
DefaultTheme.SIMPLEX.themeName -> ActiveTheme(DefaultTheme.SIMPLEX.themeName, DefaultTheme.SIMPLEX, SimplexColorPalette, SimplexColorPaletteApp)
|
||||
else -> ActiveTheme(DefaultTheme.LIGHT.themeName, DefaultTheme.LIGHT, LightColorPalette, LightColorPaletteApp)
|
||||
}
|
||||
if (theme == null) {
|
||||
return ActiveTheme(themeName, baseTheme.first, baseTheme.second, baseTheme.third, AppWallpaper())
|
||||
if (theme == null && perUserTheme == null) {
|
||||
return ActiveTheme(themeName, baseTheme.base, baseTheme.colors, baseTheme.appColors, baseTheme.wallpaper)
|
||||
}
|
||||
val backgroundTheme = if (theme.wallpaper.preset != null) PredefinedBackgroundImage.from(theme.wallpaper.preset)?.colors?.get(theme.base) else null
|
||||
return ActiveTheme(themeName, baseTheme.first, theme.colors.toColors(theme.base, backgroundTheme), theme.colors.toAppColors(theme.base, backgroundTheme), theme.wallpaper.toAppWallpaper())
|
||||
val wallpaperTheme = when {
|
||||
perChatTheme?.wallpaper != null -> if (perChatTheme.wallpaper.preset != null) PredefinedBackgroundImage.from(perChatTheme.wallpaper.preset)?.colors?.get(baseTheme.base) else null
|
||||
modeOverrides?.wallpaper != null -> if (modeOverrides.wallpaper.preset != null) PredefinedBackgroundImage.from(modeOverrides.wallpaper.preset)?.colors?.get(baseTheme.base) else null
|
||||
else -> if (theme?.wallpaper?.preset != null) PredefinedBackgroundImage.from(theme.wallpaper.preset)?.colors?.get(baseTheme.base) else null
|
||||
}
|
||||
val themeOrEmpty = theme ?: ThemeOverrides()
|
||||
return ActiveTheme(
|
||||
themeName,
|
||||
baseTheme.base,
|
||||
themeOrEmpty.toColors(themeOrEmpty.base, perChatTheme?.colors, wallpaperTheme, modeOverrides?.colors),
|
||||
themeOrEmpty.toAppColors(themeOrEmpty.base, perChatTheme?.colors, wallpaperTheme, modeOverrides?.colors),
|
||||
perChatTheme?.wallpaper?.toAppWallpaper() ?: modeOverrides?.wallpaper?.toAppWallpaper() ?: themeOrEmpty.wallpaper.toAppWallpaper()
|
||||
)
|
||||
}
|
||||
|
||||
fun overriddenColors(theme: ThemeOverrides): ActiveTheme {
|
||||
val baseTheme = when (theme.base.themeName) {
|
||||
DefaultTheme.LIGHT.themeName -> Triple(DefaultTheme.LIGHT, LightColorPalette, LightColorPaletteApp)
|
||||
DefaultTheme.DARK.themeName -> Triple(DefaultTheme.DARK, DarkColorPalette, DarkColorPaletteApp)
|
||||
DefaultTheme.SIMPLEX.themeName -> Triple(DefaultTheme.SIMPLEX, SimplexColorPalette, SimplexColorPaletteApp)
|
||||
else -> Triple(DefaultTheme.LIGHT, LightColorPalette, LightColorPaletteApp)
|
||||
}
|
||||
val backgroundTheme = if (theme.wallpaper.preset != null) PredefinedBackgroundImage.from(theme.wallpaper.preset)?.colors?.get(theme.base) else null
|
||||
return ActiveTheme(theme.base.themeName, baseTheme.first, theme.colors.toColors(theme.base, backgroundTheme), theme.colors.toAppColors(theme.base, backgroundTheme), theme.wallpaper.toAppWallpaper())
|
||||
}
|
||||
|
||||
fun currentThemeOverridesForExport(darkForSystemTheme: Boolean): ThemeOverrides {
|
||||
fun currentThemeOverridesForExport(darkForSystemTheme: Boolean, perChatTheme: ThemeModeOverride?, perUserTheme: ThemeModeOverrides?): ThemeOverrides {
|
||||
val themeName = appPrefs.currentTheme.get()!!
|
||||
val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.themeName) {
|
||||
themeName
|
||||
@@ -66,8 +69,16 @@ object ThemeManager {
|
||||
}
|
||||
val overrides = appPrefs.themeOverrides.get().toMutableMap()
|
||||
val nonFilledTheme = overrides.getOrDefault(nonSystemThemeName, ThemeOverrides())
|
||||
val backgroundTheme = if (nonFilledTheme.wallpaper.preset != null) PredefinedBackgroundImage.from(nonFilledTheme.wallpaper.preset)?.colors?.get(nonFilledTheme.base) else null
|
||||
return nonFilledTheme.copy(colors = nonFilledTheme.colors.withFilledColors(CurrentColors.value.base, backgroundTheme), wallpaper = nonFilledTheme.wallpaper.withFilledWallpaperBase64())
|
||||
val modeOverrides = if (darkForSystemTheme) perUserTheme?.dark else perUserTheme?.light
|
||||
val wallpaperTheme = when {
|
||||
perChatTheme?.wallpaper != null -> if (perChatTheme.wallpaper.preset != null) PredefinedBackgroundImage.from(perChatTheme.wallpaper.preset)?.colors?.get(nonFilledTheme.base) else null
|
||||
modeOverrides?.wallpaper != null -> if (modeOverrides.wallpaper.preset != null) PredefinedBackgroundImage.from(modeOverrides.wallpaper.preset)?.colors?.get(nonFilledTheme.base) else null
|
||||
else -> if (nonFilledTheme.wallpaper.preset != null) PredefinedBackgroundImage.from(nonFilledTheme.wallpaper.preset)?.colors?.get(nonFilledTheme.base) else null
|
||||
}
|
||||
return nonFilledTheme.copy(
|
||||
colors = nonFilledTheme.withFilledColors(CurrentColors.value.base, perChatTheme?.colors, modeOverrides?.colors, wallpaperTheme),
|
||||
wallpaper = nonFilledTheme.wallpaper.withFilledWallpaperBase64(modeOverrides)
|
||||
)
|
||||
}
|
||||
|
||||
// colors, default theme enum, localized name of theme
|
||||
@@ -106,13 +117,13 @@ object ThemeManager {
|
||||
|
||||
fun applyTheme(theme: String, darkForSystemTheme: Boolean) {
|
||||
appPrefs.currentTheme.set(theme)
|
||||
CurrentColors.value = currentColors(darkForSystemTheme, appPrefs.themeOverrides.get())
|
||||
CurrentColors.value = currentColors(darkForSystemTheme, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
|
||||
platform.androidSetNightModeIfSupported()
|
||||
}
|
||||
|
||||
fun changeDarkTheme(theme: String, darkForSystemTheme: Boolean) {
|
||||
appPrefs.systemDarkTheme.set(theme)
|
||||
CurrentColors.value = currentColors(darkForSystemTheme, appPrefs.themeOverrides.get())
|
||||
CurrentColors.value = currentColors(darkForSystemTheme, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
|
||||
}
|
||||
|
||||
fun saveAndApplyThemeColor(baseTheme: DefaultTheme, name: ThemeColor, color: Color? = null, pref: SharedPreference<Map<String, ThemeOverrides>> = appPrefs.themeOverrides) {
|
||||
@@ -132,7 +143,12 @@ object ThemeManager {
|
||||
val prevValue = overrides.getOrDefault(nonSystemThemeName, ThemeOverrides())
|
||||
overrides[nonSystemThemeName] = prevValue.withUpdatedColor(name, colorToSet?.toReadableHex())
|
||||
pref.set(overrides)
|
||||
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight, appPrefs.themeOverrides.get())
|
||||
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
|
||||
}
|
||||
|
||||
fun applyThemeColor(name: ThemeColor, color: Color? = null, pref: MutableState<ThemeModeOverride>) {
|
||||
val prevValue = pref.value
|
||||
pref.value = prevValue.withUpdatedColor(name, color?.toReadableHex())
|
||||
}
|
||||
|
||||
fun saveAndApplyBackgroundImage(baseTheme: DefaultTheme, type: BackgroundImageType?, pref: SharedPreference<Map<String, ThemeOverrides>> = appPrefs.themeOverrides) {
|
||||
@@ -145,7 +161,16 @@ object ThemeManager {
|
||||
}
|
||||
overrides[nonSystemThemeName] = prevValue.copy(wallpaper = if (type != null) ThemeWallpaper.from(type, prevValue.wallpaper.background, prevValue.wallpaper.tint) else ThemeWallpaper())
|
||||
pref.set(overrides)
|
||||
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight, appPrefs.themeOverrides.get())
|
||||
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
|
||||
}
|
||||
|
||||
fun applyBackgroundImage(type: BackgroundImageType?, pref: MutableState<ThemeModeOverride>) {
|
||||
var prevValue = pref.value
|
||||
// Overriding the whole theme on type change
|
||||
if (prevValue.wallpaper?.imageFile != type?.filename && prevValue.wallpaper?.preset != type?.filename) {
|
||||
prevValue = ThemeModeOverride()
|
||||
}
|
||||
pref.value = prevValue.copy(wallpaper = if (type != null) ThemeWallpaper.from(type, prevValue.wallpaper?.background, prevValue.wallpaper?.tint) else ThemeWallpaper())
|
||||
}
|
||||
|
||||
fun saveAndApplyThemeOverrides(theme: ThemeOverrides, pref: SharedPreference<Map<String, ThemeOverrides>> = appPrefs.themeOverrides) {
|
||||
@@ -154,7 +179,7 @@ object ThemeManager {
|
||||
overrides[theme.base.themeName] = prevValue.copy(colors = theme.colors, wallpaper = theme.wallpaper.importFromString())
|
||||
pref.set(overrides)
|
||||
appPrefs.currentTheme.set(theme.base.themeName)
|
||||
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight, appPrefs.themeOverrides.get())
|
||||
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
|
||||
}
|
||||
|
||||
fun resetAllThemeColors(darkForSystemTheme: Boolean, pref: SharedPreference<Map<String, ThemeOverrides>> = appPrefs.themeOverrides) {
|
||||
@@ -168,7 +193,7 @@ object ThemeManager {
|
||||
val prevValue = overrides[nonSystemThemeName] ?: return
|
||||
overrides[nonSystemThemeName] = prevValue.copy(colors = ThemeColors(), wallpaper = prevValue.wallpaper.copy(background = null, tint = null))
|
||||
pref.set(overrides)
|
||||
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight, appPrefs.themeOverrides.get())
|
||||
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
|
||||
}
|
||||
|
||||
fun String.colorFromReadableHex(): Color =
|
||||
|
||||
+166
-53
@@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.text.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -29,6 +30,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.model.ChatController.appPrefs
|
||||
import chat.simplex.common.model.ChatModel.controller
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
@@ -42,6 +44,7 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.datetime.Clock
|
||||
import java.util.*
|
||||
|
||||
@Composable
|
||||
fun ChatInfoView(
|
||||
@@ -339,24 +342,9 @@ fun ChatInfoLayout(
|
||||
SynchronizeConnectionButton(syncContactConnection)
|
||||
}
|
||||
|
||||
val theme = remember {
|
||||
// Using existing theme overrides as an overrides for a new theme if the same BASE theme is not found in already configured map
|
||||
(chat.chatInfo as ChatInfo.Direct).contact.uiThemes?.preferredTheme()?.copy(base = CurrentColors.value.base) ?: ThemeOverrides()
|
||||
}
|
||||
|
||||
WallpaperButton {
|
||||
ModalManager.end.showModal {
|
||||
WallpaperEditor(theme) { type, theme ->
|
||||
withBGApi {
|
||||
val changedThemes = ((chat.chatInfo as ChatInfo.Direct).contact.uiThemes ?: mapOf()).toMutableMap()
|
||||
changedThemes[theme.base.themeName] = theme.copy(wallpaper = theme.wallpaper.withFilledWallpaperPath())
|
||||
if (controller.apiSetChatUIThemes(chat.remoteHostId, chat.id, changedThemes)) {
|
||||
// Remove previous image only after saving
|
||||
removeBackgroundImage(theme.wallpaper.imageFile)
|
||||
chatModel.updateChatInfo(chat.remoteHostId, chat.chatInfo.copy(contact = chat.chatInfo.contact.copy(uiThemes = changedThemes)))
|
||||
}
|
||||
}
|
||||
}
|
||||
WallpaperEditorModal(chat)
|
||||
}
|
||||
}
|
||||
// } else if (developerTools) {
|
||||
@@ -707,74 +695,199 @@ fun ShareAddressButton(onClick: () -> Unit) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ModalData.WallpaperEditor(theme: ThemeOverrides, save: (BackgroundImageType?, ThemeOverrides) -> Unit) {
|
||||
fun ModalData.WallpaperEditorModal(chat: Chat) {
|
||||
val initialTheme = remember(CurrentColors.value.base) {
|
||||
(chat.chatInfo as? ChatInfo.Direct)?.contact?.uiThemes?.preferredTheme()?.copy(mode = CurrentColors.value.base.mode)
|
||||
?: (chat.chatInfo as? ChatInfo.Group)?.groupInfo?.uiThemes?.preferredTheme()?.copy(mode = CurrentColors.value.base.mode)
|
||||
?: ThemeModeOverride()
|
||||
}
|
||||
WallpaperEditor(
|
||||
initialTheme,
|
||||
save = { applyToMode, newTheme ->
|
||||
withBGApi {
|
||||
var changedThemes: ThemeModeOverrides? = ((chat.chatInfo as? ChatInfo.Direct)?.contact?.uiThemes ?: (chat.chatInfo as? ChatInfo.Group)?.groupInfo?.uiThemes) ?: ThemeModeOverrides()
|
||||
val changed = newTheme?.copy(mode = initialTheme.mode, wallpaper = newTheme.wallpaper?.withFilledWallpaperPath())
|
||||
changedThemes = if (applyToMode == null) {
|
||||
changedThemes?.copy(light = changed?.copy(mode = DefaultThemeMode.LIGHT), dark = changed?.copy(mode = DefaultThemeMode.DARK))
|
||||
} else if (changed?.mode == DefaultThemeMode.LIGHT) {
|
||||
changedThemes?.copy(light = changed)
|
||||
} else {
|
||||
changedThemes?.copy(dark = changed)
|
||||
}
|
||||
changedThemes = if (changedThemes?.light != null || changedThemes?.dark != null) changedThemes else null
|
||||
if (controller.apiSetChatUIThemes(chat.remoteHostId, chat.id, changedThemes)) {
|
||||
// Remove previous image only after saving
|
||||
removeBackgroundImage(initialTheme.wallpaper?.imageFile)
|
||||
if (chat.chatInfo is ChatInfo.Direct) {
|
||||
chatModel.updateChatInfo(chat.remoteHostId, chat.chatInfo.copy(contact = chat.chatInfo.contact.copy(uiThemes = changedThemes)))
|
||||
} else if (chat.chatInfo is ChatInfo.Group) {
|
||||
chatModel.updateChatInfo(chat.remoteHostId, chat.chatInfo.copy(groupInfo = chat.chatInfo.groupInfo.copy(uiThemes = changedThemes)))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
setDefaultsToAll = {
|
||||
val lightBase = DefaultTheme.LIGHT
|
||||
val darkBase = if (CurrentColors.value.base != DefaultTheme.LIGHT) CurrentColors.value.base else if (appPrefs.systemDarkTheme.get() == DefaultTheme.DARK.themeName) DefaultTheme.DARK else DefaultTheme.SIMPLEX
|
||||
val modeLight = ThemeModeOverride.withFilledAppDefaults(DefaultThemeMode.LIGHT, lightBase)
|
||||
val modeDark = ThemeModeOverride.withFilledAppDefaults(DefaultThemeMode.DARK, darkBase)
|
||||
val changedThemes = ThemeModeOverrides(light = modeLight, dark = modeDark)
|
||||
if (controller.apiSetChatUIThemes(chat.remoteHostId, chat.id, changedThemes)) {
|
||||
// Remove previous image only after saving
|
||||
removeBackgroundImage(initialTheme.wallpaper?.imageFile)
|
||||
if (chat.chatInfo is ChatInfo.Direct) {
|
||||
chatModel.updateChatInfo(chat.remoteHostId, chat.chatInfo.copy(contact = chat.chatInfo.contact.copy(uiThemes = changedThemes)))
|
||||
} else if (chat.chatInfo is ChatInfo.Group) {
|
||||
chatModel.updateChatInfo(chat.remoteHostId, chat.chatInfo.copy(groupInfo = chat.chatInfo.groupInfo.copy(uiThemes = changedThemes)))
|
||||
}
|
||||
}
|
||||
(chat.chatInfo as? ChatInfo.Direct)?.contact?.uiThemes?.preferredTheme()?.copy(mode = CurrentColors.value.base.mode)
|
||||
?: (chat.chatInfo as? ChatInfo.Group)?.groupInfo?.uiThemes?.preferredTheme()?.copy(mode = CurrentColors.value.base.mode)
|
||||
?: ThemeModeOverride()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ModalData.WallpaperEditor(theme: ThemeModeOverride, save: (applyToMode: DefaultThemeMode?, ThemeModeOverride?) -> Unit, setDefaultsToAll: suspend () -> ThemeModeOverride) {
|
||||
ColumnWithScrollBar(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
) {
|
||||
val baseTheme = CurrentColors.value.base
|
||||
val themeOverrides = remember { stateGetOrPut("themeOverrides") { theme } }
|
||||
val backgroundImageType: State<BackgroundImageType?> = remember { derivedStateOf { BackgroundImageType.from(themeOverrides.value.wallpaper) } }
|
||||
val pref = remember {
|
||||
SharedPreference<Map<String, ThemeOverrides>>(
|
||||
get = {
|
||||
mapOf(baseTheme.themeName to themeOverrides.value)
|
||||
},
|
||||
set = { value ->
|
||||
themeOverrides.value = value[baseTheme.themeName]!!
|
||||
}
|
||||
val applyToMode = remember { stateGetOrPutNullable("applyToMode") { null as DefaultThemeMode? } }
|
||||
var showMore by remember { stateGetOrPut("showMore") { false } }
|
||||
val themeModeOverride = remember { stateGetOrPut("themeModeOverride") { theme } }
|
||||
val backgroundImageType: State<BackgroundImageType?> = remember { derivedStateOf { BackgroundImageType.from(themeModeOverride.value.wallpaper) } }
|
||||
val systemDark = isInDarkTheme()
|
||||
val currentTheme by remember(themeModeOverride.value, CurrentColors.collectAsState().value) {
|
||||
mutableStateOf(
|
||||
ThemeManager.currentColors(systemDark, themeModeOverride.value, chatModel.currentUser.value?.uiThemes, appPreferences.themeOverrides.get())
|
||||
)
|
||||
}
|
||||
|
||||
AppBarTitle(stringResource(MR.strings.settings_section_title_wallpaper))
|
||||
val backgroundImage = remember { derivedStateOf { backgroundImageType.value?.image } }
|
||||
val backgroundColor = remember { derivedStateOf { themeOverrides.value.wallpaper.background?.colorFromReadableHex() } }
|
||||
val tintColor = remember { derivedStateOf { themeOverrides.value.wallpaper.tint?.colorFromReadableHex() } }
|
||||
val backgroundColor = remember { derivedStateOf { themeModeOverride.value.wallpaper?.background?.colorFromReadableHex() } }
|
||||
val tintColor = remember { derivedStateOf { themeModeOverride.value.wallpaper?.tint?.colorFromReadableHex() } }
|
||||
|
||||
fun editColor(name: ThemeColor, initialColor: Color) {
|
||||
ModalManager.end.showModalCloseable { close ->
|
||||
AppearanceScope.ColorEditor(
|
||||
name,
|
||||
initialColor,
|
||||
CurrentColors.collectAsState().value.base,
|
||||
backgroundImageType.value,
|
||||
backgroundImage.value,
|
||||
backgroundColor.value,
|
||||
tintColor.value,
|
||||
onColorChange = { color ->
|
||||
ThemeManager.applyThemeColor(name, color, themeModeOverride)
|
||||
save(applyToMode.value, themeModeOverride.value)
|
||||
},
|
||||
close
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
WallpaperSetupView(
|
||||
backgroundImageType.value,
|
||||
theme.base,
|
||||
CurrentColors.collectAsState().value.base,
|
||||
backgroundColor.value,
|
||||
tintColor.value,
|
||||
showPresetSelection = true,
|
||||
editColor = { name, initialColor ->
|
||||
ModalManager.end.showModalCloseable { close ->
|
||||
AppearanceScope.ColorEditor(
|
||||
name,
|
||||
initialColor,
|
||||
theme.base,
|
||||
backgroundImageType.value,
|
||||
backgroundImage.value,
|
||||
backgroundColor.value,
|
||||
tintColor.value,
|
||||
onColorChange = { color ->
|
||||
ThemeManager.saveAndApplyThemeColor(baseTheme, name, color, pref)
|
||||
},
|
||||
close
|
||||
)
|
||||
}
|
||||
editColor(name, initialColor)
|
||||
},
|
||||
onColorChange = { name, color ->
|
||||
ThemeManager.saveAndApplyThemeColor(baseTheme, name, color, pref)
|
||||
ThemeManager.applyThemeColor(name, color, themeModeOverride)
|
||||
save(applyToMode.value, themeModeOverride.value)
|
||||
},
|
||||
onTypeChange = { type ->
|
||||
ThemeManager.saveAndApplyBackgroundImage(baseTheme, type, pref)
|
||||
ThemeManager.applyBackgroundImage(type, themeModeOverride)
|
||||
save(applyToMode.value, themeModeOverride.value)
|
||||
}
|
||||
)
|
||||
|
||||
SectionSpacer()
|
||||
|
||||
ApplyButton {
|
||||
save(backgroundImageType.value, themeOverrides.value)
|
||||
ResetToGlobalThemeButton {
|
||||
themeModeOverride.value = ThemeModeOverride()
|
||||
save(applyToMode.value, null)
|
||||
}
|
||||
|
||||
SetDefaultThemeButton {
|
||||
if (applyToMode.value == null) {
|
||||
withBGApi {
|
||||
themeModeOverride.value = setDefaultsToAll()
|
||||
}
|
||||
} else {
|
||||
val lightBase = DefaultTheme.LIGHT
|
||||
val darkBase = if (CurrentColors.value.base != DefaultTheme.LIGHT) CurrentColors.value.base else if (appPrefs.systemDarkTheme.get() == DefaultTheme.DARK.themeName) DefaultTheme.DARK else DefaultTheme.SIMPLEX
|
||||
val mode = themeModeOverride.value.mode
|
||||
themeModeOverride.value = ThemeModeOverride.withFilledAppDefaults(mode, if (mode == DefaultThemeMode.LIGHT) lightBase else darkBase)
|
||||
save(null, themeModeOverride.value)
|
||||
}
|
||||
}
|
||||
|
||||
SectionSpacer()
|
||||
|
||||
if (!showMore) {
|
||||
AdvancedSettingsButton { showMore = true }
|
||||
} else {
|
||||
val values by remember { mutableStateOf(
|
||||
listOf(
|
||||
null to generalGetString(MR.strings.chat_theme_apply_to_all_modes),
|
||||
DefaultThemeMode.LIGHT to generalGetString(MR.strings.chat_theme_apply_to_light_mode),
|
||||
DefaultThemeMode.DARK to generalGetString(MR.strings.chat_theme_apply_to_dark_mode),
|
||||
)
|
||||
)}
|
||||
ExposedDropDownSettingRow(
|
||||
generalGetString(MR.strings.chat_theme_apply_to_mode),
|
||||
values,
|
||||
applyToMode,
|
||||
icon = null,
|
||||
enabled = remember { mutableStateOf(true) },
|
||||
onSelected = {
|
||||
applyToMode.value = it
|
||||
if (it != null && it != CurrentColors.value.base.mode) {
|
||||
val lightBase = DefaultTheme.LIGHT
|
||||
val darkBase = if (CurrentColors.value.base != DefaultTheme.LIGHT) CurrentColors.value.base else if (appPrefs.systemDarkTheme.get() == DefaultTheme.DARK.themeName) DefaultTheme.DARK else DefaultTheme.SIMPLEX
|
||||
ThemeManager.applyTheme(if (it == DefaultThemeMode.LIGHT) lightBase.themeName else darkBase.themeName, systemDark)
|
||||
}
|
||||
}
|
||||
)
|
||||
KeyChangeEffect(theme.mode) {
|
||||
themeModeOverride.value = theme
|
||||
}
|
||||
|
||||
SectionSpacer()
|
||||
|
||||
AppearanceScope.CustomizeThemeColorsSection(currentTheme) { name, color -> editColor(name, color) }
|
||||
}
|
||||
|
||||
SectionBottomSpacer()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ApplyButton(onClick: () -> Unit) {
|
||||
private fun ResetToGlobalThemeButton(onClick: () -> Unit) {
|
||||
SectionItemView(onClick) {
|
||||
Text(stringResource(MR.strings.chat_theme_reset_to_global_theme), color = MaterialTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SetDefaultThemeButton(onClick: () -> Unit) {
|
||||
SectionItemView(onClick) {
|
||||
Text(stringResource(MR.strings.chat_theme_set_default_theme), color = MaterialTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AdvancedSettingsButton(onClick: () -> Unit) {
|
||||
SettingsActionItem(
|
||||
painterResource(MR.images.ic_check),
|
||||
stringResource(MR.strings.background_image_apply),
|
||||
painterResource(MR.images.ic_arrow_downward),
|
||||
stringResource(MR.strings.background_image_advanced_settings),
|
||||
click = onClick
|
||||
)
|
||||
}
|
||||
|
||||
+2
-2
@@ -117,8 +117,8 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
||||
val clipboard = LocalClipboardManager.current
|
||||
when (chat.chatInfo) {
|
||||
is ChatInfo.Direct, is ChatInfo.Group, is ChatInfo.Local -> {
|
||||
val themeOverrides = remember(chat.chatInfo) { if (chat.chatInfo is ChatInfo.Direct) chat.chatInfo.contact.uiThemes?.preferredTheme() else if (chat.chatInfo is ChatInfo.Group) chat.chatInfo.groupInfo.uiThemes?.preferredTheme() else null }
|
||||
val overrides = if (themeOverrides != null) ThemeManager.overriddenColors(themeOverrides) else null
|
||||
val perChatTheme = remember(chat.chatInfo, CurrentColors.value.base) { if (chat.chatInfo is ChatInfo.Direct) chat.chatInfo.contact.uiThemes?.preferredTheme() else if (chat.chatInfo is ChatInfo.Group) chat.chatInfo.groupInfo.uiThemes?.preferredTheme() else null }
|
||||
val overrides = if (perChatTheme != null) ThemeManager.currentColors(isInDarkTheme(), perChatTheme, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.state.value) else null
|
||||
SimpleXThemeOverride(overrides ?: CurrentColors.collectAsState().value) {
|
||||
ChatLayout(
|
||||
chat,
|
||||
|
||||
+1
-16
@@ -235,24 +235,9 @@ fun GroupChatInfoLayout(
|
||||
SendReceiptsOptionDisabled()
|
||||
}
|
||||
|
||||
val theme = remember {
|
||||
// Using existing theme overrides as an overrides for a new theme if the same BASE theme is not found in already configured map
|
||||
(chat.chatInfo as ChatInfo.Group).groupInfo.uiThemes?.preferredTheme()?.copy(base = CurrentColors.value.base) ?: ThemeOverrides()
|
||||
}
|
||||
WallpaperButton {
|
||||
ModalManager.end.showModal {
|
||||
WallpaperEditor(theme) { type, theme ->
|
||||
withBGApi {
|
||||
val changedThemes = ((chat.chatInfo as ChatInfo.Direct).contact.uiThemes ?: mapOf()).toMutableMap()
|
||||
changedThemes[theme.base.themeName] = theme.copy(wallpaper = theme.wallpaper.withFilledWallpaperPath())
|
||||
if (controller.apiSetChatUIThemes(chat.remoteHostId, chat.id, changedThemes)) {
|
||||
// Remove previous image only after saving
|
||||
removeBackgroundImage(theme.wallpaper.imageFile)
|
||||
chatModel.updateChatInfo(chat.remoteHostId, chat.chatInfo.copy(contact = chat.chatInfo.contact.copy(uiThemes = changedThemes)))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
WallpaperEditorModal(chat)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-7
@@ -118,7 +118,7 @@ enum class PredefinedBackgroundImage(
|
||||
);
|
||||
|
||||
fun toType(): BackgroundImageType =
|
||||
BackgroundImageType.Repeated(filename, scale)
|
||||
BackgroundImageType.Repeated(filename, 1f)
|
||||
|
||||
companion object {
|
||||
fun from(filename: String): PredefinedBackgroundImage? =
|
||||
@@ -161,7 +161,9 @@ sealed class BackgroundImageType {
|
||||
@Serializable @SerialName("repeated") data class Repeated(
|
||||
override val filename: String,
|
||||
override val scale: Float,
|
||||
): BackgroundImageType()
|
||||
): BackgroundImageType() {
|
||||
val predefinedImageScale = PredefinedBackgroundImage.from(filename)?.scale ?: 1f
|
||||
}
|
||||
|
||||
@Serializable @SerialName("static") data class Static(
|
||||
override val filename: String,
|
||||
@@ -193,11 +195,11 @@ sealed class BackgroundImageType {
|
||||
|
||||
private var cachedImage: Pair<String, ImageBitmap>? = null
|
||||
|
||||
fun from(wallpaper: ThemeWallpaper): BackgroundImageType? {
|
||||
return if (wallpaper.preset != null) {
|
||||
Repeated(wallpaper.preset, wallpaper.scale)
|
||||
fun from(wallpaper: ThemeWallpaper?): BackgroundImageType? {
|
||||
return if (wallpaper?.preset != null) {
|
||||
Repeated(wallpaper.preset, wallpaper.scale ?: 1f)
|
||||
} else {
|
||||
Static(wallpaper.imageFile ?: return null, wallpaper.scale, wallpaper.scaleType ?: BackgroundImageScaleType.FILL)
|
||||
Static(wallpaper?.imageFile ?: return null, wallpaper.scale ?: 1f, wallpaper.scaleType ?: BackgroundImageScaleType.FILL)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,7 +222,7 @@ fun DrawScope.chatViewBackground(image: ImageBitmap, imageType: BackgroundImageT
|
||||
|
||||
drawRect(background)
|
||||
when (imageType) {
|
||||
is BackgroundImageType.Repeated -> repeat(imageType.scale)
|
||||
is BackgroundImageType.Repeated -> repeat(imageType.scale * imageType.predefinedImageScale)
|
||||
is BackgroundImageType.Static -> when (imageType.scaleType) {
|
||||
BackgroundImageScaleType.REPEAT -> repeat(imageType.scale)
|
||||
BackgroundImageScaleType.FILL, BackgroundImageScaleType.FIT -> {
|
||||
|
||||
+4
-1
@@ -41,9 +41,12 @@ enum class ModalPlacement {
|
||||
}
|
||||
|
||||
class ModalData {
|
||||
private val state = mutableMapOf<String, MutableState<Any>>()
|
||||
private val state = mutableMapOf<String, MutableState<Any?>>()
|
||||
fun <T> stateGetOrPut (key: String, default: () -> T): MutableState<T> =
|
||||
state.getOrPut(key) { mutableStateOf(default() as Any) } as MutableState<T>
|
||||
|
||||
fun <T> stateGetOrPutNullable (key: String, default: () -> T?): MutableState<T?> =
|
||||
state.getOrPut(key) { mutableStateOf(default() as Any?) } as MutableState<T?>
|
||||
}
|
||||
|
||||
class ModalManager(private val placement: ModalPlacement? = null) {
|
||||
|
||||
+91
-53
@@ -25,12 +25,15 @@ import chat.simplex.common.model.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.model.ChatModel
|
||||
import chat.simplex.common.model.ChatModel.controller
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.ui.theme.ThemeManager.toReadableHex
|
||||
import chat.simplex.common.ui.theme.isSystemInDarkTheme
|
||||
import chat.simplex.common.views.chat.item.PreviewChatItemView
|
||||
import chat.simplex.common.views.usersettings.AppearanceScope.WallpaperPresetSelector
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.serialization.encodeToString
|
||||
import java.net.URI
|
||||
@@ -63,6 +66,7 @@ object AppearanceScope {
|
||||
onValueChange = {
|
||||
val diff = it % 2.5f
|
||||
appPreferences.profileImageCornerRadius.set(it + (if (diff >= 1.25f) -diff + 2.5f else -diff))
|
||||
saveThemeToDatabase()
|
||||
},
|
||||
colors = SliderDefaults.colors(
|
||||
activeTickColor = Color.Transparent,
|
||||
@@ -108,6 +112,8 @@ object AppearanceScope {
|
||||
fun WallpaperPresetSelector(
|
||||
selectedBackground: BackgroundImageType?,
|
||||
baseTheme: DefaultTheme,
|
||||
initialBackgroundColor: Color?,
|
||||
initialTintColor: Color?,
|
||||
onColorChange: (ThemeColor, Color?) -> Unit,
|
||||
onTypeChange: (BackgroundImageType?) -> Unit
|
||||
) {
|
||||
@@ -143,7 +149,7 @@ object AppearanceScope {
|
||||
) {
|
||||
if (background != null) {
|
||||
val backgroundImage = remember(background.filename) { PredefinedBackgroundImage.from(background.filename)?.res?.toComposeImageBitmap() }
|
||||
ChatThemePreview(baseTheme, backgroundImage, background.toType(), withMessages = false)
|
||||
ChatThemePreview(baseTheme, backgroundImage, background.toType(), withMessages = false, backgroundColor = initialBackgroundColor, tintColor = initialTintColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -171,7 +177,7 @@ object AppearanceScope {
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
if (checked) {
|
||||
ChatThemePreview(baseTheme, backgroundImage, type, withMessages = false)
|
||||
ChatThemePreview(baseTheme, backgroundImage, type, withMessages = false, backgroundColor = initialBackgroundColor, tintColor = initialTintColor)
|
||||
} else {
|
||||
Plus()
|
||||
}
|
||||
@@ -185,11 +191,11 @@ object AppearanceScope {
|
||||
BackgroundItem(background)
|
||||
}
|
||||
item {
|
||||
OwnBackgroundItem(MaterialTheme.wallpaper.type)
|
||||
OwnBackgroundItem(selectedBackground)
|
||||
}
|
||||
}
|
||||
val backgroundImage = selectedBackground?.image
|
||||
ChatThemePreview(baseTheme, backgroundImage, selectedBackground)
|
||||
ChatThemePreview(baseTheme, backgroundImage, selectedBackground, backgroundColor = initialBackgroundColor, tintColor = initialTintColor)
|
||||
|
||||
if (appPlatform.isDesktop) {
|
||||
val itemWidth = (DEFAULT_START_MODAL_WIDTH - DEFAULT_PADDING * 2 - DEFAULT_PADDING_HALF * 3) / 4
|
||||
@@ -216,6 +222,15 @@ object AppearanceScope {
|
||||
}
|
||||
}
|
||||
|
||||
private var job: Job = Job()
|
||||
private fun saveThemeToDatabase() {
|
||||
job.cancel()
|
||||
job = withBGApi {
|
||||
delay(3000)
|
||||
controller.apiSaveAppSettings(AppSettings.current.prepareForExport())
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ThemesSection(
|
||||
systemDarkTheme: SharedPreference<String?>,
|
||||
@@ -230,12 +245,16 @@ object AppearanceScope {
|
||||
WallpaperPresetSelector(
|
||||
selectedBackground = backgroundImageType,
|
||||
baseTheme = currentTheme.base,
|
||||
initialBackgroundColor = MaterialTheme.wallpaper.background,
|
||||
initialTintColor = MaterialTheme.wallpaper.tint,
|
||||
onColorChange = { name, color ->
|
||||
ThemeManager.saveAndApplyThemeColor(baseTheme, name, color)
|
||||
saveThemeToDatabase()
|
||||
},
|
||||
onTypeChange = { type ->
|
||||
removeBackgroundImage(backgroundImageType?.filename)
|
||||
ThemeManager.saveAndApplyBackgroundImage(baseTheme, type)
|
||||
saveThemeToDatabase()
|
||||
}
|
||||
)
|
||||
|
||||
@@ -243,10 +262,12 @@ object AppearanceScope {
|
||||
val state = remember { derivedStateOf { currentTheme.name } }
|
||||
ThemeSelector(state) {
|
||||
ThemeManager.applyTheme(it, darkTheme)
|
||||
saveThemeToDatabase()
|
||||
}
|
||||
if (state.value == DefaultTheme.SYSTEM.themeName) {
|
||||
DarkThemeSelector(remember { systemDarkTheme.state }) {
|
||||
ThemeManager.changeDarkTheme(it, darkTheme)
|
||||
saveThemeToDatabase()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -278,66 +299,29 @@ object AppearanceScope {
|
||||
editColor,
|
||||
onColorChange = { name, color ->
|
||||
ThemeManager.saveAndApplyThemeColor(baseTheme, name, color)
|
||||
saveThemeToDatabase()
|
||||
},
|
||||
onTypeChange = { type ->
|
||||
removeBackgroundImage(backgroundImageType.filename)
|
||||
ThemeManager.saveAndApplyBackgroundImage(baseTheme, type)
|
||||
saveThemeToDatabase()
|
||||
}
|
||||
)
|
||||
}
|
||||
SectionDividerSpaced(maxTopPadding = true)
|
||||
}
|
||||
|
||||
SectionView(stringResource(MR.strings.theme_colors_section_title)) {
|
||||
SectionItemViewSpaceBetween({ editColor(ThemeColor.PRIMARY, currentTheme.colors.primary) }) {
|
||||
val title = generalGetString(MR.strings.color_primary)
|
||||
Text(title)
|
||||
Icon(painterResource(MR.images.ic_circle_filled), title, tint = colors.primary)
|
||||
}
|
||||
SectionItemViewSpaceBetween({ editColor(ThemeColor.PRIMARY_VARIANT, currentTheme.colors.primaryVariant) }) {
|
||||
val title = generalGetString(MR.strings.color_primary_variant)
|
||||
Text(title)
|
||||
Icon(painterResource(MR.images.ic_circle_filled), title, tint = colors.primaryVariant)
|
||||
}
|
||||
SectionItemViewSpaceBetween({ editColor(ThemeColor.SECONDARY, currentTheme.colors.secondary) }) {
|
||||
val title = generalGetString(MR.strings.color_secondary)
|
||||
Text(title)
|
||||
Icon(painterResource(MR.images.ic_circle_filled), title, tint = colors.secondary)
|
||||
}
|
||||
SectionItemViewSpaceBetween({ editColor(ThemeColor.SECONDARY_VARIANT, currentTheme.colors.secondaryVariant) }) {
|
||||
val title = generalGetString(MR.strings.color_secondary_variant)
|
||||
Text(title)
|
||||
Icon(painterResource(MR.images.ic_circle_filled), title, tint = colors.secondaryVariant)
|
||||
}
|
||||
SectionItemViewSpaceBetween({ editColor(ThemeColor.BACKGROUND, currentTheme.colors.background) }) {
|
||||
val title = generalGetString(MR.strings.color_background)
|
||||
Text(title)
|
||||
Icon(painterResource(MR.images.ic_circle_filled), title, tint = colors.background)
|
||||
}
|
||||
SectionItemViewSpaceBetween({ editColor(ThemeColor.SURFACE, currentTheme.colors.surface) }) {
|
||||
val title = generalGetString(MR.strings.color_surface)
|
||||
Text(title)
|
||||
Icon(painterResource(MR.images.ic_circle_filled), title, tint = colors.surface)
|
||||
}
|
||||
SectionItemViewSpaceBetween({ editColor(ThemeColor.TITLE, currentTheme.appColors.title) }) {
|
||||
val title = generalGetString(MR.strings.color_title)
|
||||
Text(title)
|
||||
Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.appColors.title)
|
||||
}
|
||||
SectionItemViewSpaceBetween({ editColor(ThemeColor.SENT_MESSAGE, currentTheme.appColors.sentMessage) }) {
|
||||
val title = generalGetString(MR.strings.color_sent_message)
|
||||
Text(title)
|
||||
Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.appColors.sentMessage)
|
||||
}
|
||||
SectionItemViewSpaceBetween({ editColor(ThemeColor.RECEIVED_MESSAGE, currentTheme.appColors.receivedMessage) }) {
|
||||
val title = generalGetString(MR.strings.color_received_message)
|
||||
Text(title)
|
||||
Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.appColors.receivedMessage)
|
||||
}
|
||||
CustomizeThemeColorsSection(currentTheme) { name, color ->
|
||||
editColor(name, color)
|
||||
saveThemeToDatabase()
|
||||
}
|
||||
|
||||
val isInDarkTheme = isInDarkTheme()
|
||||
if (currentTheme.base.hasChangedAnyColor(currentTheme.colors, currentTheme.appColors, currentTheme.wallpaper)) {
|
||||
SectionItemView({ ThemeManager.resetAllThemeColors(darkForSystemTheme = isInDarkTheme) }) {
|
||||
SectionItemView({
|
||||
ThemeManager.resetAllThemeColors(darkForSystemTheme = isInDarkTheme)
|
||||
saveThemeToDatabase()
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.reset_color), color = colors.primary)
|
||||
}
|
||||
}
|
||||
@@ -353,7 +337,7 @@ object AppearanceScope {
|
||||
}
|
||||
}
|
||||
SectionItemView({
|
||||
val overrides = ThemeManager.currentThemeOverridesForExport(isInDarkTheme)
|
||||
val overrides = ThemeManager.currentThemeOverridesForExport(isInDarkTheme, null, chatModel.currentUser.value?.uiThemes)
|
||||
theme.value = yaml.encodeToString<ThemeOverrides>(overrides)
|
||||
withLongRunningApi { exportThemeLauncher.launch("simplex.theme")}
|
||||
}) {
|
||||
@@ -364,6 +348,7 @@ object AppearanceScope {
|
||||
val theme = getThemeFromUri(to)
|
||||
if (theme != null) {
|
||||
ThemeManager.saveAndApplyThemeOverrides(theme)
|
||||
saveThemeToDatabase()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -376,6 +361,57 @@ object AppearanceScope {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CustomizeThemeColorsSection(currentTheme: ThemeManager.ActiveTheme, editColor: (ThemeColor, Color) -> Unit) {
|
||||
SectionView(stringResource(MR.strings.theme_colors_section_title)) {
|
||||
SectionItemViewSpaceBetween({ editColor(ThemeColor.PRIMARY, currentTheme.colors.primary) }) {
|
||||
val title = generalGetString(MR.strings.color_primary)
|
||||
Text(title)
|
||||
Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.colors.primary)
|
||||
}
|
||||
SectionItemViewSpaceBetween({ editColor(ThemeColor.PRIMARY_VARIANT, currentTheme.colors.primaryVariant) }) {
|
||||
val title = generalGetString(MR.strings.color_primary_variant)
|
||||
Text(title)
|
||||
Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.colors.primaryVariant)
|
||||
}
|
||||
SectionItemViewSpaceBetween({ editColor(ThemeColor.SECONDARY, currentTheme.colors.secondary) }) {
|
||||
val title = generalGetString(MR.strings.color_secondary)
|
||||
Text(title)
|
||||
Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.colors.secondary)
|
||||
}
|
||||
SectionItemViewSpaceBetween({ editColor(ThemeColor.SECONDARY_VARIANT, currentTheme.colors.secondaryVariant) }) {
|
||||
val title = generalGetString(MR.strings.color_secondary_variant)
|
||||
Text(title)
|
||||
Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.colors.secondaryVariant)
|
||||
}
|
||||
SectionItemViewSpaceBetween({ editColor(ThemeColor.BACKGROUND, currentTheme.colors.background) }) {
|
||||
val title = generalGetString(MR.strings.color_background)
|
||||
Text(title)
|
||||
Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.colors.background)
|
||||
}
|
||||
SectionItemViewSpaceBetween({ editColor(ThemeColor.SURFACE, currentTheme.colors.surface) }) {
|
||||
val title = generalGetString(MR.strings.color_surface)
|
||||
Text(title)
|
||||
Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.colors.surface)
|
||||
}
|
||||
SectionItemViewSpaceBetween({ editColor(ThemeColor.TITLE, currentTheme.appColors.title) }) {
|
||||
val title = generalGetString(MR.strings.color_title)
|
||||
Text(title)
|
||||
Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.appColors.title)
|
||||
}
|
||||
SectionItemViewSpaceBetween({ editColor(ThemeColor.SENT_MESSAGE, currentTheme.appColors.sentMessage) }) {
|
||||
val title = generalGetString(MR.strings.color_sent_message)
|
||||
Text(title)
|
||||
Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.appColors.sentMessage)
|
||||
}
|
||||
SectionItemViewSpaceBetween({ editColor(ThemeColor.RECEIVED_MESSAGE, currentTheme.appColors.receivedMessage) }) {
|
||||
val title = generalGetString(MR.strings.color_received_message)
|
||||
Text(title)
|
||||
Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.appColors.receivedMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ColorEditor(
|
||||
name: ThemeColor,
|
||||
@@ -489,7 +525,7 @@ object AppearanceScope {
|
||||
private fun ThemeSelector(state: State<String>, onSelected: (String) -> Unit) {
|
||||
val darkTheme = isSystemInDarkTheme()
|
||||
val values by remember(ChatController.appPrefs.appLanguage.state.value) {
|
||||
mutableStateOf(ThemeManager.allThemes(darkTheme).map { it.second.name to it.third })
|
||||
mutableStateOf(ThemeManager.allThemes(darkTheme).map { it.second.themeName to it.third })
|
||||
}
|
||||
ExposedDropDownSettingRow(
|
||||
generalGetString(MR.strings.theme),
|
||||
@@ -538,6 +574,8 @@ fun WallpaperSetupView(
|
||||
WallpaperPresetSelector(
|
||||
selectedBackground = backgroundImageType,
|
||||
baseTheme = theme,
|
||||
initialBackgroundColor = initialBackgroundColor,
|
||||
initialTintColor = initialTintColor,
|
||||
onColorChange = onColorChange,
|
||||
onTypeChange = onTypeChange,
|
||||
)
|
||||
|
||||
@@ -1550,7 +1550,14 @@
|
||||
<string name="background_image_scale_repeat">Repeat</string>
|
||||
<string name="background_image_scale_fill">Fill</string>
|
||||
<string name="background_image_scale_fit">Fit</string>
|
||||
<string name="background_image_apply">Apply</string>
|
||||
<string name="background_image_advanced_settings">Advanced settings</string>
|
||||
<string name="chat_theme_reset_to_global_theme">Reset to global theme</string>
|
||||
<string name="chat_theme_set_default_theme">Set default theme</string>
|
||||
<string name="chat_theme_apply_to_mode">Apply to</string>
|
||||
<string name="chat_theme_apply_to_all_modes">all themes</string>
|
||||
<string name="chat_theme_apply_to_light_mode">light theme</string>
|
||||
<string name="chat_theme_apply_to_dark_mode">dark theme</string>
|
||||
|
||||
|
||||
<!-- Preferences.kt -->
|
||||
<string name="chat_preferences_you_allow">You allow</string>
|
||||
|
||||
Reference in New Issue
Block a user