core, tests, ui: keep member order with role index, skip down-migration schema comparison, reload support list on return with older remote hosts

This commit is contained in:
Narasimha-sc
2026-09-30 15:38:14 +00:00
parent 1c0a4b47a3
commit cebc33cb8a
19 changed files with 127 additions and 141 deletions
+8 -16
View File
@@ -424,7 +424,6 @@ final class ChatModel: ObservableObject {
@Published var groupMembers: [GMember] = []
@Published var groupMembersIndexes: Dictionary<Int64, Int> = [:] // groupMemberId to index in groupMembers list
@Published var membersLoaded = false
var membersLoadedGroupId: Int64?
// Runtime-only relay hostnames for pre-join channel display, not persisted — lost on app restart.
// APIConnectPreparedGroup re-fetches fresh relays at connect time, so stale data doesn't affect join.
@Published var channelRelayHostnames: [Int64: [String]] = [:]
@@ -578,12 +577,9 @@ final class ChatModel: ObservableObject {
let groupMembers = await apiListMembers(groupInfo.groupId)
await MainActor.run {
if chatId == groupInfo.id {
if let groupMembers {
self.groupMembers = groupMembers.map { GMember.init($0) }
self.populateGroupMembersIndexes()
self.membersLoaded = true
self.membersLoadedGroupId = groupInfo.groupId
}
self.groupMembers = groupMembers.map { GMember.init($0) }
self.populateGroupMembersIndexes()
self.membersLoaded = true
updateView()
}
}
@@ -1331,15 +1327,11 @@ 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
}
if case let .group(groupInfo, .memberSupport(member?)?) = cInfo {
var m = getGroupMember(member.groupMemberId)?.wrapped ?? member
let supportChatAdded = m.supportChat == nil && member.supportChat != nil
m.supportChat = member.supportChat
m.memberProfile = member.memberProfile
_ = upsertGroupMember(groupInfo, m)
if supportChatAdded {
objectWillChange.send()
+4 -6
View File
@@ -592,10 +592,8 @@ private func processSendMessageCmd(toChatType: ChatType, cmd: ChatCommand) async
} else {
r = await chatApiSendCmd(cmd, bgDelay: msgDelay)
if case let .result(.newChatItems(_, aChatItems)) = r {
await MainActor.run {
for aChatItem in aChatItems {
chatModel.upsertSupportChatMember(aChatItem.chatInfo)
}
if let last = aChatItems.last {
await MainActor.run { chatModel.upsertSupportChatMember(last.chatInfo) }
}
return aChatItems.map { $0.chatItem }
}
@@ -2062,10 +2060,10 @@ func apiLeaveGroup(_ groupId: Int64) async throws -> GroupInfo {
}
// use ChatModel's loadGroupMembers from views
func apiListMembers(_ groupId: Int64) async -> [GroupMember]? {
func apiListMembers(_ groupId: Int64) async -> [GroupMember] {
let r: APIResult<ChatResponse2> = await chatApiSendCmd(.apiListMembers(groupId: groupId))
if case let .result(.groupMembers(_, group)) = r { return group.members }
return nil
return []
}
func filterMembersToAdd(_ ms: [GMember]) -> [Contact] {
@@ -359,9 +359,6 @@ struct ChatView: View {
revealedItems = Set()
stopAudioPlayer()
if let cId {
chatModel.groupMembers = []
chatModel.groupMembersIndexes.removeAll()
chatModel.membersLoaded = false
if let c = chatModel.getChat(cId) {
chat = c
}
@@ -3083,7 +3080,6 @@ func archiveReports(_ chat: Chat, _ itemIds: [Int64], _ forAll: Bool, _ onSucces
}
if let updatedChatInfo = deleted.last?.deletedChatItem.chatInfo {
ChatModel.shared.updateChatInfo(updatedChatInfo)
ChatModel.shared.upsertSupportChatMember(updatedChatInfo)
}
}
await onSuccess()
@@ -82,7 +82,7 @@ func acceptMember(_ groupInfo: GroupInfo, _ member: GroupMember, _ role: GroupMe
do {
let (gInfo, acceptedMember) = try await apiAcceptMember(groupInfo.groupId, member.groupMemberId, role)
await MainActor.run {
_ = ChatModel.shared.upsertGroupMember(gInfo, acceptedMember)
_ = ChatModel.shared.upsertGroupMember(gInfo, ChatModel.shared.withLoadedSupportChat(acceptedMember))
ChatModel.shared.updateGroup(gInfo)
dismiss?()
}
@@ -298,7 +298,7 @@ struct GroupMemberInfoView: View {
do {
let (_, stats) = try await apiGroupMemberInfo(groupInfo.apiId, member.groupMemberId)
let getCode = (member.memberActive || (groupInfo.useRelays && member.memberCurrent)) && member.memberRole != .relay
let (mem, code) = getCode ? try await apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId) : (member, nil)
let (mem, code) = getCode ? try await apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId) : (chatModel.withLoadedSupportChat(member), nil)
await MainActor.run {
_ = chatModel.upsertGroupMember(groupInfo, mem)
connectionStats = stats
@@ -578,7 +578,7 @@ struct GroupMemberInfoView: View {
connectionCode: code,
connectionVerified: member.verified,
verify: { code in
var member = groupMember.wrapped
var member = chatModel.withLoadedSupportChat(groupMember.wrapped)
if let r = apiVerifyGroupMember(member.groupId, member.groupMemberId, connectionCode: code) {
let (verified, existingCode) = r
let connCode = verified ? SecurityCode(securityCode: existingCode, verifiedAt: .now) : nil
@@ -773,7 +773,7 @@ struct GroupMemberInfoView: View {
let stats = try apiSwitchGroupMember(groupInfo.apiId, groupMember.groupMemberId)
connectionStats = stats
await MainActor.run {
chatModel.updateGroupMemberConnectionStats(groupInfo, groupMember.wrapped, stats)
chatModel.updateGroupMemberConnectionStats(groupInfo, chatModel.withLoadedSupportChat(groupMember.wrapped), stats)
dismiss()
}
} catch let error {
@@ -791,7 +791,7 @@ struct GroupMemberInfoView: View {
let stats = try apiAbortSwitchGroupMember(groupInfo.apiId, groupMember.groupMemberId)
connectionStats = stats
await MainActor.run {
chatModel.updateGroupMemberConnectionStats(groupInfo, groupMember.wrapped, stats)
chatModel.updateGroupMemberConnectionStats(groupInfo, chatModel.withLoadedSupportChat(groupMember.wrapped), stats)
}
} catch let error {
logger.error("abortSwitchMemberAddress apiAbortSwitchGroupMember error: \(responseError(error))")
@@ -869,7 +869,7 @@ func updateMemberSettings(_ gInfo: GroupInfo, _ member: GroupMember, _ memberSet
do {
try await apiSetMemberSettings(gInfo.groupId, member.groupMemberId, memberSettings)
await MainActor.run {
var mem = member
var mem = ChatModel.shared.withLoadedSupportChat(member)
mem.memberSettings = memberSettings
_ = ChatModel.shared.upsertGroupMember(gInfo, mem)
}
@@ -13,25 +13,17 @@ 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 { 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 }
.onAppear {
if !chatModel.membersLoaded {
Task { await chatModel.loadGroupMembers(groupInfo) }
}
}
}
}
@ViewBuilder private func viewBody() -> some View {