mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-05-25 12:04:22 +00:00
changes and refactoring
This commit is contained in:
+5
-1
@@ -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)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
+1
-2
@@ -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 {
|
||||
|
||||
+22
-23
@@ -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 =
|
||||
|
||||
+103
@@ -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 {
|
||||
|
||||
+4
-4
@@ -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)
|
||||
|
||||
+10
@@ -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)
|
||||
|
||||
+7
@@ -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
|
||||
|
||||
+95
-37
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-3
@@ -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
|
||||
|
||||
+172
-194
@@ -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>
|
||||
|
||||
+7
-5
@@ -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)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user