diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 1ee79e1c4f..51c934abbc 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -714,21 +714,8 @@ final class ChatModel: ObservableObject { // update chat list if let i = getChatIndex(cInfo.id) { // update preview - if cInfo.groupChatScope() == nil || cInfo.groupInfo?.membership.memberPending ?? false { - chats[i].chatItems = switch cInfo { - case .group: - if let currentPreviewItem = chats[i].chatItems.first { - if cItem.meta.itemTs >= currentPreviewItem.meta.itemTs { - [cItem] - } else { - [currentPreviewItem] - } - } else { - [cItem] - } - default: - [cItem] - } + if cInfo.inMainChatList { + chats[i].chatItems = [previewItem(cInfo, chats[i].chatItems.first, cItem)] if case .rcvNew = cItem.meta.itemStatus { unreadCollector.changeUnreadCounter(cInfo.id, by: 1, unreadMentions: cItem.meta.userMention ? 1 : 0) } @@ -748,6 +735,22 @@ final class ChatModel: ObservableObject { } } + private func previewItem(_ cInfo: ChatInfo, _ currentItem: ChatItem?, _ newItem: ChatItem) -> ChatItem { + guard case let .group(groupInfo, _) = cInfo, let currentItem else { return newItem } + if groupInfo.membership.memberPending { + if isOnboardingEvent(newItem) && currentItem.content.msgContent != nil { return currentItem } + if isOnboardingEvent(currentItem) && newItem.content.msgContent != nil { return newItem } + } + return newItem.meta.itemTs < currentItem.meta.itemTs ? currentItem : newItem + } + + private func isOnboardingEvent(_ item: ChatItem) -> Bool { + switch item.content { + case .sndGroupEvent(.userPendingReview), .rcvGroupEvent(.memberConnected), .rcvGroupE2EEInfo: true + default: false + } + } + func getCIItemsModel(_ cInfo: ChatInfo, _ ci: ChatItem) -> ItemsModel? { let cInfoScope = cInfo.groupChatScope() return if let cInfoScope = cInfoScope { @@ -774,16 +777,16 @@ final class ChatModel: ObservableObject { func upsertChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) -> Bool { // update chat list var itemAdded: Bool = false - if cInfo.groupChatScope() == nil { + if cInfo.inMainChatList { if let chat = getChat(cInfo.id) { if let pItem = chat.chatItems.last { - if pItem.id == cItem.id || (chatId == cInfo.id && im.reversedChatItems.first(where: { $0.id == cItem.id }) == nil) { + if pItem.id == cItem.id || (cInfo.groupChatScope() == nil && chatId == cInfo.id && im.reversedChatItems.first(where: { $0.id == cItem.id }) == nil) { chat.chatItems = [cItem] } } else { chat.chatItems = [cItem] } - } else { + } else if cInfo.groupChatScope() == nil { addChat(Chat(chatInfo: cInfo, chatItems: [cItem])) itemAdded = true } @@ -845,7 +848,7 @@ final class ChatModel: ObservableObject { func removeChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) { // update chat list - if cInfo.groupChatScope() == nil { + if cInfo.inMainChatList { if cItem.isRcvNew { unreadCollector.changeUnreadCounter(cInfo.id, by: -1, unreadMentions: cItem.meta.userMention ? -1 : 0) } diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift index 8156ea81c7..c5d895c20d 100644 --- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift @@ -349,10 +349,13 @@ struct ChatPreviewView: View { } @ViewBuilder private func chatMessagePreview(_ cItem: ChatItem?, _ hasFilePreview: Bool = false) -> some View { + let memberPending = chat.chatInfo.groupInfo?.membership.memberPending ?? false + let itemHasText = cItem?.content.hasMsgContent == true + let itemContentShown = showChatPreviews && memberPending && cItem?.content.msgContent != nil if chatModel.draftChatId == chat.id, let draft = chatModel.draft { let (t, hasSecrets) = messageDraft(draft) chatPreviewLayout(t, draft: true, hasFilePreview: hasFilePreview, hasSecrets: hasSecrets) - } else if cItem?.content.hasMsgContent != true, let previewText = chatPreviewInfoText() { + } else if !itemHasText, !itemContentShown, let previewText = chatPreviewInfoText() { chatPreviewInfoTextLayout(previewText) } else if let cItem = cItem { let (t, hasSecrets) = chatItemPreview(cItem) diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index a4d65a6d10..c6ab9c5dba 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2170,6 +2170,10 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { groupInfo?.useRelays == true } + public var inMainChatList: Bool { + groupChatScope() == nil || groupInfo?.membership.memberPending ?? false + } + // this works for features that are common for contacts and groups public func featureEnabled(_ feature: ChatFeature) -> Bool { switch self { 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 0df6d5e78c..5be305049f 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 @@ -421,6 +421,13 @@ object ChatModel { } } + suspend fun upsertSentChatItem(activeCtx: ChatsContext, rhId: Long?, cInfo: ChatInfo, cItem: ChatItem) { + activeCtx.upsertChatItem(rhId, cInfo, cItem) + if (activeCtx.secondaryContextFilter != null && cInfo.inMainChatList) { + chatsContext.upsertChatItem(rhId, cInfo, cItem) + } + } + // Spec: spec/state.md#ChatsContext class ChatsContext(val secondaryContextFilter: SecondaryContextFilter?) { val chats = mutableStateOf(SnapshotStateList()) @@ -594,22 +601,7 @@ object ChatModel { chat = chats[i] // update preview (for chat from main scope to show new items for invitee in pending status) if (cInfo.inMainChatList) { - val newPreviewItem = when (cInfo) { - is ChatInfo.Group -> { - val currentPreviewItem = chat.chatItems.firstOrNull() - if (currentPreviewItem != null) { - if (cItem.meta.itemTs >= currentPreviewItem.meta.itemTs) { - cItem - } else { - currentPreviewItem - } - } else { - cItem - } - } - - else -> cItem - } + val newPreviewItem = previewItem(cInfo, chat.chatItems.firstOrNull(), cItem) val wasUnread = chat.unreadTag chats[i] = chat.copy( chatItems = arrayListOf(newPreviewItem), @@ -650,6 +642,23 @@ object ChatModel { } } + private fun previewItem(cInfo: ChatInfo, currentItem: ChatItem?, newItem: ChatItem): ChatItem { + if (cInfo !is ChatInfo.Group || currentItem == null) return newItem + if (cInfo.groupInfo.membership.memberPending) { + if (isOnboardingEvent(newItem) && currentItem.content.msgContent != null) return currentItem + if (isOnboardingEvent(currentItem) && newItem.content.msgContent != null) return newItem + } + return if (newItem.meta.itemTs < currentItem.meta.itemTs) currentItem else newItem + } + + private fun isOnboardingEvent(item: ChatItem): Boolean = + when (val c = item.content) { + is CIContent.SndGroupEventContent -> c.sndGroupEvent is SndGroupEvent.UserPendingReview + is CIContent.RcvGroupEventContent -> c.rcvGroupEvent is RcvGroupEvent.MemberConnected + is CIContent.RcvGroupE2EEInfo -> true + else -> false + } + private fun chatItemBelongsToScope(cInfo: ChatInfo, cItem: ChatItem): Boolean = when (secondaryContextFilter) { null -> @@ -669,7 +678,7 @@ object ChatModel { suspend fun upsertChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem): Boolean { var itemAdded = false // update chat list - if (cInfo.groupChatScope() == null) { + if (cInfo.inMainChatList) { val i = getChatIndex(rhId, cInfo.id) val chat: Chat if (i >= 0) { @@ -682,7 +691,7 @@ object ChatModel { decreaseCounterInPrimaryContext(rhId, cInfo.id) } } - } else { + } else if (cInfo.groupChatScope() == null) { addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = arrayListOf(cItem))) itemAdded = true } @@ -727,7 +736,7 @@ object ChatModel { fun removeChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem) { // update chat list - if (cInfo.groupChatScope() == null) { + if (cInfo.inMainChatList) { if (cItem.isRcvNew) { decreaseCounterInPrimaryContext(rhId, cInfo.id) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index a15fe26e30..dd5fe87f97 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -819,7 +819,7 @@ fun ComposeView( ) if (updatedItem != null) { withContext(Dispatchers.Main) { - chatsCtx.upsertChatItem(chat.remoteHostId, cInfo, updatedItem.chatItem) + chatModel.upsertSentChatItem(chatsCtx, chat.remoteHostId, cInfo, updatedItem.chatItem) } } return updatedItem?.chatItem diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt index 7e11fdfb97..13a635b444 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt @@ -226,6 +226,9 @@ fun ChatPreviewView( fun chatPreviewText() { val previewText = chatPreviewInfoText() val ci = chat.chatItems.lastOrNull() + val memberPending = cInfo.groupInfo_?.membership?.memberPending == true + val itemHasText = ci?.content?.hasMsgContent == true + val itemContentShown = showChatPreviews && memberPending && ci?.content?.msgContent != null if (chatModelDraftChatId == chat.id && chatModelDraft != null) { val sp20 = with(LocalDensity.current) { 20.sp.toDp() } val (text: CharSequence, inlineTextContent) = remember(chatModelDraft) { chatModelDraft.message.text to messageDraft(chatModelDraft, sp20) } @@ -246,7 +249,7 @@ fun ChatPreviewView( inlineContent = inlineTextContent, modifier = Modifier.fillMaxWidth() ) - } else if (ci?.content?.hasMsgContent != true && previewText != null) { + } else if (!itemHasText && !itemContentShown && previewText != null) { Text(previewText.first, color = previewText.second) } else if (ci != null && showChatPreviews) { val (text: CharSequence, inlineTextContent) = when {