From 07fd79dcfa32e3762581fafe48b6cc9de71c20c8 Mon Sep 17 00:00:00 2001 From: Avently <7953703+avently@users.noreply.github.com> Date: Wed, 1 May 2024 21:11:49 +0700 Subject: [PATCH] changes --- apps/multiplatform/common/build.gradle.kts | 6 + .../views/usersettings/Appearance.android.kt | 92 +++- .../chat/simplex/common/model/ChatModel.kt | 2 +- .../chat/simplex/common/model/SimpleXAPI.kt | 12 +- .../chat/simplex/common/ui/theme/Theme.kt | 36 +- .../simplex/common/ui/theme/ThemeManager.kt | 2 +- .../simplex/common/views/chat/ChatView.kt | 13 +- .../simplex/common/views/chat/ComposeView.kt | 2 +- .../views/helpers/ChatViewBackground.kt | 60 +-- .../simplex/common/views/helpers/Utils.kt | 6 +- .../common/views/usersettings/Appearance.kt | 501 +++++++++++------- .../commonMain/resources/MR/base/strings.xml | 2 + .../views/usersettings/Appearance.desktop.kt | 18 +- 13 files changed, 500 insertions(+), 252 deletions(-) diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index 42e4ac2591..19b8112443 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -91,6 +91,12 @@ kotlin { // Calls lifecycle listener implementation("androidx.lifecycle:lifecycle-process:2.4.1") + + implementation("com.github.SmartToolFactory:Compose-Color-Picker-Bundle:1.2.0") { + // This library doesn't exist anymore. Excluding it helps without making any problems + exclude(group = "com.github.SmartToolFactory", module = "Compose-Image-Cropper") + } + implementation("com.github.SmartToolFactory:Compose-Extended-Colors:1.0.0-alpha07") } } val desktopMain by getting { diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt index 3b95bb9fda..6f7f6759ba 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt @@ -16,12 +16,14 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.MaterialTheme.colors import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.* import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.Dp import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat @@ -36,6 +38,14 @@ import chat.simplex.common.helpers.APPLICATION_ID import chat.simplex.common.helpers.saveAppLocale import chat.simplex.common.views.usersettings.AppearanceScope.ColorEditor import chat.simplex.res.MR +import com.godaddy.android.colorpicker.ClassicColorPicker +import com.godaddy.android.colorpicker.HsvColor +import com.smarttoolfactory.colorpicker.model.ColorModel +import com.smarttoolfactory.colorpicker.picker.* +import com.smarttoolfactory.colorpicker.selector.SelectorRectHueSaturationHSV +import com.smarttoolfactory.colorpicker.slider.SliderCircleColorDisplayValueHSV +import com.smarttoolfactory.colorpicker.widget.ColorDisplayExposedSelectionMenu +import com.smarttoolfactory.extendedcolors.util.ColorUtil import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.compose.painterResource import kotlinx.coroutines.delay @@ -151,8 +161,6 @@ fun AppearanceScope.AppearanceLayout( SectionDividerSpaced(maxTopPadding = true) ThemesSection(systemDarkTheme, showSettingsModal, editColor) - SectionDividerSpaced(maxTopPadding = true) - BackgroundImageSection(showSettingsModal) SectionBottomSpacer() } } @@ -177,3 +185,83 @@ fun PreviewAppearanceSettings() { ) } } + +@Composable +actual fun ColorPicker(initialColor: Color, onColorChanged: (Color) -> Unit) { + ColorPickerRectHueSaturationHSV( + modifier = Modifier + .fillMaxWidth() + .height(300.dp) + .padding(horizontal = DEFAULT_PADDING * 2), + initialColor = initialColor, + onColorChange = { color: Color, _ -> + onColorChanged(color) + } + ) +} + + +@Composable +fun ColorPickerRectHueSaturationHSV( + modifier: Modifier = Modifier, + selectionRadius: Dp = 8.dp, + initialColor: Color, + onColorChange: (Color, String) -> Unit +) { + + val hsvArray = ColorUtil.colorToHSV(initialColor) + + var hue by remember { mutableStateOf(hsvArray[0]) } + var saturation by remember { mutableStateOf(hsvArray[1]) } + var value by remember { mutableStateOf(hsvArray[2]) } + var alpha by remember { mutableStateOf(initialColor.alpha) } + + val currentColor = + Color.hsv(hue = hue, saturation = saturation, value = value, alpha = alpha) + + var colorModel by remember { mutableStateOf(ColorModel.HSV) } + + onColorChange(currentColor, ColorUtil.colorToHexAlpha(currentColor)) + + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally + ) { + SelectorRectHueSaturationHSV( + modifier = Modifier + .fillMaxWidth() + .aspectRatio(4 / 3f), + hue = hue, + saturation = saturation, + selectionRadius = selectionRadius, + onChange = { h, s -> + hue = h + saturation = s + }, + ) + + Column(modifier = Modifier.padding(8.dp)) { + SliderCircleColorDisplayValueHSV( + hue = hue, + saturation = saturation, + value = value, + alpha = alpha, + onValueChange = { + value = it + }, + onAlphaChange = { + alpha = it + } + ) + + // Produce crash in runtime. Probably because it relies on old Compose + /*ColorDisplayExposedSelectionMenu( + color = currentColor, + colorModel = colorModel, + onColorModelChange = { + colorModel = it + } + )*/ + } + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index d5c9c20490..81cedf3a5e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -137,7 +137,7 @@ object ChatModel { val processedCriticalError: ProcessedErrors = ProcessedErrors(60_000) val processedInternalError: ProcessedErrors = ProcessedErrors(20_000) - val backgroundImage by lazy { mutableStateOf(getBackgroundImageOrDefault()) } + val backgroundImage: MutableState by lazy { mutableStateOf(getBackgroundImageOrDefault()) } fun getUser(userId: Long): User? = if (currentUser.value?.userId == userId) { currentUser.value diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 755678c15f..43a4f46216 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -175,17 +175,17 @@ class AppPreferences { json.decodeFromString(MapSerializer(String.serializer(), ThemeOverrides.serializer()), it) }, settingsThemes) val profileImageCornerRadius = mkFloatPreference(SHARED_PREFS_PROFILE_IMAGE_CORNER_RADIUS, 22.5f) - private val _backgroundImageType = mkStrPreference(SHARED_PREFS_BACKGROUND_IMAGE, json.encodeToString(BackgroundImageType.default)) - val backgroundImageType: SharedPreference = SharedPreference( - get = fun(): BackgroundImageType { - val value = _backgroundImageType.get() ?: return BackgroundImageType.default + private val _backgroundImageType = mkStrPreference(SHARED_PREFS_BACKGROUND_IMAGE, null) + val backgroundImageType: SharedPreference = SharedPreference( + get = fun(): BackgroundImageType? { + val value = _backgroundImageType.get() ?: return null return try { json.decodeFromString(value) } catch (e: Throwable) { - BackgroundImageType.default + null } }, - set = fun(type: BackgroundImageType) { _backgroundImageType.set(json.encodeToString(type)) } + set = fun(type: BackgroundImageType?) { _backgroundImageType.set(json.encodeToString(type)) } ) val whatsNewVersion = mkStrPreference(SHARED_PREFS_WHATS_NEW_VERSION, null) 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 e6709d056a..6ebfee6a88 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 @@ -48,13 +48,15 @@ enum class DefaultTheme { data class AppColors( val title: Color, val sentMessage: Color, - val receivedMessage: Color + val receivedMessage: Color, + val wallpaperBackground: Color?, + val wallpaperTint: Color?, ) enum class ThemeColor { - PRIMARY, PRIMARY_VARIANT, SECONDARY, SECONDARY_VARIANT, BACKGROUND, SURFACE, TITLE, SENT_MESSAGE, RECEIVED_MESSAGE; + PRIMARY, PRIMARY_VARIANT, SECONDARY, SECONDARY_VARIANT, BACKGROUND, SURFACE, TITLE, SENT_MESSAGE, RECEIVED_MESSAGE, WALLPAPER_BACKGROUND, WALLPAPER_TINT; - fun fromColors(colors: Colors, appColors: AppColors): Color { + fun fromColors(colors: Colors, appColors: AppColors): Color? { return when (this) { PRIMARY -> colors.primary PRIMARY_VARIANT -> colors.primaryVariant @@ -65,6 +67,8 @@ enum class ThemeColor { TITLE -> appColors.title SENT_MESSAGE -> appColors.sentMessage RECEIVED_MESSAGE -> appColors.receivedMessage + WALLPAPER_BACKGROUND -> appColors.wallpaperBackground + WALLPAPER_TINT -> appColors.wallpaperTint } } @@ -79,6 +83,8 @@ enum class ThemeColor { TITLE -> generalGetString(MR.strings.color_title) SENT_MESSAGE -> generalGetString(MR.strings.color_sent_message) RECEIVED_MESSAGE -> generalGetString(MR.strings.color_received_message) + WALLPAPER_BACKGROUND -> generalGetString(MR.strings.color_wallpaper_background) + WALLPAPER_TINT -> generalGetString(MR.strings.color_wallpaper_tint) } } @@ -96,6 +102,8 @@ data class ThemeColors( val title: String? = null, val sentMessage: String? = null, val receivedMessage: String? = null, + val wallpaperBackground: String? = null, + val wallpaperTint: String? = null, ) { fun toColors(base: DefaultTheme): Colors { val baseColors = when (base) { @@ -127,6 +135,8 @@ data class ThemeColors( title = title?.colorFromReadableHex() ?: baseColors.title, sentMessage = sentMessage?.colorFromReadableHex() ?: baseColors.sentMessage, receivedMessage = receivedMessage?.colorFromReadableHex() ?: baseColors.receivedMessage, + wallpaperBackground = wallpaperBackground?.colorFromReadableHex() ?: baseColors.wallpaperBackground, + wallpaperTint = wallpaperTint?.colorFromReadableHex() ?: baseColors.wallpaperTint, ) } @@ -142,7 +152,9 @@ data class ThemeColors( surface = c.surface.toReadableHex(), title = ac.title.toReadableHex(), sentMessage = ac.sentMessage.toReadableHex(), - receivedMessage = ac.receivedMessage.toReadableHex() + receivedMessage = ac.receivedMessage.toReadableHex(), + wallpaperBackground = ac.wallpaperBackground?.toReadableHex(), + wallpaperTint = ac.wallpaperTint?.toReadableHex(), ) } } @@ -152,7 +164,7 @@ data class ThemeOverrides ( val base: DefaultTheme, val colors: ThemeColors ) { - fun withUpdatedColor(name: ThemeColor, color: String): ThemeOverrides { + 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) @@ -163,6 +175,8 @@ data class ThemeOverrides ( 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(wallpaperBackground = color) + ThemeColor.WALLPAPER_TINT -> colors.copy(wallpaperTint = color) }) } } @@ -210,7 +224,9 @@ val DarkColorPalette = darkColors( val DarkColorPaletteApp = AppColors( title = SimplexBlue, sentMessage = SentMessageColor, - receivedMessage = Color(0x20B1B0B5) + receivedMessage = Color(0x20B1B0B5), + wallpaperBackground = null, + wallpaperTint = null, ) val LightColorPalette = lightColors( @@ -229,7 +245,9 @@ val LightColorPalette = lightColors( val LightColorPaletteApp = AppColors( title = SimplexBlue, sentMessage = SentMessageColor, - receivedMessage = Color(0x20B1B0B5) + receivedMessage = Color(0x20B1B0B5), + wallpaperBackground = null, + wallpaperTint = null, ) val SimplexColorPalette = darkColors( @@ -249,7 +267,9 @@ val SimplexColorPalette = darkColors( val SimplexColorPaletteApp = AppColors( title = Color(0xFF267BE5), sentMessage = SentMessageColor, - receivedMessage = Color(0x20B1B0B5) + receivedMessage = Color(0x20B1B0B5), + wallpaperBackground = null, + wallpaperTint = null, ) val CurrentColors: MutableStateFlow = MutableStateFlow(ThemeManager.currentColors(isInNightMode())) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt index 4a25af8355..0419599c0c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt @@ -125,7 +125,7 @@ object ThemeManager { } val overrides = appPrefs.themeOverrides.get().toMutableMap() val prevValue = overrides[nonSystemThemeName] ?: ThemeOverrides(base = CurrentColors.value.base, colors = ThemeColors()) - overrides[nonSystemThemeName] = prevValue.withUpdatedColor(name, colorToSet.toReadableHex()) + overrides[nonSystemThemeName] = prevValue.withUpdatedColor(name, colorToSet?.toReadableHex()) appPrefs.themeOverrides.set(overrides) CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index a6c7d602fb..58336ca33d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -597,14 +597,17 @@ fun ChatLayout( drawerContentColor = LocalContentColor.current, backgroundColor = Color.Unspecified ) { contentPadding -> - val backgroundImage = remember { chatModel.backgroundImage } - val backgroundImageType = remember { appPrefs.backgroundImageType.state } - val defaultBackgroundColor = backgroundImageType.value.defaultBackgroundColor - val defaultTintColor = backgroundImageType.value.defaultTintColor + val backgroundImage = remember { chatModel.backgroundImage }.value + val backgroundImageType = remember { appPrefs.backgroundImageType.state }.value + val defaultBackgroundColor = backgroundImageType?.defaultBackgroundColor + val defaultTintColor = backgroundImageType?.defaultTintColor BoxWithConstraints(Modifier .fillMaxHeight() .background(MaterialTheme.colors.background) - .drawBehind { chatViewBackground(backgroundImage.value, backgroundImageType.value, defaultBackgroundColor, defaultTintColor) } + .then(if (backgroundImage != null && backgroundImageType != null && defaultBackgroundColor != null && defaultTintColor != null) + Modifier.drawBehind { chatViewBackground(backgroundImage, backgroundImageType, defaultBackgroundColor, defaultTintColor) } + else + Modifier) .padding(contentPadding) ) { ChatItemsList( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index 26ff8796d4..d4e9f0dea5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -861,7 +861,7 @@ fun ComposeView( } } Row( - modifier = Modifier.padding(end = 8.dp), + modifier = Modifier.background(MaterialTheme.colors.background).padding(end = 8.dp), verticalAlignment = Alignment.Bottom, ) { val isGroupAndProhibitedFiles = chat.chatInfo is ChatInfo.Group && !chat.chatInfo.groupInfo.fullGroupPreferences.files.on(chat.chatInfo.groupInfo.membership) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatViewBackground.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatViewBackground.kt index cf457b2934..55a45d16a2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatViewBackground.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatViewBackground.kt @@ -9,9 +9,8 @@ import androidx.compose.ui.graphics.drawscope.clipRect import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize +import chat.simplex.common.ui.theme.CurrentColors import chat.simplex.res.MR -import chat.simplex.common.ui.theme.ThemeManager.colorFromReadableHex -import chat.simplex.common.ui.theme.ThemeManager.toReadableHex import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.StringResource import kotlinx.serialization.SerialName @@ -20,13 +19,13 @@ import kotlin.math.* @Serializable enum class PredefinedBackgroundImage(val res: ImageResource, val filename: String, val text: StringResource, val type: BackgroundImageType) { - @SerialName("cat") CAT(MR.images.background_cat, "background_cat", MR.strings.background_cat, BackgroundImageType.Repeated(false, "background_cat", 0.5f)), - @SerialName("hearts") HEARTS(MR.images.background_hearts, "background_hearts", MR.strings.background_hearts, BackgroundImageType.Repeated(false, "background_hearts", 0.5f)), - @SerialName("school") SCHOOL(MR.images.background_school, "background_school", MR.strings.background_school, BackgroundImageType.Repeated(false, "background_school", 0.5f)), - @SerialName("internet") INTERNET(MR.images.background_internet, "background_internet", MR.strings.background_internet, BackgroundImageType.Repeated(false, "background_internet", 0.5f)), - @SerialName("space") SPACE(MR.images.background_space, "background_space", MR.strings.background_space, BackgroundImageType.Repeated(false, "background_space", 0.5f)), - @SerialName("pets") PETS(MR.images.background_pets, "background_pets", MR.strings.background_pets, BackgroundImageType.Repeated(false, "background_pets", 0.5f)), - @SerialName("rabbit") RABBIT(MR.images.background_rabbit, "background_rabbit", MR.strings.background_rabbit, BackgroundImageType.Repeated(false, "background_rabbit", 0.5f)); + @SerialName("cat") CAT(MR.images.background_cat, "simplex_cat", MR.strings.background_cat, BackgroundImageType.Repeated("simplex_cat", 0.5f)), + @SerialName("hearts") HEARTS(MR.images.background_hearts, "simplex_hearts", MR.strings.background_hearts, BackgroundImageType.Repeated("simplex_hearts", 0.5f)), + @SerialName("school") SCHOOL(MR.images.background_school, "simplex_school", MR.strings.background_school, BackgroundImageType.Repeated("simplex_school", 0.5f)), + @SerialName("internet") INTERNET(MR.images.background_internet, "simplex_internet", MR.strings.background_internet, BackgroundImageType.Repeated("simplex_internet", 0.5f)), + @SerialName("space") SPACE(MR.images.background_space, "simplex_space", MR.strings.background_space, BackgroundImageType.Repeated("simplex_space", 0.5f)), + @SerialName("pets") PETS(MR.images.background_pets, "simplex_pets", MR.strings.background_pets, BackgroundImageType.Repeated("simplex_pets", 0.5f)), + @SerialName("rabbit") RABBIT(MR.images.background_rabbit, "simplex_rabbit", MR.strings.background_rabbit, BackgroundImageType.Repeated("simplex_rabbit", 0.5f)); companion object { fun from(filename: String): PredefinedBackgroundImage? = @@ -45,55 +44,24 @@ enum class BackgroundImageScale(val contentScale: ContentScale, val text: String @Serializable sealed class BackgroundImageType { - abstract val custom: Boolean abstract val filename: String @Serializable @SerialName("repeated") data class Repeated( - override val custom: Boolean = true, override val filename: String, val scale: Float, - val backgroundColor: String? = null, - val tintColor: String? = null ): BackgroundImageType() @Serializable @SerialName("static") data class Static( - override val custom: Boolean = true, override val filename: String, val scale: BackgroundImageScale, - val backgroundColor: String? = null, - val tintColor: String? = null ): BackgroundImageType() - val background: Color? by lazy { - when (this) { - is Repeated -> backgroundColor?.colorFromReadableHex() - is Static -> backgroundColor?.colorFromReadableHex() - } - } + val background: Color? + get() = CurrentColors.value.appColors.wallpaperBackground - val tint: Color? by lazy { - when (this) { - is Repeated -> tintColor?.colorFromReadableHex() - is Static -> tintColor?.colorFromReadableHex() - } - } + val tint: Color? + get() = CurrentColors.value.appColors.wallpaperTint - fun toPredefined(): PredefinedBackgroundImage? = - when (this) { - is Repeated -> if (!custom) PredefinedBackgroundImage.from(filename) else null - is Static -> if (!custom) PredefinedBackgroundImage.from(filename) else null - } - - fun copyBackgroundColor(color: Color?): BackgroundImageType = - when (this) { - is Repeated -> copy(backgroundColor = color?.toReadableHex()) - is Static -> copy(backgroundColor = color?.toReadableHex()) - } - - fun copyTintColor(color: Color?): BackgroundImageType = - when (this) { - is Repeated -> copy(tintColor = color?.toReadableHex()) - is Static -> copy(tintColor = color?.toReadableHex()) - } + fun toPredefined(): PredefinedBackgroundImage? = PredefinedBackgroundImage.from(filename) val defaultBackgroundColor: Color @Composable get() = if (this is Static) MaterialTheme.colors.background else MaterialTheme.colors.background @@ -104,7 +72,7 @@ sealed class BackgroundImageType { companion object { val default: BackgroundImageType = - Repeated(custom = false, PredefinedBackgroundImage.CAT.filename, 1f) + Repeated(PredefinedBackgroundImage.CAT.filename, 1f) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt index 3a6b0fdd49..fc62cbc356 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt @@ -176,9 +176,9 @@ fun getBackgroundImageFromUri(uri: URI, withAlertOnException: Boolean = true): P return null } -fun getBackgroundImageOrDefault(): ImageBitmap { - val type = appPreferences.backgroundImageType.get() - val res = if (type.custom) { +fun getBackgroundImageOrDefault(filename: String? = null): ImageBitmap? { + val type = appPreferences.backgroundImageType.get() ?: return null + val res = if (type is BackgroundImageType.Static) { File(getBackgroundImageFilePath(type.filename)).inputStream().use { loadImageBitmap(it) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt index c0de6c2a1f..3e0ef84148 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt @@ -5,8 +5,11 @@ import SectionItemView import SectionItemViewSpaceBetween import SectionSpacer import SectionView +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.grid.* +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.material.MaterialTheme.colors import androidx.compose.runtime.* @@ -16,10 +19,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.* import androidx.compose.ui.graphics.* import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.unit.* import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -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.ui.theme.* @@ -35,6 +37,8 @@ import kotlinx.serialization.encodeToString import java.net.URI import java.util.* import kotlin.collections.ArrayList +import kotlin.math.ceil +import kotlin.math.roundToInt @Composable expect fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)) @@ -71,6 +75,169 @@ object AppearanceScope { } } + @Composable + fun ChatThemePreview(backgroundImage: ImageBitmap?, backgroundImageType: BackgroundImageType?, withMessages: Boolean = true) { + val themeBackgroundColor = MaterialTheme.colors.background + val defaultBackgroundColor = backgroundImageType?.defaultBackgroundColor + val defaultTintColor = backgroundImageType?.defaultTintColor + Column(Modifier + .drawBehind { + if (backgroundImage != null && backgroundImageType != null && defaultBackgroundColor != null && defaultTintColor != null) { + chatViewBackground(backgroundImage, backgroundImageType, defaultBackgroundColor, defaultTintColor) + } else { + drawRect(themeBackgroundColor) + } + } + .padding(DEFAULT_PADDING_HALF) + ) { + if (withMessages) { + PreviewChatItemView(ChatItem.getSampleData(1, CIDirection.DirectRcv(), Clock.System.now(), stringResource(MR.strings.background_image_preview_hello_bob))) + PreviewChatItemView(ChatItem.getSampleData(2, CIDirection.DirectSnd(), Clock.System.now(), stringResource(MR.strings.background_image_preview_hello_alice))) + } else { + Box(Modifier.fillMaxSize()) + } + } + } + + @Composable + fun CustomizeBackgroundImageView() { + ColumnWithScrollBar( + Modifier.fillMaxWidth(), + ) { + AppBarTitle(stringResource(MR.strings.choose_background_image_title)) + + val backgroundImage = remember { chatModel.backgroundImage }.value + val backgroundImageType = remember { appPrefs.backgroundImageType.state }.value + if (backgroundImage != null && backgroundImageType != null) { + ChatThemePreview(backgroundImage, backgroundImageType) + + SectionSpacer() + val isInDarkTheme = isInDarkTheme() + val resetColors = { + ThemeManager.saveAndApplyThemeColor(ThemeColor.WALLPAPER_BACKGROUND, null, isInDarkTheme) + ThemeManager.saveAndApplyThemeColor(ThemeColor.WALLPAPER_TINT, null, isInDarkTheme) + } + val imageTypeState = remember { + mutableStateOf(if (backgroundImageType is BackgroundImageType.Static) "" else backgroundImageType.filename) + } + val imageTypeValues = remember { + PredefinedBackgroundImage.entries.map { it.filename to generalGetString(it.text) } + ("" to generalGetString(MR.strings.background_choose_own_image)) + } + val importBackgroundImageLauncher = rememberFileChooserLauncher(true) { to: URI? -> + if (to != null) { + val res = saveBackgroundImage(to) + if (res != null) { + val (filename, backgroundImage) = res + imageTypeState.value = "" + chatModel.backgroundImage.value = backgroundImage + appPrefs.backgroundImageType.set(BackgroundImageType.Static(filename, BackgroundImageScale.CROP)) + removeBackgroundImages(filename) + resetColors() + } + } + } + ExposedDropDownSettingRow( + stringResource(MR.strings.settings_section_title_background_image), + imageTypeValues, + imageTypeState, + onSelected = { filename -> + if (filename.isEmpty()) { + withLongRunningApi { importBackgroundImageLauncher.launch("image/*") } + } else { + imageTypeState.value = filename + appPrefs.backgroundImageType.set(PredefinedBackgroundImage.from(filename)!!.type) + chatModel.backgroundImage.value = getBackgroundImageOrDefault() + removeBackgroundImages() + } + } + ) + if (backgroundImageType is BackgroundImageType.Repeated) { + val state = remember(backgroundImageType.scale) { mutableStateOf(backgroundImageType.scale) } + Row { + Text("${state.value}", Modifier.width(50.dp)) + Slider( + state.value, + valueRange = 0.2f..2f, + onValueChange = { + appPrefs.backgroundImageType.set(backgroundImageType.copy(scale = it)) + } + ) + } + } else if (backgroundImageType is BackgroundImageType.Static) { + val state = remember(backgroundImageType.scale) { mutableStateOf(backgroundImageType.scale) } + val values = remember { + BackgroundImageScale.entries.map { it to generalGetString(it.text) } + } + ExposedDropDownSettingRow( + stringResource(MR.strings.background_image_scale), + values, + state, + onSelected = { scale -> + appPrefs.backgroundImageType.set(backgroundImageType.copy(scale = scale)) + } + ) + } + + SectionSpacer() + var selectedTab by rememberSaveable { mutableStateOf(0) } + val availableTabs = listOf( + stringResource(MR.strings.background_image_background_color), + stringResource(MR.strings.background_image_tint_color), + ) + TabRow( + selectedTabIndex = selectedTab, + backgroundColor = Color.Transparent, + contentColor = MaterialTheme.colors.primary, + ) { + availableTabs.forEachIndexed { index, title -> + Tab( + selected = selectedTab == index, + onClick = { + selectedTab = index + }, + text = { Text(title, fontSize = 13.sp) }, + selectedContentColor = MaterialTheme.colors.primary, + unselectedContentColor = MaterialTheme.colors.secondary, + ) + } + } + val defaultBackgroundColor = backgroundImageType.defaultBackgroundColor + val defaultTintColor = backgroundImageType.defaultTintColor + if (selectedTab == 0) { + var currentColor by remember(backgroundImageType.background) { mutableStateOf(backgroundImageType.background ?: defaultBackgroundColor) } + ColorPicker(backgroundImageType.background ?: defaultBackgroundColor) { + currentColor = it + ThemeManager.saveAndApplyThemeColor(ThemeColor.WALLPAPER_BACKGROUND, it, isInDarkTheme) + } + val clipboard = LocalClipboardManager.current + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) { + Text(currentColor.toReadableHex(), modifier = Modifier.clickable { clipboard.shareText(currentColor.toReadableHex()) }) + Text("#" + currentColor.toReadableHex().substring(3), modifier = Modifier.clickable { clipboard.shareText("#" + currentColor.toReadableHex().substring(3)) }) + } + } else { + var currentColor by remember(backgroundImageType.tint) { mutableStateOf(backgroundImageType.tint ?: defaultTintColor) } + ColorPicker(backgroundImageType.tint ?: defaultTintColor) { + currentColor = it + ThemeManager.saveAndApplyThemeColor(ThemeColor.WALLPAPER_TINT, it, isInDarkTheme) + } + val clipboard = LocalClipboardManager.current + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) { + Text(currentColor.toReadableHex(), modifier = Modifier.clickable { clipboard.shareText(currentColor.toReadableHex()) }) + Text("#" + currentColor.toReadableHex().substring(3), modifier = Modifier.clickable { clipboard.shareText("#" + currentColor.toReadableHex().substring(3)) }) + } + } + + if (backgroundImageType.background != null || backgroundImageType.tint != null) { + SectionSpacer() + SectionItemView(resetColors) { + Text(generalGetString(MR.strings.reset_color), color = colors.primary) + } + } + SectionBottomSpacer() + } + } + } + @Composable fun ThemesSection( systemDarkTheme: SharedPreference, @@ -79,6 +246,112 @@ object AppearanceScope { ) { val currentTheme by CurrentColors.collectAsState() SectionView(stringResource(MR.strings.settings_section_title_themes)) { + val selectedBackground = remember { appPrefs.backgroundImageType.state }.value + val cornerRadius = remember { appPreferences.profileImageCornerRadius.state } + fun setBackground(type: BackgroundImageType?) { + appPrefs.backgroundImageType.set(type) + chatModel.backgroundImage.value = getBackgroundImageOrDefault() + removeBackgroundImages(type?.filename) + } + @Composable + fun Checked() { + Box(Modifier.size(40.dp).background(MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.9f), RoundedCornerShape(cornerRadius.value.roundToInt()))) + Icon(painterResource(MR.images.ic_check_filled), null, Modifier.size(25.dp), tint = MaterialTheme.colors.primary) + } + @Composable + fun Plus() { + Icon(painterResource(MR.images.ic_add), null, Modifier.size(25.dp), tint = MaterialTheme.colors.primary) + } + + val backgrounds = PredefinedBackgroundImage.entries.toList() + + fun LazyGridScope.gridContent(width: Dp, height: Dp) { + @Composable + fun BackgroundItem(background: PredefinedBackgroundImage?) { + Box( + Modifier + .size(width, height) + .background(MaterialTheme.colors.background).clip(RoundedCornerShape(percent = cornerRadius.value.roundToInt())) + .clickable { setBackground(background?.type) }, + contentAlignment = Alignment.Center + ) { + if (background != null) { + val backgroundImage = remember(background.filename) { PredefinedBackgroundImage.from(background.filename)?.res?.toComposeImageBitmap() } + ChatThemePreview(backgroundImage, background.type, withMessages = false) + } + if ((background == null && selectedBackground == null) || (selectedBackground?.filename == background?.filename)) { + Checked() + } + } + } + + @Composable + fun OwnBackgroundItem(type: BackgroundImageType?) { + val importBackgroundImageLauncher = rememberFileChooserLauncher(true) { to: URI? -> + if (to != null) { + val res = saveBackgroundImage(to) + if (res != null) { + val (filename, backgroundImage) = res + setBackground(BackgroundImageType.Static(filename, BackgroundImageScale.CROP)) + } + } + } + Box( + Modifier + .size(width, height) + .background(MaterialTheme.colors.background).clip(RoundedCornerShape(percent = cornerRadius.value.roundToInt())) + .clickable { + withLongRunningApi { importBackgroundImageLauncher.launch("image/*") } + }, + contentAlignment = Alignment.Center + ) { + val backgroundImage = remember { chatModel.backgroundImage }.value + if (type is BackgroundImageType.Static && backgroundImage != null) { + ChatThemePreview(backgroundImage, type, withMessages = false) + Checked() + } else { + Plus() + } + } + } + + item { + BackgroundItem(null) + } + items(items = backgrounds) { background -> + BackgroundItem(background) + } + item { + OwnBackgroundItem(remember { appPrefs.backgroundImageType.state }.value) + } + } + if (appPlatform.isDesktop) { + val itemWidth = (DEFAULT_START_MODAL_WIDTH - DEFAULT_PADDING * 2 - DEFAULT_PADDING_HALF * 3) / 4 + val itemHeight = (DEFAULT_START_MODAL_WIDTH - DEFAULT_PADDING * 2) / 3 + val rows = ceil((PredefinedBackgroundImage.entries.size + 2) / 4f).roundToInt() + LazyVerticalGrid( + columns = GridCells.Fixed(4), + Modifier.height(itemHeight * rows + DEFAULT_PADDING_HALF * (rows - 1) + DEFAULT_PADDING * 2), + contentPadding = PaddingValues(DEFAULT_PADDING), + verticalArrangement = Arrangement.spacedBy(DEFAULT_PADDING_HALF), + horizontalArrangement = Arrangement.spacedBy(DEFAULT_PADDING_HALF), + ) { + gridContent(itemWidth, itemHeight) + } + } else { + LazyHorizontalGrid( + rows = GridCells.Fixed(1), + Modifier.height(70.dp + DEFAULT_PADDING * 2), + contentPadding = PaddingValues(DEFAULT_PADDING), + horizontalArrangement = Arrangement.spacedBy(DEFAULT_PADDING_HALF), + ) { + gridContent(50.dp, 70.dp) + } + } + + SectionItemView(showSettingsModal{ _ -> CustomizeBackgroundImageView() }) { Text(stringResource(MR.strings.choose_background_image_title)) } + + val darkTheme = isSystemInDarkTheme() val state = remember { derivedStateOf { currentTheme.name } } ThemeSelector(state) { @@ -93,169 +366,6 @@ object AppearanceScope { SectionItemView(showSettingsModal { _ -> CustomizeThemeView(editColor) }) { Text(stringResource(MR.strings.customize_theme_title)) } } - @Composable - fun BackgroundImageSection( - showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), - ) { - SectionView(stringResource(MR.strings.settings_section_title_background_image).uppercase()) { - SectionItemView(showSettingsModal{ _ -> CustomizeBackgroundImageView() }) { Text(stringResource(MR.strings.choose_background_image_title)) } - } - } - - @Composable - fun CustomizeBackgroundImageView() { - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - ) { - AppBarTitle(stringResource(MR.strings.choose_background_image_title)) - - val backgroundImage = remember { chatModel.backgroundImage } - val backgroundImageType = remember { appPrefs.backgroundImageType.state } - val defaultBackgroundColor = backgroundImageType.value.defaultBackgroundColor - val defaultTintColor = backgroundImageType.value.defaultTintColor - Column(Modifier - .drawBehind { chatViewBackground(backgroundImage.value, backgroundImageType.value, defaultBackgroundColor, defaultTintColor) } - .padding(DEFAULT_PADDING_HALF) - ) { - PreviewChatItemView(ChatItem.getSampleData(1, CIDirection.DirectRcv(), Clock.System.now(), stringResource(MR.strings.background_image_preview_hello_bob))) - PreviewChatItemView(ChatItem.getSampleData(2, CIDirection.DirectSnd(), Clock.System.now(), stringResource(MR.strings.background_image_preview_hello_alice))) - } - - SectionSpacer() - - val resetColors = { appPrefs.backgroundImageType.set(backgroundImageType.value.copyBackgroundColor(null).copyTintColor(null)) } - - val imageTypeState = remember { - val type = appPrefs.backgroundImageType.get() - mutableStateOf(if (type.custom) "" else type.filename) - } - val imageTypeValues = remember { - PredefinedBackgroundImage.entries.map { it.filename to generalGetString(it.text) } + ("" to generalGetString(MR.strings.background_choose_own_image)) - } - val importBackgroundImageLauncher = rememberFileChooserLauncher(true) { to: URI? -> - if (to != null) { - val res = saveBackgroundImage(to) - if (res != null) { - val (filename, backgroundImage) = res - imageTypeState.value = "" - chatModel.backgroundImage.value = backgroundImage - appPrefs.backgroundImageType.set(BackgroundImageType.Static(custom = true, filename, BackgroundImageScale.CROP, null)) - removeBackgroundImages(filename) - resetColors() - } - } - } - ExposedDropDownSettingRow( - stringResource(MR.strings.settings_section_title_background_image), - imageTypeValues, - imageTypeState, - onSelected = { filename -> - if (filename.isEmpty()) { - withLongRunningApi { importBackgroundImageLauncher.launch("image/*") } - } else { - imageTypeState.value = filename - appPrefs.backgroundImageType.set(PredefinedBackgroundImage.from(filename)!!.type) - chatModel.backgroundImage.value = getBackgroundImageOrDefault() - removeBackgroundImages() - } - } - ) - - val type = backgroundImageType.value - if (type is BackgroundImageType.Repeated) { - val state = remember(type.scale) { mutableStateOf(type.scale) } - val values = remember { - listOf( - 0.25f to "0.25x", - 0.5f to "0.5x", - 0.75f to "0.75x", - 1f to "1x", - ) - } - ExposedDropDownSettingRow( - stringResource(MR.strings.background_image_scale), - values, - state, - onSelected = { scale -> - appPrefs.backgroundImageType.set(type.copy(scale = scale)) - } - ) - } else if (type is BackgroundImageType.Static) { - val state = remember(type.scale) { mutableStateOf(type.scale) } - val values = remember { - BackgroundImageScale.entries.map { it to generalGetString(it.text) } - } - ExposedDropDownSettingRow( - stringResource(MR.strings.background_image_scale), - values, - state, - onSelected = { scale -> - appPrefs.backgroundImageType.set(type.copy(scale = scale)) - } - ) - } - - SectionSpacer() - - var selectedTab by rememberSaveable { mutableStateOf(0) } - val availableTabs = listOf( - stringResource(MR.strings.background_image_background_color), - stringResource(MR.strings.background_image_tint_color), - ) - TabRow( - selectedTabIndex = selectedTab, - backgroundColor = Color.Transparent, - contentColor = MaterialTheme.colors.primary, - ) { - availableTabs.forEachIndexed { index, title -> - Tab( - selected = selectedTab == index, - onClick = { - selectedTab = index - }, - text = { Text(title, fontSize = 13.sp) }, - selectedContentColor = MaterialTheme.colors.primary, - unselectedContentColor = MaterialTheme.colors.secondary, - ) - } - } - - if (selectedTab == 0) { - var currentColor by remember(backgroundImageType.value.background) { mutableStateOf(backgroundImageType.value.background ?: defaultBackgroundColor) } - ColorPicker(backgroundImageType.value.background ?: defaultBackgroundColor) { - currentColor = it - appPrefs.backgroundImageType.set(appPrefs.backgroundImageType.get().copyBackgroundColor(currentColor)) - } - - val clipboard = LocalClipboardManager.current - Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) { - Text(currentColor.toReadableHex(), modifier = Modifier.clickable { clipboard.shareText(currentColor.toReadableHex()) }) - Text("#" + currentColor.toReadableHex().substring(3), modifier = Modifier.clickable { clipboard.shareText("#" + currentColor.toReadableHex().substring(3)) }) - } - } else { - var currentColor by remember(backgroundImageType.value.tint) { mutableStateOf(backgroundImageType.value.tint ?: defaultTintColor) } - ColorPicker(backgroundImageType.value.tint ?: defaultTintColor) { - currentColor = it - appPrefs.backgroundImageType.set(appPrefs.backgroundImageType.get().copyTintColor(currentColor)) - } - - val clipboard = LocalClipboardManager.current - Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) { - Text(currentColor.toReadableHex(), modifier = Modifier.clickable { clipboard.shareText(currentColor.toReadableHex()) }) - Text("#" + currentColor.toReadableHex().substring(3), modifier = Modifier.clickable { clipboard.shareText("#" + currentColor.toReadableHex().substring(3)) }) - } - } - - if (backgroundImageType.value.background != null || backgroundImageType.value.tint != null) { - SectionSpacer() - SectionItemView(resetColors) { - Text(generalGetString(MR.strings.reset_color), color = colors.primary) - } - } - SectionBottomSpacer() - } - } - @Composable fun CustomizeThemeView(editColor: (ThemeColor, Color) -> Unit) { ColumnWithScrollBar( @@ -265,6 +375,29 @@ object AppearanceScope { AppBarTitle(stringResource(MR.strings.customize_theme_title)) + val backgroundImageType = remember { appPrefs.backgroundImageType.state }.value + val backgroundImage = remember { chatModel.backgroundImage } + ChatThemePreview(backgroundImage.value, backgroundImageType) + SectionSpacer() + + if (backgroundImageType != null) { + SectionView(stringResource(MR.strings.settings_section_title_background_image).uppercase()) { + val wallpaperBackgroundColor = currentTheme.appColors.wallpaperBackground ?: backgroundImageType.defaultBackgroundColor + SectionItemViewSpaceBetween({ editColor(ThemeColor.WALLPAPER_BACKGROUND, wallpaperBackgroundColor) }) { + val title = generalGetString(MR.strings.color_wallpaper_background) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = wallpaperBackgroundColor) + } + val wallpaperTintColor = currentTheme.appColors.wallpaperTint ?: backgroundImageType.defaultTintColor + SectionItemViewSpaceBetween({ editColor(ThemeColor.WALLPAPER_TINT, wallpaperTintColor) }) { + val title = generalGetString(MR.strings.color_wallpaper_tint) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = wallpaperTintColor) + } + } + SectionSpacer() + } + SectionView(stringResource(MR.strings.theme_colors_section_title)) { SectionItemViewSpaceBetween({ editColor(ThemeColor.PRIMARY, currentTheme.colors.primary) }) { val title = generalGetString(MR.strings.color_primary) @@ -364,16 +497,38 @@ object AppearanceScope { .fillMaxWidth() ) { AppBarTitle(name.text) + + val supportedLiveChange = name in listOf(ThemeColor.SECONDARY, ThemeColor.RECEIVED_MESSAGE, ThemeColor.SENT_MESSAGE, ThemeColor.WALLPAPER_BACKGROUND, ThemeColor.WALLPAPER_TINT) + if (supportedLiveChange) { + val backgroundImageType = remember { appPrefs.backgroundImageType.state }.value + val backgroundImage = remember { chatModel.backgroundImage } + ChatThemePreview(backgroundImage.value, backgroundImageType) + SectionSpacer() + } + + val isInDarkTheme = isInDarkTheme() var currentColor by remember { mutableStateOf(initialColor) } ColorPicker(initialColor) { currentColor = it + if (supportedLiveChange) { + ThemeManager.saveAndApplyThemeColor(name, currentColor, isInDarkTheme) + } + } + val savedColor = remember { mutableStateOf(initialColor) } + DisposableEffect(Unit) { + onDispose { + if (currentColor != savedColor.value) { + // Rollback changes since they weren't saved + ThemeManager.saveAndApplyThemeColor(name, savedColor.value, isInDarkTheme) + } + } } SectionSpacer() - val isInDarkTheme = isInDarkTheme() TextButton( onClick = { ThemeManager.saveAndApplyThemeColor(name, currentColor, isInDarkTheme) + savedColor.value = currentColor close() }, Modifier.align(Alignment.CenterHorizontally), @@ -384,17 +539,7 @@ object AppearanceScope { } } - @Composable - fun ColorPicker(initialColor: Color, onColorChanged: (Color) -> Unit) { - ClassicColorPicker(modifier = Modifier - .fillMaxWidth() - .height(300.dp), - color = HsvColor.from(color = initialColor), showAlphaBar = true, - onColorChanged = { color: HsvColor -> - onColorChanged(color.toColor()) - } - ) - } + @Composable fun LangSelector(state: State, onSelected: (String) -> Unit) { @@ -472,3 +617,5 @@ object AppearanceScope { //} } +@Composable +expect fun ColorPicker(initialColor: Color, onColorChanged: (Color) -> Unit) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index e8829e84c9..ecbb79e34b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1534,6 +1534,8 @@ Title Sent message Received message + Wallpaper background + Wallpaper tint Choose… diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt index cad1575a05..d30e8acca7 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt @@ -9,14 +9,18 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp import chat.simplex.common.model.ChatModel import chat.simplex.common.model.SharedPreference import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.common.platform.defaultLocale +import chat.simplex.common.ui.theme.DEFAULT_PADDING import chat.simplex.common.ui.theme.ThemeColor import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.AppearanceScope.ColorEditor import chat.simplex.res.MR +import com.godaddy.android.colorpicker.ClassicColorPicker +import com.godaddy.android.colorpicker.HsvColor import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.delay import java.util.Locale @@ -68,8 +72,18 @@ fun AppearanceScope.AppearanceLayout( SectionDividerSpaced(maxTopPadding = true) ThemesSection(systemDarkTheme, showSettingsModal, editColor) - SectionDividerSpaced(maxTopPadding = true) - BackgroundImageSection(showSettingsModal) SectionBottomSpacer() } } + +@Composable +actual fun ColorPicker(initialColor: Color, onColorChanged: (Color) -> Unit) { + ClassicColorPicker(modifier = Modifier + .fillMaxWidth() + .height(300.dp), + color = HsvColor.from(color = initialColor), showAlphaBar = true, + onColorChanged = { color: HsvColor -> + onColorChanged(color.toColor()) + } + ) +}