mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-08-04 15:20:19 +00:00
android, desktop: fix chatView state propagation (#4676)
This commit is contained in:
+31
-50
@@ -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<String?>, 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<String?>, 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<String?>, 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<String?>, 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<String?>, 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<String?>, 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<Long?>,
|
||||
chatInfo: State<ChatInfo>,
|
||||
chatInfo: State<ChatInfo?>,
|
||||
unreadCount: State<Int>,
|
||||
composeState: MutableState<ComposeState>,
|
||||
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: 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<Long?>,
|
||||
chatInfo: State<ChatInfo>,
|
||||
remoteHostId: Long?,
|
||||
chatInfo: ChatInfo,
|
||||
unreadCount: State<Int>,
|
||||
composeState: MutableState<ComposeState>,
|
||||
searchValue: State<String>,
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user