diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index 7542a0b8c6..4c8bfbc3ca 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -203,7 +203,9 @@ fun MainScreen() { } if (appPlatform.isAndroid) { AndroidWrapInCallLayout { - ModalManager.fullscreen.showInView() + ActiveChatThemeProvider { + ModalManager.fullscreen.showInView() + } } SwitchingUsersView() } @@ -422,45 +424,68 @@ fun EndPartOfScreen() { ModalManager.end.showInView() } +// Toolbars rendered alongside or on behalf of the active chat (the chatlist column +// in desktop two-pane, modals opened from a chat on Android) need to share that +// chat's theme. Wrap such UI surfaces with this so panelBackgroundColor() reads +// the right tint via LocalActiveTheme. +@Composable +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 + } + CompositionLocalProvider(LocalActiveTheme provides effectiveTheme, content = content) +} + // Spec: spec/client/navigation.md#DesktopScreen @Composable fun DesktopScreen(userPickerState: MutableStateFlow) { - Box(Modifier.width(DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier)) { - StartPartOfScreen(userPickerState) - tryOrShowError("UserPicker", error = {}) { - UserPicker(chatModel, userPickerState, setPerformLA = AppLock::setPerformLA) + ActiveChatThemeProvider { + Box(Modifier.width(DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier)) { + StartPartOfScreen(userPickerState) + tryOrShowError("UserPicker", error = {}) { + UserPicker(chatModel, userPickerState, setPerformLA = AppLock::setPerformLA) + } } - } - Box(Modifier.widthIn(max = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier)) { - ModalManager.start.showInView() - SwitchingUsersView() - } - Row(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier).clipToBounds()) { - Box(Modifier.widthIn(min = DEFAULT_MIN_CENTER_MODAL_WIDTH).weight(1f)) { - CenterPartOfScreen() + Box(Modifier.widthIn(max = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier)) { + ModalManager.start.showInView() + SwitchingUsersView() } - if (ModalManager.end.hasModalsOpen()) { - VerticalDivider() + Row(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier).clipToBounds()) { + Box(Modifier.widthIn(min = DEFAULT_MIN_CENTER_MODAL_WIDTH).weight(1f)) { + CenterPartOfScreen() + } + if (ModalManager.end.hasModalsOpen()) { + VerticalDivider() + } + Box(Modifier.widthIn(max = DEFAULT_END_MODAL_WIDTH * fontSizeSqrtMultiplier).clipToBounds()) { + EndPartOfScreen() + } } - Box(Modifier.widthIn(max = DEFAULT_END_MODAL_WIDTH * fontSizeSqrtMultiplier).clipToBounds()) { - EndPartOfScreen() + if (userPickerState.collectAsState().value.isVisible() || (ModalManager.start.hasModalsOpen && !ModalManager.center.hasModalsOpen)) { + Box( + Modifier + .fillMaxSize() + .padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier) + .clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = { + if (chatModel.centerPanelBackgroundClickHandler == null || chatModel.centerPanelBackgroundClickHandler?.invoke() == false) { + ModalManager.start.closeModals() + userPickerState.value = AnimatedViewState.HIDING + } + }) + ) } + VerticalDivider(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier)) + ModalManager.fullscreen.showInView() } - if (userPickerState.collectAsState().value.isVisible() || (ModalManager.start.hasModalsOpen && !ModalManager.center.hasModalsOpen)) { - Box( - Modifier - .fillMaxSize() - .padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier) - .clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = { - if (chatModel.centerPanelBackgroundClickHandler == null || chatModel.centerPanelBackgroundClickHandler?.invoke() == false) { - ModalManager.start.closeModals() - userPickerState.value = AnimatedViewState.HIDING - } - }) - ) - } - VerticalDivider(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier)) - ModalManager.fullscreen.showInView() } @Composable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Color.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Color.kt index c4013d9e6d..bd6a96f391 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Color.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Color.kt @@ -3,7 +3,6 @@ package chat.simplex.common.ui.theme import androidx.compose.material.LocalContentColor import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.ui.graphics.* import androidx.compose.ui.graphics.colorspace.ColorSpaces import chat.simplex.common.platform.appPlatform @@ -57,13 +56,12 @@ val NoteFolderIconColor: Color @Composable get() = MaterialTheme.appColors.prima * BLACK and SIMPLEX themes are not tinted (BLACK keeps pure dark, SIMPLEX has its own custom panel). */ @Composable fun panelBackgroundColor(): Color { - return currentWallpaperPanelTint() - ?: MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.97f) + val state = LocalActiveTheme.current + return currentWallpaperPanelTint(state) + ?: state.colors.background.mixWith(state.colors.onBackground, 0.97f) } -@Composable -private fun currentWallpaperPanelTint(): Color? { - val state = CurrentColors.collectAsState().value +private fun currentWallpaperPanelTint(state: ThemeManager.ActiveTheme): Color? { val type = state.wallpaper.type as? WallpaperType.Preset ?: return null val preset = PresetWallpaper.from(type.filename) ?: return null val hue = preset.hue(state.base) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt index 2069d7f67a..2d4dfd53d7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt @@ -792,6 +792,7 @@ expect fun isSystemInDarkTheme(): Boolean internal val LocalAppColors = staticCompositionLocalOf { LightColorPaletteApp } internal val LocalAppWallpaper = staticCompositionLocalOf { AppWallpaper() } +internal val LocalActiveTheme = staticCompositionLocalOf { CurrentColors.value } val MaterialTheme.appColors: AppColors @Composable @@ -873,6 +874,7 @@ fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) { LocalContentColor provides MaterialTheme.colors.onBackground, LocalAppColors provides rememberedAppColors, LocalAppWallpaper provides rememberedWallpaper, + LocalActiveTheme provides theme, LocalDensity provides density, content = content ) @@ -901,6 +903,7 @@ fun SimpleXThemeOverride(theme: ThemeManager.ActiveTheme, content: @Composable ( LocalContentColor provides MaterialTheme.colors.onBackground, LocalAppColors provides rememberedAppColors, LocalAppWallpaper provides rememberedWallpaper, + LocalActiveTheme provides theme, content = content) } )