From 706d6bf65b8affea286d5727a34f0500647edc21 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Wed, 18 Oct 2023 11:23:35 +0100 Subject: [PATCH] core, ui: prevent old sent items re-added to chat, and "new" status overriding "sent" (#3246) * core, ui: prevent old sent items re-added to chat, and "new" status overriding "sent" * clear item statuses when changing current chat * remove iOS hack * remote state/published from chatItemStatuses --- apps/ios/Shared/Model/ChatModel.swift | 18 ++++++++++-------- apps/ios/Shared/Model/SimpleXAPI.swift | 8 +++----- apps/ios/Shared/Views/Chat/ChatView.swift | 1 + .../chat/simplex/common/model/ChatModel.kt | 14 ++++++++++++-- .../chat/simplex/common/model/SimpleXAPI.kt | 7 ++----- .../views/chat/group/GroupMemberInfoView.kt | 1 + .../views/chatlist/ChatListNavLinkView.kt | 1 + .../common/views/newchat/AddGroupView.kt | 1 + src/Simplex/Chat.hs | 8 +++----- 9 files changed, 34 insertions(+), 25 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index b1a83dd5e6..3cc52d502d 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -62,6 +62,7 @@ final class ChatModel: ObservableObject { // current chat @Published var chatId: String? @Published var reversedChatItems: [ChatItem] = [] + var chatItemStatuses: Dictionary = [:] @Published var chatToTop: String? @Published var groupMembers: [GroupMember] = [] // items in the terminal view @@ -306,7 +307,11 @@ final class ChatModel: ObservableObject { return false } else { withAnimation(itemAnimation()) { - reversedChatItems.insert(cItem, at: hasLiveDummy ? 1 : 0) + var ci = cItem + if let status = chatItemStatuses.removeValue(forKey: ci.id), case .sndNew = ci.meta.itemStatus { + ci.meta.itemStatus = status + } + reversedChatItems.insert(ci, at: hasLiveDummy ? 1 : 0) } return true } @@ -319,23 +324,19 @@ final class ChatModel: ObservableObject { } } - func updateChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) { + func updateChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem, status: CIStatus? = nil) { if chatId == cInfo.id, let i = getChatItemIndex(cItem) { withAnimation { _updateChatItem(at: i, with: cItem) } + } else if let status = status { + chatItemStatuses.updateValue(status, forKey: cItem.id) } } private func _updateChatItem(at i: Int, with cItem: ChatItem) { - let ci = reversedChatItems[i] reversedChatItems[i] = cItem reversedChatItems[i].viewTimestamp = .now - // on some occasions the confirmation of message being accepted by the server (tick) - // arrives earlier than the response from API, and item remains without tick - if case .sndNew = cItem.meta.itemStatus { - reversedChatItems[i].meta.itemStatus = ci.meta.itemStatus - } } private func getChatItemIndex(_ cItem: ChatItem) -> Int? { @@ -474,6 +475,7 @@ final class ChatModel: ObservableObject { } // clear current chat if chatId == cInfo.id { + chatItemStatuses = [:] reversedChatItems = [] } } diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 2e724bcd83..6fe1bef8ee 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -312,6 +312,7 @@ func loadChat(chat: Chat, search: String = "") { do { let cInfo = chat.chatInfo let m = ChatModel.shared + m.chatItemStatuses = [:] m.reversedChatItems = [] let chat = try apiGetChat(type: cInfo.chatType, id: cInfo.apiId, search: search) m.updateChatInfo(chat.chatInfo) @@ -1421,11 +1422,8 @@ func processReceivedMsg(_ res: ChatResponse) async { case let .chatItemStatusUpdated(user, aChatItem): let cInfo = aChatItem.chatInfo let cItem = aChatItem.chatItem - if !cItem.isDeletedContent { - let added = active(user) ? await MainActor.run { m.upsertChatItem(cInfo, cItem) } : true - if added && cItem.showNotification { - NtfManager.shared.notifyMessageReceived(user, cInfo, cItem) - } + if !cItem.isDeletedContent && active(user) { + await MainActor.run { m.updateChatItem(cInfo, cItem, status: cItem.meta.itemStatus) } } if let endTask = m.messageDelivery[cItem.id] { switch cItem.meta.itemStatus { diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 81473709cb..21af0ebe17 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -91,6 +91,7 @@ struct ChatView: View { chatModel.chatId = nil DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { if chatModel.chatId == nil { + chatModel.chatItemStatuses = [:] chatModel.reversedChatItems = [] } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index f3655d0008..132384219e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -53,6 +53,7 @@ object ChatModel { // current chat val chatId = mutableStateOf(null) val chatItems = mutableStateListOf() + val chatItemStatuses = mutableMapOf() val groupMembers = mutableStateListOf() val terminalItems = mutableStateListOf() @@ -272,7 +273,13 @@ object ChatModel { Log.d(TAG, "TODOCHAT: upsertChatItem: updated in chat $chatId from ${cInfo.id} ${cItem.id}, size ${chatItems.size}") false } else { - chatItems.add(cItem) + val status = chatItemStatuses.remove(cItem.id) + val ci = if (status != null && cItem.meta.itemStatus is CIStatus.SndNew) { + cItem.copy(meta = cItem.meta.copy(itemStatus = status)) + } else { + cItem + } + chatItems.add(ci) Log.d(TAG, "TODOCHAT: upsertChatItem: added to chat $chatId from ${cInfo.id} ${cItem.id}, size ${chatItems.size}") true } @@ -282,13 +289,15 @@ object ChatModel { } } - suspend fun updateChatItem(cInfo: ChatInfo, cItem: ChatItem) { + suspend fun updateChatItem(cInfo: ChatInfo, cItem: ChatItem, status: CIStatus? = null) { withContext(Dispatchers.Main) { if (chatId.value == cInfo.id) { val itemIndex = chatItems.indexOfFirst { it.id == cItem.id } if (itemIndex >= 0) { chatItems[itemIndex] = cItem } + } else if (status != null) { + chatItemStatuses[cItem.id] = status } } } @@ -326,6 +335,7 @@ object ChatModel { } // clear current chat if (chatId.value == cInfo.id) { + chatItemStatuses.clear() chatItems.clear() } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index cc8d481b59..6bd269f24d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -1489,11 +1489,8 @@ object ChatController { is CR.ChatItemStatusUpdated -> { val cInfo = r.chatItem.chatInfo val cItem = r.chatItem.chatItem - if (!cItem.isDeletedContent) { - val added = if (active(r.user)) chatModel.upsertChatItem(cInfo, cItem) else true - if (added && cItem.showNotification) { - ntfManager.notifyMessageReceived(r.user, cInfo, cItem) - } + if (!cItem.isDeletedContent && active(r.user)) { + chatModel.updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus) } } is CR.ChatItemUpdated -> diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt index e14089ec52..c76fb73a6b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt @@ -73,6 +73,7 @@ fun GroupMemberInfoView( chatModel.addChat(c) } chatModel.chatItems.clear() + chatModel.chatItemStatuses.clear() chatModel.chatItems.addAll(c.chatItems) chatModel.chatId.value = c.id closeAll() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt index 566c981811..b45543df4e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt @@ -141,6 +141,7 @@ suspend fun openChat(chatInfo: ChatInfo, chatModel: ChatModel) { suspend fun openChat(chat: Chat, chatModel: ChatModel) { chatModel.chatItems.clear() + chatModel.chatItemStatuses.clear() chatModel.chatItems.addAll(chat.chatItems) chatModel.chatId.value = chat.chatInfo.id } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt index 6ad919c27d..be446f6087 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt @@ -41,6 +41,7 @@ fun AddGroupView(chatModel: ChatModel, close: () -> Unit) { if (groupInfo != null) { chatModel.addChat(Chat(chatInfo = ChatInfo.Group(groupInfo), chatItems = listOf())) chatModel.chatItems.clear() + chatModel.chatItemStatuses.clear() chatModel.chatId.value = groupInfo.id setGroupMembers(groupInfo, chatModel) close.invoke() diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 82499baee6..7e2c2b71e7 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -584,11 +584,11 @@ processChatCommand = \case timed_ <- sndContactCITimed live ct itemTTL (msgContainer, quotedItem_) <- prepareMsg fInv_ timed_ (msg@SndMessage {sharedMsgId}, _) <- sendDirectContactMessage ct (XMsgNew msgContainer) + ci <- saveSndChatItem' user (CDDirectSnd ct) msg (CISndMsgContent mc) ciFile_ quotedItem_ timed_ live case ft_ of Just ft@FileTransferMeta {fileInline = Just IFMSent} -> sendDirectFileInline ct ft sharedMsgId _ -> pure () - ci <- saveSndChatItem' user (CDDirectSnd ct) msg (CISndMsgContent mc) ciFile_ quotedItem_ timed_ live forM_ (timed_ >>= timedDeleteAt') $ startProximateTimedItemThread user (ChatRef CTDirect contactId, chatItemId' ci) pure $ CRNewChatItem user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) @@ -649,11 +649,11 @@ processChatCommand = \case timed_ <- sndGroupCITimed live gInfo itemTTL (msgContainer, quotedItem_) <- prepareMsg fInv_ timed_ membership (msg@SndMessage {sharedMsgId}, sentToMembers) <- sendGroupMessage user gInfo ms (XMsgNew msgContainer) - mapM_ (sendGroupFileInline ms sharedMsgId) ft_ ci <- saveSndChatItem' user (CDGroupSnd gInfo) msg (CISndMsgContent mc) ciFile_ quotedItem_ timed_ live withStore' $ \db -> forM_ sentToMembers $ \GroupMember {groupMemberId} -> createGroupSndStatus db (chatItemId' ci) groupMemberId CISSndNew + mapM_ (sendGroupFileInline ms sharedMsgId) ft_ forM_ (timed_ >>= timedDeleteAt') $ startProximateTimedItemThread user (ChatRef CTGroup groupId, chatItemId' ci) pure $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci) @@ -5208,9 +5208,7 @@ deliverMessage conn@Connection {connId} cmEventTag msgBody msgId = do let msgFlags = MsgFlags {notification = hasNotification cmEventTag} agentMsgId <- withAgent $ \a -> sendMessage a (aConnId conn) msgFlags msgBody let sndMsgDelivery = SndMsgDelivery {connId, agentMsgId} - withStoreCtx' - (Just $ "createSndMsgDelivery, sndMsgDelivery: " <> show sndMsgDelivery <> ", msgId: " <> show msgId <> ", cmEventTag: " <> show cmEventTag <> ", msgDeliveryStatus: MDSSndAgent") - $ \db -> createSndMsgDelivery db sndMsgDelivery msgId + withStore' $ \db -> createSndMsgDelivery db sndMsgDelivery msgId sendGroupMessage :: (MsgEncodingI e, ChatMonad m) => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> m (SndMessage, [GroupMember]) sendGroupMessage user GroupInfo {groupId} members chatMsgEvent =