changes and refactoring

This commit is contained in:
Avently
2024-05-03 17:13:54 +07:00
parent 572dcb1556
commit a74bfffee3
12 changed files with 433 additions and 275 deletions
@@ -58,6 +58,7 @@ enum class AppIcon(val image: ImageResource) {
@Composable
actual fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)) {
val appIcon = remember { mutableStateOf(findEnabledIcon()) }
val darkTheme = chat.simplex.common.ui.theme.isSystemInDarkTheme()
fun setAppIcon(newIcon: AppIcon) {
if (appIcon.value == newIcon) return
@@ -76,6 +77,9 @@ actual fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatMod
appIcon.value = newIcon
}
val theme = CurrentColors.collectAsState().value.base
val backgroundImage = CurrentColors.collectAsState().value.wallpaper.type?.image
val backgroundImageType = CurrentColors.collectAsState().value.wallpaper.type
AppearanceScope.AppearanceLayout(
appIcon,
m.controller.appPrefs.appLanguage,
@@ -84,7 +88,7 @@ actual fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatMod
showSettingsModal = showSettingsModal,
editColor = { name, initialColor ->
ModalManager.start.showModalCloseable { close ->
ColorEditor(name, initialColor, close)
ColorEditor(name, initialColor, theme, backgroundImageType, backgroundImage, onColorChange = { color -> ThemeManager.saveAndApplyThemeColor(name, color, darkTheme) }, close = close)
}
},
)
@@ -16,7 +16,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import chat.simplex.res.MR
import java.io.File
enum class DefaultTheme {
SYSTEM, LIGHT, DARK, SIMPLEX;
@@ -197,7 +196,7 @@ data class ThemeWallpaper (
)
}
fun import(): ThemeWallpaper =
fun importFromString(): ThemeWallpaper =
if (preset == null && image != null) {
// Need to save image from string and to save its path
try {
@@ -4,9 +4,8 @@ import androidx.compose.material.Colors
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.res.MR
import chat.simplex.common.model.AppPreferences
import chat.simplex.common.model.ChatController
import chat.simplex.common.platform.platform
import chat.simplex.common.views.helpers.BackgroundImageType
import chat.simplex.common.views.helpers.generalGetString
@@ -27,9 +26,9 @@ object ThemeManager {
else -> SimplexColorPalette to DefaultTheme.SIMPLEX
}
fun currentColors(darkForSystemTheme: Boolean): ActiveTheme {
fun currentColors(darkForSystemTheme: Boolean, pref: SharedPreference<Map<String, ThemeOverrides>> = appPrefs.themeOverrides): ActiveTheme {
val themeName = appPrefs.currentTheme.get()!!
val themeOverrides = appPrefs.themeOverrides.get()
val themeOverrides = pref.get()
val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.name) {
themeName
@@ -97,16 +96,16 @@ object ThemeManager {
fun applyTheme(theme: String, darkForSystemTheme: Boolean) {
appPrefs.currentTheme.set(theme)
CurrentColors.value = currentColors(darkForSystemTheme)
CurrentColors.value = currentColors(darkForSystemTheme, appPrefs.themeOverrides)
platform.androidSetNightModeIfSupported()
}
fun changeDarkTheme(theme: String, darkForSystemTheme: Boolean) {
appPrefs.systemDarkTheme.set(theme)
CurrentColors.value = currentColors(darkForSystemTheme)
CurrentColors.value = currentColors(darkForSystemTheme, appPrefs.themeOverrides)
}
fun saveAndApplyThemeColor(name: ThemeColor, color: Color? = null, darkForSystemTheme: Boolean) {
fun saveAndApplyThemeColor(name: ThemeColor, color: Color? = null, darkForSystemTheme: Boolean, pref: SharedPreference<Map<String, ThemeOverrides>> = appPrefs.themeOverrides) {
val themeName = appPrefs.currentTheme.get()!!
val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.name) {
themeName
@@ -124,48 +123,48 @@ object ThemeManager {
else -> return
}
}
val overrides = appPrefs.themeOverrides.get().toMutableMap()
val overrides = pref.get().toMutableMap()
val prevValue = overrides[nonSystemThemeName] ?: ThemeOverrides(base = CurrentColors.value.base, colors = ThemeColors(), wallpaper = ThemeWallpaper())
overrides[nonSystemThemeName] = prevValue.withUpdatedColor(name, colorToSet?.toReadableHex())
appPrefs.themeOverrides.set(overrides)
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight)
pref.set(overrides)
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight, appPrefs.themeOverrides)
}
fun saveAndApplyBackgroundImage(type: BackgroundImageType?, darkForSystemTheme: Boolean) {
fun saveAndApplyBackgroundImage(type: BackgroundImageType?, darkForSystemTheme: Boolean, pref: SharedPreference<Map<String, ThemeOverrides>> = appPrefs.themeOverrides) {
val themeName = appPrefs.currentTheme.get()!!
val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.name) {
themeName
} else {
if (darkForSystemTheme) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.name
}
val overrides = appPrefs.themeOverrides.get().toMutableMap()
val overrides = pref.get().toMutableMap()
val prevValue = overrides[nonSystemThemeName] ?: ThemeOverrides(base = CurrentColors.value.base, colors = ThemeColors(), wallpaper = ThemeWallpaper())
overrides[nonSystemThemeName] = prevValue.copy(wallpaper = if (type != null) ThemeWallpaper.from(type, prevValue.wallpaper.background, prevValue.wallpaper.tint) else ThemeWallpaper())
appPrefs.themeOverrides.set(overrides)
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight)
pref.set(overrides)
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight, appPrefs.themeOverrides)
}
fun saveAndApplyThemeOverrides(theme: ThemeOverrides, darkForSystemTheme: Boolean) {
val overrides = appPrefs.themeOverrides.get().toMutableMap()
fun saveAndApplyThemeOverrides(theme: ThemeOverrides, darkForSystemTheme: Boolean, pref: SharedPreference<Map<String, ThemeOverrides>> = appPrefs.themeOverrides) {
val overrides = pref.get().toMutableMap()
val prevValue = overrides[theme.base.name] ?: ThemeOverrides(base = CurrentColors.value.base, colors = ThemeColors(), wallpaper = ThemeWallpaper())
overrides[theme.base.name] = prevValue.copy(colors = theme.colors, wallpaper = theme.wallpaper.import())
appPrefs.themeOverrides.set(overrides)
overrides[theme.base.name] = prevValue.copy(colors = theme.colors, wallpaper = theme.wallpaper.importFromString())
pref.set(overrides)
appPrefs.currentTheme.set(theme.base.name)
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight)
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight, appPrefs.themeOverrides)
}
fun resetAllThemeColors(darkForSystemTheme: Boolean) {
fun resetAllThemeColors(darkForSystemTheme: Boolean, pref: SharedPreference<Map<String, ThemeOverrides>> = appPrefs.themeOverrides) {
val themeName = appPrefs.currentTheme.get()!!
val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.name) {
themeName
} else {
if (darkForSystemTheme) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.name
}
val overrides = appPrefs.themeOverrides.get().toMutableMap()
val overrides = pref.get().toMutableMap()
val prevValue = overrides[nonSystemThemeName] ?: return
overrides[nonSystemThemeName] = prevValue.copy(colors = ThemeColors(), wallpaper = prevValue.wallpaper.copy(background = null, tint = null))
appPrefs.themeOverrides.set(overrides)
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight)
pref.set(overrides)
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight, appPrefs.themeOverrides)
}
fun String.colorFromReadableHex(): Color =
@@ -29,11 +29,13 @@ 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.*
import chat.simplex.common.views.usersettings.*
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.ThemeManager.colorFromReadableHex
import chat.simplex.common.views.chatlist.updateChatSettings
import chat.simplex.common.views.newchat.*
import chat.simplex.res.MR
@@ -337,6 +339,16 @@ fun ChatInfoLayout(
if (cStats != null && cStats.ratchetSyncAllowed) {
SynchronizeConnectionButton(syncContactConnection)
}
// Should come from API
val type = remember { BackgroundImageType.default }
val theme = remember { ThemeOverrides(CurrentColors.value.base, ThemeColors()) }
WallpaperButton {
ModalManager.end.showModal {
WallpaperEditor(type, theme) { type, theme ->
// apply to chat
}
}
}
// } else if (developerTools) {
// SynchronizeConnectionButtonForce(syncContactConnectionForce)
// }
@@ -642,6 +654,15 @@ private fun SendReceiptsOption(currentUser: User, state: State<SendReceipts>, on
)
}
@Composable
fun WallpaperButton(onClick: () -> Unit) {
SettingsActionItem(
painterResource(MR.images.ic_image),
stringResource(MR.strings.settings_section_title_wallpaper),
click = onClick
)
}
@Composable
fun ClearChatButton(onClick: () -> Unit) {
SettingsActionItem(
@@ -675,6 +696,88 @@ fun ShareAddressButton(onClick: () -> Unit) {
)
}
@Composable
fun ModalData.WallpaperEditor(type: BackgroundImageType, theme: ThemeOverrides, save: (BackgroundImageType?, ThemeOverrides) -> Unit) {
ColumnWithScrollBar(
Modifier
.fillMaxSize()
) {
val systemDark = chat.simplex.common.ui.theme.isSystemInDarkTheme()
val backgroundImageType: MutableState<BackgroundImageType?> = remember { stateGetOrPut("backgroundImageType") { type } }
val background = backgroundImageType.value
val themeOverrides = remember { stateGetOrPut("themeOverrides") { theme } }
val pref = remember {
SharedPreference<Map<String, ThemeOverrides>>(
get = {
mapOf(CurrentColors.value.base.name to themeOverrides.value)
},
set = { value ->
themeOverrides.value = value[CurrentColors.value.base.name]!!
}
)
}
AppBarTitle(stringResource(MR.strings.settings_section_title_wallpaper))
val backgroundImage = remember(background?.filename) { background?.image }
val backgroundColor = remember { mutableStateOf(pref.get()[CurrentColors.value.base.name]!!.wallpaper.background?.colorFromReadableHex()) }
val tintColor = remember { mutableStateOf(pref.get()[CurrentColors.value.base.name]!!.wallpaper.tint?.colorFromReadableHex()) }
AppearanceScope.ChatThemePreview(theme.base, backgroundImage, background, backgroundColor.value, tintColor.value)
SectionSpacer()
WallpaperSetupView(
background,
theme.base,
backgroundColor.value,
tintColor.value,
showPresetSelection = true,
editColor = { name, initialColor ->
ModalManager.end.showModalCloseable { close ->
AppearanceScope.ColorEditor(
name,
initialColor,
theme.base,
backgroundImageType.value,
backgroundImage,
backgroundColor.value,
tintColor.value,
onColorChange = { color ->
ThemeManager.saveAndApplyThemeColor(name, color, systemDark, pref)
if (name == ThemeColor.WALLPAPER_BACKGROUND) backgroundColor.value = color
else if (name == ThemeColor.WALLPAPER_TINT) tintColor.value = color
},
close
)
}
},
onColorChange = { name, color ->
ThemeManager.saveAndApplyThemeColor(name, color, systemDark, pref)
if (name == ThemeColor.WALLPAPER_BACKGROUND) backgroundColor.value = color
else if (name == ThemeColor.WALLPAPER_TINT) tintColor.value = color
},
onTypeChange = { type ->
ThemeManager.saveAndApplyBackgroundImage(type, systemDark, pref)
backgroundImageType.value = type
}
)
SectionSpacer()
ApplyButton {
save(backgroundImageType.value, themeOverrides.value)
}
}
}
@Composable
private fun ApplyButton(onClick: () -> Unit) {
SettingsActionItem(
painterResource(MR.images.ic_check),
stringResource(MR.strings.background_image_apply),
click = onClick
)
}
private fun setContactAlias(chat: Chat, localAlias: String, chatModel: ChatModel) = withBGApi {
val chatRh = chat.remoteHostId
chatModel.controller.apiSetContactAlias(chatRh, chat.chatInfo.apiId, localAlias)?.let {
@@ -599,13 +599,13 @@ fun ChatLayout(
) { contentPadding ->
val backgroundImage = CurrentColors.collectAsState().value.wallpaper.type?.image
val backgroundImageType = CurrentColors.collectAsState().value.wallpaper.type
val defaultBackgroundColor = backgroundImageType?.defaultBackgroundColor
val defaultTintColor = backgroundImageType?.defaultTintColor
val backgroundColor = CurrentColors.value.wallpaper.background ?: backgroundImageType?.defaultBackgroundColor(CurrentColors.value.base)
val tintColor = CurrentColors.value.wallpaper.tint ?: backgroundImageType?.defaultTintColor(CurrentColors.value.base)
BoxWithConstraints(Modifier
.fillMaxHeight()
.background(MaterialTheme.colors.background)
.then(if (backgroundImage != null && backgroundImageType != null && defaultBackgroundColor != null && defaultTintColor != null)
Modifier.drawBehind { chatViewBackground(backgroundImage, backgroundImageType, defaultBackgroundColor, defaultTintColor) }
.then(if (backgroundImage != null && backgroundImageType != null && backgroundColor != null && tintColor != null)
Modifier.drawBehind { chatViewBackground(backgroundImage, backgroundImageType, backgroundColor, tintColor) }
else
Modifier)
.padding(contentPadding)
@@ -233,6 +233,16 @@ fun GroupChatInfoLayout(
} else {
SendReceiptsOptionDisabled()
}
// Should come from API
val type = remember { BackgroundImageType.default }
val theme = remember { ThemeOverrides(CurrentColors.value.base, ThemeColors()) }
WallpaperButton {
ModalManager.end.showModal {
WallpaperEditor(type, theme) { type, theme ->
// apply to chat
}
}
}
}
SectionTextFooter(stringResource(MR.strings.only_group_owners_can_change_prefs))
SectionDividerSpaced(maxTopPadding = true)
@@ -191,6 +191,13 @@ fun FramedItemView(
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage
Box(Modifier
.clip(RoundedCornerShape(18.dp))
.background(
when {
transparentBackground -> Color.Transparent
sent -> MaterialTheme.colors.background
else -> MaterialTheme.colors.background
}
)
.background(
when {
transparentBackground -> Color.Transparent
@@ -11,6 +11,7 @@ import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.CurrentColors
import chat.simplex.common.ui.theme.DefaultTheme
import chat.simplex.res.MR
import dev.icerock.moko.resources.ImageResource
import dev.icerock.moko.resources.StringResource
@@ -20,14 +21,42 @@ import java.io.File
import kotlin.math.*
@Serializable
enum class PredefinedBackgroundImage(val res: ImageResource, val filename: String, val scale: Float, val text: StringResource) {
@SerialName("cat") CAT(MR.images.background_cat, "simplex_cat", 0.5f, MR.strings.background_cat),
@SerialName("hearts") HEARTS(MR.images.background_hearts, "simplex_hearts", 0.5f, MR.strings.background_hearts),
@SerialName("school") SCHOOL(MR.images.background_school, "simplex_school", 0.5f, MR.strings.background_school),
@SerialName("internet") INTERNET(MR.images.background_internet, "simplex_internet", 0.5f, MR.strings.background_internet),
@SerialName("space") SPACE(MR.images.background_space, "simplex_space", 0.5f, MR.strings.background_space),
@SerialName("pets") PETS(MR.images.background_pets, "simplex_pets", 0.5f, MR.strings.background_pets),
@SerialName("rabbit") RABBIT(MR.images.background_rabbit, "simplex_rabbit", 0.5f, MR.strings.background_rabbit);
enum class PredefinedBackgroundImage(
val res: ImageResource,
val filename: String,
val text: StringResource,
val scale: Float,
val background: Map<DefaultTheme, Color>,
val tint: Map<DefaultTheme, Color>
) {
@SerialName("cat") CAT(MR.images.background_cat, "simplex_cat", MR.strings.background_cat, 0.5f,
mapOf(DefaultTheme.LIGHT to Color.White, DefaultTheme.DARK to Color.Black, DefaultTheme.SIMPLEX to Color.Black),
mapOf(DefaultTheme.LIGHT to Color.Blue, DefaultTheme.DARK to Color.Blue, DefaultTheme.SIMPLEX to Color.Blue)
),
@SerialName("hearts") HEARTS(MR.images.background_hearts, "simplex_hearts", MR.strings.background_hearts, 0.5f,
mapOf(DefaultTheme.LIGHT to Color.White, DefaultTheme.DARK to Color.Black, DefaultTheme.SIMPLEX to Color.Black),
mapOf(DefaultTheme.LIGHT to Color.Blue, DefaultTheme.DARK to Color.Blue, DefaultTheme.SIMPLEX to Color.Blue)
),
@SerialName("school") SCHOOL(MR.images.background_school, "simplex_school", MR.strings.background_school, 0.5f,
mapOf(DefaultTheme.LIGHT to Color.White, DefaultTheme.DARK to Color.Black, DefaultTheme.SIMPLEX to Color.Black),
mapOf(DefaultTheme.LIGHT to Color.Blue, DefaultTheme.DARK to Color.Blue, DefaultTheme.SIMPLEX to Color.Blue)
),
@SerialName("internet") INTERNET(MR.images.background_internet, "simplex_internet", MR.strings.background_internet, 0.5f,
mapOf(DefaultTheme.LIGHT to Color.White, DefaultTheme.DARK to Color.Black, DefaultTheme.SIMPLEX to Color.Black),
mapOf(DefaultTheme.LIGHT to Color.Blue, DefaultTheme.DARK to Color.Blue, DefaultTheme.SIMPLEX to Color.Blue)
),
@SerialName("space") SPACE(MR.images.background_space, "simplex_space", MR.strings.background_space, 0.5f,
mapOf(DefaultTheme.LIGHT to Color.White, DefaultTheme.DARK to Color.Black, DefaultTheme.SIMPLEX to Color.Black),
mapOf(DefaultTheme.LIGHT to Color.Blue, DefaultTheme.DARK to Color.Blue, DefaultTheme.SIMPLEX to Color.Blue)
),
@SerialName("pets") PETS(MR.images.background_pets, "simplex_pets", MR.strings.background_pets, 0.5f,
mapOf(DefaultTheme.LIGHT to Color.White, DefaultTheme.DARK to Color.Black, DefaultTheme.SIMPLEX to Color.Black),
mapOf(DefaultTheme.LIGHT to Color.Blue, DefaultTheme.DARK to Color.Blue, DefaultTheme.SIMPLEX to Color.Blue)
),
@SerialName("rabbit") RABBIT(MR.images.background_rabbit, "simplex_rabbit", MR.strings.background_rabbit, 0.5f,
mapOf(DefaultTheme.LIGHT to Color.White, DefaultTheme.DARK to Color.Black, DefaultTheme.SIMPLEX to Color.Black),
mapOf(DefaultTheme.LIGHT to Color.Blue, DefaultTheme.DARK to Color.Blue, DefaultTheme.SIMPLEX to Color.Blue)
);
fun toType(): BackgroundImageType =
BackgroundImageType.Repeated(filename, scale)
@@ -48,6 +77,7 @@ enum class BackgroundImageScaleType(val contentScale: ContentScale, val text: St
@Serializable
sealed class BackgroundImageType {
abstract val filename: String
abstract val scale: Float
val image by lazy {
val cache = cachedImage
@@ -66,33 +96,32 @@ sealed class BackgroundImageType {
@Serializable @SerialName("repeated") data class Repeated(
override val filename: String,
val scale: Float,
override val scale: Float,
): BackgroundImageType()
@Serializable @SerialName("static") data class Static(
override val filename: String,
val scale: Float,
override val scale: Float,
val scaleType: BackgroundImageScaleType,
): BackgroundImageType()
val background: Color?
get() = CurrentColors.value.wallpaper.background
val tint: Color?
get() = CurrentColors.value.wallpaper.tint
val defaultBackgroundColor: Color
@Composable get() = if (this is Static)
MaterialTheme.colors.background
else
@Composable
fun defaultBackgroundColor(theme: DefaultTheme): Color =
if (this is Repeated) {
PredefinedBackgroundImage.from(filename)!!.background[theme]!!
} else {
MaterialTheme.colors.background
}
val defaultTintColor: Color
@Composable get() = if (this is Repeated || (this is Static && this.scaleType == BackgroundImageScaleType.REPEAT))
@Composable
fun defaultTintColor(theme: DefaultTheme): Color =
if (this is Repeated) {
PredefinedBackgroundImage.from(filename)!!.tint[theme]!!
} else if (this is Static && scaleType == BackgroundImageScaleType.REPEAT) {
MaterialTheme.colors.primary
else
} else {
MaterialTheme.colors.background.copy(0.9f)
}
companion object {
val default: BackgroundImageType
@@ -102,7 +131,7 @@ sealed class BackgroundImageType {
}
}
fun DrawScope.chatViewBackground(image: ImageBitmap, imageType: BackgroundImageType, defaultBackground: Color, defaultTint: Color) = clipRect {
fun DrawScope.chatViewBackground(image: ImageBitmap, imageType: BackgroundImageType, background: Color, tint: Color) = clipRect {
fun repeat(imageScale: Float) {
val scale = imageScale * density
for (h in 0..(size.height / image.height / scale).roundToInt()) {
@@ -111,22 +140,51 @@ fun DrawScope.chatViewBackground(image: ImageBitmap, imageType: BackgroundImageT
image,
dstOffset = IntOffset(x = (w * image.width * scale).roundToInt(), y = (h * image.height * scale).roundToInt()),
dstSize = IntSize((image.width * scale).roundToInt(), (image.height * scale).roundToInt()),
colorFilter = ColorFilter.tint(imageType.tint ?: defaultTint)
colorFilter = ColorFilter.tint(tint, BlendMode.SrcIn)
)
}
}
}
drawRect(imageType.background ?: defaultBackground)
if (imageType is BackgroundImageType.Repeated) {
repeat(imageType.scale)
} else if (imageType is BackgroundImageType.Static && imageType.scaleType == BackgroundImageScaleType.REPEAT) {
repeat(imageType.scale)
} else if (imageType is BackgroundImageType.Static) {
val scale = imageType.scaleType.contentScale.computeScaleFactor(Size(image.width.toFloat(), image.height.toFloat()), Size(size.width, size.height))
val scaledWidth = (image.width * scale.scaleX).roundToInt()
val scaledHeight = (image.height * scale.scaleY).roundToInt()
drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight))
drawRect(imageType.tint ?: defaultTint)
drawRect(background)
when (imageType) {
is BackgroundImageType.Repeated -> repeat(imageType.scale)
is BackgroundImageType.Static -> when (imageType.scaleType) {
BackgroundImageScaleType.REPEAT -> repeat(imageType.scale)
BackgroundImageScaleType.FILL, BackgroundImageScaleType.FIT -> {
val scale = imageType.scaleType.contentScale.computeScaleFactor(Size(image.width.toFloat(), image.height.toFloat()), Size(size.width, size.height))
val scaledWidth = (image.width * scale.scaleX).roundToInt()
val scaledHeight = (image.height * scale.scaleY).roundToInt()
drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight))
if (imageType.scaleType == BackgroundImageScaleType.FIT) {
if (scaledWidth < size.width) {
// has black lines at left and right sides
var x = (size.width - scaledWidth) / 2
while (x > 0) {
drawImage(image, dstOffset = IntOffset(x = (x - scaledWidth).roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight))
x -= scaledWidth
}
x = size.width - (size.width - scaledWidth) / 2
while (x < size.width) {
drawImage(image, dstOffset = IntOffset(x = x.roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight))
x += scaledWidth
}
} else {
// has black lines at top and bottom sides
var y = (size.height - scaledHeight) / 2
while (y > 0) {
drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = (y - scaledHeight).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight))
y -= scaledHeight
}
y = size.height - (size.height - scaledHeight) / 2
while (y < size.height) {
drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = y.roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight))
y += scaledHeight
}
}
}
drawRect(tint)
}
}
}
}
@@ -14,13 +14,10 @@ import chat.simplex.common.views.chatlist.connectIfOpenedViaUri
import chat.simplex.res.MR
import com.charleskorn.kaml.decodeFromStream
import dev.icerock.moko.resources.StringResource
import dev.icerock.moko.resources.compose.painterResource
import kotlinx.coroutines.*
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.decodeFromStream
import java.io.*
import java.net.URI
import java.nio.file.CopyOption
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.text.SimpleDateFormat
@@ -292,6 +289,7 @@ fun saveBackgroundImage(uri: URI): String? {
Files.copy(inputStream!!, destFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
} catch (e: Exception) {
Log.e(TAG, "Error saving background image: ${e.stackTraceToString()}")
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error), e.stackTraceToString())
return null
}
return destFile.name
@@ -1,19 +1,18 @@
package chat.simplex.common.views.usersettings
import SectionBottomSpacer
import SectionDividerSpaced
import SectionItemView
import SectionItemViewSpaceBetween
import SectionSpacer
import SectionView
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.*
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.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.*
@@ -28,6 +27,7 @@ import chat.simplex.common.views.helpers.*
import chat.simplex.common.model.ChatModel
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.res.MR
import kotlinx.datetime.Clock
@@ -35,8 +35,7 @@ import kotlinx.serialization.encodeToString
import java.net.URI
import java.util.*
import kotlin.collections.ArrayList
import kotlin.math.ceil
import kotlin.math.roundToInt
import kotlin.math.*
@Composable
expect fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit))
@@ -74,14 +73,21 @@ object AppearanceScope {
}
@Composable
fun ChatThemePreview(backgroundImage: ImageBitmap?, backgroundImageType: BackgroundImageType?, withMessages: Boolean = true) {
fun ChatThemePreview(
theme: DefaultTheme,
backgroundImage: ImageBitmap?,
backgroundImageType: BackgroundImageType?,
backgroundColor: Color? = CurrentColors.value.wallpaper.background,
tintColor: Color? = CurrentColors.value.wallpaper.tint,
withMessages: Boolean = true
) {
val themeBackgroundColor = MaterialTheme.colors.background
val defaultBackgroundColor = backgroundImageType?.defaultBackgroundColor
val defaultTintColor = backgroundImageType?.defaultTintColor
val backgroundColor = backgroundColor ?: backgroundImageType?.defaultBackgroundColor(theme)
val tintColor = tintColor ?: backgroundImageType?.defaultTintColor(theme)
Column(Modifier
.drawBehind {
if (backgroundImage != null && backgroundImageType != null && defaultBackgroundColor != null && defaultTintColor != null) {
chatViewBackground(backgroundImage, backgroundImageType, defaultBackgroundColor, defaultTintColor)
if (backgroundImage != null && backgroundImageType != null && backgroundColor != null && tintColor != null) {
chatViewBackground(backgroundImage, backgroundImageType, backgroundColor, tintColor)
} else {
drawRect(themeBackgroundColor)
}
@@ -97,157 +103,6 @@ object AppearanceScope {
}
}
@Composable
fun CustomizeBackgroundImageView() {
ColumnWithScrollBar(
Modifier.fillMaxWidth(),
) {
AppBarTitle(stringResource(MR.strings.choose_background_image_title))
val backgroundImage = CurrentColors.collectAsState().value.wallpaper.type?.image
val backgroundImageType = CurrentColors.collectAsState().value.wallpaper.type
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 systemDark = isSystemInDarkTheme()
val importBackgroundImageLauncher = rememberFileChooserLauncher(true) { to: URI? ->
if (to != null) {
val filename = saveBackgroundImage(to)
if (filename != null) {
imageTypeState.value = ""
ThemeManager.saveAndApplyBackgroundImage(BackgroundImageType.Static(filename, 1f, BackgroundImageScaleType.FILL), systemDark)
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
ThemeManager.saveAndApplyBackgroundImage(PredefinedBackgroundImage.from(filename)!!.toType(), systemDark)
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 = {
ThemeManager.saveAndApplyBackgroundImage(backgroundImageType.copy(scale = it), systemDark)
}
)
}
} else if (backgroundImageType is BackgroundImageType.Static) {
val state = remember(backgroundImageType.scaleType) { mutableStateOf(backgroundImageType.scaleType) }
val values = remember {
BackgroundImageScaleType.entries.map { it to generalGetString(it.text) }
}
ExposedDropDownSettingRow(
stringResource(MR.strings.background_image_scale),
values,
state,
onSelected = { scaleType ->
ThemeManager.saveAndApplyBackgroundImage(backgroundImageType.copy(scaleType = scaleType), systemDark)
}
)
if (backgroundImageType.scaleType == BackgroundImageScaleType.REPEAT) {
val state = remember(backgroundImageType.scale) { mutableStateOf(backgroundImageType.scale) }
Row {
Text("${state.value}", Modifier.width(50.dp))
Slider(
state.value,
valueRange = 0.2f..2f,
onValueChange = {
ThemeManager.saveAndApplyBackgroundImage(backgroundImageType.copy(scale = it), systemDark)
}
)
}
}
}
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<String?>,
@@ -260,7 +115,7 @@ object AppearanceScope {
val selectedBackground = CurrentColors.collectAsState().value.wallpaper.type
val cornerRadius = remember { appPreferences.profileImageCornerRadius.state }
fun setBackground(type: BackgroundImageType?) {
if (type is BackgroundImageType.Static) {
if (type is BackgroundImageType.Static || CurrentColors.value.wallpaper.type is BackgroundImageType.Static) {
ThemeManager.saveAndApplyThemeColor(ThemeColor.WALLPAPER_BACKGROUND, null, systemDark)
ThemeManager.saveAndApplyThemeColor(ThemeColor.WALLPAPER_TINT, null, systemDark)
}
@@ -282,18 +137,21 @@ object AppearanceScope {
fun LazyGridScope.gridContent(width: Dp, height: Dp) {
@Composable
fun BackgroundItem(background: PredefinedBackgroundImage?) {
val checked = (background == null && selectedBackground == null) || (selectedBackground?.filename == background?.filename)
Box(
Modifier
.size(width, height)
.background(MaterialTheme.colors.background).clip(RoundedCornerShape(percent = cornerRadius.value.roundToInt()))
.background(MaterialTheme.colors.background)
.clip(RoundedCornerShape(percent = cornerRadius.value.roundToInt()))
.border(1.dp, if (checked) MaterialTheme.colors.primary.copy(0.8f) else MaterialTheme.colors.onBackground.copy(0.1f), RoundedCornerShape(percent = cornerRadius.value.roundToInt()))
.clickable { setBackground(background?.toType()) },
contentAlignment = Alignment.Center
) {
if (background != null) {
val backgroundImage = remember(background.filename) { PredefinedBackgroundImage.from(background.filename)?.res?.toComposeImageBitmap() }
ChatThemePreview(backgroundImage, background.toType(), withMessages = false)
ChatThemePreview(CurrentColors.value.base, backgroundImage, background.toType(), withMessages = false)
}
if ((background == null && selectedBackground == null) || (selectedBackground?.filename == background?.filename)) {
if (checked) {
Checked()
}
}
@@ -301,6 +159,8 @@ object AppearanceScope {
@Composable
fun OwnBackgroundItem(type: BackgroundImageType?) {
val backgroundImage = CurrentColors.collectAsState().value.wallpaper.type?.image
val checked = type is BackgroundImageType.Static && backgroundImage != null
val importBackgroundImageLauncher = rememberFileChooserLauncher(true) { to: URI? ->
if (to != null) {
val filename = saveBackgroundImage(to)
@@ -313,14 +173,14 @@ object AppearanceScope {
Modifier
.size(width, height)
.background(MaterialTheme.colors.background).clip(RoundedCornerShape(percent = cornerRadius.value.roundToInt()))
.border(1.dp, if (checked) MaterialTheme.colors.primary.copy(0.8f) else MaterialTheme.colors.onBackground.copy(0.1f), RoundedCornerShape(percent = cornerRadius.value.roundToInt()))
.clickable {
withLongRunningApi { importBackgroundImageLauncher.launch("image/*") }
},
contentAlignment = Alignment.Center
) {
val backgroundImage = CurrentColors.collectAsState().value.wallpaper.type?.image
if (type is BackgroundImageType.Static && backgroundImage != null) {
ChatThemePreview(backgroundImage, type, withMessages = false)
if (checked) {
ChatThemePreview(CurrentColors.value.base, backgroundImage, type, withMessages = false)
Checked()
} else {
Plus()
@@ -362,9 +222,6 @@ object AppearanceScope {
}
}
SectionItemView(showSettingsModal{ _ -> CustomizeBackgroundImageView() }) { Text(stringResource(MR.strings.choose_background_image_title)) }
val darkTheme = isSystemInDarkTheme()
val state = remember { derivedStateOf { currentTheme.name } }
ThemeSelector(state) {
@@ -390,25 +247,27 @@ object AppearanceScope {
val backgroundImage = CurrentColors.collectAsState().value.wallpaper.type?.image
val backgroundImageType = CurrentColors.collectAsState().value.wallpaper.type
ChatThemePreview(backgroundImage, backgroundImageType)
ChatThemePreview(CurrentColors.value.base, backgroundImage, backgroundImageType)
SectionSpacer()
if (backgroundImageType != null) {
SectionView(stringResource(MR.strings.settings_section_title_background_image).uppercase()) {
val wallpaperBackgroundColor = currentTheme.wallpaper.background ?: 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.wallpaper.tint ?: 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)
val systemDark = isSystemInDarkTheme()
WallpaperSetupView(
backgroundImageType,
CurrentColors.value.base,
CurrentColors.value.wallpaper.background,
CurrentColors.value.wallpaper.tint,
showPresetSelection = false,
editColor,
onColorChange = { name, color ->
ThemeManager.saveAndApplyThemeColor(name, color, systemDark)
},
onTypeChange = { type ->
ThemeManager.saveAndApplyBackgroundImage(type, systemDark)
removeBackgroundImages(type?.filename)
}
}
SectionSpacer()
)
SectionDividerSpaced(maxTopPadding = true)
}
SectionView(stringResource(MR.strings.theme_colors_section_title)) {
@@ -503,6 +362,12 @@ object AppearanceScope {
fun ColorEditor(
name: ThemeColor,
initialColor: Color,
theme: DefaultTheme,
backgroundImageType: BackgroundImageType?,
backgroundImage: ImageBitmap?,
previewBackgroundColor: Color? = CurrentColors.value.wallpaper.background,
previewTintColor: Color? = CurrentColors.value.wallpaper.tint,
onColorChange: (Color) -> Unit,
close: () -> Unit,
) {
Column(
@@ -513,9 +378,7 @@ object AppearanceScope {
val supportedLiveChange = name in listOf(ThemeColor.SECONDARY, ThemeColor.RECEIVED_MESSAGE, ThemeColor.SENT_MESSAGE, ThemeColor.WALLPAPER_BACKGROUND, ThemeColor.WALLPAPER_TINT)
if (supportedLiveChange) {
val backgroundImage = CurrentColors.collectAsState().value.wallpaper.type?.image
val backgroundImageType = CurrentColors.collectAsState().value.wallpaper.type
ChatThemePreview(backgroundImage, backgroundImageType)
ChatThemePreview(theme, backgroundImage, backgroundImageType, previewBackgroundColor, previewTintColor)
SectionSpacer()
}
@@ -524,28 +387,41 @@ object AppearanceScope {
ColorPicker(initialColor) {
currentColor = it
if (supportedLiveChange) {
ThemeManager.saveAndApplyThemeColor(name, currentColor, isInDarkTheme)
onColorChange(currentColor)
}
}
val clipboard = LocalClipboardManager.current
Row(Modifier.fillMaxWidth().padding(if (appPlatform.isAndroid) 50.dp else 0.dp), 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)) })
}
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)
onColorChange(savedColor.value)
}
}
}
SectionSpacer()
Row(Modifier.align(Alignment.CenterHorizontally)) {
Box(Modifier.size(80.dp, 40.dp).background(savedColor.value).clickable {
onColorChange(savedColor.value)
currentColor = savedColor.value
})
Box(Modifier.size(80.dp, 40.dp).background(currentColor))
}
SectionSpacer()
TextButton(
onClick = {
ThemeManager.saveAndApplyThemeColor(name, currentColor, isInDarkTheme)
onColorChange(currentColor)
savedColor.value = currentColor
close()
},
Modifier.align(Alignment.CenterHorizontally),
colors = ButtonDefaults.textButtonColors(contentColor = currentColor)
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colors.primary)
) {
Text(generalGetString(MR.strings.save_color))
}
@@ -594,7 +470,7 @@ object AppearanceScope {
@Composable
private fun ThemeSelector(state: State<String>, onSelected: (String) -> Unit) {
val darkTheme = chat.simplex.common.ui.theme.isSystemInDarkTheme()
val darkTheme = isSystemInDarkTheme()
val values by remember(ChatController.appPrefs.appLanguage.state.value) {
mutableStateOf(ThemeManager.allThemes(darkTheme).map { it.second.name to it.third })
}
@@ -630,5 +506,107 @@ object AppearanceScope {
//}
}
@Composable
fun WallpaperSetupView(
backgroundImageType: BackgroundImageType?,
theme: DefaultTheme,
initialBackgroundColor: Color?,
initialTintColor: Color?,
showPresetSelection: Boolean,
editColor: (ThemeColor, Color) -> Unit,
onColorChange: (ThemeColor, Color?) -> Unit,
onTypeChange: (BackgroundImageType?) -> Unit
) {
SectionView(stringResource(MR.strings.settings_section_title_wallpaper).uppercase()) {
if (showPresetSelection) {
val resetColors = {
onColorChange(ThemeColor.WALLPAPER_BACKGROUND, null)
onColorChange(ThemeColor.WALLPAPER_TINT, null)
}
val imageTypeState: MutableState<String?> = remember {
mutableStateOf(if (backgroundImageType is BackgroundImageType.Static) "" else backgroundImageType?.filename)
}
val imageTypeValues = remember {
listOf(null as String? to generalGetString(MR.strings.background_choose_none)) + 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 filename = saveBackgroundImage(to)
if (filename != null) {
imageTypeState.value = ""
onTypeChange(BackgroundImageType.Static(filename, 1f, BackgroundImageScaleType.FILL))
resetColors()
}
}
}
ExposedDropDownSettingRow(
stringResource(MR.strings.settings_section_title_image),
imageTypeValues,
imageTypeState,
onSelected = { filename ->
if (filename == null) {
imageTypeState.value = null
onTypeChange(null)
}
else if (filename.isEmpty()) {
withLongRunningApi { importBackgroundImageLauncher.launch("image/*") }
} else {
imageTypeState.value = filename
onTypeChange(PredefinedBackgroundImage.from(filename)!!.toType())
}
}
)
}
if (backgroundImageType is BackgroundImageType.Static) {
val state = remember(backgroundImageType.scaleType) { mutableStateOf(backgroundImageType.scaleType) }
val values = remember {
BackgroundImageScaleType.entries.map { it to generalGetString(it.text) }
}
ExposedDropDownSettingRow(
stringResource(MR.strings.background_image_scale),
values,
state,
onSelected = { scaleType ->
onTypeChange(backgroundImageType.copy(scaleType = scaleType))
}
)
}
if (backgroundImageType is BackgroundImageType.Repeated || backgroundImageType is BackgroundImageType.Static && backgroundImageType.scaleType == BackgroundImageScaleType.REPEAT) {
val state = remember(backgroundImageType.scale) { mutableStateOf(backgroundImageType.scale) }
Row(Modifier.padding(horizontal = DEFAULT_PADDING), verticalAlignment = Alignment.CenterVertically) {
Text("${state.value}".substring(0, min("${state.value}".length, 4)), Modifier.width(50.dp))
Slider(
state.value,
valueRange = 0.2f..2f,
onValueChange = {
if (backgroundImageType is BackgroundImageType.Repeated) {
onTypeChange(backgroundImageType.copy(scale = it))
} else if (backgroundImageType is BackgroundImageType.Static) {
onTypeChange(backgroundImageType.copy(scale = it))
}
}
)
}
}
if (backgroundImageType != null) {
val wallpaperBackgroundColor = initialBackgroundColor ?: backgroundImageType.defaultBackgroundColor(theme)
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 = initialTintColor ?: backgroundImageType.defaultTintColor(theme)
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)
}
}
}
}
@Composable
expect fun ColorPicker(initialColor: Color, onColorChanged: (Color) -> Unit)
@@ -712,7 +712,6 @@
<string name="socks_proxy_setting_limitations"><![CDATA[<b>Please note</b>: message and file relays are connected via SOCKS proxy. Calls and sending link previews use direct connection.]]></string>
<string name="appearance_settings">Appearance</string>
<string name="customize_theme_title">Customize theme</string>
<string name="choose_background_image_title">Choose background</string>
<string name="theme_colors_section_title">THEME COLORS</string>
<string name="app_version_title">App version</string>
<string name="app_version_name">App version: v%s</string>
@@ -1035,7 +1034,8 @@
<string name="settings_section_title_icon">APP ICON</string>
<string name="settings_section_title_themes">THEMES</string>
<string name="settings_section_title_profile_images">Profile images</string>
<string name="settings_section_title_background_image">Background image</string>
<string name="settings_section_title_wallpaper">Wallpaper</string>
<string name="settings_section_title_image">Image</string>
<string name="settings_section_title_messages">MESSAGES AND FILES</string>
<string name="settings_section_title_calls">CALLS</string>
<string name="settings_section_title_network_connection">Network connection</string>
@@ -1534,10 +1534,11 @@
<string name="color_title">Title</string>
<string name="color_sent_message">Sent message</string>
<string name="color_received_message">Received message</string>
<string name="color_wallpaper_background">Wallpaper background</string>
<string name="color_wallpaper_tint">Wallpaper tint</string>
<string name="color_wallpaper_background">Background</string>
<string name="color_wallpaper_tint">Tint</string>
<!-- Backgrounds -->
<string name="background_choose_none">None</string>
<string name="background_choose_own_image">Choose…</string>
<string name="background_cat">Cat</string>
<string name="background_hearts">Hearts</string>
@@ -1548,12 +1549,11 @@
<string name="background_rabbit">Rabbit</string>
<string name="background_image_preview_hello_alice">Hello, Alice</string>
<string name="background_image_preview_hello_bob">Hello, Bob</string>
<string name="background_image_background_color">Background</string>
<string name="background_image_tint_color">Tint</string>
<string name="background_image_scale">Scale</string>
<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>
<!-- Preferences.kt -->
<string name="chat_preferences_you_allow">You allow</string>
@@ -4,8 +4,7 @@ import SectionBottomSpacer
import SectionDividerSpaced
import SectionView
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -14,8 +13,7 @@ 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.ui.theme.*
import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.usersettings.AppearanceScope.ColorEditor
import chat.simplex.res.MR
@@ -27,13 +25,17 @@ import java.util.Locale
@Composable
actual fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)) {
val darkTheme = isSystemInDarkTheme()
val theme = CurrentColors.collectAsState().value.base
val backgroundImage = CurrentColors.collectAsState().value.wallpaper.type?.image
val backgroundImageType = CurrentColors.collectAsState().value.wallpaper.type
AppearanceScope.AppearanceLayout(
m.controller.appPrefs.appLanguage,
m.controller.appPrefs.systemDarkTheme,
showSettingsModal = showSettingsModal,
editColor = { name, initialColor ->
ModalManager.start.showModalCloseable { close ->
ColorEditor(name, initialColor, close)
ColorEditor(name, initialColor, theme, backgroundImageType, backgroundImage, onColorChange = { color -> ThemeManager.saveAndApplyThemeColor(name, color, darkTheme) }, close = close)
}
},
)