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
@@ -383,14 +383,14 @@ object ChatModel {
fun getGroupChat(groupId: Long): Chat? = chats.value.firstOrNull { it.chatInfo is ChatInfo.Group && it.chatInfo.apiId == groupId }
suspend fun upsertSupportChatMember(rhId: Long?, cInfo: ChatInfo) {
if (cInfo !is ChatInfo.Group) return
if (cInfo !is ChatInfo.Group || remoteHostId() != rhId) return
val member = (cInfo.groupChatScope as? GroupChatScopeInfo.MemberSupport)?.groupMember_ ?: return
val current = groupMembersIndexes.value[member.groupMemberId]?.let { groupMembers.value.getOrNull(it) }
val current = getGroupMember(member.groupMemberId)
chatsContext.upsertGroupMember(rhId, cInfo.groupInfo, current?.copy(supportChat = member.supportChat, memberProfile = member.memberProfile) ?: member)
}
fun withLoadedSupportChat(member: GroupMember): GroupMember {
val supportChat = groupMembersIndexes.value[member.groupMemberId]?.let { groupMembers.value.getOrNull(it) }?.supportChat
val supportChat = getGroupMember(member.groupMemberId)?.supportChat
return if (supportChat != null) member.copy(supportChat = supportChat) else member
}
@@ -986,8 +986,10 @@ object ChatModel {
// stale data, should be cleared at that point, otherwise, duplicated items will be here which will produce crashes in LazyColumn
groupMembers.value = emptyList()
groupMembersIndexes.value = emptyMap()
membersLoaded.value = false
supportMembersLoaded.value = false
if (chatId.value == groupInfo.id) {
membersLoaded.value = false
supportMembersLoaded.value = false
}
}
val memberIndex = groupMembersIndexes.value[member.groupMemberId]
val updated = chatItems.value.map {
@@ -1211,9 +1211,7 @@ object ChatController {
val r = sendCmd(rh, cmd)
return when {
r is API.Result && r.res is CR.NewChatItems -> {
withContext(Dispatchers.Main) {
r.res.chatItems.forEach { chatModel.upsertSupportChatMember(rh, it.chatInfo) }
}
r.res.chatItems.lastOrNull()?.let { withContext(Dispatchers.Main) { chatModel.upsertSupportChatMember(rh, it.chatInfo) } }
r.res.chatItems
}
r is API.Error && r.err is ChatError.ChatErrorStore && r.err.storeError is StoreError.LargeMsg && cmd is CC.ApiSendMessages -> {
@@ -2488,11 +2486,11 @@ object ChatController {
return null
}
suspend fun apiListMembers(rh: Long?, groupId: Long): List<GroupMember>? {
suspend fun apiListMembers(rh: Long?, groupId: Long): List<GroupMember> {
val r = sendCmd(rh, CC.ApiListMembers(groupId))
if (r is API.Result && r.res is CR.GroupMembers) return r.res.group.members
Log.e(TAG, "apiListMembers bad response: ${r.responseType} ${r.details}")
return null
return emptyList()
}
suspend fun apiListSupportMembers(rh: Long?, groupId: Long): List<GroupMember>? {
@@ -3445,7 +3445,6 @@ private fun archiveReports(chatRh: Long?, chatInfo: ChatInfo, itemIds: List<Long
}
deleted.lastOrNull()?.deletedChatItem?.chatInfo?.let { updatedChatInfo ->
chatModel.chatsContext.updateChatInfo(chatRh, updatedChatInfo)
chatModel.upsertSupportChatMember(chatRh, updatedChatInfo)
}
}
withContext(Dispatchers.Main) {
@@ -117,7 +117,7 @@ private fun acceptMember(rhId: Long?, groupInfo: GroupInfo, member: GroupMember,
val r = chatModel.controller.apiAcceptMember(rhId, groupInfo.groupId, member.groupMemberId, role)
if (r != null) {
withContext(Dispatchers.Main) {
chatModel.chatsContext.upsertGroupMember(rhId, r.first, r.second)
chatModel.chatsContext.upsertGroupMember(rhId, r.first, chatModel.withLoadedSupportChat(r.second))
chatModel.chatsContext.updateGroup(rhId, r.first)
}
}
@@ -41,20 +41,19 @@ fun ModalData.MemberSupportView(
KeyChangeEffect(chatModel.chatId.value) {
ModalManager.end.closeModals()
}
val membersLoading = remember { stateGetOrPut("membersLoading") { false } }
LaunchedEffect(Unit) {
if (rhId != null) setSupportMembers(rhId, groupInfo, chatModel)
}
LaunchedEffect(chatModel.membersLoaded.value, chatModel.supportMembersLoaded.value) {
if (!chatModel.membersLoaded.value && !chatModel.supportMembersLoaded.value && chatModel.chatId.value == groupInfo.id && !membersLoading.value) {
membersLoading.value = true
withBGApi {
try {
setSupportMembers(rhId, groupInfo, chatModel)
} finally {
membersLoading.value = false
}
}
if (rhId != null || chatModel.chatId.value != groupInfo.id) return@LaunchedEffect
val otherGroupMembers = chatModel.groupMembers.value.firstOrNull()?.let { it.groupId != groupInfo.groupId } == true
if ((!chatModel.membersLoaded.value && !chatModel.supportMembersLoaded.value) || otherGroupMembers) {
setSupportMembers(rhId, groupInfo, chatModel)
}
}
ModalView(close = close) {
ModalView(
close = close
) {
MemberSupportViewLayout(
chat,
groupInfo,
@@ -253,7 +253,7 @@ suspend fun apiFindMessages(chatsCtx: ChatModel.ChatsContext, ch: Chat, contentT
suspend fun setGroupMembers(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel) = coroutineScope {
// groupMembers loading can take a long time and if the user already closed the screen, coroutine may be canceled
val groupMembers = chatModel.controller.apiListMembers(rhId, groupInfo.groupId) ?: return@coroutineScope
val groupMembers = chatModel.controller.apiListMembers(rhId, groupInfo.groupId)
val currentMembersById = chatModel.groupMembers.value.associateBy { it.id }
val newMembers = groupMembers.map { newMember ->
val currentMember = currentMembersById[newMember.id]
@@ -265,8 +265,7 @@ suspend fun setGroupMembers(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatMo
newMember
}
}
withContext(Dispatchers.Main) {
if (chatModel.chatId.value != groupInfo.id && chatModel.creatingChannelId.value != groupInfo.id) return@withContext
withContext(NonCancellable + Dispatchers.Main) {
chatModel.groupMembersIndexes.value = emptyMap()
chatModel.groupMembers.value = newMembers
chatModel.membersLoaded.value = true
@@ -274,11 +273,15 @@ suspend fun setGroupMembers(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatMo
}
}
suspend fun setSupportMembers(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel) = coroutineScope {
suspend fun setSupportMembers(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel) {
val supportMembers = chatModel.controller.apiListSupportMembers(rhId, groupInfo.groupId)
?: return@coroutineScope setGroupMembers(rhId, groupInfo, chatModel)
withContext(Dispatchers.Main) {
if (chatModel.chatId.value != groupInfo.id) return@withContext
if (supportMembers == null) {
setGroupMembers(rhId, groupInfo, chatModel)
return
}
withContext(NonCancellable + Dispatchers.Main) {
if (chatModel.chatId.value != groupInfo.id || chatModel.remoteHostId() != rhId) return@withContext
if (chatModel.groupMembers.value.any { it.groupId != groupInfo.groupId }) chatModel.membersLoaded.value = false
val membersById = LinkedHashMap<Long, GroupMember>()
chatModel.groupMembers.value.forEach { if (it.groupId == groupInfo.groupId) membersById[it.groupMemberId] = it }
supportMembers.forEach { member ->