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 c63cceff34..36b7899c97 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 @@ -35,7 +35,7 @@ fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stoppe topBar = { if (!oneHandUI.value) { Column { - ShareListToolbar(chatModel, userPickerState, stopped) { searchInList = it.trim() } + ShareListToolbar(chatModel, stopped) { searchInList = it.trim() } Divider() } } @@ -44,7 +44,7 @@ fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stoppe if (oneHandUI.value) { Column { Divider() - ShareListToolbar(chatModel, userPickerState, stopped) { searchInList = it.trim() } + ShareListToolbar(chatModel, stopped) { searchInList = it.trim() } } } } @@ -92,16 +92,6 @@ fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stoppe } } } - if (appPlatform.isAndroid) { - tryOrShowError("UserPicker", error = {}) { - UserPicker( - chatModel, - userPickerState, - contentAlignment = if (oneHandUI.value) Alignment.BottomStart else Alignment.TopStart, - drawerState = scaffoldState.drawerState, - ) - } - } } private fun hasSimplexLink(msg: String): Boolean { @@ -117,7 +107,7 @@ private fun EmptyList() { } @Composable -private fun ShareListToolbar(chatModel: ChatModel, userPickerState: MutableStateFlow, stopped: Boolean, onSearchValueChanged: (String) -> Unit) { +private fun ShareListToolbar(chatModel: ChatModel, stopped: Boolean, onSearchValueChanged: (String) -> Unit) { var showSearch by rememberSaveable { mutableStateOf(false) } val hideSearchOnBack = { onSearchValueChanged(""); showSearch = false } if (showSearch) { @@ -133,7 +123,24 @@ private fun ShareListToolbar(chatModel: ChatModel, userPickerState: MutableState .filter { u -> !u.user.activeUser && !u.user.hidden } .all { u -> u.unreadCount == 0 } UserProfileButton(chatModel.currentUser.value?.profile?.image, allRead) { - userPickerState.value = AnimatedViewState.VISIBLE + if (appPlatform.isAndroid) { + ModalManager.start.showCustomModal { close -> + val search = rememberSaveable { mutableStateOf("") } + ModalView( + { close() }, + endButtons = { + SearchTextField(Modifier.fillMaxWidth(), placeholder = stringResource(MR.strings.search_verb), alwaysVisible = true) { search.value = it } + }, + content = { + ShareListUserPicker( + chatModel = chatModel, + search = search, + close = close, + ) + } + ) + } + } } } else -> NavigationButtonBack(onButtonClicked = { 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 e77da4501c..23253b4105 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 @@ -8,11 +8,13 @@ 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.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.* +import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer @@ -32,11 +34,11 @@ import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.platform.* import chat.simplex.common.views.CreateProfile +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 import chat.simplex.res.MR -import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.* import kotlinx.coroutines.flow.* @@ -51,6 +53,84 @@ private fun UserPickerOptionRow(icon: Painter, text: String, click: (() -> Unit) } } +@Composable +fun ShareListUserPicker( + chatModel: ChatModel, + search: MutableState, + close: () -> Unit, +) { + val switchingProfile = remember { mutableStateOf(false) } + val searchTextOrPassword = rememberSaveable { search } + val profiles = remember { + chatModel.users.map { it.user }.sortedBy { !it.activeUser } + } + val filteredProfiles by remember { + derivedStateOf { filteredProfiles(profiles, searchTextOrPassword.value) } + } + + var progressByTimeout by rememberSaveable { mutableStateOf(false) } + + LaunchedEffect(switchingProfile.value) { + progressByTimeout = if (switchingProfile.value) { + delay(500) + switchingProfile.value + } else { + false + } + } + + BoxWithConstraints { + Column( + Modifier + .fillMaxSize() + .alpha(if (progressByTimeout) 0.6f else 1f) + ) { + LazyColumnWithScrollBar(userScrollEnabled = !switchingProfile.value) { + item { + AppBarTitle(stringResource(MR.strings.select_chat_profile), hostDevice(chatModel.remoteHostId), bottomPadding = DEFAULT_PADDING) + } + + itemsIndexed(filteredProfiles) { _, p -> + ProfilePickerOption( + title = p.chatViewName, + disabled = switchingProfile.value || p.activeUser, + image = { ProfileImage(size = 42.dp, image = p.image) }, + selected = p.activeUser, + onSelected = { + switchingProfile.value = true + withApi { + try { + controller.changeActiveUser( + rhId = p.remoteHostId, + toUserId = p.userId, + viewPwd = if (p.hidden) searchTextOrPassword.value else null + ) + + if (chatModel.currentUser.value?.userId != p.userId) { + AlertManager.shared.showAlertMsg( + generalGetString( + MR.strings.switching_profile_error_title + ), + String.format(generalGetString(MR.strings.switching_profile_error_message), p.chatViewName) + ) + } + + close.invoke() + } finally { + switchingProfile.value = false + } + } + } + ) + } + } + } + if (progressByTimeout) { + DefaultProgressView("") + } + } +} + @Composable private fun UsersLayout( chatModel: ChatModel, 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 544f5f72bb..2eafb00464 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 @@ -208,7 +208,7 @@ private fun RetryButton(onClick: () -> Unit) { } @Composable -private fun ProfilePickerOption( +fun ProfilePickerOption( title: String, selected: Boolean, disabled: Boolean, @@ -267,7 +267,7 @@ private fun ProfilePickerOption( ) } -private fun filteredProfiles(users: List, searchTextOrPassword: String): List { +fun filteredProfiles(users: List, searchTextOrPassword: String): List { val s = searchTextOrPassword.trim() val lower = s.lowercase() return users.filter { u ->