mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-09-30 10:59:43 +00:00
ui: keep support list load running when a member chat is opened, avoid repeated loads
This commit is contained in:
@@ -1333,13 +1333,17 @@ final class ChatModel: ObservableObject {
|
||||
func upsertSupportChatMember(_ cInfo: ChatInfo) {
|
||||
if case let .group(groupInfo, .memberSupport(member?)?) = cInfo, chatId == groupInfo.id {
|
||||
var m = member
|
||||
var supportChatAdded = false
|
||||
if let current = getGroupMember(member.groupMemberId)?.wrapped {
|
||||
supportChatAdded = current.supportChat == nil && member.supportChat != nil
|
||||
m = current
|
||||
m.supportChat = member.supportChat
|
||||
m.memberProfile = member.memberProfile
|
||||
}
|
||||
_ = upsertGroupMember(groupInfo, m)
|
||||
objectWillChange.send()
|
||||
if supportChatAdded {
|
||||
objectWillChange.send()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,26 +13,25 @@ struct MemberSupportView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@State private var searchText: String = ""
|
||||
@State private var membersLoading = false
|
||||
@FocusState private var searchFocussed
|
||||
var groupInfo: GroupInfo
|
||||
@Binding var scrollToItemId: ChatItem.ID?
|
||||
|
||||
var body: some View {
|
||||
viewBody()
|
||||
.onAppear {
|
||||
if !chatModel.membersLoaded || chatModel.membersLoadedGroupId != groupInfo.groupId {
|
||||
Task {
|
||||
await chatModel.loadGroupMembers(groupInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: chatModel.membersLoaded) { loaded in
|
||||
if !loaded && chatModel.chatId == groupInfo.id {
|
||||
Task {
|
||||
await chatModel.loadGroupMembers(groupInfo)
|
||||
}
|
||||
}
|
||||
.onAppear { loadMembersIfNeeded() }
|
||||
.onChange(of: chatModel.membersLoaded) { _ in loadMembersIfNeeded() }
|
||||
}
|
||||
|
||||
private func loadMembersIfNeeded() {
|
||||
if (!chatModel.membersLoaded || chatModel.membersLoadedGroupId != groupInfo.groupId) && chatModel.chatId == groupInfo.id && !membersLoading {
|
||||
membersLoading = true
|
||||
Task {
|
||||
await chatModel.loadGroupMembers(groupInfo)
|
||||
await MainActor.run { membersLoading = false }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func viewBody() -> some View {
|
||||
|
||||
+10
-2
@@ -41,9 +41,17 @@ fun ModalData.MemberSupportView(
|
||||
KeyChangeEffect(chatModel.chatId.value) {
|
||||
ModalManager.end.closeModals()
|
||||
}
|
||||
val membersLoading = remember { stateGetOrPut("membersLoading") { false } }
|
||||
LaunchedEffect(chatModel.membersLoaded.value) {
|
||||
if (!chatModel.membersLoaded.value && chatModel.chatId.value == groupInfo.id) {
|
||||
setGroupMembers(rhId, groupInfo, chatModel)
|
||||
if (!chatModel.membersLoaded.value && chatModel.chatId.value == groupInfo.id && !membersLoading.value) {
|
||||
membersLoading.value = true
|
||||
withBGApi {
|
||||
try {
|
||||
setGroupMembers(rhId, groupInfo, chatModel)
|
||||
} finally {
|
||||
membersLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ModalView(close = close) {
|
||||
|
||||
@@ -48,14 +48,13 @@ Load the list once per open group, then keep it current from what arrives.
|
||||
- the mark-read response;
|
||||
- the initial load of a support chat.
|
||||
- `JoinedGroupMember` and `JoinedGroupMemberConnecting` are emitted right after the "new member pending review" item, and they carry the member with zero support stats. Their handlers keep the support stats already in the list, so they do not erase the badge the item event just set. On Kotlin that item event can also be applied after them; the merge covers either order.
|
||||
- The member list loads only if `membersLoaded` is false. On both platforms the list also reloads when `membersLoaded` is reset while it is open and its group is still the open chat. Examples are `ChatView` recomposing after an Android configuration change, and the iOS reset on resume. The mention picker already uses this flag the same way, and it is reset when leaving the group.
|
||||
- The member list loads only if `membersLoaded` is false. The load runs outside the list screen's lifetime (`withBGApi` on Kotlin, a `Task` on iOS) and is tracked by an in-progress flag in the list's state. So opening a member's chat before the first load finishes neither cancels it nor causes another full load on return. On both platforms the list also reloads when `membersLoaded` is reset while it is open and its group is still the open chat. Examples are `ChatView` recomposing after an Android configuration change, and the iOS reset on resume. The mention picker already uses this flag the same way, and it is reset when leaving the group.
|
||||
- `apiListMembers` returns `null`/`nil` on error on both platforms, and the member load then keeps the current state, so a failed load does not mark members as loaded. iOS still runs the load's completion, so group info still opens.
|
||||
- iOS clears the loaded members and resets `membersLoaded` when the open chat changes without going through the chat list (notification tap, "forwarded from", member info), as Kotlin already does. Because group info can also be opened from a message avatar without reloading members, the iOS list additionally reloads unless members were loaded for this group (`membersLoadedGroupId`).
|
||||
- iOS resets `membersLoaded` when chats are refreshed on resume, because the notification extension may have changed support chats while the app was suspended.
|
||||
- Kotlin `upsertGroupMember` also resets `membersLoaded` when it clears another group's stale members.
|
||||
- Kotlin `setGroupMembers` now writes on the main thread, where all upserts run, so an upsert can no longer land between clearing the index and rebuilding it and add a duplicate. It writes its result only if the group is still the open chat (or the channel being created), as iOS `loadGroupMembers` already does. Without this check, a slow load from a previously opened channel could finish after a chat switch and mark another group's members as loaded. The old reload on every return hid that.
|
||||
- The refresh button is removed. The list is kept current by the updates above.
|
||||
- iOS also sends `objectWillChange` (only for the open group), because updating a `GMember` in place does not re-render or re-sort the list.
|
||||
|
||||
A member's first support message arrives as a `NewChatItems` event with that member, so a new support chat appears in the list without a reload.
|
||||
|
||||
@@ -64,8 +63,9 @@ A member's first support message arrives as a `NewChatItems` event with that mem
|
||||
- A failed member load (`apiListMembers` error) is not retried while the list stays open; it is retried when the list is reopened.
|
||||
- A full member load that is in flight when a support-chat update arrives overwrites that update with its snapshot. For example, the first list load can race a member's first support message. The member then reappears on their next message, when their chat is opened, or when the group is reopened.
|
||||
- Support stats snapshots from different events and responses are applied in arrival order, so a rare reordering can briefly show an older count until the next update for that member.
|
||||
- On iOS, handlers that update an existing member in place without publishing a change, such as "Mark read" from the context menu or accept, update the row but not the list order or filter until the next `ChatModel` change. The existing TODO in `deleteMemberSupportChat` describes the same mechanism. Previously, returning to the list also re-sorted it.
|
||||
- On iOS, updating an existing member in place (including the new support-stats upserts) updates that row's badges, because rows observe their `GMember`, but not the list order or filter until the next `ChatModel` change. A re-render is forced only when an existing member gets their first support chat, so that the member appears in the list. It is not forced on every stats change, because re-sorting the list while a member's support chat is pushed from it (`NavigationLink(isActive:)`) risks popping that chat on older iOS versions. The existing TODO in `deleteMemberSupportChat` describes the same mechanism. Previously, returning to the list also re-sorted it.
|
||||
- The connection-state labels in rows (failed, disabled, inactive) come from `activeConn`. Neither app handles `ConnectionDisabled` or `ConnectionInactive`, so these labels now refresh only on the next full member event for that member (role, profile, connected) or when the group is reopened.
|
||||
- On iOS, a reset on resume while a member's support chat is open over the list runs one full member load in the background (the list stays in the navigation stack). An open started during that load waits for it once.
|
||||
|
||||
## Not addressed
|
||||
|
||||
|
||||
Reference in New Issue
Block a user