diff --git a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt index 953b598630..de91125f26 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt @@ -26,8 +26,7 @@ import androidx.lifecycle.* import chat.simplex.app.MainActivity.Companion.enteredBackground import chat.simplex.app.model.* import chat.simplex.app.model.NtfManager.Companion.getUserIdFromIntent -import chat.simplex.app.ui.theme.SimpleButton -import chat.simplex.app.ui.theme.SimpleXTheme +import chat.simplex.app.ui.theme.* import chat.simplex.app.views.SplashView import chat.simplex.app.views.call.ActiveCallView import chat.simplex.app.views.call.IncomingCallAlertView @@ -85,7 +84,7 @@ class MainActivity: FragmentActivity() { SimpleXTheme { Surface( Modifier - .background(MaterialTheme.colors.background) + .themedBackground() .fillMaxSize() ) { MainPage( diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt index 1fe7e1aa1a..a9196e62e5 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt @@ -14,7 +14,6 @@ import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -30,6 +29,8 @@ import kotlinx.coroutines.* import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlinx.serialization.* +import kotlinx.serialization.builtins.MapSerializer +import kotlinx.serialization.builtins.serializer import kotlinx.serialization.json.* import java.util.Date @@ -57,6 +58,7 @@ enum class SimplexLinkMode { class AppPreferences(val context: Context) { private val sharedPreferences: SharedPreferences = context.getSharedPreferences(SHARED_PREFS_ID, Context.MODE_PRIVATE) + private val sharedPreferencesThemes: SharedPreferences = context.getSharedPreferences(SHARED_PREFS_THEMES_ID, Context.MODE_PRIVATE) // deprecated, remove in 2024 private val runServiceInBackground = mkBoolPreference(SHARED_PREFS_RUN_SERVICE_IN_BACKGROUND, true) @@ -147,7 +149,12 @@ class AppPreferences(val context: Context) { val confirmDBUpgrades = mkBoolPreference(SHARED_PREFS_CONFIRM_DB_UPGRADES, false) val currentTheme = mkStrPreference(SHARED_PREFS_CURRENT_THEME, DefaultTheme.SYSTEM.name) - val primaryColor = mkIntPreference(SHARED_PREFS_PRIMARY_COLOR, LightColorPalette.primary.toArgb()) + val systemDarkTheme = mkStrPreference(SHARED_PREFS_SYSTEM_DARK_THEME, DefaultTheme.BLUE.name) + val themeOverrides = mkMapPreference(SHARED_PREFS_THEMES, mapOf(), encode = { + json.encodeToString(MapSerializer(String.serializer(), ThemeOverrides.serializer()), it) + }, decode = { + json.decodeFromString(MapSerializer(String.serializer(), ThemeOverrides.serializer()), it) + }, sharedPreferencesThemes) val whatsNewVersion = mkStrPreference(SHARED_PREFS_WHATS_NEW_VERSION, null) @@ -204,8 +211,15 @@ class AppPreferences(val context: Context) { } ) + private fun mkMapPreference(prefName: String, default: Map, encode: (Map) -> String, decode: (String) -> Map, prefs: SharedPreferences = sharedPreferences): SharedPreference> = + SharedPreference( + get = fun() = decode(prefs.getString(prefName, encode(default))!!), + set = fun(value) = prefs.edit().putString(prefName, encode(value)).apply() + ) + companion object { internal const val SHARED_PREFS_ID = "chat.simplex.app.SIMPLEX_APP_PREFS" + internal const val SHARED_PREFS_THEMES_ID = "chat.simplex.app.THEMES" private const val SHARED_PREFS_AUTO_RESTART_WORKER_VERSION = "AutoRestartWorkerVersion" private const val SHARED_PREFS_RUN_SERVICE_IN_BACKGROUND = "RunServiceInBackground" private const val SHARED_PREFS_NOTIFICATIONS_MODE = "NotificationsMode" @@ -258,7 +272,8 @@ class AppPreferences(val context: Context) { private const val SHARED_PREFS_ENCRYPTION_STARTED_AT = "EncryptionStartedAt" private const val SHARED_PREFS_CONFIRM_DB_UPGRADES = "ConfirmDBUpgrades" private const val SHARED_PREFS_CURRENT_THEME = "CurrentTheme" - private const val SHARED_PREFS_PRIMARY_COLOR = "PrimaryColor" + private const val SHARED_PREFS_SYSTEM_DARK_THEME = "SystemDarkTheme" + private const val SHARED_PREFS_THEMES = "Themes" private const val SHARED_PREFS_WHATS_NEW_VERSION = "WhatsNewVersion" } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Theme.kt b/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Theme.kt index ccd7cce6f2..80453b11b2 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Theme.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Theme.kt @@ -2,16 +2,60 @@ package chat.simplex.app.ui.theme import android.app.UiModeManager import android.content.Context +import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material.* import androidx.compose.runtime.* -import androidx.compose.ui.graphics.Color +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.* import androidx.compose.ui.unit.dp import chat.simplex.app.SimplexApp import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.serialization.Serializable enum class DefaultTheme { - SYSTEM, DARK, LIGHT + SYSTEM, LIGHT, DARK, BLUE; + + // Call in only with base theme, not SYSTEM + fun hasChangedPrimary(colors: Colors): Boolean { + return when (this) { + SYSTEM -> return false + LIGHT -> colors.primary != LightColorPalette.primary + DARK -> colors.primary != DarkColorPalette.primary + BLUE -> colors.primary != BlueColorPalette.primary + } + } +} + +@Serializable +data class ThemeColors( + val primary: String? = null +) { + fun toColors(base: DefaultTheme): Colors = when (base) { + DefaultTheme.LIGHT -> LightColorPalette.copy(primary = primary?.colorFromReadableHex() ?: LightColorPalette.primary) + DefaultTheme.DARK -> DarkColorPalette.copy(primary = primary?.colorFromReadableHex() ?: DarkColorPalette.primary) + DefaultTheme.BLUE -> BlueColorPalette.copy(primary = primary?.colorFromReadableHex() ?: BlueColorPalette.primary) + // shouldn't be here + DefaultTheme.SYSTEM -> LightColorPalette.copy(primary = primary?.colorFromReadableHex() ?: LightColorPalette.primary) + } +} + +private fun String.colorFromReadableHex(): Color = + Color(this.replace("#", "").toLongOrNull(16) ?: Color.White.toArgb().toLong()) + +@Serializable +data class ThemeOverrides ( + val base: DefaultTheme, + val colors: ThemeColors +) + +fun Modifier.themedBackground(baseTheme: DefaultTheme = CurrentColors.value.base, shape: Shape = RectangleShape): Modifier { + return if (baseTheme == DefaultTheme.BLUE) { + this.background(brush = Brush.linearGradient(listOf(Color(0xff0C0B13), Color(0xff151D36)), Offset(0f, Float.POSITIVE_INFINITY), Offset(Float.POSITIVE_INFINITY, 0f)), shape = shape) + } else { + this.background(color = CurrentColors.value.colors.background, shape = shape) + } } val DEFAULT_PADDING = 20.dp @@ -22,10 +66,10 @@ val DEFAULT_BOTTOM_BUTTON_PADDING = 20.dp val DarkColorPalette = darkColors( primary = SimplexBlue, // If this value changes also need to update #0088ff in string resource files - primaryVariant = SimplexGreen, + primaryVariant = SimplexBlue, secondary = DarkGray, // background = Color.Black, -// surface = Color.Black, + surface = Color(0xFF121212), // background = Color(0xFF121212), // surface = Color(0xFF121212), error = Color.Red, @@ -35,25 +79,39 @@ val DarkColorPalette = darkColors( ) val LightColorPalette = lightColors( primary = SimplexBlue, // If this value changes also need to update #0088ff in string resource files - primaryVariant = SimplexGreen, + primaryVariant = SimplexBlue, secondary = LightGray, error = Color.Red, // background = Color.White, -// surface = Color.White + surface = Color.White, // onPrimary = Color.White, // onSecondary = Color.Black, // onBackground = Color.Black, // onSurface = Color.Black, ) -val CurrentColors: MutableStateFlow> = MutableStateFlow(ThemeManager.currentColors(isInNightMode())) +val BlueColorPalette = darkColors( + primary = Color(0xff70F0F9), // If this value changes also need to update #0088ff in string resource files + primaryVariant = Color(0xff298AE7), + secondary = Color(0xff2C464D), + background = Color(0xff111528), + // surface = Color.Black, + // background = Color(0xFF121212), + surface = Color(0xFF1C1C22), + error = Color.Red, + onBackground = Color(0xFFFFFBFA), + onSurface = Color(0xFFFFFBFA), + // onError: Color = Color.Black, +) + +val CurrentColors: MutableStateFlow = MutableStateFlow(ThemeManager.currentColors(isInNightMode())) // Non-@Composable implementation private fun isInNightMode() = (SimplexApp.context.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager).nightMode == UiModeManager.MODE_NIGHT_YES @Composable -fun isInDarkTheme(): Boolean = !CurrentColors.collectAsState().value.first.isLight +fun isInDarkTheme(): Boolean = !CurrentColors.collectAsState().value.colors.isLight @Composable fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) { @@ -64,14 +122,14 @@ fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) { } val systemDark = isSystemInDarkTheme() LaunchedEffect(systemDark) { - if (CurrentColors.value.second == DefaultTheme.SYSTEM && CurrentColors.value.first.isLight == systemDark) { + if (SimplexApp.context.chatModel.controller.appPrefs.currentTheme.get() == DefaultTheme.SYSTEM.name && CurrentColors.value.colors.isLight == systemDark) { // Change active colors from light to dark and back based on system theme ThemeManager.applyTheme(DefaultTheme.SYSTEM.name, systemDark) } } val theme by CurrentColors.collectAsState() MaterialTheme( - colors = theme.first, + colors = theme.colors, typography = Typography, shapes = Shapes, content = content diff --git a/apps/android/app/src/main/java/chat/simplex/app/ui/theme/ThemeManager.kt b/apps/android/app/src/main/java/chat/simplex/app/ui/theme/ThemeManager.kt index a1d5ee4526..6a7174b477 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/ui/theme/ThemeManager.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/ui/theme/ThemeManager.kt @@ -7,22 +7,41 @@ import chat.simplex.app.R import chat.simplex.app.SimplexApp import chat.simplex.app.model.AppPreferences import chat.simplex.app.views.helpers.generalGetString +import okhttp3.internal.toHexString object ThemeManager { private val appPrefs: AppPreferences by lazy { - AppPreferences(SimplexApp.context) + SimplexApp.context.chatModel.controller.appPrefs } - fun currentColors(darkForSystemTheme: Boolean): Pair { - val theme = appPrefs.currentTheme.get()!! - val systemThemeColors = if (darkForSystemTheme) DarkColorPalette else LightColorPalette - val res = when (theme) { - DefaultTheme.SYSTEM.name -> Pair(systemThemeColors, DefaultTheme.SYSTEM) - DefaultTheme.DARK.name -> Pair(DarkColorPalette, DefaultTheme.DARK) - DefaultTheme.LIGHT.name -> Pair(LightColorPalette, DefaultTheme.LIGHT) - else -> Pair(systemThemeColors, DefaultTheme.SYSTEM) + data class ActiveTheme(val name: String, val base: DefaultTheme, val colors: Colors) + + private fun systemDarkThemeColors(): Pair = when (appPrefs.systemDarkTheme.get()) { + DefaultTheme.DARK.name -> DarkColorPalette to DefaultTheme.DARK + DefaultTheme.BLUE.name -> BlueColorPalette to DefaultTheme.BLUE + else -> BlueColorPalette to DefaultTheme.BLUE + } + + fun currentColors(darkForSystemTheme: Boolean): ActiveTheme { + val themeName = appPrefs.currentTheme.get()!! + val themeOverrides = appPrefs.themeOverrides.get() + + val theme = if (themeName != DefaultTheme.SYSTEM.name) { + themeOverrides[themeName] + } else { + themeOverrides[if (darkForSystemTheme) appPrefs.systemDarkTheme.get() else DefaultTheme.LIGHT.name] } - return res.copy(first = res.first.copy(primary = Color(appPrefs.primaryColor.get()))) + val baseTheme = when (themeName) { + DefaultTheme.SYSTEM.name -> if (darkForSystemTheme) systemDarkThemeColors() else Pair(LightColorPalette, DefaultTheme.LIGHT) + DefaultTheme.LIGHT.name -> Pair(LightColorPalette, DefaultTheme.LIGHT) + DefaultTheme.DARK.name -> Pair(DarkColorPalette, DefaultTheme.DARK) + DefaultTheme.BLUE.name -> Pair(BlueColorPalette, DefaultTheme.BLUE) + else -> if (theme != null) theme.colors.toColors(theme.base) to theme.base else Pair(LightColorPalette, DefaultTheme.LIGHT) + } + if (theme == null) { + return ActiveTheme(themeName, baseTheme.second, baseTheme.first) + } + return ActiveTheme(themeName, baseTheme.second, theme.colors.toColors(theme.base)) } // colors, default theme enum, localized name of theme @@ -30,7 +49,7 @@ object ThemeManager { val allThemes = ArrayList>() allThemes.add( Triple( - if (darkForSystemTheme) DarkColorPalette else LightColorPalette, + if (darkForSystemTheme) systemDarkThemeColors().first else LightColorPalette, DefaultTheme.SYSTEM, generalGetString(R.string.theme_system) ) @@ -49,16 +68,47 @@ object ThemeManager { generalGetString(R.string.theme_dark) ) ) + allThemes.add( + Triple( + BlueColorPalette, + DefaultTheme.BLUE, + generalGetString(R.string.theme_blue) + ) + ) return allThemes } - fun applyTheme(name: String, darkForSystemTheme: Boolean) { - appPrefs.currentTheme.set(name) + fun applyTheme(theme: String, darkForSystemTheme: Boolean) { + appPrefs.currentTheme.set(theme) CurrentColors.value = currentColors(darkForSystemTheme) } - fun saveAndApplyPrimaryColor(color: Color) { - appPrefs.primaryColor.set(color.toArgb()) - CurrentColors.value = currentColors(!CurrentColors.value.first.isLight) + fun changeDarkTheme(theme: String, darkForSystemTheme: Boolean) { + appPrefs.systemDarkTheme.set(theme) + CurrentColors.value = currentColors(darkForSystemTheme) + } + + fun saveAndApplyPrimaryColor(color: Color? = null, darkForSystemTheme: Boolean) { + val themeName = appPrefs.currentTheme.get()!! + val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.name) { + themeName + } else { + if (darkForSystemTheme) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.name + } + val color = color ?: when(themeName) { + DefaultTheme.LIGHT.name -> LightColorPalette.primary + DefaultTheme.DARK.name -> DarkColorPalette.primary + DefaultTheme.BLUE.name -> BlueColorPalette.primary + else -> (if (darkForSystemTheme) systemDarkThemeColors().first else LightColorPalette).primary + } + val overrides = appPrefs.themeOverrides.get().toMutableMap() + val prevValue = overrides[nonSystemThemeName] + val current = prevValue?.copy(colors = prevValue.colors.copy(primary = color.toReadableHex())) + ?: ThemeOverrides(base = CurrentColors.value.base, colors = ThemeColors(primary = color.toReadableHex())) + overrides[nonSystemThemeName] = current + appPrefs.themeOverrides.set(overrides) + CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight) } } + +private fun Color.toReadableHex(): String = "#" + toArgb().toHexString() diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/SplashView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/SplashView.kt index f37a6fca8e..014a8d6013 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/SplashView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/SplashView.kt @@ -6,12 +6,13 @@ import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import chat.simplex.app.ui.theme.themedBackground @Composable fun SplashView() { Surface( Modifier - .background(MaterialTheme.colors.background) + .themedBackground() .fillMaxSize() ) { // Image( diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt index 8641b4a08a..b342cc731d 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt @@ -100,7 +100,7 @@ fun TerminalLayout( modifier = Modifier .padding(contentPadding) .fillMaxWidth() - .background(MaterialTheme.colors.background) + .themedBackground() ) { TerminalLog(terminalItems) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/call/CallView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/call/CallView.kt index 9025ba32db..4d9f43017b 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/call/CallView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/call/CallView.kt @@ -367,7 +367,7 @@ fun CallInfoView(call: Call, alignment: Alignment.Horizontal) { // horizontalAlignment = Alignment.CenterHorizontally, // verticalArrangement = Arrangement.spacedBy(12.dp), // modifier = Modifier -// .background(MaterialTheme.colors.background) +// .themedBackground() // .fillMaxSize() // ) { // WebRTCView(callCommand) { apiMsg -> diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/call/IncomingCallActivity.kt b/apps/android/app/src/main/java/chat/simplex/app/views/call/IncomingCallActivity.kt index 55abc1a941..0230a74852 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/call/IncomingCallActivity.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/call/IncomingCallActivity.kt @@ -97,7 +97,7 @@ fun IncomingCallActivityView(m: ChatModel) { SimpleXTheme { Surface( Modifier - .background(MaterialTheme.colors.background) + .themedBackground() .fillMaxSize()) { if (showCallView) { Box { @@ -226,7 +226,7 @@ fun PreviewIncomingCallLockScreenAlert() { SimpleXTheme(true) { Surface( Modifier - .background(MaterialTheme.colors.background) + .themedBackground() .fillMaxSize()) { IncomingCallLockScreenAlertLayout( invitation = RcvCallInvitation( diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt index 1731ba49a6..5596ff735f 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt @@ -326,7 +326,7 @@ fun ChatLayout( Box( Modifier .fillMaxWidth() - .background(MaterialTheme.colors.background) + .themedBackground() ) { ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { ModalBottomSheetLayout( diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt index 1580a265d2..fdd310b5f9 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt @@ -96,7 +96,6 @@ fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean, FragmentActivity) Column( modifier = Modifier .fillMaxSize() - .background(MaterialTheme.colors.background) ) { if (chatModel.chats.isNotEmpty()) { ChatList(chatModel, search = searchInList) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ShareListView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ShareListView.kt index f601e65378..c7fd5a3807 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ShareListView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ShareListView.kt @@ -19,8 +19,7 @@ import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.unit.dp import chat.simplex.app.R import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.HighOrLowlight -import chat.simplex.app.ui.theme.Indigo +import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* import kotlinx.coroutines.flow.MutableStateFlow @@ -36,7 +35,7 @@ fun ShareListView(chatModel: ChatModel, stopped: Boolean) { Column( modifier = Modifier .fillMaxSize() - .background(MaterialTheme.colors.background) + .themedBackground() ) { if (chatModel.chats.isNotEmpty()) { ShareList(chatModel, search = searchInList) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/UserPicker.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/UserPicker.kt index d073d2d2de..2c62d1357e 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/UserPicker.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/UserPicker.kt @@ -107,7 +107,7 @@ fun UserPicker( .width(IntrinsicSize.Min) .height(IntrinsicSize.Min) .shadow(8.dp, RoundedCornerShape(corner = CornerSize(25.dp)), clip = true) - .background(if (isInDarkTheme()) DarkGrayBackground else MaterialTheme.colors.background, RoundedCornerShape(corner = CornerSize(25.dp))) + .background(MaterialTheme.colors.surface, RoundedCornerShape(corner = CornerSize(25.dp))) .clip(RoundedCornerShape(corner = CornerSize(25.dp))) ) { Column(Modifier.weight(1f).verticalScroll(rememberScrollState())) { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/CloseSheetBar.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/CloseSheetBar.kt index 1c9cb170d0..d513e48a1e 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/CloseSheetBar.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/CloseSheetBar.kt @@ -4,6 +4,7 @@ import android.content.res.Configuration import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign @@ -48,7 +49,7 @@ fun AppBarTitle(title: String, withPadding: Boolean = true) { .padding(bottom = DEFAULT_PADDING * 1.5f, start = if (withPadding) DEFAULT_PADDING else 0.dp, end = if (withPadding) DEFAULT_PADDING else 0.dp,), overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.h1, - color = MaterialTheme.colors.primary, + color = if (CurrentColors.collectAsState().value.base == DefaultTheme.BLUE) MaterialTheme.colors.primaryVariant else MaterialTheme.colors.primary, textAlign = TextAlign.Center ) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultDropdownMenu.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultDropdownMenu.kt index e6ec87e14d..c15e7c5fc7 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultDropdownMenu.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultDropdownMenu.kt @@ -1,5 +1,6 @@ package chat.simplex.app.views.helpers +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.RoundedCornerShape @@ -19,7 +20,7 @@ fun DefaultDropdownMenu( dropdownMenuItems: (@Composable () -> Unit)? ) { MaterialTheme( - colors = MaterialTheme.colors.copy(surface = if (isInDarkTheme()) Color(0xFF0B0B0B) else MaterialTheme.colors.background), + colors = MaterialTheme.colors.copy(surface = MaterialTheme.colors.surface), shapes = MaterialTheme.shapes.copy(medium = RoundedCornerShape(corner = CornerSize(25.dp))) ) { DropdownMenu( @@ -27,6 +28,7 @@ fun DefaultDropdownMenu( onDismissRequest = { showMenu.value = false }, Modifier .widthIn(min = 250.dp) + .background(MaterialTheme.colors.surface) .padding(vertical = 4.dp), offset = offset, ) { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ModalView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ModalView.kt index 7e834806a1..3ba958a540 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ModalView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ModalView.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import chat.simplex.app.TAG import chat.simplex.app.ui.theme.isInDarkTheme +import chat.simplex.app.ui.theme.themedBackground import java.util.concurrent.atomic.AtomicBoolean @Composable @@ -24,7 +25,7 @@ fun ModalView( ) { BackHandler(onBack = close) Surface(Modifier.fillMaxSize()) { - Column(Modifier.background(background)) { + Column(if (background != MaterialTheme.colors.background) Modifier.background(background) else Modifier.themedBackground()) { CloseSheetBar(close, endButtons) Box(modifier) { content() } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SimpleButton.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SimpleButton.kt index 9f32850e95..0ee4228a6c 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SimpleButton.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SimpleButton.kt @@ -1,14 +1,15 @@ package chat.simplex.app.ui.theme +import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.ui.res.painterResource import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.text.font.FontWeight @@ -79,7 +80,7 @@ fun SimpleButtonIconEnded( @Composable fun SimpleButtonFrame(click: () -> Unit, modifier: Modifier = Modifier, disabled: Boolean = false, content: @Composable () -> Unit) { - Surface(shape = RoundedCornerShape(20.dp)) { + Box(Modifier.clip(RoundedCornerShape(20.dp))) { val modifier = if (disabled) modifier else modifier.clickable { click() } Row( verticalAlignment = Alignment.CenterVertically, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/WhatsNewView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/WhatsNewView.kt index a3acb2e34b..70c6da3998 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/WhatsNewView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/WhatsNewView.kt @@ -9,6 +9,7 @@ import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource @@ -71,7 +72,7 @@ fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) { ) { if (currentVersion.value > 0) { val prev = currentVersion.value - 1 - Surface(shape = RoundedCornerShape(20.dp)) { + Box(Modifier.clip(RoundedCornerShape(20.dp))) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), @@ -87,7 +88,7 @@ fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) { Spacer(Modifier.fillMaxWidth().weight(1f)) if (currentVersion.value < versionDescriptions.lastIndex) { val next = currentVersion.value + 1 - Surface(shape = RoundedCornerShape(20.dp)) { + Box(Modifier.clip(RoundedCornerShape(20.dp))) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), @@ -113,17 +114,7 @@ fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) { .verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(DEFAULT_PADDING) ) { - Text( - String.format(generalGetString(R.string.new_in_version), v.version), - Modifier - .fillMaxWidth() - .padding(DEFAULT_PADDING), - textAlign = TextAlign.Center, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.h1, - fontWeight = FontWeight.Normal, - color = HighOrLowlight - ) + AppBarTitle(String.format(generalGetString(R.string.new_in_version), v.version)) v.features.forEach { feature -> featureDescription(painterResource(feature.icon), feature.titleId, feature.descrId, feature.link) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/Appearance.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/Appearance.kt index ae84518bfd..9d0c454d1c 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/Appearance.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/Appearance.kt @@ -39,6 +39,7 @@ import chat.simplex.app.views.helpers.* import com.godaddy.android.colorpicker.* import kotlinx.coroutines.delay import java.util.* +import kotlin.collections.ArrayList enum class AppIcon(val resId: Int) { DEFAULT(R.mipmap.icon), @@ -69,6 +70,7 @@ fun AppearanceView(m: ChatModel) { AppearanceLayout( appIcon, m.controller.appPrefs.appLanguage, + m.controller.appPrefs.systemDarkTheme, changeIcon = ::setAppIcon, editPrimaryColor = { primary -> ModalManager.shared.showModalCloseable { close -> @@ -81,6 +83,7 @@ fun AppearanceView(m: ChatModel) { @Composable fun AppearanceLayout( icon: MutableState, languagePref: SharedPreference, + systemDarkTheme: SharedPreference, changeIcon: (AppIcon) -> Unit, editPrimaryColor: (Color) -> Unit, ) { @@ -144,21 +147,31 @@ fun AppearanceView(m: ChatModel) { val currentTheme by CurrentColors.collectAsState() SectionView(stringResource(R.string.settings_section_title_themes)) { val darkTheme = isSystemInDarkTheme() - val state = remember { derivedStateOf { currentTheme.second } } + val state = remember { derivedStateOf { currentTheme.name } } ThemeSelector(state) { - ThemeManager.applyTheme(it.name, darkTheme) + ThemeManager.applyTheme(it, darkTheme) } - SectionItemViewSpaceBetween({ editPrimaryColor(currentTheme.first.primary) }) { + if (state.value == DefaultTheme.SYSTEM.name) { + val systemDarkTheme = remember { systemDarkTheme.state } + PreferenceToggle(generalGetString(R.string.simplex_blue_as_dark_theme), systemDarkTheme.value == DefaultTheme.BLUE.name) { + ThemeManager.changeDarkTheme(if (it) DefaultTheme.BLUE.name else DefaultTheme.DARK.name, darkTheme) + } + /*DarkThemeSelector(systemDarkTheme) { + ThemeManager.changeDarkTheme(it, darkTheme) + }*/ + } + SectionItemViewSpaceBetween({ editPrimaryColor(currentTheme.colors.primary) }) { val title = generalGetString(R.string.color_primary) Text(title) Icon(painterResource(R.drawable.ic_circle_filled), title, tint = colors.primary) } } - if (currentTheme.first.primary != LightColorPalette.primary) { + if (currentTheme.base.hasChangedPrimary(currentTheme.colors)) { SectionCustomFooter(PaddingValues(start = 7.dp, end = 7.dp, top = 5.dp)) { + val isInDarkTheme = isInDarkTheme() TextButton( onClick = { - ThemeManager.saveAndApplyPrimaryColor(LightColorPalette.primary) + ThemeManager.saveAndApplyPrimaryColor(darkForSystemTheme = isInDarkTheme) }, ) { Text(generalGetString(R.string.reset_color)) @@ -185,10 +198,10 @@ fun ColorEditor( } SectionSpacer() - + val isInDarkTheme = isInDarkTheme() TextButton( onClick = { - ThemeManager.saveAndApplyPrimaryColor(currentColor) + ThemeManager.saveAndApplyPrimaryColor(currentColor, isInDarkTheme) close() }, Modifier.align(Alignment.CenterHorizontally), @@ -241,9 +254,9 @@ private fun LangSelector(state: State, onSelected: (String) -> Unit) { } @Composable -private fun ThemeSelector(state: State, onSelected: (DefaultTheme) -> Unit) { +private fun ThemeSelector(state: State, onSelected: (String) -> Unit) { val darkTheme = isSystemInDarkTheme() - val values by remember { mutableStateOf(ThemeManager.allThemes(darkTheme).map { it.second to it.third }) } + val values by remember { mutableStateOf(ThemeManager.allThemes(darkTheme).map { it.second.name to it.third }) } ExposedDropDownSettingRow( generalGetString(R.string.theme), values, @@ -254,6 +267,24 @@ private fun ThemeSelector(state: State, onSelected: (DefaultTheme) ) } +@Composable +private fun DarkThemeSelector(state: State, onSelected: (String) -> Unit) { + val values by remember { + val darkThemes = ArrayList>() + darkThemes.add(DefaultTheme.DARK.name to generalGetString(R.string.theme_dark)) + darkThemes.add(DefaultTheme.BLUE.name to generalGetString(R.string.theme_blue)) + mutableStateOf(darkThemes.toList()) + } + ExposedDropDownSettingRow( + generalGetString(R.string.dark_theme), + values, + state, + icon = null, + enabled = remember { mutableStateOf(true) }, + onSelected = { if (it != null) onSelected(it) } + ) +} + //private fun openSystemLangPicker(activity: Activity) { // activity.startActivity(Intent(Settings.ACTION_APP_LOCALE_SETTINGS, Uri.parse("package:" + SimplexApp.context.packageName))) //} @@ -271,6 +302,7 @@ fun PreviewAppearanceSettings() { AppearanceLayout( icon = remember { mutableStateOf(AppIcon.DARK_BLUE) }, languagePref = SharedPreference({ null }, {}), + systemDarkTheme = SharedPreference({ null }, {}), changeIcon = {}, editPrimaryColor = {}, ) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt index c28aa38f5a..e8ff0d8d1a 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt @@ -134,8 +134,9 @@ fun SettingsLayout( showVersion: () -> Unit, withAuth: (title: String, desc: String, block: () -> Unit) -> Unit ) { + val theme = CurrentColors.collectAsState() val uriHandler = LocalUriHandler.current - Box(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).background(MaterialTheme.colors.background)) { + Box(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).themedBackground(theme.value.base)) { Column( Modifier .fillMaxSize() diff --git a/apps/android/app/src/main/res/values/strings.xml b/apps/android/app/src/main/res/values/strings.xml index 578c14a298..3adab34212 100644 --- a/apps/android/app/src/main/res/values/strings.xml +++ b/apps/android/app/src/main/res/values/strings.xml @@ -1156,12 +1156,15 @@ System Light Dark + Blue System Theme + Blue in dark mode + Dark theme Save color Reset colors Accent