android, desktop: scrolling user profiles (#4939)

* android, desktop: scrolling user profiles

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
This commit is contained in:
Diogo
2024-09-26 09:00:10 +01:00
committed by GitHub
parent 9199fbffd5
commit 67472b6285
4 changed files with 285 additions and 250 deletions

View File

@@ -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<UserInfo>,
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)
}
}

View File

@@ -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<AnimatedViewState>,
) {
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<AnimatedViewState>,
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<UserInfo>,
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<AnimatedViewState>) {
delay(500)
userPickerState.value = AnimatedViewState.HIDING

View File

@@ -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<UserInfo>,
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<
}
}
}
}
}

View File

@@ -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