From 67472b6285a2aaa4c5d25d72059ffca60e41964c Mon Sep 17 00:00:00 2001 From: Diogo Date: Thu, 26 Sep 2024 09:00:10 +0100 Subject: [PATCH] android, desktop: scrolling user profiles (#4939) * android, desktop: scrolling user profiles --------- Co-authored-by: Evgeny Poberezkin Co-authored-by: Avently <7953703+avently@users.noreply.github.com> --- .../views/chatlist/UserPicker.android.kt | 145 ++++---- .../common/views/chatlist/UserPicker.kt | 333 +++++++++--------- .../views/chatlist/UserPicker.desktop.kt | 55 ++- apps/multiplatform/gradle.properties | 2 +- 4 files changed, 285 insertions(+), 250 deletions(-) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt index 6c16a75874..b68756c669 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt @@ -5,14 +5,19 @@ import androidx.compose.foundation.* import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.material.DrawerDefaults.ScrimOpacity import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.graphics.* import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.User @@ -21,95 +26,101 @@ import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.onboarding.OnboardingStage -import chat.simplex.res.MR -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.CancellationException import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow +private val USER_PICKER_IMAGE_SIZE = 44.dp +private val USER_PICKER_ROW_PADDING = 16.dp + @Composable -actual fun UserPickerInactiveUsersSection( +actual fun UserPickerUsersSection( users: List, stopped: Boolean, - onShowAllProfilesClicked: () -> Unit, onUserClicked: (user: User) -> Unit, ) { val scrollState = rememberScrollState() + val screenWidthDp = windowWidth() if (users.isNotEmpty()) { SectionItemView( - padding = PaddingValues( - start = 16.dp, - top = if (windowOrientation() == WindowOrientation.PORTRAIT) DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL else DEFAULT_PADDING_HALF, - bottom = DEFAULT_PADDING_HALF), + padding = PaddingValues(), disabled = stopped ) { Box { Row( - modifier = Modifier.padding(end = DEFAULT_PADDING + 30.dp).horizontalScroll(scrollState) + modifier = Modifier.horizontalScroll(scrollState), ) { - users.forEach { u -> - UserPickerInactiveUserBadge(u, stopped) { - onUserClicked(it) - withBGApi { - delay(500) - scrollState.scrollTo(0) + Spacer(Modifier.width(DEFAULT_PADDING)) + Row(horizontalArrangement = Arrangement.spacedBy(USER_PICKER_ROW_PADDING)) { + users.forEach { u -> + UserPickerUserBox(u, stopped, modifier = Modifier.userBoxWidth(u.user, users.size, screenWidthDp)) { + onUserClicked(it) + withBGApi { + delay(500) + scrollState.scrollTo(0) + } } } - Spacer(Modifier.width(20.dp)) - } - Spacer(Modifier.width(60.dp)) - } - Row( - horizontalArrangement = Arrangement.End, - modifier = Modifier - .fillMaxWidth() - .padding(end = DEFAULT_PADDING + 30.dp) - .height(60.dp) - ) { - Canvas(modifier = Modifier.size(60.dp)) { - drawRect( - brush = Brush.horizontalGradient( - colors = listOf( - Color.Transparent, - CurrentColors.value.colors.surface, - ) - ), - ) - } - } - Row( - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .height(60.dp) - .fillMaxWidth() - .padding(end = DEFAULT_PADDING) - ) { - IconButton( - onClick = onShowAllProfilesClicked, - enabled = !stopped - ) { - Icon( - painterResource(MR.images.ic_chevron_right), - stringResource(MR.strings.your_chat_profiles), - tint = MaterialTheme.colors.secondary, - modifier = Modifier.size(34.dp) - ) } + Spacer(Modifier.width(DEFAULT_PADDING)) } } } - } else { - UserPickerOptionRow( - painterResource(MR.images.ic_manage_accounts), - stringResource(MR.strings.your_chat_profiles), - onShowAllProfilesClicked + } +} +@Composable +fun UserPickerUserBox( + userInfo: UserInfo, + stopped: Boolean, + modifier: Modifier = Modifier, + onClick: (user: User) -> Unit, +) { + Row( + modifier = modifier + .userPickerBoxModifier() + .clickable ( + onClick = { onClick(userInfo.user) }, + enabled = !stopped + ) + .background(MaterialTheme.colors.background) + .padding(USER_PICKER_ROW_PADDING), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(USER_PICKER_ROW_PADDING) + ) { + Box { + ProfileImageForActiveCall(size = USER_PICKER_IMAGE_SIZE, image = userInfo.user.profile.image, color = MaterialTheme.colors.secondaryVariant) + + if (userInfo.unreadCount > 0 && !userInfo.user.activeUser) { + unreadBadge(userInfo.unreadCount, userInfo.user.showNtfs, false) + } + } + val user = userInfo.user + Text( + user.displayName, + fontWeight = if (user.activeUser) FontWeight.Bold else FontWeight.Normal, + maxLines = 1, + overflow = TextOverflow.Ellipsis, ) } } +@Composable +private fun Modifier.userPickerBoxModifier(): Modifier { + val percent = remember { appPreferences.profileImageCornerRadius.state } + val r = kotlin.math.max(0f, percent.value) + + val cornerSize = when { + r >= 50 -> 50 + r <= 0 -> 0 + else -> r.toInt() + } + + val shape = RoundedCornerShape(CornerSize(cornerSize)) + return this.clip(shape).border(1.dp, MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 1 - userPickerAlpha() - 0.02f), shape) +} + + private fun calculateFraction(pos: Float) = (pos / 1f).coerceIn(0f, 1f) @@ -150,7 +161,7 @@ actual fun PlatformUserPicker(modifier: Modifier, pickerState: MutableStateFlow< isLight = colors.isLight, drawerShadingColor = shadingColor, toolbarOnTop = !appPrefs.oneHandUI.get(), - navBarColor = colors.surface + navBarColor = colors.background.mixWith(colors.onBackground, 1 - userPickerAlpha()) ) } else if (ModalManager.start.modalCount.value == 0) { platform.androidSetDrawerStatusAndNavBarColor( @@ -220,3 +231,13 @@ private fun Modifier.draggableBottomDrawerModifier( orientation = Orientation.Vertical, resistance = null ) + +private fun Modifier.userBoxWidth(user: User, totalUsers: Int, windowWidth: Dp): Modifier { + return if (totalUsers == 1) { + this.width(windowWidth - DEFAULT_PADDING * 2) + } else if (user.activeUser) { + this.width(windowWidth - DEFAULT_PADDING - (USER_PICKER_ROW_PADDING * 3) - USER_PICKER_IMAGE_SIZE) + } else { + this.widthIn(max = (windowWidth - (DEFAULT_PADDING * 2)) * 0.618f) + } +} \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt index 19bc2afbd5..8546dc4fb3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt @@ -1,13 +1,11 @@ package chat.simplex.common.views.chatlist import SectionItemView -import SectionView import TextIconSpaced import androidx.compose.foundation.* import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsHoveredAsState import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.* import androidx.compose.material.* import androidx.compose.runtime.* @@ -32,7 +30,6 @@ import chat.simplex.common.views.helpers.* import chat.simplex.common.platform.* import chat.simplex.common.views.CreateProfile import chat.simplex.common.views.localauth.VerticalDivider -import chat.simplex.common.views.newchat.* import chat.simplex.common.views.remote.* import chat.simplex.common.views.usersettings.* import chat.simplex.common.views.usersettings.AppearanceScope.ColorModeSwitcher @@ -41,6 +38,8 @@ import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.* import kotlinx.coroutines.flow.* +private val USER_PICKER_SECTION_SPACING = 32.dp + @Composable fun UserPicker( chatModel: ChatModel, @@ -57,7 +56,7 @@ fun UserPicker( derivedStateOf { chatModel.users .filter { u -> u.user.activeUser || !u.user.hidden } - .sortedByDescending { it.user.activeUser } + .sortedByDescending { it.user.activeOrder } } } val remoteHosts by remember { @@ -143,10 +142,33 @@ fun UserPicker( .height(IntrinsicSize.Min) .fillMaxWidth() .then(if (newChat.isVisible()) Modifier.shadow(8.dp, clip = true) else Modifier) - .background(MaterialTheme.colors.surface) - .padding(vertical = DEFAULT_PADDING), + .background(if (appPlatform.isAndroid) MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, alpha = 1 - userPickerAlpha()) else MaterialTheme.colors.surface) + .padding(bottom = USER_PICKER_SECTION_SPACING - DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL), pickerState = userPickerState ) { + val showCustomModal: (@Composable() (ModalData.(ChatModel, () -> Unit) -> Unit)) -> () -> Unit = { modalView -> + { + ModalManager.start.showCustomModal { close -> modalView(chatModel, close) } + } + } + val stopped = remember { chatModel.chatRunning }.value == false + val onUserClicked: (user: User) -> Unit = { user -> + if (!user.activeUser) { + userPickerState.value = AnimatedViewState.HIDING + withBGApi { + controller.showProgressIfNeeded { + ModalManager.closeAllModalsEverywhere() + chatModel.controller.changeActiveUser(user.remoteHostId, user.userId, null) + } + } + } else { + showCustomModal { chatModel, close -> UserProfileView(chatModel, close) }() + withBGApi { + closePicker(userPickerState) + } + } + } + @Composable fun FirstSection() { if (remoteHosts.isNotEmpty()) { @@ -170,87 +192,24 @@ fun UserPicker( } ) } - ActiveUserSection( - chatModel = chatModel, - userPickerState = userPickerState, - ) + val currentUser = remember { chatModel.currentUser }.value + if (appPlatform.isAndroid) { + Column(modifier = Modifier.padding(top = USER_PICKER_SECTION_SPACING, bottom = USER_PICKER_SECTION_SPACING - DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL - 3.dp)) { + UserPickerUsersSection( + users = users, + onUserClicked = onUserClicked, + stopped = stopped + ) + } + } else if (currentUser != null) { + SectionItemView({ onUserClicked(currentUser) }, 80.dp, padding = PaddingValues(start = 16.dp, end = DEFAULT_PADDING), disabled = stopped) { + ProfilePreview(currentUser.profile, stopped = stopped) + } + } } @Composable fun SecondSection() { - GlobalSettingsSection( - chatModel = chatModel, - userPickerState = userPickerState, - setPerformLA = setPerformLA, - onUserClicked = { user -> - userPickerState.value = AnimatedViewState.HIDING - if (!user.activeUser) { - withBGApi { - controller.showProgressIfNeeded { - ModalManager.closeAllModalsEverywhere() - chatModel.controller.changeActiveUser(user.remoteHostId, user.userId, null) - } - } - } - }, - onShowAllProfilesClicked = { - doWithAuth( - generalGetString(MR.strings.auth_open_chat_profiles), - generalGetString(MR.strings.auth_log_in_using_credential) - ) { - ModalManager.start.showCustomModal { close -> - val search = rememberSaveable { mutableStateOf("") } - val profileHidden = rememberSaveable { mutableStateOf(false) } - ModalView( - { close() }, - endButtons = { - SearchTextField(Modifier.fillMaxWidth(), placeholder = stringResource(MR.strings.search_verb), alwaysVisible = true) { search.value = it } - }, - content = { UserProfilesView(chatModel, search, profileHidden) }) - } - } - } - ) - } - - if (appPlatform.isDesktop || windowOrientation() == WindowOrientation.PORTRAIT) { - Column { - FirstSection() - Divider(Modifier.padding(DEFAULT_PADDING)) - SecondSection() - } - } else { - Row { - Box(Modifier.weight(1f)) { - FirstSection() - } - VerticalDivider() - Box(Modifier.weight(1f)) { - SecondSection() - } - } - } - } -} - -@Composable -private fun ActiveUserSection( - chatModel: ChatModel, - userPickerState: MutableStateFlow, -) { - val showCustomModal: (@Composable() (ModalData.(ChatModel, () -> Unit) -> Unit)) -> () -> Unit = { modalView -> - { - ModalManager.start.showCustomModal { close -> modalView(chatModel, close) } - } - } - val currentUser = remember { chatModel.currentUser }.value - val stopped = chatModel.chatRunning.value == false - - if (currentUser != null) { - SectionView { - SectionItemView(showCustomModal { chatModel, close -> UserProfileView(chatModel, close) }, 80.dp, padding = PaddingValues(start = 16.dp, end = DEFAULT_PADDING), disabled = stopped) { - ProfilePreview(currentUser.profile, stopped = stopped) - } UserPickerOptionRow( painterResource(MR.images.ic_qr_code), if (chatModel.userAddress.value != null) generalGetString(MR.strings.your_simplex_contact_address) else generalGetString(MR.strings.create_simplex_address), @@ -266,9 +225,22 @@ private fun ActiveUserSection( }), disabled = stopped ) - } - } else { - SectionView { + if (appPlatform.isDesktop) { + Divider(Modifier.padding(DEFAULT_PADDING)) + + val inactiveUsers = users.filter { !it.user.activeUser } + + if (inactiveUsers.isNotEmpty()) { + Column(modifier = Modifier.padding(vertical = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL)) { + UserPickerUsersSection( + users = inactiveUsers, + onUserClicked = onUserClicked, + stopped = stopped + ) + } + } + } + if (chatModel.desktopNoUserNoRemote) { UserPickerOptionRow( painterResource(MR.images.ic_manage_accounts), @@ -284,76 +256,121 @@ private fun ActiveUserSection( } } ) + } else { + UserPickerOptionRow( + painterResource(MR.images.ic_manage_accounts), + stringResource(MR.strings.your_chat_profiles), + { + doWithAuth( + generalGetString(MR.strings.auth_open_chat_profiles), + generalGetString(MR.strings.auth_log_in_using_credential) + ) { + ModalManager.start.showCustomModal { close -> + val search = rememberSaveable { mutableStateOf("") } + val profileHidden = rememberSaveable { mutableStateOf(false) } + ModalView( + { close() }, + endButtons = { + SearchTextField(Modifier.fillMaxWidth(), placeholder = stringResource(MR.strings.search_verb), alwaysVisible = true) { search.value = it } + }, + content = { UserProfilesView(chatModel, search, profileHidden) }) + } + } + }, + disabled = stopped + ) + } + } + + if (appPlatform.isDesktop || windowOrientation() == WindowOrientation.PORTRAIT) { + Column { + FirstSection() + SecondSection() + GlobalSettingsSection( + userPickerState = userPickerState, + setPerformLA = setPerformLA, + ) + } + } else { + Column { + FirstSection() + Row { + Box(Modifier.weight(1f)) { + Column { + SecondSection() + } + } + VerticalDivider() + Box(Modifier.weight(1f)) { + Column { + GlobalSettingsSection( + userPickerState = userPickerState, + setPerformLA = setPerformLA, + ) + } + } + } } } } } +fun userPickerAlpha(): Float { + return when (CurrentColors.value.base) { + DefaultTheme.LIGHT -> 0.05f + DefaultTheme.DARK -> 0.05f + DefaultTheme.BLACK -> 0.075f + DefaultTheme.SIMPLEX -> 0.035f + } +} + @Composable private fun GlobalSettingsSection( - chatModel: ChatModel, userPickerState: MutableStateFlow, setPerformLA: (Boolean) -> Unit, - onUserClicked: (user: User) -> Unit, - onShowAllProfilesClicked: () -> Unit ) { - val stopped = chatModel.chatRunning.value == false - val users by remember { - derivedStateOf { - chatModel.users - .filter { u -> !u.user.hidden && !u.user.activeUser } - } - } + val stopped = remember { chatModel.chatRunning }.value == false - SectionView(headerBottomPadding = if (appPlatform.isDesktop || windowOrientation() == WindowOrientation.PORTRAIT) DEFAULT_PADDING else 0.dp) { - UserPickerInactiveUsersSection( - users = users, - onShowAllProfilesClicked = onShowAllProfilesClicked, - onUserClicked = onUserClicked, - stopped = stopped - ) + if (appPlatform.isAndroid) { + val text = generalGetString(MR.strings.settings_section_title_use_from_desktop).lowercase().capitalize(Locale.current) - if (appPlatform.isAndroid) { - val text = generalGetString(MR.strings.settings_section_title_use_from_desktop).lowercase().capitalize(Locale.current) - - UserPickerOptionRow( - painterResource(MR.images.ic_desktop), - text, - click = { - ModalManager.start.showCustomModal { close -> - ConnectDesktopView(close) - } - } - ) - } else { - UserPickerOptionRow( - icon = painterResource(MR.images.ic_smartphone_300), - text = stringResource(if (remember { chat.simplex.common.platform.chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles), - click = { - userPickerState.value = AnimatedViewState.HIDING - ModalManager.start.showModal { - ConnectMobileView() - } - }, - disabled = stopped - ) - } - - SectionItemView( + UserPickerOptionRow( + painterResource(MR.images.ic_desktop), + text, click = { - ModalManager.start.showModalCloseable { close -> - SettingsView(chatModel, setPerformLA, close) + ModalManager.start.showCustomModal { close -> + ConnectDesktopView(close) + } + } + ) + } else { + UserPickerOptionRow( + icon = painterResource(MR.images.ic_smartphone_300), + text = stringResource(if (remember { chat.simplex.common.platform.chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles), + click = { + userPickerState.value = AnimatedViewState.HIDING + ModalManager.start.showModal { + ConnectMobileView() } }, - padding = PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING + 2.dp) - ) { - val text = generalGetString(MR.strings.settings_section_title_settings).lowercase().capitalize(Locale.current) - Icon(painterResource(MR.images.ic_settings), text, tint = MaterialTheme.colors.secondary) - TextIconSpaced() - Text(text, color = Color.Unspecified) - Spacer(Modifier.weight(1f)) - ColorModeSwitcher() - } + disabled = stopped + ) + } + + SectionItemView( + click = { + ModalManager.start.showModalCloseable { close -> + SettingsView(chatModel, setPerformLA, close) + } + }, + padding = if (appPlatform.isDesktop) PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING + 2.dp) else PaddingValues(start = DEFAULT_PADDING, end = DEFAULT_PADDING_HALF) + ) { + val text = generalGetString(MR.strings.settings_section_title_settings).lowercase().capitalize(Locale.current) + Icon(painterResource(MR.images.ic_settings), text, tint = MaterialTheme.colors.secondary) + TextIconSpaced() + Text(text, color = Color.Unspecified) + Spacer(Modifier.weight(1f)) + ColorModeSwitcher() } } @@ -361,7 +378,7 @@ private fun GlobalSettingsSection( fun UserProfilePickerItem( u: User, unreadCount: Int = 0, - enabled: Boolean = chatModel.chatRunning.value == true || chatModel.connectedToRemote, + enabled: Boolean = remember { chatModel.chatRunning }.value == true || chatModel.connectedToRemote, onLongClick: () -> Unit = {}, openSettings: () -> Unit = {}, onClick: () -> Unit @@ -410,7 +427,7 @@ fun UserProfilePickerItem( } @Composable -fun UserProfileRow(u: User, enabled: Boolean = chatModel.chatRunning.value == true || chatModel.connectedToRemote) { +fun UserProfileRow(u: User, enabled: Boolean = remember { chatModel.chatRunning }.value == true || chatModel.connectedToRemote) { Row( Modifier .widthIn(max = windowWidth() * 0.7f) @@ -433,31 +450,13 @@ fun UserProfileRow(u: User, enabled: Boolean = chatModel.chatRunning.value == tr @Composable fun UserPickerOptionRow(icon: Painter, text: String, click: (() -> Unit)? = null, disabled: Boolean = false) { - SectionItemView(click, disabled = disabled, extraPadding = true) { + SectionItemView(click, disabled = disabled, extraPadding = appPlatform.isDesktop) { Icon(icon, text, tint = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.secondary) TextIconSpaced() Text(text = text, color = if (disabled) MaterialTheme.colors.secondary else Color.Unspecified) } } -@Composable -fun UserPickerInactiveUserBadge(userInfo: UserInfo, stopped: Boolean, size: Dp = 60.dp, onClick: (user: User) -> Unit) { - Box { - IconButton( - onClick = { onClick(userInfo.user) }, - enabled = !stopped - ) { - Box { - ProfileImage(size = size, image = userInfo.user.profile.image, color = MaterialTheme.colors.secondaryVariant) - - if (userInfo.unreadCount > 0) { - unreadBadge(userInfo.unreadCount, userInfo.user.showNtfs) - } - } - } - } -} - @OptIn(ExperimentalLayoutApi::class) @Composable private fun DevicePickerRow( @@ -471,7 +470,7 @@ private fun DevicePickerRow( Modifier .fillMaxWidth() .sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT) - .padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING, top = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL), + .padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING, top = DEFAULT_PADDING + DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL), horizontalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { @@ -517,10 +516,9 @@ private fun DevicePickerRow( } @Composable -expect fun UserPickerInactiveUsersSection( +expect fun UserPickerUsersSection( users: List, stopped: Boolean, - onShowAllProfilesClicked: () -> Unit, onUserClicked: (user: User) -> Unit, ) @@ -606,14 +604,14 @@ fun HostDisconnectButton(onClick: (() -> Unit)?) { } @Composable -private fun BoxScope.unreadBadge(unreadCount: Int, userMuted: Boolean) { +fun BoxScope.unreadBadge(unreadCount: Int, userMuted: Boolean, hasPadding: Boolean) { Text( if (unreadCount > 0) unreadCountStr(unreadCount) else "", color = Color.White, fontSize = 10.sp, style = TextStyle(textAlign = TextAlign.Center), modifier = Modifier - .offset(y = 3.sp.toDp()) + .offset(y = if (hasPadding) 3.sp.toDp() else -4.sp.toDp(), x = if (hasPadding) 0.dp else 4.sp.toDp()) .background(if (userMuted) MaterialTheme.colors.primaryVariant else MaterialTheme.colors.secondary, shape = CircleShape) .badgeLayout() .padding(horizontal = 2.sp.toDp()) @@ -622,7 +620,6 @@ private fun BoxScope.unreadBadge(unreadCount: Int, userMuted: Boolean) { ) } - private suspend fun closePicker(userPickerState: MutableStateFlow) { delay(500) userPickerState.value = AnimatedViewState.HIDING diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt index 583d5437c3..d9b53b9485 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt @@ -1,31 +1,31 @@ package chat.simplex.common.views.chatlist import androidx.compose.animation.* -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import chat.simplex.common.model.User import chat.simplex.common.model.UserInfo import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* -import chat.simplex.res.MR -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.flow.MutableStateFlow @Composable -actual fun UserPickerInactiveUsersSection( +actual fun UserPickerUsersSection( users: List, stopped: Boolean, - onShowAllProfilesClicked: () -> Unit, onUserClicked: (user: User) -> Unit, ) { if (users.isNotEmpty()) { @@ -34,13 +34,13 @@ actual fun UserPickerInactiveUsersSection( val horizontalPadding = DEFAULT_PADDING_HALF + 8.dp Column(Modifier - .padding(horizontal = horizontalPadding, vertical = DEFAULT_PADDING_HALF) - .height(55.dp * rowsToDisplay + (if (rowsToDisplay > 1) DEFAULT_PADDING else 0.dp)) + .padding(horizontal = horizontalPadding) + .height((55.dp + 16.sp.toDp()) * rowsToDisplay + (if (rowsToDisplay > 1) DEFAULT_PADDING else 0.dp)) ) { ColumnWithScrollBar( verticalArrangement = Arrangement.spacedBy(DEFAULT_PADDING) ) { - val spaceBetween = (((DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier) - (horizontalPadding)) - (55.dp * 5)) / 5 + val spaceBetween = (((DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier) - (horizontalPadding)) - (65.dp * 5)) / 5 userRows.forEach { row -> Row( @@ -48,8 +48,31 @@ actual fun UserPickerInactiveUsersSection( horizontalArrangement = Arrangement.spacedBy(spaceBetween), ) { row.forEach { u -> - UserPickerInactiveUserBadge(u, stopped, size = 55.dp) { - onUserClicked(u.user) + Column(modifier = Modifier + .clickable ( + onClick = { onUserClicked(u.user) }, + enabled = !stopped + ), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + val user = u.user + Box { + ProfileImage(size = 55.dp, image = user.profile.image, color = MaterialTheme.colors.secondaryVariant) + + if (u.unreadCount > 0 && !user.activeUser) { + unreadBadge(u.unreadCount, user.showNtfs, true) + } + } + + Text( + user.displayName, + fontSize = 12.sp, + fontWeight = if (user.activeUser) FontWeight.Bold else FontWeight.Normal, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.width(65.dp), + textAlign = TextAlign.Center + ) } } } @@ -57,12 +80,6 @@ actual fun UserPickerInactiveUsersSection( } } } - - UserPickerOptionRow( - painterResource(MR.images.ic_manage_accounts), - stringResource(MR.strings.your_chat_profiles), - onShowAllProfilesClicked - ) } @Composable @@ -83,4 +100,4 @@ actual fun PlatformUserPicker(modifier: Modifier, pickerState: MutableStateFlow< } } } -} \ No newline at end of file +} diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index f07eea9ec7..4507f940cb 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -27,7 +27,7 @@ kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 android.version_name=6.1-beta.1 -android.version_code=240 +android.version_code=242 desktop.version_name=6.1-beta.1 desktop.version_code=67