android: choosing theme and accent color (#967)

* Theme selector
- ability to select from three default choices: system theme, light, dark
- ability to choose color accent (primary color)

* Removed unused code and made small changes to colors and buttons
This commit is contained in:
Stanislav Dmitrenko
2022-08-24 23:15:26 +03:00
committed by GitHub
parent 53a71cf28c
commit a5e74ea2f0
28 changed files with 358 additions and 84 deletions

View File

@@ -94,6 +94,7 @@ dependencies {
implementation "androidx.navigation:navigation-compose:2.4.1"
implementation "com.google.accompanist:accompanist-insets:0.23.0"
implementation 'androidx.webkit:webkit:1.4.0'
implementation "com.godaddy.android.colorpicker:compose-color-picker:0.4.2"
def work_version = "2.7.1"
implementation "androidx.work:work-runtime-ktx:$work_version"

View File

@@ -5,7 +5,6 @@ import android.app.ActivityManager
import android.app.ActivityManager.RunningAppProcessInfo
import android.app.Application
import android.content.*
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.PowerManager
@@ -16,12 +15,14 @@ import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Bolt
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.fragment.app.FragmentActivity
import chat.simplex.app.*
import chat.simplex.app.R
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.call.*
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.onboarding.OnboardingStage
@@ -95,6 +96,9 @@ class AppPreferences(val context: Context) {
val networkTCPKeepIntvl = mkIntPreference(SHARED_PREFS_NETWORK_TCP_KEEP_INTVL, KeepAliveOpts.defaults.keepIntvl)
val networkTCPKeepCnt = mkIntPreference(SHARED_PREFS_NETWORK_TCP_KEEP_CNT, KeepAliveOpts.defaults.keepCnt)
val currentTheme = mkStrPreference(SHARED_PREFS_CURRENT_THEME, DefaultTheme.SYSTEM.name)
val primaryColor = mkIntPreference(SHARED_PREFS_PRIMARY_COLOR, LightColorPalette.primary.toArgb())
private fun mkIntPreference(prefName: String, default: Int) =
Preference(
get = fun() = sharedPreferences.getInt(prefName, default),
@@ -161,6 +165,8 @@ class AppPreferences(val context: Context) {
private const val SHARED_PREFS_NETWORK_TCP_KEEP_IDLE = "NetworkTCPKeepIdle"
private const val SHARED_PREFS_NETWORK_TCP_KEEP_INTVL = "NetworkTCPKeepIntvl"
private const val SHARED_PREFS_NETWORK_TCP_KEEP_CNT = "NetworkTCPKeepCnt"
private const val SHARED_PREFS_CURRENT_THEME = "CurrentTheme"
private const val SHARED_PREFS_PRIMARY_COLOR = "PrimaryColor"
}
}

View File

@@ -2,10 +2,15 @@ package chat.simplex.app.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import kotlinx.coroutines.flow.MutableStateFlow
private val DarkColorPalette = darkColors(
enum class DefaultTheme {
SYSTEM, DARK, LIGHT
}
val DarkColorPalette = darkColors(
primary = SimplexBlue, // If this value changes also need to update #0088ff in string resource files
primaryVariant = SimplexGreen,
secondary = DarkGray,
@@ -18,7 +23,7 @@ private val DarkColorPalette = darkColors(
onSurface = Color(0xFFFFFBFA),
// onError: Color = Color.Black,
)
private val LightColorPalette = lightColors(
val LightColorPalette = lightColors(
primary = SimplexBlue, // If this value changes also need to update #0088ff in string resource files
primaryVariant = SimplexGreen,
secondary = LightGray,
@@ -30,16 +35,28 @@ private val LightColorPalette = lightColors(
// onSurface = Color.Black,
)
@Composable
fun SimpleXTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
val colors = if (darkTheme) {
DarkColorPalette
} else {
LightColorPalette
}
val CurrentColors: MutableStateFlow<Pair<Colors, DefaultTheme>> = MutableStateFlow(ThemeManager.currentColors(true))
@Composable
fun isInDarkTheme(): Boolean = !CurrentColors.collectAsState().value.first.isLight
@Composable
fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) {
LaunchedEffect(darkTheme) {
// For preview
if (darkTheme != null)
CurrentColors.value = ThemeManager.currentColors(darkTheme)
}
val systemDark = isSystemInDarkTheme()
LaunchedEffect(systemDark) {
if (CurrentColors.value.second == DefaultTheme.SYSTEM && CurrentColors.value.first.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 = colors,
colors = theme.first,
typography = Typography,
shapes = Shapes,
content = content

View File

@@ -0,0 +1,64 @@
package chat.simplex.app.ui.theme
import androidx.compose.material.Colors
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import chat.simplex.app.R
import chat.simplex.app.SimplexApp
import chat.simplex.app.model.AppPreferences
import chat.simplex.app.views.helpers.generalGetString
object ThemeManager {
private val appPrefs: AppPreferences by lazy {
AppPreferences(SimplexApp.context)
}
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)
}
return res.copy(first = res.first.copy(primary = Color(appPrefs.primaryColor.get())))
}
// colors, default theme enum, localized name of theme
fun allThemes(darkForSystemTheme: Boolean): List<Triple<Colors, DefaultTheme, String>> {
val allThemes = ArrayList<Triple<Colors, DefaultTheme, String>>()
allThemes.add(
Triple(
if (darkForSystemTheme) DarkColorPalette else LightColorPalette,
DefaultTheme.SYSTEM,
generalGetString(R.string.theme_system)
)
)
allThemes.add(
Triple(
LightColorPalette,
DefaultTheme.LIGHT,
generalGetString(R.string.theme_light)
)
)
allThemes.add(
Triple(
DarkColorPalette,
DefaultTheme.DARK,
generalGetString(R.string.theme_dark)
)
)
return allThemes
}
fun applyTheme(name: String, darkForSystemTheme: Boolean) {
appPrefs.currentTheme.set(name)
CurrentColors.value = currentColors(darkForSystemTheme)
}
fun saveAndApplyPrimaryColor(color: Color) {
appPrefs.primaryColor.set(color.toArgb())
CurrentColors.value = currentColors(!CurrentColors.value.first.isLight)
}
}

View File

@@ -45,7 +45,7 @@ fun IncomingCallAlertLayout(
ignoreCall: () -> Unit,
acceptCall: () -> Unit
) {
val color = if (isSystemInDarkTheme()) IncomingCallDark else IncomingCallLight
val color = if (isInDarkTheme()) IncomingCallDark else IncomingCallLight
Column(Modifier.background(color).padding(top = 16.dp, bottom = 16.dp, start = 16.dp, end = 8.dp)) {
IncomingCallInfo(invitation)
Spacer(Modifier.height(8.dp))

View File

@@ -198,7 +198,7 @@ fun ChatInfoHeader(cInfo: ChatInfo) {
Modifier.padding(horizontal = 8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
ChatInfoImage(cInfo, size = 192.dp, iconColor = if (isSystemInDarkTheme()) GroupDark else SettingsSecondaryLight)
ChatInfoImage(cInfo, size = 192.dp, iconColor = if (isInDarkTheme()) GroupDark else SettingsSecondaryLight)
Text(
cInfo.displayName, style = MaterialTheme.typography.h1.copy(fontWeight = FontWeight.Normal),
color = MaterialTheme.colors.onBackground,

View File

@@ -114,7 +114,7 @@ fun ChatView(chatModel: ChatModel) {
ModalManager.shared.showCustomModal { close ->
ModalView(
close = close, modifier = Modifier,
background = if (isSystemInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
) {
ChatInfoView(chatModel, connStats, close)
}
@@ -124,7 +124,7 @@ fun ChatView(chatModel: ChatModel) {
ModalManager.shared.showCustomModal { close ->
ModalView(
close = close, modifier = Modifier,
background = if (isSystemInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
) {
GroupChatInfoView(chatModel, close)
}
@@ -189,7 +189,7 @@ fun ChatView(chatModel: ChatModel) {
ModalManager.shared.showCustomModal { close ->
ModalView(
close = close, modifier = Modifier,
background = if (isSystemInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
) {
AddGroupMembersView(groupInfo, chatModel, close)
}

View File

@@ -1,5 +1,4 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
@@ -31,7 +30,7 @@ fun ComposeFileView(fileName: String, cancelFile: () -> Unit, cancelEnabled: Boo
Modifier
.padding(start = 4.dp, end = 2.dp)
.size(36.dp),
tint = if (isSystemInDarkTheme()) FileDark else FileLight
tint = if (isInDarkTheme()) FileDark else FileLight
)
Text(fileName)
Spacer(Modifier.weight(1f))

View File

@@ -93,7 +93,7 @@ fun AddGroupMembersLayout(
ChatInfoToolbarTitle(
ChatInfo.Group(groupInfo),
imageSize = 60.dp,
iconColor = if (isSystemInDarkTheme()) GroupDark else SettingsSecondaryLight
iconColor = if (isInDarkTheme()) GroupDark else SettingsSecondaryLight
)
}
SectionSpacer()

View File

@@ -47,7 +47,7 @@ fun GroupChatInfoView(chatModel: ChatModel, close: () -> Unit) {
ModalManager.shared.showCustomModal { close ->
ModalView(
close = close, modifier = Modifier,
background = if (isSystemInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
) {
AddGroupMembersView(groupInfo, chatModel, close)
}
@@ -60,7 +60,7 @@ fun GroupChatInfoView(chatModel: ChatModel, close: () -> Unit) {
ModalManager.shared.showCustomModal { close ->
ModalView(
close = close, modifier = Modifier,
background = if (isSystemInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
) {
GroupMemberInfoView(groupInfo, member, connStats, chatModel, close)
}

View File

@@ -139,7 +139,7 @@ fun GroupMemberInfoHeader(member: GroupMember) {
Modifier.padding(horizontal = 8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
ProfileImage(size = 192.dp, member.image, color = if (isSystemInDarkTheme()) GroupDark else SettingsSecondaryLight)
ProfileImage(size = 192.dp, member.image, color = if (isInDarkTheme()) GroupDark else SettingsSecondaryLight)
Text(
member.displayName, style = MaterialTheme.typography.h1.copy(fontWeight = FontWeight.Normal),
color = MaterialTheme.colors.onBackground,

View File

@@ -2,7 +2,6 @@ package chat.simplex.app.views.chat.item
import android.widget.Toast
import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
@@ -39,7 +38,7 @@ fun CIFileView(
@Composable
fun fileIcon(
innerIcon: ImageVector? = null,
color: Color = if (isSystemInDarkTheme()) FileDark else FileLight
color: Color = if (isInDarkTheme()) FileDark else FileLight
) {
Box(
contentAlignment = Alignment.Center
@@ -105,7 +104,7 @@ fun CIFileView(
fun progressIndicator() {
CircularProgressIndicator(
Modifier.size(32.dp),
color = if (isSystemInDarkTheme()) FileDark else FileLight,
color = if (isInDarkTheme()) FileDark else FileLight,
strokeWidth = 4.dp
)
}

View File

@@ -3,7 +3,6 @@ package chat.simplex.app.views.chat.item
import android.content.res.Configuration
import android.util.Log
import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
@@ -39,7 +38,7 @@ fun CIGroupInvitationView(
val p = groupInvitation.groupProfile
val iconColor =
if (action) MaterialTheme.colors.primary
else if (isSystemInDarkTheme()) FileDark else FileLight
else if (isInDarkTheme()) FileDark else FileLight
Row(
Modifier

View File

@@ -1,8 +1,7 @@
package chat.simplex.app.views.chat.item
import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.runtime.Composable
@@ -16,7 +15,6 @@ import androidx.compose.ui.unit.sp
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.SimplexBlue
import kotlinx.datetime.Clock
@Composable
@@ -56,7 +54,7 @@ fun CIStatusView(status: CIStatus, metaColor: Color = HighOrLowlight) {
Icon(Icons.Filled.WarningAmber, stringResource(R.string.icon_descr_sent_msg_status_send_failed), Modifier.height(12.dp), tint = Color.Yellow)
}
is CIStatus.RcvNew -> {
Icon(Icons.Filled.Circle, stringResource(R.string.icon_descr_received_msg_status_unread), Modifier.height(12.dp), tint = SimplexBlue)
Icon(Icons.Filled.Circle, stringResource(R.string.icon_descr_received_msg_status_unread), Modifier.height(12.dp), tint = MaterialTheme.colors.primary)
}
else -> {}
}

View File

@@ -87,7 +87,7 @@ fun FramedItemView(
Modifier
.padding(top = 6.dp, end = 4.dp)
.size(22.dp),
tint = if (isSystemInDarkTheme()) FileDark else FileLight
tint = if (isInDarkTheme()) FileDark else FileLight
)
}
else -> ciQuotedMsgView(qi)

View File

@@ -2,7 +2,6 @@ package chat.simplex.app.views.chatlist
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.*
@@ -88,7 +87,7 @@ fun ChatPreviewView(chat: Chat, stopped: Boolean) {
metaText = ci.timestampText,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.body1.copy(color = if (isSystemInDarkTheme()) MessagePreviewDark else MessagePreviewLight, lineHeight = 22.sp),
style = MaterialTheme.typography.body1.copy(color = if (isInDarkTheme()) MessagePreviewDark else MessagePreviewLight, lineHeight = 22.sp),
)
} else {
when (cInfo) {

View File

@@ -1,6 +1,5 @@
package chat.simplex.app.views.chatlist
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
@@ -37,7 +36,7 @@ fun ContactConnectionView(contactConnection: PendingContactConnection) {
fontWeight = FontWeight.Bold,
color = HighOrLowlight
)
Text(contactConnection.description, maxLines = 2, color = if (isSystemInDarkTheme()) MessagePreviewDark else MessagePreviewLight)
Text(contactConnection.description, maxLines = 2, color = if (isInDarkTheme()) MessagePreviewDark else MessagePreviewLight)
}
val ts = getTimestampText(contactConnection.updatedAt)
Column(

View File

@@ -1,6 +1,5 @@
package chat.simplex.app.views.chatlist
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
@@ -33,7 +32,7 @@ fun ContactRequestView(contactRequest: ChatInfo.ContactRequest) {
fontWeight = FontWeight.Bold,
color = MaterialTheme.colors.primary
)
Text(stringResource(R.string.contact_wants_to_connect_with_you), maxLines = 2, color = if (isSystemInDarkTheme()) MessagePreviewDark else MessagePreviewLight)
Text(stringResource(R.string.contact_wants_to_connect_with_you), maxLines = 2, color = if (isInDarkTheme()) MessagePreviewDark else MessagePreviewLight)
}
val ts = getTimestampText(contactRequest.contactRequest.updatedAt)
Column(

View File

@@ -2,8 +2,9 @@ package chat.simplex.app.views.helpers
import android.util.Log
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.*
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import chat.simplex.app.R
import chat.simplex.app.TAG

View File

@@ -13,8 +13,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.*
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import chat.simplex.app.ui.theme.ToolbarDark
import chat.simplex.app.ui.theme.ToolbarLight
import chat.simplex.app.ui.theme.*
@Composable
fun DefaultTopAppBar(
@@ -39,7 +38,7 @@ fun DefaultTopAppBar(
SearchTextField(Modifier.fillMaxWidth(), stringResource(android.R.string.search_go), onSearchValueChanged)
}
},
backgroundColor = if (isSystemInDarkTheme()) ToolbarDark else ToolbarLight,
backgroundColor = if (isInDarkTheme()) ToolbarDark else ToolbarLight,
navigationIcon = navigationButton,
buttons = if (!showSearch) buttons else emptyList(),
centered = !showSearch

View File

@@ -1,5 +1,4 @@
import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
@@ -8,8 +7,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.*
import chat.simplex.app.ui.theme.GroupDark
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.*
@Composable
fun SectionView(title: String? = null, content: (@Composable () -> Unit)) {
@@ -20,7 +18,7 @@ fun SectionView(title: String? = null, content: (@Composable () -> Unit)) {
modifier = Modifier.padding(start = 16.dp, bottom = 5.dp), fontSize = 12.sp
)
}
Surface(color = if (isSystemInDarkTheme()) GroupDark else MaterialTheme.colors.background) {
Surface(color = if (isInDarkTheme()) GroupDark else MaterialTheme.colors.background) {
Column(Modifier.padding(horizontal = 6.dp).fillMaxWidth()) { content() }
}
}
@@ -40,6 +38,27 @@ fun SectionItemView(click: (() -> Unit)? = null, height: Dp = 46.dp, disabled: B
}
}
@Composable
fun SectionItemViewSpaceBetween(
click: (() -> Unit)? = null,
height: Dp = 46.dp,
padding: PaddingValues = PaddingValues(horizontal = 8.dp),
disabled: Boolean = false,
content: (@Composable () -> Unit)
) {
val modifier = Modifier
.padding(padding)
.fillMaxWidth()
.height(height)
Row(
if (click == null || disabled) modifier else modifier.clickable(onClick = click),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
content()
}
}
@Composable
fun SectionTextFooter(text: String) {
Text(
@@ -51,9 +70,9 @@ fun SectionTextFooter(text: String) {
}
@Composable
fun SectionCustomFooter(content: (@Composable () -> Unit)) {
fun SectionCustomFooter(padding: PaddingValues = PaddingValues(start = 16.dp, end = 16.dp, top = 5.dp), content: (@Composable () -> Unit)) {
Row(
Modifier.padding(horizontal = 16.dp).padding(top = 5.dp)
Modifier.padding(padding)
) {
content()
}

View File

@@ -46,7 +46,7 @@ fun AddGroupView(chatModel: ChatModel, close: () -> Unit) {
ModalManager.shared.showCustomModal { close ->
ModalView(
close = close, modifier = Modifier,
background = if (isSystemInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
) {
AddGroupMembersView(groupInfo, chatModel, close)
}

View File

@@ -3,7 +3,6 @@ package chat.simplex.app.views.onboarding
import android.content.res.Configuration
import androidx.annotation.StringRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
@@ -77,7 +76,7 @@ fun SimpleXInfoLayout(
@Composable
fun SimpleXLogo() {
Image(
painter = painterResource(if (isSystemInDarkTheme()) R.drawable.logo_light else R.drawable.logo),
painter = painterResource(if (isInDarkTheme()) R.drawable.logo_light else R.drawable.logo),
contentDescription = stringResource(R.string.image_descr_simplex_logo),
modifier = Modifier
.padding(vertical = 20.dp)

View File

@@ -1,5 +1,8 @@
package chat.simplex.app.views.usersettings
import SectionCustomFooter
import SectionItemViewSpaceBetween
import SectionSpacer
import SectionView
import android.content.ComponentName
import android.content.pm.PackageManager
@@ -10,11 +13,13 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.material.*
import androidx.compose.material.MaterialTheme.colors
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Circle
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.*
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@@ -24,7 +29,10 @@ import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
import chat.simplex.app.*
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
import com.godaddy.android.colorpicker.*
enum class AppIcon(val resId: Int) {
DEFAULT(R.mipmap.icon),
@@ -32,7 +40,9 @@ enum class AppIcon(val resId: Int) {
}
@Composable
fun AppearanceView() {
fun AppearanceView(
showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit),
) {
val appIcon = remember { mutableStateOf(findEnabledIcon()) }
fun setAppIcon(newIcon: AppIcon) {
@@ -54,18 +64,33 @@ fun AppearanceView() {
AppearanceLayout(
appIcon,
changeIcon = ::setAppIcon
changeIcon = ::setAppIcon,
showThemeSelector = showCustomModal { _, close ->
ModalView(
close = close, modifier = Modifier,
background = if (isInDarkTheme()) colors.background else SettingsBackgroundLight
) { ThemeSelectorView() }
},
editPrimaryColor = { primary ->
showCustomModal { _, close ->
ModalView(
close = close, modifier = Modifier,
background = if (isInDarkTheme()) colors.background else SettingsBackgroundLight
) { ColorEditor(primary, close) }
}()
},
)
}
@Composable fun AppearanceLayout(
icon: MutableState<AppIcon>,
changeIcon: (AppIcon) -> Unit
changeIcon: (AppIcon) -> Unit,
showThemeSelector: () -> Unit,
editPrimaryColor: (Color) -> Unit,
) {
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
stringResource(R.string.appearance_settings),
@@ -73,10 +98,7 @@ fun AppearanceView() {
style = MaterialTheme.typography.h1
)
SectionView(stringResource(R.string.settings_section_title_icon)) {
LazyRow(
Modifier
.padding(horizontal = 8.dp)
) {
LazyRow {
items(AppIcon.values().size, { index -> AppIcon.values()[index] }) { index ->
val item = AppIcon.values()[index]
val mipmap = ContextCompat.getDrawable(LocalContext.current, item.resId)!!
@@ -97,9 +119,82 @@ fun AppearanceView() {
}
}
}
SectionSpacer()
val currentTheme by CurrentColors.collectAsState()
SectionView(stringResource(R.string.settings_section_title_themes)) {
Column(
Modifier.padding(horizontal = 8.dp)
) {
SectionItemViewSpaceBetween(showThemeSelector, padding = PaddingValues()) {
Text(generalGetString(R.string.theme))
}
Spacer(Modifier.padding(horizontal = 4.dp))
SectionItemViewSpaceBetween({ editPrimaryColor(currentTheme.first.primary) }, padding = PaddingValues()) {
val title = generalGetString(R.string.color_primary)
Text(title)
Icon(Icons.Filled.Circle, title, tint = colors.primary)
}
}
}
if (currentTheme.first.primary != LightColorPalette.primary) {
SectionCustomFooter(PaddingValues(start = 7.dp, end = 7.dp, top = 5.dp)) {
TextButton(
onClick = {
ThemeManager.saveAndApplyPrimaryColor(LightColorPalette.primary)
},
) {
Text(generalGetString(R.string.reset_color))
}
}
}
}
}
@Composable
fun ColorEditor(
initialColor: Color,
close: () -> Unit,
) {
Column(
Modifier
.fillMaxWidth()
) {
var currentColor by remember { mutableStateOf(initialColor) }
ColorPicker(initialColor) {
currentColor = it
}
SectionSpacer()
TextButton(
onClick = {
ThemeManager.saveAndApplyPrimaryColor(currentColor)
close()
},
Modifier.align(Alignment.CenterHorizontally),
colors = ButtonDefaults.textButtonColors(contentColor = currentColor)
) {
Text(generalGetString(R.string.save_color))
}
}
}
@Composable
fun ColorPicker(initialColor: Color, onColorChanged: (Color) -> Unit) {
ClassicColorPicker(
color = initialColor,
modifier = Modifier
.fillMaxWidth()
.height(300.dp),
showAlphaBar = false,
onColorChanged = { color: HsvColor ->
onColorChanged(color.toColor())
}
)
}
private fun findEnabledIcon(): AppIcon = AppIcon.values().first { icon ->
SimplexApp.context.packageManager.getComponentEnabledSetting(
ComponentName(BuildConfig.APPLICATION_ID, "chat.simplex.app.MainActivity_${icon.name.lowercase()}")
@@ -112,7 +207,9 @@ fun PreviewAppearanceSettings() {
SimpleXTheme {
AppearanceLayout(
icon = remember { mutableStateOf(AppIcon.DARK_BLUE) },
changeIcon = {}
changeIcon = {},
showThemeSelector = {},
editPrimaryColor = {},
)
}
}

View File

@@ -58,24 +58,12 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) {
showModal = { modalView -> { ModalManager.shared.showModal { modalView(chatModel) } } },
showSettingsModal = { modalView -> { ModalManager.shared.showCustomModal { close ->
ModalView(close = close, modifier = Modifier,
background = if (isSystemInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight) {
background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight) {
modalView(chatModel)
}
} } },
showCustomModal = { modalView -> { ModalManager.shared.showCustomModal { close -> modalView(chatModel, close) } } },
showTerminal = { ModalManager.shared.showCustomModal { close -> TerminalView(chatModel, close) } },
showAppearance = {
withApi {
ModalManager.shared.showCustomModal { close ->
ModalView(
close = close, modifier = Modifier,
background = if (isSystemInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
) {
AppearanceView()
}
}
}
}
// showVideoChatPrototype = { ModalManager.shared.showCustomModal { close -> CallViewDebug(close) } },
)
}
@@ -88,7 +76,7 @@ val simplexTeamUri =
//fun showSectionedModal(chatModel: ChatModel, modalView: (@Composable (ChatModel) -> Unit)) {
// ModalManager.shared.showCustomModal { close ->
// ModalView(close = close, modifier = Modifier,
// background = if (isSystemInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight) {
// background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight) {
// modalView(chatModel)
// }
// }
@@ -106,7 +94,6 @@ fun SettingsLayout(
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit),
showTerminal: () -> Unit,
showAppearance: () -> Unit
// showVideoChatPrototype: () -> Unit
) {
val uriHandler = LocalUriHandler.current
@@ -114,7 +101,7 @@ fun SettingsLayout(
Column(
Modifier
.fillMaxSize()
.background(if (isSystemInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight)
.background(if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight)
.padding(top = 16.dp)
) {
Text(
@@ -142,7 +129,7 @@ fun SettingsLayout(
SectionDivider()
SettingsActionItem(Icons.Outlined.Lock, stringResource(R.string.privacy_and_security), showSettingsModal { PrivacySettingsView(it, setPerformLA) }, disabled = stopped)
SectionDivider()
SettingsActionItem(Icons.Outlined.LightMode, stringResource(R.string.appearance_settings), showAppearance, disabled = stopped)
SettingsActionItem(Icons.Outlined.LightMode, stringResource(R.string.appearance_settings), showSettingsModal { AppearanceView(showCustomModal) }, disabled = stopped)
SectionDivider()
SettingsActionItem(Icons.Outlined.WifiTethering, stringResource(R.string.network_and_servers), showSettingsModal { NetworkAndServersView(it, showModal, showSettingsModal) }, disabled = stopped)
}
@@ -288,7 +275,7 @@ fun SettingsLayout(
tint = HighOrLowlight,
)
Spacer(Modifier.padding(horizontal = 4.dp))
Text(annotatedStringResource(R.string.install_simplex_chat_for_terminal))
Text(generalGetString(R.string.install_simplex_chat_for_terminal), color = MaterialTheme.colors.primary)
}
}
@@ -355,7 +342,6 @@ fun PreviewSettingsLayout() {
showSettingsModal = { {} },
showCustomModal = { {} },
showTerminal = {},
showAppearance = {},
// showVideoChatPrototype = {}
)
}

View File

@@ -0,0 +1,69 @@
package chat.simplex.app.views.usersettings
import SectionItemViewSpaceBetween
import SectionView
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.ui.theme.*
@Composable
fun ThemeSelectorView() {
val darkTheme = isSystemInDarkTheme()
val allThemes by remember { mutableStateOf(ThemeManager.allThemes(darkTheme)) }
ThemeSelectorLayout(
allThemes,
onSelectTheme = {
ThemeManager.applyTheme(it, darkTheme)
},
)
}
@Composable fun ThemeSelectorLayout(
allThemes: List<Triple<Colors, DefaultTheme, String>>,
onSelectTheme: (String) -> Unit,
) {
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start,
) {
Text(
stringResource(R.string.settings_section_title_themes).lowercase().capitalize(Locale.current),
Modifier.padding(start = 16.dp, bottom = 24.dp),
style = MaterialTheme.typography.h1
)
val currentTheme by CurrentColors.collectAsState()
SectionView(null) {
LazyColumn(
Modifier.padding(horizontal = 8.dp)
) {
items(allThemes.size) { index ->
val item = allThemes[index]
val onClick = {
onSelectTheme(item.second.name)
}
SectionItemViewSpaceBetween(onClick, padding = PaddingValues()) {
Text(item.third)
if (currentTheme.second == item.second) {
Icon(Icons.Outlined.Check, item.third, tint = HighOrLowlight)
}
}
Spacer(Modifier.padding(horizontal = 4.dp))
}
}
}
}
}

View File

@@ -272,7 +272,7 @@
<string name="chat_lock">Блокировка SimpleX</string>
<string name="chat_console">Консоль</string>
<string name="smp_servers">SMP серверы</string>
<string name="install_simplex_chat_for_terminal"><font color="#0088ff"><xliff:g id="appNameFull">SimpleX Chat</xliff:g> для терминала</font></string>
<string name="install_simplex_chat_for_terminal"><xliff:g id="appNameFull">SimpleX Chat</xliff:g> для терминала</string>
<string name="use_simplex_chat_servers__question">Использовать серверы предосталенные <xliff:g id="appNameFull">SimpleX Chat</xliff:g>?</string>
<string name="saved_SMP_servers_will_br_removed">Сохраненные SMP серверы будут удалены.</string>
<string name="your_SMP_servers">Ваши SMP серверы</string>
@@ -468,6 +468,7 @@
<string name="settings_experimental_features">Экспериментальные функции</string>
<string name="settings_section_title_socks">SOCKS ПРОКСИ</string>
<string name="settings_section_title_icon">ИКОНКА</string>
<string name="settings_section_title_themes">ТЕМЫ</string>
<!-- DatabaseView.kt -->
<string name="your_chat_database">Данные чата</string>
@@ -635,4 +636,15 @@
<string name="update_network_settings_question">Обновить настройки сети?</string>
<string name="updating_settings_will_reconnect_client_to_all_servers">Обновление настроек приведет к переподключению клиента ко всем серверам.</string>
<string name="update_network_settings_confirmation">Обновить</string>
<!-- Default themes -->
<string name="theme_system">Системная</string>
<string name="theme_light">Светлая</string>
<string name="theme_dark">Темная</string>
<!-- Appearance.kt -->
<string name="theme">Тема</string>
<string name="save_color">Сохранить цвет</string>
<string name="reset_color">Сбросить цвета</string>
<string name="color_primary">Акцент</string>
</resources>

View File

@@ -277,7 +277,7 @@
<string name="chat_lock">SimpleX Lock</string>
<string name="chat_console">Chat console</string>
<string name="smp_servers">SMP servers</string>
<string name="install_simplex_chat_for_terminal">Install <font color="#0088ff"><xliff:g id="appNameFull">SimpleX Chat</xliff:g> for terminal</font></string>
<string name="install_simplex_chat_for_terminal">Install <xliff:g id="appNameFull">SimpleX Chat</xliff:g> for terminal</string>
<string name="use_simplex_chat_servers__question">Use <xliff:g id="appNameFull">SimpleX Chat</xliff:g> servers?</string>
<string name="saved_SMP_servers_will_br_removed">Saved SMP servers will be removed.</string>
<string name="your_SMP_servers">Your SMP servers</string>
@@ -470,6 +470,7 @@
<string name="settings_experimental_features">Experimental features</string>
<string name="settings_section_title_socks">SOCKS PROXY</string>
<string name="settings_section_title_icon">APP ICON</string>
<string name="settings_section_title_themes">THEMES</string>
<!-- DatabaseView.kt -->
<string name="your_chat_database">Your chat database</string>
@@ -637,4 +638,15 @@
<string name="update_network_settings_question">Update network settings?</string>
<string name="updating_settings_will_reconnect_client_to_all_servers">Updating settings will re-connect the client to all servers.</string>
<string name="update_network_settings_confirmation">Update</string>
<!-- Default themes -->
<string name="theme_system">System</string>
<string name="theme_light">Light</string>
<string name="theme_dark">Dark</string>
<!-- Appearance.kt -->
<string name="theme">Theme</string>
<string name="save_color">Save color</string>
<string name="reset_color">Reset colors</string>
<string name="color_primary">Accent</string>
</resources>