mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
android: blue theme and more options
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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 <K, V> mkMapPreference(prefName: String, default: Map<K, V>, encode: (Map<K, V>) -> String, decode: (String) -> Map<K, V>, prefs: SharedPreferences = sharedPreferences): SharedPreference<Map<K,V>> =
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Pair<Colors, DefaultTheme>> = 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<ThemeManager.ActiveTheme> = 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
|
||||
|
||||
@@ -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<Colors, DefaultTheme> {
|
||||
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<Colors, DefaultTheme> = 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<Triple<Colors, DefaultTheme, String>>()
|
||||
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()
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -100,7 +100,7 @@ fun TerminalLayout(
|
||||
modifier = Modifier
|
||||
.padding(contentPadding)
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colors.background)
|
||||
.themedBackground()
|
||||
) {
|
||||
TerminalLog(terminalItems)
|
||||
}
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -326,7 +326,7 @@ fun ChatLayout(
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colors.background)
|
||||
.themedBackground()
|
||||
) {
|
||||
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
|
||||
ModalBottomSheetLayout(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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())) {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
+3
-1
@@ -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,
|
||||
) {
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<AppIcon>,
|
||||
languagePref: SharedPreference<String?>,
|
||||
systemDarkTheme: SharedPreference<String?>,
|
||||
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<String>, onSelected: (String) -> Unit) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ThemeSelector(state: State<DefaultTheme>, onSelected: (DefaultTheme) -> Unit) {
|
||||
private fun ThemeSelector(state: State<String>, 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<DefaultTheme>, onSelected: (DefaultTheme)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DarkThemeSelector(state: State<String?>, onSelected: (String) -> Unit) {
|
||||
val values by remember {
|
||||
val darkThemes = ArrayList<Pair<String, String>>()
|
||||
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 = {},
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1156,12 +1156,15 @@
|
||||
<string name="theme_system">System</string>
|
||||
<string name="theme_light">Light</string>
|
||||
<string name="theme_dark">Dark</string>
|
||||
<string name="theme_blue">Blue</string>
|
||||
|
||||
<!-- Languages -->
|
||||
<string name="language_system">System</string>
|
||||
|
||||
<!-- Appearance.kt -->
|
||||
<string name="theme">Theme</string>
|
||||
<string name="simplex_blue_as_dark_theme">Blue in dark mode</string>
|
||||
<string name="dark_theme" translatable="false">Dark theme</string>
|
||||
<string name="save_color">Save color</string>
|
||||
<string name="reset_color">Reset colors</string>
|
||||
<string name="color_primary">Accent</string>
|
||||
|
||||
Reference in New Issue
Block a user