mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-23 00:02:28 +00:00
android, desktop, ios: show pending invitee's support messages in chat list preview
A member pending review has no main scope to write in, so their "chat with admins" messages are what the group's row in the chat list should show. New support items reach the row since #5909 and #7072; this covers edits, status changes and deletions of those items, the invitee's own edits, and which item the row shows. Chat list: - upsertChatItem and removeChatItem use ChatInfo.inMainChatList (added on iOS here, as in Kotlin) instead of the main-scope test, so edits, status changes and deletions of a support item reach the row that shows it; only a main scope chat can still be added to the list. On iOS, an item not loaded in the open chat replaces the preview only for main-scope items. - previewItem, extracted from addChatItem on both platforms, picks the row's item: for a pending invitee the onboarding events (pending review, "end-to-end encrypted" info, member connected) do not replace a message, and a message replaces them, whatever their timestamps; otherwise the later item wins. Sent edits reach only the active chats context, so ChatModel.upsertSentChatItem updates the primary context as well when the active context is a support chat shown in the main list, as addSentChatItem does for new items. Cross context dispatch stays with the caller, as in processReceivedMsg; no ChatsContext method touches the other context. The chat preview row shows the message instead of the "reviewed by admins" status when there is text or, with previews enabled, content that is rendered without text (a media item without a caption).
This commit is contained in:
1 parent
c571762b00
commit
6525367493
6 files changed
+63
-41
No files matched your search
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+28
-19
@@ -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<Chat>())
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+4
-1
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user