From 915bbed4004ab10b4d9bc9293d0d07d04b27ef51 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 13 Aug 2024 18:11:04 +0000 Subject: [PATCH] android, desktop: fix chatView state propagation (#4676) --- .../simplex/common/views/chat/ChatView.kt | 81 +++++++------------ 1 file changed, 31 insertions(+), 50 deletions(-) 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 b6180c71e6..3a062cb757 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 @@ -10,7 +10,6 @@ import androidx.compose.foundation.lazy.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.mapSaver import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.* import androidx.compose.ui.draw.drawWithCache @@ -49,26 +48,17 @@ import kotlin.math.sign // staleChatId means the id that was before chatModel.chatId becomes null. It's needed for Android only to make transition from chat // to chat list smooth. Otherwise, chat view will become blank right before the transition starts fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) -> Unit) { - val shouldReturn = remember { mutableStateOf(false) } val remoteHostId = remember { derivedStateOf { chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.remoteHostId } } val showSearch = rememberSaveable { mutableStateOf(false) } - val activeChatInfo = remember { - derivedStateOf { - val info = chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.chatInfo - if (info == null) { - shouldReturn.value = true - } - return@derivedStateOf info ?: ChatInfo.Direct.sampleData - } - } + val activeChatInfo = remember { derivedStateOf { chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.chatInfo } } val user = chatModel.currentUser.value - if (shouldReturn.value || user == null) { + val chatInfo = activeChatInfo.value + if (chatInfo == null || user == null) { LaunchedEffect(Unit) { chatModel.chatId.value = null ModalManager.end.closeModals() } } else { - val chatInfo = activeChatInfo.value val searchText = rememberSaveable { mutableStateOf("") } val useLinkPreviews = chatModel.controller.appPrefs.privacyLinkPreviews.get() val composeState = rememberSaveable(saver = ComposeState.saver()) { @@ -268,8 +258,8 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - } }, loadPrevMessages = { - if (chatModel.chatId.value != activeChatInfo.value.id) return@ChatLayout - val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout) + val c = chatModel.getChat(chatInfo.id) + if (chatModel.chatId.value != chatInfo.id) return@ChatLayout val firstId = chatModel.chatItems.value.firstOrNull()?.id if (c != null && firstId != null) { withBGApi { @@ -279,7 +269,6 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - }, deleteMessage = { itemId, mode -> withBGApi { - val cInfo = chatInfo val toDeleteItem = chatModel.chatItems.value.firstOrNull { it.id == itemId } val toModerate = toDeleteItem?.memberToModerate(chatInfo) val groupInfo = toModerate?.first @@ -295,8 +284,8 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - } else { chatModel.controller.apiDeleteChatItems( chatRh, - type = cInfo.chatType, - id = cInfo.apiId, + type = chatInfo.chatType, + id = chatInfo.apiId, itemIds = listOf(itemId), mode = mode ) @@ -307,9 +296,9 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - toChatItem = deleted.toChatItem?.chatItem withChats { if (toChatItem != null) { - upsertChatItem(chatRh, cInfo, toChatItem) + upsertChatItem(chatRh, chatInfo, toChatItem) } else { - removeChatItem(chatRh, cInfo, deletedChatItem) + removeChatItem(chatRh, chatInfo, deletedChatItem) } } } @@ -489,8 +478,8 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - changeNtfsState = { enabled, currentValue -> toggleNotifications(chatRh, chatInfo, enabled, chatModel, currentValue) }, onSearchValueChanged = { value -> if (searchText.value == value) return@ChatLayout - if (chatModel.chatId.value != activeChatInfo.value.id) return@ChatLayout - val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout) ?: return@ChatLayout + val c = chatModel.getChat(chatInfo.id) ?: return@ChatLayout + if (chatModel.chatId.value != chatInfo.id) return@ChatLayout withBGApi { apiFindMessages(c, chatModel, value) searchText.value = value @@ -559,7 +548,7 @@ fun startChatCall(remoteHostId: Long?, chatInfo: ChatInfo, media: CallMediaType) @Composable fun ChatLayout( remoteHostId: State, - chatInfo: State, + chatInfo: State, unreadCount: State, composeState: MutableState, composeView: (@Composable () -> Unit), @@ -609,7 +598,7 @@ fun ChatLayout( Modifier .fillMaxWidth() .desktopOnExternalDrag( - enabled = !attachmentDisabled.value && rememberUpdatedState(chatInfo.value).value.userCanSend, + enabled = !attachmentDisabled.value && rememberUpdatedState(chatInfo.value).value?.userCanSend == true, onFiles = { paths -> composeState.onFilesAttached(paths.map { it.toURI() }) }, onImage = { // TODO: file is not saved anywhere?! @@ -647,7 +636,10 @@ fun ChatLayout( Scaffold( topBar = { if (selectedChatItems.value == null) { - ChatInfoToolbar(chatInfo, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch) + val chatInfo = chatInfo.value + if (chatInfo != null) { + ChatInfoToolbar(chatInfo, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch) + } } else { SelectedItemsTopToolbar(selectedChatItems) } @@ -672,13 +664,17 @@ fun ChatLayout( Modifier) .padding(contentPadding) ) { - ChatItemsList( - remoteHostId, chatInfo, unreadCount, composeState, searchValue, - useLinkPreviews, linkMode, selectedChatItems, showMemberInfo, loadPrevMessages, deleteMessage, deleteMessages, - receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat, forwardItem, - updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember, - setReaction, showItemDetails, markRead, setFloatingButton, onComposed, developerTools, showViaProxy, - ) + val remoteHostId = remember { remoteHostId }.value + val chatInfo = remember { chatInfo }.value + if (chatInfo != null) { + ChatItemsList( + remoteHostId, chatInfo, unreadCount, composeState, searchValue, + useLinkPreviews, linkMode, selectedChatItems, showMemberInfo, loadPrevMessages, deleteMessage, deleteMessages, + receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat, forwardItem, + updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember, + setReaction, showItemDetails, markRead, setFloatingButton, onComposed, developerTools, showViaProxy, + ) + } } } } @@ -688,7 +684,7 @@ fun ChatLayout( @Composable fun ChatInfoToolbar( - chatInfo: State, + chatInfo: ChatInfo, back: () -> Unit, info: () -> Unit, startCall: (CallMediaType) -> Unit, @@ -713,7 +709,6 @@ fun ChatInfoToolbar( if (appPlatform.isAndroid) { BackHandler(onBack = onBackClicked) } - val chatInfo = chatInfo.value val barButtons = arrayListOf<@Composable RowScope.() -> Unit>() val menuItems = arrayListOf<@Composable () -> Unit>() val activeCall by remember { chatModel.activeCall } @@ -918,22 +913,10 @@ private fun ContactVerifiedShield() { 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) - -val CIListStateSaver = run { - val scrolledKey = "scrolled" - val countKey = "itemCount" - val keyboardKey = "keyboardState" - mapSaver( - save = { mapOf(scrolledKey to it.scrolled, countKey to it.itemCount, keyboardKey to it.keyboardState) }, - restore = { CIListState(it[scrolledKey] as Boolean, it[countKey] as Int, it[keyboardKey] as KeyboardState) } - ) -} - @Composable fun BoxWithConstraintsScope.ChatItemsList( - remoteHostId: State, - chatInfo: State, + remoteHostId: Long?, + chatInfo: ChatInfo, unreadCount: State, composeState: MutableState, searchValue: State, @@ -967,8 +950,6 @@ fun BoxWithConstraintsScope.ChatItemsList( ) { val listState = rememberLazyListState() val scope = rememberCoroutineScope() - val remoteHostId = remember { remoteHostId }.value - val chatInfo = remember { chatInfo }.value ScrollToBottom(chatInfo.id, listState, chatModel.chatItems) var prevSearchEmptiness by rememberSaveable { mutableStateOf(searchValue.value.isEmpty()) } // Scroll to bottom when search value changes from something to nothing and back