mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-08-22 03:40:17 +00:00
refactor: extract repeated theme/color resolution patterns
Three duplicated patterns surfaced by the audit, each consolidated into a single named helper. 1. ThemeOverrides.toColors and toAppColors had the same five-source priority chain (per-chat → per-user → own → preset → base) inlined for nine non-bubble fields. Move it into resolveColor so each call site states the data, not the resolution rule. (Bubble fields keep their wallpaper-aware logic — different rule, separate concern.) 2. The chat's effective theme — chatInfo.uiThemes.preferredMode resolved through ThemeManager.currentColors — was computed inline in ChatView (for SimpleXThemeOverride) and again in App.ActiveChatThemeProvider (for the chatlist column / Android modal stack). Pull both into a shared rememberActiveChatTheme. Both call sites also adopt the wider remember key (whole theme, not just base) that the toolbar fix already needed; behavior is unchanged when the chat has no override. 3. The toolbar code-output copy in Appearance reimplemented oklch conversion inline (Color → Oklab → atan2 → degrees) instead of calling the existing Color.toOklch() extension. Replace with the extension, which also adds a small near-zero-chroma guard the inline version was missing. Pure refactor — no user-visible behavior change. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
19318576f3
commit
d947050f31
@@ -433,15 +433,7 @@ private fun ActiveChatThemeProvider(content: @Composable () -> Unit) {
|
||||
val theme by CurrentColors.collectAsState()
|
||||
val chatId by chatModel.chatId
|
||||
val activeChat = chatId?.let { id -> chatModel.chats.value.firstOrNull { it.chatInfo.id == id } }
|
||||
val effectiveTheme = remember(activeChat?.chatInfo, theme) {
|
||||
val perChatTheme = when (val ci = activeChat?.chatInfo) {
|
||||
is ChatInfo.Direct -> ci.contact.uiThemes?.preferredMode(!theme.colors.isLight)
|
||||
is ChatInfo.Group -> ci.groupInfo.uiThemes?.preferredMode(!theme.colors.isLight)
|
||||
else -> null
|
||||
}
|
||||
if (perChatTheme != null) ThemeManager.currentColors(null, perChatTheme, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
|
||||
else theme
|
||||
}
|
||||
val effectiveTheme = rememberActiveChatTheme(activeChat?.chatInfo, theme)
|
||||
CompositionLocalProvider(LocalActiveTheme provides effectiveTheme, content = content)
|
||||
}
|
||||
|
||||
|
||||
+44
-9
@@ -11,6 +11,7 @@ import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.*
|
||||
import chat.simplex.common.model.ChatController
|
||||
import chat.simplex.common.model.ChatController.appPrefs
|
||||
import chat.simplex.common.model.ChatInfo
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.ui.theme.ThemeManager.colorFromReadableHex
|
||||
import chat.simplex.common.ui.theme.ThemeManager.toReadableHex
|
||||
@@ -345,6 +346,22 @@ data class ThemesFile(
|
||||
val themes: List<ThemeOverrides> = emptyList()
|
||||
)
|
||||
|
||||
// Theme color priority: per-chat → per-user → this override's own colors →
|
||||
// preset-wallpaper-derived theme → base palette. Used by toColors / toAppColors
|
||||
// for every non-bubble field; bubble colors have wallpaper-aware logic that
|
||||
// can't share this chain.
|
||||
private fun resolveColor(
|
||||
perChatColor: String?,
|
||||
perUserColor: String?,
|
||||
ownColor: String?,
|
||||
presetColor: Color?,
|
||||
baseColor: Color,
|
||||
): Color = perChatColor?.colorFromReadableHex()
|
||||
?: perUserColor?.colorFromReadableHex()
|
||||
?: ownColor?.colorFromReadableHex()
|
||||
?: presetColor
|
||||
?: baseColor
|
||||
|
||||
// Spec: spec/services/theme.md#ThemeOverrides
|
||||
@Serializable
|
||||
data class ThemeOverrides (
|
||||
@@ -395,12 +412,12 @@ data class ThemeOverrides (
|
||||
DefaultTheme.BLACK -> BlackColorPalette
|
||||
}
|
||||
return baseColors.copy(
|
||||
primary = perChatTheme?.primary?.colorFromReadableHex() ?: perUserTheme?.primary?.colorFromReadableHex() ?: colors.primary?.colorFromReadableHex() ?: presetWallpaperTheme?.primary ?: baseColors.primary,
|
||||
primaryVariant = perChatTheme?.primaryVariant?.colorFromReadableHex() ?: perUserTheme?.primaryVariant?.colorFromReadableHex() ?: colors.primaryVariant?.colorFromReadableHex() ?: presetWallpaperTheme?.primaryVariant ?: baseColors.primaryVariant,
|
||||
secondary = perChatTheme?.secondary?.colorFromReadableHex() ?: perUserTheme?.secondary?.colorFromReadableHex() ?: colors.secondary?.colorFromReadableHex() ?: presetWallpaperTheme?.secondary ?: baseColors.secondary,
|
||||
secondaryVariant = perChatTheme?.secondaryVariant?.colorFromReadableHex() ?: perUserTheme?.secondaryVariant?.colorFromReadableHex() ?: colors.secondaryVariant?.colorFromReadableHex() ?: presetWallpaperTheme?.secondaryVariant ?: baseColors.secondaryVariant,
|
||||
background = perChatTheme?.background?.colorFromReadableHex() ?: perUserTheme?.background?.colorFromReadableHex() ?: colors.background?.colorFromReadableHex() ?: presetWallpaperTheme?.background ?: baseColors.background,
|
||||
surface = perChatTheme?.surface?.colorFromReadableHex() ?: perUserTheme?.surface?.colorFromReadableHex() ?: colors.surface?.colorFromReadableHex() ?: presetWallpaperTheme?.surface ?: baseColors.surface,
|
||||
primary = resolveColor(perChatTheme?.primary, perUserTheme?.primary, colors.primary, presetWallpaperTheme?.primary, baseColors.primary),
|
||||
primaryVariant = resolveColor(perChatTheme?.primaryVariant, perUserTheme?.primaryVariant, colors.primaryVariant, presetWallpaperTheme?.primaryVariant, baseColors.primaryVariant),
|
||||
secondary = resolveColor(perChatTheme?.secondary, perUserTheme?.secondary, colors.secondary, presetWallpaperTheme?.secondary, baseColors.secondary),
|
||||
secondaryVariant = resolveColor(perChatTheme?.secondaryVariant, perUserTheme?.secondaryVariant, colors.secondaryVariant, presetWallpaperTheme?.secondaryVariant, baseColors.secondaryVariant),
|
||||
background = resolveColor(perChatTheme?.background, perUserTheme?.background, colors.background, presetWallpaperTheme?.background, baseColors.background),
|
||||
surface = resolveColor(perChatTheme?.surface, perUserTheme?.surface, colors.surface, presetWallpaperTheme?.surface, baseColors.surface),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -417,9 +434,9 @@ data class ThemeOverrides (
|
||||
val receivedMessageFallback = colors.receivedMessage?.colorFromReadableHex() ?: presetWallpaperTheme?.receivedMessage ?: baseColors.receivedMessage
|
||||
val receivedQuoteFallback = colors.receivedQuote?.colorFromReadableHex() ?: presetWallpaperTheme?.receivedQuote ?: baseColors.receivedQuote
|
||||
return baseColors.copy(
|
||||
title = perChatTheme?.title?.colorFromReadableHex() ?: perUserTheme?.title?.colorFromReadableHex() ?: colors.title?.colorFromReadableHex() ?: presetWallpaperTheme?.title ?: baseColors.title,
|
||||
primaryVariant2 = perChatTheme?.primaryVariant2?.colorFromReadableHex() ?: perUserTheme?.primaryVariant2?.colorFromReadableHex() ?: colors.primaryVariant2?.colorFromReadableHex() ?: presetWallpaperTheme?.primaryVariant2 ?: baseColors.primaryVariant2,
|
||||
toolbar = perChatTheme?.toolbar?.colorFromReadableHex() ?: perUserTheme?.toolbar?.colorFromReadableHex() ?: colors.toolbar?.colorFromReadableHex() ?: presetWallpaperTheme?.toolbar ?: baseColors.toolbar,
|
||||
title = resolveColor(perChatTheme?.title, perUserTheme?.title, colors.title, presetWallpaperTheme?.title, baseColors.title),
|
||||
primaryVariant2 = resolveColor(perChatTheme?.primaryVariant2, perUserTheme?.primaryVariant2, colors.primaryVariant2, presetWallpaperTheme?.primaryVariant2, baseColors.primaryVariant2),
|
||||
toolbar = resolveColor(perChatTheme?.toolbar, perUserTheme?.toolbar, colors.toolbar, presetWallpaperTheme?.toolbar, baseColors.toolbar),
|
||||
sentMessage = if (perChatTheme?.sentMessage != null) perChatTheme.sentMessage.colorFromReadableHex()
|
||||
else if (perUserTheme != null && (perChatWallpaperType == null || perUserWallpaperType == null || perChatWallpaperType.sameType(perUserWallpaperType))) perUserTheme.sentMessage?.colorFromReadableHex() ?: sentMessageFallback
|
||||
else sentMessageFallback,
|
||||
@@ -882,6 +899,24 @@ fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) {
|
||||
)
|
||||
}
|
||||
|
||||
/** Resolve the active theme to apply to a UI surface that is "owned" by a
|
||||
* specific chat. Returns the chat's theme override (resolved through the
|
||||
* per-user / app-settings chain) when one exists; otherwise returns the
|
||||
* passed [theme] unchanged. Same logic used at chat scope (SimpleXThemeOverride
|
||||
* caller in ChatView) and at scopes that follow the active chat from the
|
||||
* outside (ActiveChatThemeProvider in App). */
|
||||
@Composable
|
||||
fun rememberActiveChatTheme(chatInfo: ChatInfo?, theme: ThemeManager.ActiveTheme): ThemeManager.ActiveTheme =
|
||||
remember(chatInfo, theme) {
|
||||
val perChatTheme = when (chatInfo) {
|
||||
is ChatInfo.Direct -> chatInfo.contact.uiThemes?.preferredMode(!theme.colors.isLight)
|
||||
is ChatInfo.Group -> chatInfo.groupInfo.uiThemes?.preferredMode(!theme.colors.isLight)
|
||||
else -> null
|
||||
}
|
||||
if (perChatTheme != null) ThemeManager.currentColors(null, perChatTheme, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
|
||||
else theme
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SimpleXThemeOverride(theme: ThemeManager.ActiveTheme, content: @Composable () -> Unit) {
|
||||
MaterialTheme(
|
||||
|
||||
+3
-3
@@ -252,11 +252,11 @@ fun ChatView(
|
||||
when (chatInfo) {
|
||||
is ChatInfo.Direct, is ChatInfo.Group, is ChatInfo.Local -> {
|
||||
var groupMembersJob: Job = remember { Job() }
|
||||
val perChatTheme = remember(chatInfo, CurrentColors.value.base) { if (chatInfo is ChatInfo.Direct) chatInfo.contact.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) else if (chatInfo is ChatInfo.Group) chatInfo.groupInfo.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) else null }
|
||||
val overrides = if (perChatTheme != null) ThemeManager.currentColors(null, perChatTheme, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) else null
|
||||
val theme by CurrentColors.collectAsState()
|
||||
val effectiveTheme = rememberActiveChatTheme(chatInfo, theme)
|
||||
val fullDeleteAllowed = remember(chatInfo) { chatInfo.featureEnabled(ChatFeature.FullDelete) }
|
||||
|
||||
SimpleXThemeOverride(overrides ?: CurrentColors.collectAsState().value) {
|
||||
SimpleXThemeOverride(effectiveTheme) {
|
||||
val onSearchValueChanged: (String) -> Unit = onSearchValueChanged@{ value ->
|
||||
val sameText = searchText.value == value
|
||||
// showSearch can be false with empty text when it was closed manually after clicking on message from search to load .around it
|
||||
|
||||
+1
-4
@@ -1332,10 +1332,7 @@ object AppearanceScope {
|
||||
if (name == ThemeColor.TOOLBAR && appPrefs.developerTools.get()) {
|
||||
SectionSpacer()
|
||||
val toolbarCode = if (currentColor.alpha >= 0.01f) {
|
||||
val oklab = currentColor.convert(androidx.compose.ui.graphics.colorspace.ColorSpaces.Oklab)
|
||||
val L = oklab.component1(); val a = oklab.component2(); val b = oklab.component3()
|
||||
val C = kotlin.math.sqrt(a * a + b * b)
|
||||
val H = Math.toDegrees(kotlin.math.atan2(b.toDouble(), a.toDouble())).toFloat().let { if (it < 0) it + 360f else it }
|
||||
val (L, C, H) = currentColor.toOklch()
|
||||
"toolbar = oklch(${L}f, ${C}f, ${H}f, ${currentColor.alpha}f),"
|
||||
} else "// toolbar: no tint (transparent)"
|
||||
val preset = (wallpaperType as? WallpaperType.Preset)?.let { PresetWallpaper.from(it.filename) }
|
||||
|
||||
Reference in New Issue
Block a user