From ee146cdc7b4be10f5d6e6396d144d8830fff6402 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 4 Dec 2024 23:49:45 +0700 Subject: [PATCH] android, desktop, core: option to show toolbar in chat at the top in reachable UI (#5316) * android, desktop: ability to show toolbar in chat at the top in reachable UI * rename * core AppSettings * ios AppSettings * rename * strings, enable reachable chat toolbar when enabled reachable app toolbars --------- Co-authored-by: Evgeny Poberezkin --- .../Views/UserSettings/AppSettings.swift | 2 + apps/ios/SimpleXChat/APITypes.swift | 5 ++- apps/ios/SimpleXChat/AppGroup.swift | 4 +- .../platform/ScrollableColumn.android.kt | 2 + .../views/usersettings/Appearance.android.kt | 7 ++- .../chat/simplex/common/model/SimpleXAPI.kt | 14 ++++-- .../common/platform/ScrollableColumn.kt | 2 + .../chat/simplex/common/views/TerminalView.kt | 2 +- .../simplex/common/views/chat/ChatView.kt | 45 ++++++++++--------- .../views/chat/group/GroupChatInfoView.kt | 2 +- .../common/views/chatlist/ChatListView.kt | 2 +- .../common/views/chatlist/ShareListView.kt | 2 +- .../common/views/newchat/NewChatSheet.kt | 4 +- .../common/views/newchat/NewChatView.kt | 2 +- .../commonMain/resources/MR/base/strings.xml | 3 +- .../platform/ScrollableColumn.desktop.kt | 13 +++--- .../views/usersettings/Appearance.desktop.kt | 3 ++ src/Simplex/Chat/AppSettings.hs | 16 ++++--- 18 files changed, 83 insertions(+), 47 deletions(-) diff --git a/apps/ios/Shared/Views/UserSettings/AppSettings.swift b/apps/ios/Shared/Views/UserSettings/AppSettings.swift index aa7f885ac6..44e0b20958 100644 --- a/apps/ios/Shared/Views/UserSettings/AppSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/AppSettings.swift @@ -65,6 +65,7 @@ extension AppSettings { if let val = uiCurrentThemeIds { currentThemeIdsDefault.set(val) } if let val = uiThemes { themeOverridesDefault.set(val.skipDuplicates()) } if let val = oneHandUI { groupDefaults.setValue(val, forKey: GROUP_DEFAULT_ONE_HAND_UI) } + if let val = chatBottomBar { groupDefaults.setValue(val, forKey: GROUP_DEFAULT_CHAT_BOTTOM_BAR) } } public static var current: AppSettings { @@ -100,6 +101,7 @@ extension AppSettings { c.uiCurrentThemeIds = currentThemeIdsDefault.get() c.uiThemes = themeOverridesDefault.get() c.oneHandUI = groupDefaults.bool(forKey: GROUP_DEFAULT_ONE_HAND_UI) + c.chatBottomBar = groupDefaults.bool(forKey: GROUP_DEFAULT_CHAT_BOTTOM_BAR) return c } } diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index ca9bb70ea4..2bd76dea63 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -2655,6 +2655,7 @@ public struct AppSettings: Codable, Equatable { public var uiCurrentThemeIds: [String: String]? = nil public var uiThemes: [ThemeOverrides]? = nil public var oneHandUI: Bool? = nil + public var chatBottomBar: Bool? = nil public func prepareForExport() -> AppSettings { var empty = AppSettings() @@ -2689,6 +2690,7 @@ public struct AppSettings: Codable, Equatable { if uiCurrentThemeIds != def.uiCurrentThemeIds { empty.uiCurrentThemeIds = uiCurrentThemeIds } if uiThemes != def.uiThemes { empty.uiThemes = uiThemes } if oneHandUI != def.oneHandUI { empty.oneHandUI = oneHandUI } + if chatBottomBar != def.chatBottomBar { empty.chatBottomBar = chatBottomBar } return empty } @@ -2723,7 +2725,8 @@ public struct AppSettings: Codable, Equatable { uiDarkColorScheme: DefaultTheme.SIMPLEX.themeName, uiCurrentThemeIds: nil as [String: String]?, uiThemes: nil as [ThemeOverrides]?, - oneHandUI: false + oneHandUI: false, + chatBottomBar: true ) } } diff --git a/apps/ios/SimpleXChat/AppGroup.swift b/apps/ios/SimpleXChat/AppGroup.swift index 5ae3c9b901..c754f0740d 100644 --- a/apps/ios/SimpleXChat/AppGroup.swift +++ b/apps/ios/SimpleXChat/AppGroup.swift @@ -57,6 +57,7 @@ public let GROUP_DEFAULT_CONFIRM_DB_UPGRADES = "confirmDBUpgrades" public let GROUP_DEFAULT_CALL_KIT_ENABLED = "callKitEnabled" public let GROUP_DEFAULT_PQ_EXPERIMENTAL_ENABLED = "pqExperimentalEnabled" // no longer used public let GROUP_DEFAULT_ONE_HAND_UI = "oneHandUI" +public let GROUP_DEFAULT_CHAT_BOTTOM_BAR = "chatBottomBar" public let APP_GROUP_NAME = "group.chat.simplex.app" @@ -94,7 +95,8 @@ public func registerGroupDefaults() { GROUP_DEFAULT_CONFIRM_DB_UPGRADES: false, GROUP_DEFAULT_CALL_KIT_ENABLED: true, GROUP_DEFAULT_PQ_EXPERIMENTAL_ENABLED: false, - GROUP_DEFAULT_ONE_HAND_UI: true + GROUP_DEFAULT_ONE_HAND_UI: true, + GROUP_DEFAULT_CHAT_BOTTOM_BAR: true ]) } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/ScrollableColumn.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/ScrollableColumn.android.kt index cf95604504..60197f3851 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/ScrollableColumn.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/ScrollableColumn.android.kt @@ -29,6 +29,7 @@ actual fun LazyColumnWithScrollBar( flingBehavior: FlingBehavior, userScrollEnabled: Boolean, additionalBarOffset: State?, + chatBottomBar: State, fillMaxSize: Boolean, content: LazyListScope.() -> Unit ) { @@ -91,6 +92,7 @@ actual fun LazyColumnWithScrollBarNoAppBar( flingBehavior: FlingBehavior, userScrollEnabled: Boolean, additionalBarOffset: State?, + chatBottomBar: State, content: LazyListScope.() -> Unit ) { val state = state ?: rememberLazyListState() diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt index e5450e8e49..320a8e876a 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt @@ -106,7 +106,12 @@ fun AppearanceScope.AppearanceLayout( } // } - SettingsPreferenceItem(icon = null, stringResource(MR.strings.one_hand_ui), ChatModel.controller.appPrefs.oneHandUI) + SettingsPreferenceItem(icon = null, stringResource(MR.strings.one_hand_ui), ChatModel.controller.appPrefs.oneHandUI) { enabled -> + if (enabled) appPrefs.chatBottomBar.set(true) + } + if (remember { appPrefs.oneHandUI.state }.value) { + SettingsPreferenceItem(icon = null, stringResource(MR.strings.chat_bottom_bar), ChatModel.controller.appPrefs.chatBottomBar) + } } SectionDividerSpaced() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 9531712554..701b32f6f6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -257,6 +257,7 @@ class AppPreferences { val iosCallKitCallsInRecents = mkBoolPreference(SHARED_PREFS_IOS_CALL_KIT_CALLS_IN_RECENTS, false) val oneHandUI = mkBoolPreference(SHARED_PREFS_ONE_HAND_UI, true) + val chatBottomBar = mkBoolPreference(SHARED_PREFS_CHAT_BOTTOM_BAR, true) val hintPreferences: List, Boolean>> = listOf( laNoticeShown to false, @@ -431,6 +432,7 @@ class AppPreferences { private const val SHARED_PREFS_SHOULD_IMPORT_APP_SETTINGS = "ShouldImportAppSettings" private const val SHARED_PREFS_CONFIRM_DB_UPGRADES = "ConfirmDBUpgrades" private const val SHARED_PREFS_ONE_HAND_UI = "OneHandUI" + private const val SHARED_PREFS_CHAT_BOTTOM_BAR = "ChatBottomBar" private const val SHARED_PREFS_SELF_DESTRUCT = "LocalAuthenticationSelfDestruct" private const val SHARED_PREFS_SELF_DESTRUCT_DISPLAY_NAME = "LocalAuthenticationSelfDestructDisplayName" private const val SHARED_PREFS_PQ_EXPERIMENTAL_ENABLED = "PQExperimentalEnabled" // no longer used @@ -438,7 +440,6 @@ class AppPreferences { private const val SHARED_PREFS_CURRENT_THEME_IDs = "CurrentThemeIds" private const val SHARED_PREFS_SYSTEM_DARK_THEME = "SystemDarkTheme" 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_CHAT_ITEM_ROUNDNESS = "ChatItemRoundness" private const val SHARED_PREFS_CHAT_ITEM_TAIL = "ChatItemTail" @@ -6882,7 +6883,8 @@ data class AppSettings( var uiDarkColorScheme: String? = null, var uiCurrentThemeIds: Map? = null, var uiThemes: List? = null, - var oneHandUI: Boolean? = null + var oneHandUI: Boolean? = null, + var chatBottomBar: Boolean? = null ) { fun prepareForExport(): AppSettings { val empty = AppSettings() @@ -6917,6 +6919,7 @@ data class AppSettings( if (uiCurrentThemeIds != def.uiCurrentThemeIds) { empty.uiCurrentThemeIds = uiCurrentThemeIds } if (uiThemes != def.uiThemes) { empty.uiThemes = uiThemes } if (oneHandUI != def.oneHandUI) { empty.oneHandUI = oneHandUI } + if (chatBottomBar != def.chatBottomBar) { empty.chatBottomBar = chatBottomBar } return empty } @@ -6962,6 +6965,7 @@ data class AppSettings( uiCurrentThemeIds?.let { def.currentThemeIds.set(it) } uiThemes?.let { def.themeOverrides.set(it.skipDuplicates()) } oneHandUI?.let { def.oneHandUI.set(it) } + chatBottomBar?.let { if (appPlatform.isAndroid) def.chatBottomBar.set(it) else def.chatBottomBar.set(true) } } companion object { @@ -6996,7 +7000,8 @@ data class AppSettings( uiDarkColorScheme = DefaultTheme.SIMPLEX.themeName, uiCurrentThemeIds = null, uiThemes = null, - oneHandUI = true + oneHandUI = true, + chatBottomBar = true, ) val current: AppSettings @@ -7032,7 +7037,8 @@ data class AppSettings( uiDarkColorScheme = def.systemDarkTheme.get() ?: DefaultTheme.SIMPLEX.themeName, uiCurrentThemeIds = def.currentThemeIds.get(), uiThemes = def.themeOverrides.get(), - oneHandUI = def.oneHandUI.get() + oneHandUI = def.oneHandUI.get(), + chatBottomBar = def.chatBottomBar.get() ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/ScrollableColumn.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/ScrollableColumn.kt index b0be547a31..b4e823bd45 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/ScrollableColumn.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/ScrollableColumn.kt @@ -23,6 +23,7 @@ expect fun LazyColumnWithScrollBar( flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(), userScrollEnabled: Boolean = true, additionalBarOffset: State? = null, + chatBottomBar: State = remember { mutableStateOf(true) }, // by default, this function will include .fillMaxSize() without you doing anything. If you don't need it, pass `false` here // maxSize (at least maxHeight) is needed for blur on appBars to work correctly fillMaxSize: Boolean = true, @@ -41,6 +42,7 @@ expect fun LazyColumnWithScrollBarNoAppBar( flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(), userScrollEnabled: Boolean = true, additionalBarOffset: State? = null, + chatBottomBar: State = remember { mutableStateOf(true) }, content: LazyListScope.() -> Unit ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt index b6eb4c8996..6a6db0da85 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt @@ -156,7 +156,7 @@ fun TerminalLog(floating: Boolean, composeViewHeight: State) { LazyColumnWithScrollBar ( reverseLayout = true, contentPadding = PaddingValues( - top = topPaddingToContent(), + top = topPaddingToContent(false), bottom = composeViewHeight.value ), state = listState, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 30f76bb878..ddf25a6e3b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -658,6 +658,8 @@ fun ChatLayout( Box(Modifier.fillMaxSize().chatViewBackgroundModifier(MaterialTheme.colors, MaterialTheme.wallpaper, LocalAppBarHandler.current?.backgroundGraphicsLayerSize, LocalAppBarHandler.current?.backgroundGraphicsLayer)) { val remoteHostId = remember { remoteHostId }.value val chatInfo = remember { chatInfo }.value + val oneHandUI = remember { appPrefs.oneHandUI.state } + val chatBottomBar = remember { appPrefs.chatBottomBar.state } AdaptingBottomPaddingLayout(Modifier, CHAT_COMPOSE_LAYOUT_ID, composeViewHeight) { if (chatInfo != null) { Box(Modifier.fillMaxSize()) { @@ -670,25 +672,23 @@ fun ChatLayout( ) } } - val oneHandUI = remember { appPrefs.oneHandUI.state } Box( Modifier .layoutId(CHAT_COMPOSE_LAYOUT_ID) .align(Alignment.BottomCenter) .imePadding() .navigationBarsPadding() - .then(if (oneHandUI.value) Modifier.padding(bottom = AppBarHeight * fontSizeSqrtMultiplier) else Modifier) + .then(if (oneHandUI.value && chatBottomBar.value) Modifier.padding(bottom = AppBarHeight * fontSizeSqrtMultiplier) else Modifier) ) { composeView() } } - val oneHandUI = remember { appPrefs.oneHandUI.state } - if (oneHandUI.value) { + if (oneHandUI.value && chatBottomBar.value) { StatusBarBackground() } else { NavigationBarBackground(true, oneHandUI.value, noAlpha = true) } - Box(if (oneHandUI.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) { + Box(if (oneHandUI.value && chatBottomBar.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) { if (selectedChatItems.value == null) { if (chatInfo != null) { ChatInfoToolbar(chatInfo, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch) @@ -856,12 +856,13 @@ fun BoxScope.ChatInfoToolbar( } } val oneHandUI = remember { appPrefs.oneHandUI.state } + val chatBottomBar = remember { appPrefs.chatBottomBar.state } DefaultAppBar( navigationButton = { if (appPlatform.isAndroid || showSearch.value) { NavigationButtonBack(onBackClicked) } }, title = { ChatInfoToolbarTitle(chatInfo) }, onTitleClick = if (chatInfo is ChatInfo.Local) null else info, showSearch = showSearch.value, - onTop = !oneHandUI.value, + onTop = !oneHandUI.value || !chatBottomBar.value, onSearchValueChanged = onSearchValueChanged, buttons = { barButtons.forEach { it() } } ) @@ -873,11 +874,11 @@ fun BoxScope.ChatInfoToolbar( showMenu, modifier = Modifier.onSizeChanged { with(density) { width.value = it.width.toDp().coerceAtLeast(250.dp) - if (oneHandUI.value && (appPlatform.isDesktop || (platform.androidApiLevel ?: 0) >= 30)) height.value = it.height.toDp() + if (oneHandUI.value && chatBottomBar.value && (appPlatform.isDesktop || (platform.androidApiLevel ?: 0) >= 30)) height.value = it.height.toDp() } }, - offset = DpOffset(-width.value, if (oneHandUI.value) -height.value else AppBarHeight) + offset = DpOffset(-width.value, if (oneHandUI.value && chatBottomBar.value) -height.value else AppBarHeight) ) { - if (oneHandUI.value) { + if (oneHandUI.value && chatBottomBar.value) { menuItems.asReversed().forEach { it() } } else { menuItems.forEach { it() } @@ -964,7 +965,7 @@ fun BoxScope.ChatItemsList( val reversedChatItems = remember { derivedStateOf { chatModel.chatItems.asReversed() } } val revealedItems = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(setOf()) } val mergedItems = remember { derivedStateOf { MergedItems.create(reversedChatItems.value, unreadCount, revealedItems.value, chatModel.chatState) } } - val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent().roundToPx() }) + val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent(true).roundToPx() }) /** determines height based on window info and static height of two AppBars. It's needed because in the first graphic frame height of * [composeViewHeight] is unknown, but we need to set scroll position for unread messages already so it will be correct before the first frame appears * */ @@ -1264,10 +1265,11 @@ fun BoxScope.ChatItemsList( state = listState.value, reverseLayout = true, contentPadding = PaddingValues( - top = topPaddingToContent(), + top = topPaddingToContent(true), bottom = composeViewHeight.value ), - additionalBarOffset = composeViewHeight + additionalBarOffset = composeViewHeight, + chatBottomBar = remember { appPrefs.chatBottomBar.state } ) { val mergedItemsValue = mergedItems.value itemsIndexed(mergedItemsValue.items, key = { _, merged -> keyForItem(merged.newest().item) }) { index, merged -> @@ -1310,8 +1312,8 @@ fun BoxScope.ChatItemsList( } } } - FloatingButtons(loadingMoreItems, mergedItems, unreadCount, maxHeight, composeViewHeight, remoteHostId, chatInfo, searchValue, markChatRead, listState) - FloatingDate(Modifier.padding(top = 10.dp + topPaddingToContent()).align(Alignment.TopCenter), mergedItems, listState) + FloatingButtons(loadingMoreItems, mergedItems, unreadCount, maxHeight, composeViewHeight, searchValue, markChatRead, listState) + FloatingDate(Modifier.padding(top = 10.dp + topPaddingToContent(true)).align(Alignment.TopCenter), mergedItems, listState) LaunchedEffect(Unit) { snapshotFlow { listState.value.isScrollInProgress } @@ -1400,14 +1402,12 @@ fun BoxScope.FloatingButtons( unreadCount: State, maxHeight: State, composeViewHeight: State, - remoteHostId: Long?, - chatInfo: ChatInfo, searchValue: State, markChatRead: () -> Unit, listState: State ) { val scope = rememberCoroutineScope() - val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent().roundToPx() }) + val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent(true).roundToPx() }) val bottomUnreadCount = remember { derivedStateOf { if (unreadCount.value == 0) return@derivedStateOf 0 @@ -1447,7 +1447,7 @@ fun BoxScope.FloatingButtons( val showDropDown = remember { mutableStateOf(false) } TopEndFloatingButton( - Modifier.padding(end = DEFAULT_PADDING, top = 24.dp + topPaddingToContent()).align(Alignment.TopEnd), + Modifier.padding(end = DEFAULT_PADDING, top = 24.dp + topPaddingToContent(true)).align(Alignment.TopEnd), topUnreadCount, onClick = { val index = mergedItems.value.items.indexOfLast { it.hasUnread() } @@ -1465,7 +1465,7 @@ fun BoxScope.FloatingButtons( DefaultDropdownMenu( showDropDown, modifier = Modifier.onSizeChanged { with(density) { width.value = it.width.toDp().coerceAtLeast(250.dp) } }, - offset = DpOffset(-DEFAULT_PADDING - width.value, 24.dp + fabSize + topPaddingToContent()) + offset = DpOffset(-DEFAULT_PADDING - width.value, 24.dp + fabSize + topPaddingToContent(true)) ) { ItemAction( generalGetString(MR.strings.mark_read), @@ -1615,9 +1615,10 @@ private fun TopEndFloatingButton( } @Composable -fun topPaddingToContent(): Dp { +fun topPaddingToContent(chatView: Boolean): Dp { val oneHandUI = remember { appPrefs.oneHandUI.state } - return if (oneHandUI.value) { + val chatBottomBar = remember { appPrefs.chatBottomBar.state } + return if (oneHandUI.value && (!chatView || chatBottomBar.value)) { WindowInsets.statusBars.asPaddingValues().calculateTopPadding() } else { AppBarHeight * fontSizeSqrtMultiplier + WindowInsets.statusBars.asPaddingValues().calculateTopPadding() @@ -1634,7 +1635,7 @@ private fun FloatingDate( val nearBottomIndex = remember(chatModel.chatId) { mutableStateOf(if (isNearBottom.value) -1 else 0) } val showDate = remember(chatModel.chatId) { mutableStateOf(false) } val density = LocalDensity.current.density - val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent().roundToPx() }) + val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent(true).roundToPx() }) val fontSizeSqrtMultiplier = fontSizeSqrtMultiplier val lastVisibleItemDate = remember { derivedStateOf { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index 804222f264..5ee6e40e6e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -306,7 +306,7 @@ fun ModalData.GroupChatInfoLayout( contentPadding = if (oneHandUI.value) { PaddingValues(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + DEFAULT_PADDING + 5.dp, bottom = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()) } else { - PaddingValues(top = topPaddingToContent()) + PaddingValues(top = topPaddingToContent(false)) }, state = listState ) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 20bb65ec7d..ff776bc8ca 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -761,7 +761,7 @@ private fun BoxScope.ChatList(searchText: MutableState, listStat val searchShowingSimplexLink = remember { mutableStateOf(false) } val searchChatFilteredBySimplexLink = remember { mutableStateOf(null) } val chats = filteredChats(showUnreadAndFavorites, searchShowingSimplexLink, searchChatFilteredBySimplexLink, searchText.value.text, allChats.value.toList()) - val topPaddingToContent = topPaddingToContent() + val topPaddingToContent = topPaddingToContent(false) val blankSpaceSize = if (oneHandUI.value) WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + AppBarHeight * fontSizeSqrtMultiplier else topPaddingToContent LazyColumnWithScrollBar( if (!oneHandUI.value) Modifier.imePadding() else Modifier, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt index 9ca2c1e2cd..e048c39fe7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt @@ -194,7 +194,7 @@ private fun ShareList( filteredChats(false, mutableStateOf(false), mutableStateOf(null), search, sorted) } } - val topPaddingToContent = topPaddingToContent() + val topPaddingToContent = topPaddingToContent(false) LazyColumnWithScrollBar( modifier = Modifier.then(if (oneHandUI.value) Modifier.consumeWindowInsets(WindowInsets.navigationBars.only(WindowInsetsSides.Vertical)) else Modifier).imePadding(), contentPadding = PaddingValues( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt index 72118224e6..cb4991c99f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt @@ -334,7 +334,7 @@ private fun ModalData.NewChatSheetLayout( @Composable fun NonOneHandLazyColumn() { - val blankSpaceSize = topPaddingToContent() + val blankSpaceSize = topPaddingToContent(false) LazyColumnWithScrollBar( Modifier.imePadding(), state = listState, @@ -646,7 +646,7 @@ private fun ModalData.DeletedContactsView(rh: RemoteHostInfo?, closeDeletedChats ) Box { - val topPaddingToContent = topPaddingToContent() + val topPaddingToContent = topPaddingToContent(false) LazyColumnWithScrollBar( if (!oneHandUI.value) Modifier.imePadding() else Modifier, contentPadding = PaddingValues( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index e08d46d880..058c82c2fe 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -400,7 +400,7 @@ fun ActiveProfilePicker( .fillMaxSize() .alpha(if (progressByTimeout) 0.6f else 1f) ) { - LazyColumnWithScrollBar(Modifier.padding(top = topPaddingToContent()), userScrollEnabled = !switchingProfile.value) { + LazyColumnWithScrollBar(Modifier.padding(top = topPaddingToContent(false)), userScrollEnabled = !switchingProfile.value) { item { val oneHandUI = remember { appPrefs.oneHandUI.state } if (oneHandUI.value) { diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index ddf8805e8a..df0b3d5cd7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1396,7 +1396,8 @@ Database downgrade Incompatible database version Confirm database upgrades - Reachable chat toolbar + Reachable app toolbars + Reachable chat toolbar Toggle chat list: You can change it in Appearance settings. Show console in new window diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt index fc806feb2b..785c3b40fa 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt @@ -36,6 +36,7 @@ actual fun LazyColumnWithScrollBar( flingBehavior: FlingBehavior, userScrollEnabled: Boolean, additionalBarOffset: State?, + chatBottomBar: State, fillMaxSize: Boolean, content: LazyListScope.() -> Unit ) { @@ -92,7 +93,7 @@ actual fun LazyColumnWithScrollBar( val modifier = if (fillMaxSize) Modifier.fillMaxSize().then(modifier) else modifier Box(Modifier.copyViewToAppBar(remember { appPrefs.appearanceBarsBlurRadius.state }.value, LocalAppBarHandler.current?.graphicsLayer).nestedScroll(connection)) { LazyColumn(modifier.then(scrollModifier), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content) - ScrollBar(reverseLayout, state, scrollBarAlpha, scrollJob, scrollBarDraggingState, additionalBarOffset) + ScrollBar(reverseLayout, state, scrollBarAlpha, scrollJob, scrollBarDraggingState, additionalBarOffset, chatBottomBar) } } @@ -107,6 +108,7 @@ actual fun LazyColumnWithScrollBarNoAppBar( flingBehavior: FlingBehavior, userScrollEnabled: Boolean, additionalBarOffset: State?, + chatBottomBar: State, content: LazyListScope.() -> Unit ) { val scope = rememberCoroutineScope() @@ -133,7 +135,7 @@ actual fun LazyColumnWithScrollBarNoAppBar( val scrollBarDraggingState = remember { mutableStateOf(false) } Box { LazyColumn(modifier.then(scrollModifier), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content) - ScrollBar(reverseLayout, state, scrollBarAlpha, scrollJob, scrollBarDraggingState, additionalBarOffset) + ScrollBar(reverseLayout, state, scrollBarAlpha, scrollJob, scrollBarDraggingState, additionalBarOffset, chatBottomBar) } } @@ -144,15 +146,16 @@ private fun ScrollBar( scrollBarAlpha: Animatable, scrollJob: MutableState, scrollBarDraggingState: MutableState, - additionalBarHeight: State? + additionalBarHeight: State?, + chatBottomBar: State, ) { val oneHandUI = remember { appPrefs.oneHandUI.state } val padding = if (additionalBarHeight != null) { - PaddingValues(top = if (oneHandUI.value) 0.dp else AppBarHeight * fontSizeSqrtMultiplier, bottom = additionalBarHeight.value) + PaddingValues(top = if (oneHandUI.value && chatBottomBar.value) 0.dp else AppBarHeight * fontSizeSqrtMultiplier, bottom = additionalBarHeight.value) } else if (reverseLayout) { PaddingValues(bottom = AppBarHeight * fontSizeSqrtMultiplier) } else { - PaddingValues(top = if (oneHandUI.value) 0.dp else AppBarHeight * fontSizeSqrtMultiplier) + PaddingValues(top = if (oneHandUI.value && chatBottomBar.value) 0.dp else AppBarHeight * fontSizeSqrtMultiplier) } Box(Modifier.fillMaxSize().padding(padding), contentAlignment = Alignment.CenterEnd) { DesktopScrollBar(rememberScrollbarAdapter(state), Modifier.fillMaxHeight(), scrollBarAlpha, scrollJob, reverseLayout, scrollBarDraggingState) diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt index 91ff8831ce..c270bddb73 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt @@ -58,6 +58,9 @@ fun AppearanceScope.AppearanceLayout( } } SettingsPreferenceItem(icon = null, stringResource(MR.strings.one_hand_ui), ChatModel.controller.appPrefs.oneHandUI) + if (remember { appPrefs.oneHandUI.state }.value && !remember { appPrefs.chatBottomBar.state }.value) { + SettingsPreferenceItem(icon = null, stringResource(MR.strings.chat_bottom_bar), ChatModel.controller.appPrefs.chatBottomBar) + } } SectionDividerSpaced() ThemesSection(systemDarkTheme) diff --git a/src/Simplex/Chat/AppSettings.hs b/src/Simplex/Chat/AppSettings.hs index e75546d206..1efa69fad4 100644 --- a/src/Simplex/Chat/AppSettings.hs +++ b/src/Simplex/Chat/AppSettings.hs @@ -56,7 +56,8 @@ data AppSettings = AppSettings uiDarkColorScheme :: Maybe DarkColorScheme, uiCurrentThemeIds :: Maybe (Map ThemeColorScheme Text), uiThemes :: Maybe [UITheme], - oneHandUI :: Maybe Bool + oneHandUI :: Maybe Bool, + chatBottomBar :: Maybe Bool } deriving (Show) @@ -105,7 +106,8 @@ defaultAppSettings = uiDarkColorScheme = Just DCSSimplex, uiCurrentThemeIds = Nothing, uiThemes = Nothing, - oneHandUI = Just True + oneHandUI = Just True, + chatBottomBar = Just True } defaultParseAppSettings :: AppSettings @@ -141,7 +143,8 @@ defaultParseAppSettings = uiDarkColorScheme = Nothing, uiCurrentThemeIds = Nothing, uiThemes = Nothing, - oneHandUI = Nothing + oneHandUI = Nothing, + chatBottomBar = Nothing } combineAppSettings :: AppSettings -> AppSettings -> AppSettings @@ -177,7 +180,8 @@ combineAppSettings platformDefaults storedSettings = uiDarkColorScheme = p uiDarkColorScheme, uiCurrentThemeIds = p uiCurrentThemeIds, uiThemes = p uiThemes, - oneHandUI = p oneHandUI + oneHandUI = p oneHandUI, + chatBottomBar = p chatBottomBar } where p :: (AppSettings -> Maybe a) -> Maybe a @@ -230,6 +234,7 @@ instance FromJSON AppSettings where uiCurrentThemeIds <- p "uiCurrentThemeIds" uiThemes <- p "uiThemes" oneHandUI <- p "oneHandUI" + chatBottomBar <- p "chatBottomBar" pure AppSettings { appPlatform, @@ -262,7 +267,8 @@ instance FromJSON AppSettings where uiDarkColorScheme, uiCurrentThemeIds, uiThemes, - oneHandUI + oneHandUI, + chatBottomBar } where p key = v .:? key <|> pure Nothing