desktop: zoom and font size (#4421)

* desktop: font scale

* new line

* moved to slider

* default value highlighting

* clickable

* more places with adapted scale

* attachment and edit icons

* verified

* icons in chat view

* zoom

* new chat button size

* preview icons

* android support

* preview

* text scale in chat view's text field

* paddings

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
This commit is contained in:
Stanislav Dmitrenko
2024-07-10 00:41:23 +07:00
committed by GitHub
parent a9d2535292
commit f4be0278b6
24 changed files with 245 additions and 70 deletions
@@ -29,6 +29,7 @@ import androidx.core.widget.doAfterTextChanged
import androidx.core.widget.doOnTextChanged
import chat.simplex.common.R
import chat.simplex.common.helpers.toURI
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.model.ChatModel
import chat.simplex.common.ui.theme.CurrentColors
import chat.simplex.common.views.chat.*
@@ -107,7 +108,7 @@ actual fun PlatformTextField(
editText.maxLines = 16
editText.inputType = InputType.TYPE_TEXT_FLAG_CAP_SENTENCES or editText.inputType
editText.setTextColor(textColor.toArgb())
editText.textSize = textStyle.value.fontSize.value
editText.textSize = textStyle.value.fontSize.value * appPrefs.fontScale.get()
val drawable = androidAppContext.getDrawable(R.drawable.send_msg_view_background)!!
DrawableCompat.setTint(drawable, tintColor.toArgb())
editText.background = drawable
@@ -135,7 +136,7 @@ actual fun PlatformTextField(
editText
}) {
it.setTextColor(textColor.toArgb())
it.textSize = textStyle.value.fontSize.value
it.textSize = textStyle.value.fontSize.value * appPrefs.fontScale.get()
DrawableCompat.setTint(it.background, tintColor.toArgb())
it.isFocusable = composeState.value.preview !is ComposePreview.VoicePreview
it.isFocusableInTouchMode = it.isFocusable
@@ -554,7 +554,7 @@ fun CallPermissionsView(pipActive: Boolean, hasVideo: Boolean, cancel: () -> Uni
}
} else {
ColumnWithScrollBar(Modifier.fillMaxSize()) {
Spacer(Modifier.height(AppBarHeight))
Spacer(Modifier.height(AppBarHeight * fontSizeSqrtMultiplier))
AppBarTitle(stringResource(MR.strings.permissions_required))
Spacer(Modifier.weight(1f))
@@ -137,6 +137,9 @@ fun AppearanceScope.AppearanceLayout(
}
}
SectionDividerSpaced(maxBottomPadding = true)
FontScaleSection()
SectionBottomSpacer()
}
}
@@ -18,6 +18,7 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.dp
import chat.simplex.common.views.usersettings.SetDeliveryReceiptsView
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.CreateFirstProfile
@@ -36,6 +37,7 @@ import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlin.math.sqrt
data class SettingsViewState(
val userPickerState: MutableStateFlow<AnimatedViewState>,
@@ -333,21 +335,21 @@ fun EndPartOfScreen() {
fun DesktopScreen(settingsState: SettingsViewState) {
Box {
// 56.dp is a size of unused space of settings drawer
Box(Modifier.width(DEFAULT_START_MODAL_WIDTH + 56.dp)) {
Box(Modifier.width(DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier + 56.dp)) {
StartPartOfScreen(settingsState)
}
Box(Modifier.widthIn(max = DEFAULT_START_MODAL_WIDTH)) {
Box(Modifier.widthIn(max = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier)) {
ModalManager.start.showInView()
SwitchingUsersView()
}
Row(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH).clipToBounds()) {
Row(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier).clipToBounds()) {
Box(Modifier.widthIn(min = DEFAULT_MIN_CENTER_MODAL_WIDTH).weight(1f)) {
CenterPartOfScreen()
}
if (ModalManager.end.hasModalsOpen()) {
VerticalDivider()
}
Box(Modifier.widthIn(max = DEFAULT_END_MODAL_WIDTH).clipToBounds()) {
Box(Modifier.widthIn(max = DEFAULT_END_MODAL_WIDTH * fontSizeSqrtMultiplier).clipToBounds()) {
EndPartOfScreen()
}
}
@@ -357,14 +359,14 @@ fun DesktopScreen(settingsState: SettingsViewState) {
Box(
Modifier
.fillMaxSize()
.padding(start = DEFAULT_START_MODAL_WIDTH)
.padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier)
.clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = {
ModalManager.start.closeModals()
scope.launch { settingsState.scaffoldState.drawerState.close() }
})
)
}
VerticalDivider(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH))
VerticalDivider(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier))
tryOrShowError("UserPicker", error = {}) {
UserPicker(chatModel, userPickerState) {
scope.launch { if (scaffoldState.drawerState.isOpen) scaffoldState.drawerState.close() else scaffoldState.drawerState.open() }
@@ -198,6 +198,8 @@ class AppPreferences {
}, settingsThemes)
val themeOverrides = mkThemeOverridesPreference()
val profileImageCornerRadius = mkFloatPreference(SHARED_PREFS_PROFILE_IMAGE_CORNER_RADIUS, 22.5f)
val fontScale = mkFloatPreference(SHARED_PREFS_FONT_SCALE, 1f)
val densityScale = mkFloatPreference(SHARED_PREFS_DENSITY_SCALE, 1f)
val whatsNewVersion = mkStrPreference(SHARED_PREFS_WHATS_NEW_VERSION, null)
val lastMigratedVersionCode = mkIntPreference(SHARED_PREFS_LAST_MIGRATED_VERSION_CODE, 0)
@@ -380,6 +382,8 @@ class AppPreferences {
private const val SHARED_PREFS_THEMES_OLD = "Themes"
private const val SHARED_PREFS_THEME_OVERRIDES = "ThemeOverrides"
private const val SHARED_PREFS_PROFILE_IMAGE_CORNER_RADIUS = "ProfileImageCornerRadius"
private const val SHARED_PREFS_FONT_SCALE = "FontScale"
private const val SHARED_PREFS_DENSITY_SCALE = "DensityScale"
private const val SHARED_PREFS_WHATS_NEW_VERSION = "WhatsNewVersion"
private const val SHARED_PREFS_LAST_MIGRATED_VERSION_CODE = "LastMigratedVersionCode"
private const val SHARED_PREFS_CUSTOM_DISAPPEARING_MESSAGE_TIME = "CustomDisappearingMessageTime"
@@ -6,6 +6,8 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.*
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import chat.simplex.common.model.ChatController
import chat.simplex.common.model.ChatController.appPrefs
@@ -777,6 +779,7 @@ fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) {
typography = Typography,
shapes = Shapes,
content = {
val density = Density(LocalDensity.current.density * desktopDensityScaleMultiplier, LocalDensity.current.fontScale * fontSizeMultiplier)
val rememberedAppColors = remember {
// Explicitly creating a new object here so we don't mutate the initial [appColors]
// provided, and overwrite the values set in it.
@@ -791,6 +794,7 @@ fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) {
LocalContentColor provides MaterialTheme.colors.onBackground,
LocalAppColors provides rememberedAppColors,
LocalAppWallpaper provides rememberedWallpaper,
LocalDensity provides density,
content = content)
}
)
@@ -821,9 +821,9 @@ fun ChatInfoToolbar(
buttons = barButtons
)
Divider(Modifier.padding(top = AppBarHeight))
Divider(Modifier.padding(top = AppBarHeight * fontSizeSqrtMultiplier))
Box(Modifier.fillMaxWidth().wrapContentSize(Alignment.TopEnd).offset(y = AppBarHeight)) {
Box(Modifier.fillMaxWidth().wrapContentSize(Alignment.TopEnd).offset(y = AppBarHeight * fontSizeSqrtMultiplier)) {
DefaultDropdownMenu(showMenu) {
menuItems.forEach { it() }
}
@@ -837,9 +837,9 @@ fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Colo
verticalAlignment = Alignment.CenterVertically
) {
if (cInfo.incognito) {
IncognitoImage(size = 36.dp, Indigo)
IncognitoImage(size = 36.dp * fontSizeSqrtMultiplier, Indigo)
}
ChatInfoImage(cInfo, size = imageSize, iconColor)
ChatInfoImage(cInfo, size = imageSize * fontSizeSqrtMultiplier, iconColor)
Column(
Modifier.padding(start = 8.dp),
horizontalAlignment = Alignment.CenterHorizontally
@@ -865,7 +865,7 @@ fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Colo
@Composable
private fun ContactVerifiedShield() {
Icon(painterResource(MR.images.ic_verified_user), null, Modifier.size(18.dp).padding(end = 3.dp, top = 1.dp), tint = MaterialTheme.colors.secondary)
Icon(painterResource(MR.images.ic_verified_user), null, Modifier.size(18.dp * fontSizeSqrtMultiplier).padding(end = 3.dp, top = 1.dp), tint = MaterialTheme.colors.secondary)
}
data class CIListState(val scrolled: Boolean, val itemCount: Int, val keyboardState: KeyboardState)
@@ -1283,7 +1283,7 @@ val MEMBER_IMAGE_SIZE: Dp = 38.dp
@Composable
fun MemberImage(member: GroupMember) {
ProfileImage(MEMBER_IMAGE_SIZE, member.memberProfile.image, backgroundColor = MaterialTheme.colors.background)
ProfileImage(MEMBER_IMAGE_SIZE * fontSizeSqrtMultiplier, member.memberProfile.image, backgroundColor = MaterialTheme.colors.background)
}
@Composable
@@ -13,10 +13,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.font.FontStyle
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatModel.controller
import chat.simplex.common.model.ChatModel.filesToDelete
@@ -884,7 +886,7 @@ fun ComposeView(
&& !nextSendGrpInv.value
IconButton(
attachmentClicked,
Modifier.padding(bottom = if (appPlatform.isAndroid) 0.dp else 7.dp),
Modifier.padding(bottom = if (appPlatform.isAndroid) 0.dp else with(LocalDensity.current) { 7.sp.toDp() }),
enabled = attachmentEnabled
) {
Icon(
@@ -15,10 +15,12 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.*
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.*
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.chat.item.ItemAction
import chat.simplex.common.views.helpers.*
@@ -99,7 +101,7 @@ fun SendMsgView(
if (showDeleteTextButton.value) {
DeleteTextButton(composeState)
}
Box(Modifier.align(Alignment.BottomEnd).padding(bottom = if (appPlatform.isAndroid) 0.dp else 5.dp)) {
Box(Modifier.align(Alignment.BottomEnd).padding(bottom = if (appPlatform.isAndroid) 0.dp else with(LocalDensity.current) { 5.sp.toDp() } * fontSizeSqrtMultiplier)) {
val sendButtonSize = remember { Animatable(36f) }
val sendButtonAlpha = remember { Animatable(1f) }
val scope = rememberCoroutineScope()
@@ -91,7 +91,7 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf
if (newChatSheetState.value.isVisible()) hideNewChatSheet(true) else showNewChatSheet()
}
},
Modifier.padding(end = DEFAULT_PADDING - 16.dp + endPadding, bottom = DEFAULT_PADDING - 16.dp),
Modifier.padding(end = DEFAULT_PADDING - 16.dp + endPadding, bottom = DEFAULT_PADDING - 16.dp).size(AppBarHeight * fontSizeSqrtMultiplier),
elevation = FloatingActionButtonDefaults.elevation(
defaultElevation = 0.dp,
pressedElevation = 0.dp,
@@ -101,7 +101,7 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf
backgroundColor = if (!stopped) MaterialTheme.colors.primary else MaterialTheme.colors.secondary,
contentColor = Color.White
) {
Icon(if (!newChatSheetState.collectAsState().value.isVisible()) painterResource(MR.images.ic_edit_filled) else painterResource(MR.images.ic_close), stringResource(MR.strings.add_contact_or_create_group))
Icon(if (!newChatSheetState.collectAsState().value.isVisible()) painterResource(MR.images.ic_edit_filled) else painterResource(MR.images.ic_close), stringResource(MR.strings.add_contact_or_create_group), Modifier.size(24.dp * fontSizeSqrtMultiplier))
}
}
}
@@ -259,7 +259,7 @@ private fun ChatListToolbar(drawerState: DrawerState, userPickerState: MutableSt
onSearchValueChanged = {},
buttons = barButtons
)
Divider(Modifier.padding(top = AppBarHeight))
Divider(Modifier.padding(top = AppBarHeight * fontSizeSqrtMultiplier))
}
@Composable
@@ -316,7 +316,7 @@ fun UserProfileButton(image: String?, allRead: Boolean, onButtonClicked: () -> U
Box {
ProfileImage(
image = image,
size = 37.dp,
size = 37.dp * fontSizeSqrtMultiplier,
color = MaterialTheme.colors.secondaryVariant.mixWith(MaterialTheme.colors.onBackground, 0.97f)
)
if (!allRead) {
@@ -355,6 +355,7 @@ private fun BoxScope.unreadBadge(text: String? = "") {
private fun ToggleFilterEnabledButton() {
val pref = remember { ChatController.appPrefs.showUnreadAndFavorites }
IconButton(onClick = { pref.set(!pref.get()) }) {
val sp16 = with(LocalDensity.current) { 16.sp.toDp() }
Icon(
painterResource(MR.images.ic_filter_list),
null,
@@ -364,7 +365,7 @@ private fun ToggleFilterEnabledButton() {
.background(color = if (pref.state.value) MaterialTheme.colors.primary else Color.Unspecified, shape = RoundedCornerShape(50))
.border(width = 1.dp, color = if (pref.state.value) MaterialTheme.colors.primary else Color.Unspecified, shape = RoundedCornerShape(50))
.padding(3.dp)
.size(16.dp)
.size(sp16)
)
}
}
@@ -388,7 +389,7 @@ private fun ChatListSearchBar(listState: LazyListState, searchText: MutableState
Row(verticalAlignment = Alignment.CenterVertically) {
val focusRequester = remember { FocusRequester() }
var focused by remember { mutableStateOf(false) }
Icon(painterResource(MR.images.ic_search), null, Modifier.padding(horizontal = DEFAULT_PADDING_HALF), tint = MaterialTheme.colors.secondary)
Icon(painterResource(MR.images.ic_search), null, Modifier.padding(horizontal = DEFAULT_PADDING_HALF).size(24.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.secondary)
SearchTextField(
Modifier.weight(1f).onFocusChanged { focused = it.hasFocus }.focusRequester(focusRequester),
placeholder = stringResource(MR.strings.search_or_paste_simplex_link),
@@ -47,10 +47,11 @@ fun ChatPreviewView(
@Composable
fun inactiveIcon() {
val sp18 = with(LocalDensity.current) { 18.sp.toDp() }
Icon(
painterResource(MR.images.ic_cancel_filled),
stringResource(MR.strings.icon_descr_group_inactive),
Modifier.size(18.dp).background(MaterialTheme.colors.background, CircleShape),
Modifier.size(sp18).background(MaterialTheme.colors.background, CircleShape),
tint = MaterialTheme.colors.secondary
)
}
@@ -87,10 +88,11 @@ fun ChatPreviewView(
@Composable
fun VerifiedIcon() {
Icon(painterResource(MR.images.ic_verified_user), null, Modifier.size(19.dp).padding(end = 3.dp, top = 1.dp), tint = MaterialTheme.colors.secondary)
val sp19 = with(LocalDensity.current) { 19.sp.toDp() }
Icon(painterResource(MR.images.ic_verified_user), null, Modifier.size(sp19).padding(end = 3.dp, top = 1.dp), tint = MaterialTheme.colors.secondary)
}
fun messageDraft(draft: ComposeState): Pair<AnnotatedString.Builder.() -> Unit, Map<String, InlineTextContent>> {
fun messageDraft(draft: ComposeState, sp20: Dp): Pair<AnnotatedString.Builder.() -> Unit, Map<String, InlineTextContent>> {
fun attachment(): Pair<ImageResource, String?>? =
when (draft.preview) {
is ComposePreview.FilePreview -> MR.images.ic_draft_filled to draft.preview.fileName
@@ -115,12 +117,12 @@ fun ChatPreviewView(
"editIcon" to InlineTextContent(
Placeholder(20.sp, 20.sp, PlaceholderVerticalAlign.TextCenter)
) {
Icon(painterResource(MR.images.ic_edit_note), null, tint = MaterialTheme.colors.primary)
Icon(painterResource(MR.images.ic_edit_note), null, Modifier.size(sp20), tint = MaterialTheme.colors.primary)
},
"attachmentIcon" to InlineTextContent(
Placeholder(20.sp, 20.sp, PlaceholderVerticalAlign.TextCenter)
) {
Icon(if (attachment?.first != null) painterResource(attachment.first) else painterResource(MR.images.ic_edit_note), null, tint = MaterialTheme.colors.secondary)
Icon(if (attachment?.first != null) painterResource(attachment.first) else painterResource(MR.images.ic_edit_note), null, Modifier.size(sp20), tint = MaterialTheme.colors.secondary)
}
)
return inlineContentBuilder to inlineContent
@@ -167,8 +169,9 @@ fun ChatPreviewView(
val ci = chat.chatItems.lastOrNull()
if (ci != null) {
if (showChatPreviews || (chatModelDraftChatId == chat.id && chatModelDraft != null)) {
val sp20 = with(LocalDensity.current) { 20.sp.toDp() }
val (text: CharSequence, inlineTextContent) = when {
chatModelDraftChatId == chat.id && chatModelDraft != null -> remember(chatModelDraft) { chatModelDraft.message to messageDraft(chatModelDraft) }
chatModelDraftChatId == chat.id && chatModelDraft != null -> remember(chatModelDraft) { chatModelDraft.message to messageDraft(chatModelDraft, sp20) }
ci.meta.itemDeleted == null -> ci.text to null
else -> markedDeletedText(ci.meta) to null
}
@@ -220,10 +223,11 @@ fun ChatPreviewView(
@Composable
fun progressView() {
val sp15 = with(LocalDensity.current) { 15.sp.toDp() }
CircularProgressIndicator(
Modifier
.padding(horizontal = 2.dp)
.size(15.dp),
.size(sp15),
color = MaterialTheme.colors.secondary,
strokeWidth = 1.5.dp
)
@@ -231,6 +235,7 @@ fun ChatPreviewView(
@Composable
fun chatStatusImage() {
val sp19 = with(LocalDensity.current) { 19.sp.toDp() }
if (cInfo is ChatInfo.Direct) {
if (cInfo.contact.active && cInfo.contact.activeConn != null) {
val descr = contactNetworkStatus?.statusString
@@ -244,7 +249,7 @@ fun ChatPreviewView(
contentDescription = descr,
tint = MaterialTheme.colors.secondary,
modifier = Modifier
.size(19.dp)
.size(sp19)
)
else ->
@@ -266,7 +271,7 @@ fun ChatPreviewView(
Row {
Box(contentAlignment = Alignment.BottomEnd) {
ChatInfoImage(cInfo, size = 72.dp)
ChatInfoImage(cInfo, size = 72.dp * fontSizeSqrtMultiplier)
Box(Modifier.padding(end = 6.dp, bottom = 6.dp)) {
chatPreviewImageOverlayIcon()
}
@@ -295,9 +300,13 @@ fun ChatPreviewView(
)
val n = chat.chatStats.unreadCount
val showNtfsIcon = !chat.chatInfo.ntfsEnabled && (chat.chatInfo is ChatInfo.Direct || chat.chatInfo is ChatInfo.Group)
val sp17 = with(LocalDensity.current) { 17.sp.toDp() }
val sp21 = with(LocalDensity.current) { 21.sp.toDp() }
val sp23 = with(LocalDensity.current) { 23.sp.toDp() }
val sp46 = with(LocalDensity.current) { 46.sp.toDp() }
if (n > 0 || chat.chatStats.unreadChat) {
Box(
Modifier.padding(top = 24.dp),
Modifier.padding(top = sp23, end = with(LocalDensity.current) { 3.sp.toDp() }),
contentAlignment = Alignment.Center
) {
Text(
@@ -313,7 +322,7 @@ fun ChatPreviewView(
}
} else if (showNtfsIcon) {
Box(
Modifier.padding(top = 24.dp),
Modifier.padding(top = sp21),
contentAlignment = Alignment.Center
) {
Icon(
@@ -323,12 +332,12 @@ fun ChatPreviewView(
modifier = Modifier
.padding(horizontal = 3.dp)
.padding(vertical = 1.dp)
.size(17.dp)
.size(sp17)
)
}
} else if (chat.chatInfo.chatSettings?.favorite == true) {
Box(
Modifier.padding(top = 24.dp),
Modifier.padding(top = sp21),
contentAlignment = Alignment.Center
) {
Icon(
@@ -338,12 +347,12 @@ fun ChatPreviewView(
modifier = Modifier
.padding(horizontal = 3.dp)
.padding(vertical = 1.dp)
.size(17.dp)
.size(sp17)
)
}
}
Box(
Modifier.padding(top = 50.dp),
Modifier.padding(top = sp46),
contentAlignment = Alignment.Center
) {
chatStatusImage()
@@ -355,12 +364,13 @@ fun ChatPreviewView(
@Composable
fun IncognitoIcon(incognito: Boolean) {
if (incognito) {
val sp21 = with(LocalDensity.current) { 21.sp.toDp() }
Icon(
painterResource(MR.images.ic_theater_comedy),
contentDescription = null,
tint = MaterialTheme.colors.secondary,
modifier = Modifier
.size(21.dp)
.size(sp21)
)
}
}
@@ -26,6 +26,7 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
@@ -126,6 +127,7 @@ fun SubscriptionStatusIndicatorView(subs: SMPServerSubs, sess: ServerSessions, l
horizontalArrangement = Arrangement.spacedBy(DEFAULT_SPACE_AFTER_ICON)
) {
if (pref.state.value && leadingPercentage) SubscriptionStatusIndicatorPercentage(percentageText)
val sp16 = with(LocalDensity.current) { 16.sp.toDp() }
SubscriptionStatusIcon(
color = when(statusColorAndPercentage.color) {
SubscriptionColorType.ACTIVE -> MaterialTheme.colors.primary
@@ -133,7 +135,7 @@ fun SubscriptionStatusIndicatorView(subs: SMPServerSubs, sess: ServerSessions, l
SubscriptionColorType.ACTIVE_DISCONNECTED -> WarningOrange
SubscriptionColorType.DISCONNECTED -> MaterialTheme.colors.secondary
},
modifier = Modifier.size(16.dp),
modifier = Modifier.size(sp16),
variableValue = statusColorAndPercentage.variableValue)
if (pref.state.value && !leadingPercentage) SubscriptionStatusIndicatorPercentage(percentageText)
}
@@ -309,7 +309,7 @@ fun UserProfileRow(u: User, enabled: Boolean = chatModel.chatRunning.value == tr
) {
ProfileImage(
image = u.image,
size = 54.dp
size = 54.dp * fontSizeSqrtMultiplier
)
Text(
u.displayName,
@@ -354,7 +354,7 @@ fun RemoteHostRow(h: RemoteHostInfo) {
.padding(start = 17.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(painterResource(MR.images.ic_smartphone_300), h.hostDeviceName, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
Icon(painterResource(MR.images.ic_smartphone_300), h.hostDeviceName, Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground)
Text(
h.hostDeviceName,
modifier = Modifier.padding(start = 26.dp, end = 8.dp),
@@ -395,7 +395,7 @@ fun LocalDeviceRow(active: Boolean) {
.padding(start = 17.dp, end = DEFAULT_PADDING),
verticalAlignment = Alignment.CenterVertically
) {
Icon(painterResource(MR.images.ic_desktop), stringResource(MR.strings.this_device), Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
Icon(painterResource(MR.images.ic_desktop), stringResource(MR.strings.this_device), Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground)
Text(
stringResource(MR.strings.this_device),
modifier = Modifier.padding(start = 26.dp, end = 8.dp),
@@ -409,7 +409,7 @@ fun LocalDeviceRow(active: Boolean) {
private fun UseFromDesktopPickerItem(onClick: () -> Unit) {
SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) {
val text = generalGetString(MR.strings.settings_section_title_use_from_desktop).lowercase().capitalize(Locale.current)
Icon(painterResource(MR.images.ic_desktop), text, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
Icon(painterResource(MR.images.ic_desktop), text, Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground)
Spacer(Modifier.width(DEFAULT_PADDING + 6.dp))
Text(text, color = MenuTextColor)
}
@@ -419,7 +419,7 @@ private fun UseFromDesktopPickerItem(onClick: () -> Unit) {
private fun LinkAMobilePickerItem(onClick: () -> Unit) {
SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) {
val text = generalGetString(MR.strings.link_a_mobile)
Icon(painterResource(MR.images.ic_smartphone_300), text, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
Icon(painterResource(MR.images.ic_smartphone_300), text, Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground)
Spacer(Modifier.width(DEFAULT_PADDING + 6.dp))
Text(text, color = MenuTextColor)
}
@@ -429,7 +429,7 @@ private fun LinkAMobilePickerItem(onClick: () -> Unit) {
private fun CreateInitialProfile(onClick: () -> Unit) {
SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) {
val text = generalGetString(MR.strings.create_chat_profile)
Icon(painterResource(MR.images.ic_manage_accounts), text, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
Icon(painterResource(MR.images.ic_manage_accounts), text, Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground)
Spacer(Modifier.width(DEFAULT_PADDING + 6.dp))
Text(text, color = MenuTextColor)
}
@@ -439,7 +439,7 @@ private fun CreateInitialProfile(onClick: () -> Unit) {
private fun SettingsPickerItem(onClick: () -> Unit) {
SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) {
val text = generalGetString(MR.strings.settings_section_title_settings).lowercase().capitalize(Locale.current)
Icon(painterResource(MR.images.ic_settings), text, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
Icon(painterResource(MR.images.ic_settings), text, Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground)
Spacer(Modifier.width(DEFAULT_PADDING + 6.dp))
Text(text, color = MenuTextColor)
}
@@ -449,7 +449,7 @@ private fun SettingsPickerItem(onClick: () -> Unit) {
private fun CancelPickerItem(onClick: () -> Unit) {
SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) {
val text = generalGetString(MR.strings.cancel_verb)
Icon(painterResource(MR.images.ic_close), text, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
Icon(painterResource(MR.images.ic_close), text, Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground)
Spacer(Modifier.width(DEFAULT_PADDING + 6.dp))
Text(text, color = MenuTextColor)
}
@@ -459,7 +459,7 @@ private fun CancelPickerItem(onClick: () -> Unit) {
fun HostDisconnectButton(onClick: (() -> Unit)?) {
val interactionSource = remember { MutableInteractionSource() }
val hovered = interactionSource.collectIsHoveredAsState().value
IconButton(onClick ?: {}, Modifier.requiredSize(20.dp), enabled = onClick != null) {
IconButton(onClick ?: {}, Modifier.requiredSize(20.dp * fontSizeSqrtMultiplier), enabled = onClick != null) {
Icon(
painterResource(if (onClick == null) MR.images.ic_desktop else if (hovered) MR.images.ic_wifi_off else MR.images.ic_wifi),
null,
@@ -22,15 +22,13 @@ fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, tintColor: Co
Column(
Modifier
.fillMaxWidth()
.heightIn(min = AppBarHeight)
.padding(horizontal = AppBarHorizontalPadding),
.heightIn(min = AppBarHeight * fontSizeSqrtMultiplier)
.padding(horizontal = AppBarHorizontalPadding)
) {
Row(
Modifier
.padding(top = 4.dp), // Like in DefaultAppBar
content = {
Row(
Modifier.fillMaxWidth().height(TextFieldDefaults.MinHeight),
Modifier.fillMaxWidth().height(AppBarHeight * fontSizeSqrtMultiplier),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
@@ -7,6 +7,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.*
import androidx.compose.ui.unit.Dp
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
import androidx.compose.ui.unit.dp
@@ -44,10 +45,10 @@ fun DefaultTopAppBar(
}
@Composable
fun NavigationButtonBack(onButtonClicked: (() -> Unit)?, tintColor: Color = if (onButtonClicked != null) MaterialTheme.colors.primary else MaterialTheme.colors.secondary) {
fun NavigationButtonBack(onButtonClicked: (() -> Unit)?, tintColor: Color = if (onButtonClicked != null) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, height: Dp = 24.dp) {
IconButton(onButtonClicked ?: {}, enabled = onButtonClicked != null) {
Icon(
painterResource(MR.images.ic_arrow_back_ios_new), stringResource(MR.strings.back), tint = tintColor
painterResource(MR.images.ic_arrow_back_ios_new), stringResource(MR.strings.back), Modifier.height(height), tint = tintColor
)
}
}
@@ -84,7 +85,7 @@ private fun TopAppBar(
Box(
modifier
.fillMaxWidth()
.height(AppBarHeight)
.height(AppBarHeight * fontSizeSqrtMultiplier)
.background(backgroundColor)
.padding(horizontal = 4.dp),
contentAlignment = Alignment.CenterStart,
@@ -8,12 +8,14 @@ import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.model.ChatModel
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import kotlinx.coroutines.flow.MutableStateFlow
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.math.min
import kotlin.math.sqrt
@Composable
fun ModalView(
@@ -89,7 +91,7 @@ class ModalManager(private val placement: ModalPlacement? = null) {
if (placement == ModalPlacement.CENTER) {
ChatModel.chatId.value = null
} else if (placement == ModalPlacement.END) {
desktopExpandWindowToWidth(DEFAULT_START_MODAL_WIDTH + DEFAULT_MIN_CENTER_MODAL_WIDTH + DEFAULT_END_MODAL_WIDTH)
desktopExpandWindowToWidth(DEFAULT_START_MODAL_WIDTH * sqrt(appPrefs.fontScale.get()) + DEFAULT_MIN_CENTER_MODAL_WIDTH + DEFAULT_END_MODAL_WIDTH * sqrt(appPrefs.fontScale.get()))
}
}
@@ -8,6 +8,7 @@ import androidx.compose.ui.platform.*
import androidx.compose.ui.text.*
import androidx.compose.ui.unit.*
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.ThemeOverrides
import chat.simplex.common.views.chatlist.connectIfOpenedViaUri
@@ -519,6 +520,15 @@ fun includeMoreFailedComposables() {
lastExecutedComposables.clear()
}
val fontSizeMultiplier: Float
@Composable get() = remember { appPrefs.fontScale.state }.value
val fontSizeSqrtMultiplier: Float
@Composable get() = sqrt(remember { appPrefs.fontScale.state }.value)
val desktopDensityScaleMultiplier: Float
@Composable get() = if (appPlatform.isDesktop) remember { appPrefs.densityScale.state }.value else 1f
@Composable
fun DisposableEffectOnGone(always: () -> Unit = {}, whenDispose: () -> Unit = {}, whenGone: () -> Unit) {
DisposableEffect(Unit) {
@@ -17,6 +17,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.model.ChatController.getNetCfg
import chat.simplex.common.model.ChatController.startChat
import chat.simplex.common.model.ChatController.startChatWithTemporaryDatabase
@@ -38,6 +39,7 @@ import kotlinx.serialization.*
import java.io.File
import java.net.URLEncoder
import kotlin.math.max
import kotlin.math.sqrt
@Serializable
data class MigrationFileLinkData(
@@ -426,7 +428,8 @@ fun LargeProgressView(value: Float, title: String, description: String) {
Box(Modifier.padding(DEFAULT_PADDING).fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator(
progress = value,
(if (appPlatform.isDesktop) Modifier.size(DEFAULT_START_MODAL_WIDTH) else Modifier.size(windowWidth() - DEFAULT_PADDING * 2))
(if (appPlatform.isDesktop) Modifier.size(DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier) else Modifier.size(windowWidth() - DEFAULT_PADDING *
2))
.rotate(-90f),
color = MaterialTheme.colors.primary,
strokeWidth = 25.dp
@@ -125,11 +125,11 @@ private fun NewChatSheetLayout(
Box(contentAlignment = Alignment.CenterEnd) {
Button(
actions[index],
shape = RoundedCornerShape(21.dp),
shape = RoundedCornerShape(21.dp * fontSizeSqrtMultiplier),
colors = ButtonDefaults.textButtonColors(backgroundColor = backgroundColor),
elevation = null,
contentPadding = PaddingValues(horizontal = DEFAULT_PADDING_HALF, vertical = DEFAULT_PADDING_HALF),
modifier = Modifier.height(42.dp)
modifier = Modifier.height(42.dp * fontSizeSqrtMultiplier)
) {
Text(
stringResource(titles[index]),
@@ -140,7 +140,7 @@ private fun NewChatSheetLayout(
Icon(
painterResource(icons[index]),
stringResource(titles[index]),
Modifier.size(42.dp),
Modifier.size(42.dp * fontSizeSqrtMultiplier),
tint = if (isInDarkTheme()) MaterialTheme.colors.primary else MaterialTheme.colors.primary
)
}
@@ -152,7 +152,7 @@ private fun NewChatSheetLayout(
}
FloatingActionButton(
onClick = { if (!stopped) closeNewChatSheet(true) },
Modifier.padding(end = DEFAULT_PADDING, bottom = DEFAULT_PADDING),
Modifier.padding(end = DEFAULT_PADDING, bottom = DEFAULT_PADDING).size(AppBarHeight * fontSizeSqrtMultiplier),
elevation = FloatingActionButtonDefaults.elevation(
defaultElevation = 0.dp,
pressedElevation = 0.dp,
@@ -164,11 +164,11 @@ private fun NewChatSheetLayout(
) {
Icon(
painterResource(MR.images.ic_edit_filled), stringResource(MR.strings.add_contact_or_create_group),
Modifier.graphicsLayer { alpha = 1 - animatedFloat.value }
Modifier.graphicsLayer { alpha = 1 - animatedFloat.value }.size(24.dp * fontSizeSqrtMultiplier)
)
Icon(
painterResource(MR.images.ic_close), stringResource(MR.strings.add_contact_or_create_group),
Modifier.graphicsLayer { alpha = animatedFloat.value }
Modifier.graphicsLayer { alpha = animatedFloat.value }.size(24.dp * fontSizeSqrtMultiplier)
)
}
}
@@ -19,6 +19,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.*
import androidx.compose.ui.graphics.*
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.*
import dev.icerock.moko.resources.compose.painterResource
@@ -82,6 +83,66 @@ object AppearanceScope {
}
}
@Composable
fun FontScaleSection() {
val localFontScale = remember { mutableStateOf(appPrefs.fontScale.get()) }
SectionView(stringResource(MR.strings.appearance_font_size).uppercase(), padding = PaddingValues(horizontal = DEFAULT_PADDING)) {
Row(Modifier.padding(top = 10.dp), verticalAlignment = Alignment.CenterVertically) {
Box(Modifier.size(60.dp)
.background(MaterialTheme.colors.surface, RoundedCornerShape(percent = 22))
.clip(RoundedCornerShape(percent = 22))
.clickable {
localFontScale.value = 1f
appPrefs.fontScale.set(localFontScale.value)
},
contentAlignment = Alignment.Center) {
CompositionLocalProvider(
LocalDensity provides Density(LocalDensity.current.density, localFontScale.value)
) {
Text("Aa", color = if (localFontScale.value == 1f) MaterialTheme.colors.primary else MaterialTheme.colors.onBackground)
}
}
Spacer(Modifier.width(10.dp))
// Text("${(localFontScale.value * 100).roundToInt()}%", Modifier.width(70.dp), textAlign = TextAlign.Center, fontSize = 12.sp)
if (appPlatform.isAndroid) {
Slider(
localFontScale.value,
valueRange = 0.75f..1.25f,
steps = 11,
onValueChange = {
val diff = it % 0.05f
localFontScale.value = String.format(Locale.US, "%.2f", it + (if (diff >= 0.025f) -diff + 0.05f else -diff)).toFloatOrNull() ?: 1f
},
onValueChangeFinished = {
appPrefs.fontScale.set(localFontScale.value)
},
colors = SliderDefaults.colors(
activeTickColor = Color.Transparent,
inactiveTickColor = Color.Transparent,
)
)
} else {
Slider(
localFontScale.value,
valueRange = 0.7f..1.5f,
steps = 9,
onValueChange = {
val diff = it % 0.1f
localFontScale.value = String.format(Locale.US, "%.1f", it + (if (diff >= 0.05f) -diff + 0.1f else -diff)).toFloatOrNull() ?: 1f
},
onValueChangeFinished = {
appPrefs.fontScale.set(localFontScale.value)
},
colors = SliderDefaults.colors(
activeTickColor = Color.Transparent,
inactiveTickColor = Color.Transparent,
)
)
}
}
}
}
@Composable
fun ChatThemePreview(
theme: DefaultTheme,
@@ -225,8 +286,8 @@ object AppearanceScope {
}
if (appPlatform.isDesktop) {
val itemWidth = (DEFAULT_START_MODAL_WIDTH - DEFAULT_PADDING * 2 - DEFAULT_PADDING_HALF * 3) / 4
val itemHeight = (DEFAULT_START_MODAL_WIDTH - DEFAULT_PADDING * 2) / 4
val itemWidth = (DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier - DEFAULT_PADDING * 2 - DEFAULT_PADDING_HALF * 3) / 4
val itemHeight = (DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier - DEFAULT_PADDING * 2) / 4
val rows = ceil((PresetWallpaper.entries.size + 2) / 4f).roundToInt()
LazyVerticalGrid(
columns = GridCells.Fixed(4),
@@ -174,11 +174,14 @@ fun SettingsLayout(
Box(
Modifier
.fillMaxWidth()
.height(AppBarHeight * fontSizeSqrtMultiplier)
.background(MaterialTheme.colors.background)
.background(if (isInDarkTheme()) ToolbarDark else ToolbarLight)
.padding(start = 4.dp, top = 8.dp)
.padding(start = 4.dp, top = 8.dp),
contentAlignment = Alignment.CenterStart
) {
NavigationButtonBack(closeSettings)
val sp24 = with(LocalDensity.current) { 24.sp.toDp() }
NavigationButtonBack(closeSettings, height = sp24)
}
}
}
@@ -1606,6 +1606,8 @@
<string name="color_wallpaper_background">Wallpaper background</string>
<string name="color_wallpaper_tint">Wallpaper accent</string>
<string name="theme_remove_image">Remove image</string>
<string name="appearance_font_size">Font size</string>
<string name="appearance_zoom">Zoom</string>
<!-- Wallpapers -->
<string name="wallpaper_preview_hello_alice">Good afternoon!</string>
@@ -14,6 +14,7 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.*
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.DEFAULT_START_MODAL_WIDTH
import chat.simplex.common.ui.theme.SimpleXTheme
@@ -26,6 +27,7 @@ import kotlinx.coroutines.*
import java.awt.event.WindowEvent
import java.awt.event.WindowFocusListener
import java.io.File
import kotlin.math.sqrt
import kotlin.system.exitProcess
val simplexWindowState = SimplexWindowState()
@@ -195,7 +197,8 @@ private fun ApplicationScope.AppWindow(closedByError: MutableState<Boolean>) {
if (remember { ChatController.appPrefs.developerTools.state }.value && remember { ChatController.appPrefs.terminalAlwaysVisible.state }.value && remember { ChatController.appPrefs.appLanguage.state }.value != "") {
var hiddenUntilRestart by remember { mutableStateOf(false) }
if (!hiddenUntilRestart) {
val cWindowState = rememberWindowState(placement = WindowPlacement.Floating, width = DEFAULT_START_MODAL_WIDTH, height = 768.dp)
val cWindowState = rememberWindowState(placement = WindowPlacement.Floating, width = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier, height =
768.dp)
Window(state = cWindowState, onCloseRequest = { hiddenUntilRestart = true }, title = stringResource(MR.strings.chat_console)) {
SimpleXTheme {
TerminalView(ChatModel) { hiddenUntilRestart = true }
@@ -3,18 +3,29 @@ package chat.simplex.common.views.usersettings
import SectionBottomSpacer
import SectionDividerSpaced
import SectionView
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.*
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.model.ChatModel
import chat.simplex.common.model.SharedPreference
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.DEFAULT_PADDING
import chat.simplex.common.views.helpers.*
import chat.simplex.res.MR
import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.delay
import java.util.Locale
import kotlin.math.roundToInt
@Composable
actual fun AppearanceView(m: ChatModel) {
@@ -55,6 +66,56 @@ fun AppearanceScope.AppearanceLayout(
SectionDividerSpaced(maxTopPadding = true)
ProfileImageSection()
SectionDividerSpaced(maxBottomPadding = true)
FontScaleSection()
SectionDividerSpaced(maxBottomPadding = true)
DensityScaleSection()
SectionBottomSpacer()
}
}
@Composable
fun DensityScaleSection() {
val localDensityScale = remember { mutableStateOf(appPrefs.densityScale.get()) }
SectionView(stringResource(MR.strings.appearance_zoom).uppercase(), padding = PaddingValues(horizontal = DEFAULT_PADDING)) {
Row(Modifier.padding(top = 10.dp), verticalAlignment = Alignment.CenterVertically) {
Box(Modifier.size(60.dp)
.background(MaterialTheme.colors.surface, RoundedCornerShape(percent = 22))
.clip(RoundedCornerShape(percent = 22))
.clickable {
localDensityScale.value = 1f
appPrefs.densityScale.set(localDensityScale.value)
},
contentAlignment = Alignment.Center) {
CompositionLocalProvider(
LocalDensity provides Density(LocalDensity.current.density * localDensityScale.value, LocalDensity.current.fontScale)
) {
Text("${localDensityScale.value}",
color = if (localDensityScale.value == 1f) MaterialTheme.colors.primary else MaterialTheme.colors.onBackground,
fontSize = 12.sp,
maxLines = 1
)
}
}
Spacer(Modifier.width(10.dp))
Slider(
localDensityScale.value,
valueRange = 1f..2f,
steps = 11,
onValueChange = {
val diff = it % 0.1f
localDensityScale.value = String.format(Locale.US, "%.1f", it + (if (diff >= 0.05f) -diff + 0.1f else -diff)).toFloatOrNull() ?: 1f
},
onValueChangeFinished = {
appPrefs.densityScale.set(localDensityScale.value)
},
colors = SliderDefaults.colors(
activeTickColor = Color.Transparent,
inactiveTickColor = Color.Transparent,
)
)
}
}
}