ios: optimise mark-as-read performance (#4434)

* ios: optimise mark-as-read rendering performance

* merge master

* minor

* make chat observable

* make model EnvironmentObject

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
This commit is contained in:
Arturs Krumins
2024-07-11 10:57:56 +03:00
committed by GitHub
parent f84ac713d7
commit 41c4f13939
3 changed files with 42 additions and 45 deletions

View File

@@ -74,6 +74,7 @@ final class ChatModel: ObservableObject {
@Published var chatToTop: String?
@Published var groupMembers: [GMember] = []
@Published var groupMembersIndexes: Dictionary<Int64, Int> = [:] // groupMemberId to index in groupMembers list
@Published var membersLoaded = false
// items in the terminal view
@Published var showingTerminal = false
@Published var terminalItems: [TerminalItem] = []
@@ -195,6 +196,18 @@ final class ChatModel: ObservableObject {
return nil
}
func loadGroupMembers(_ groupInfo: GroupInfo, updateView: @escaping () -> Void = {}) async {
let groupMembers = await apiListMembers(groupInfo.groupId)
await MainActor.run {
if chatId == groupInfo.id {
self.groupMembers = groupMembers.map { GMember.init($0) }
self.populateGroupMembersIndexes()
self.membersLoaded = true
updateView()
}
}
}
private func getChatIndex(_ id: String) -> Int? {
chats.firstIndex(where: { $0.id == id })
}
@@ -390,8 +403,8 @@ final class ChatModel: ObservableObject {
}
func removeChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) {
if cItem.isRcvNew {
decreaseUnreadCounter(cInfo)
if cItem.isRcvNew, let chatIndex = getChatIndex(cInfo.id) {
decreaseUnreadCounter(chatIndex)
}
// update previews
if let chat = getChat(cInfo.id) {
@@ -536,13 +549,18 @@ final class ChatModel: ObservableObject {
}
}
func markChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) {
if chatId == cInfo.id, let i = getChatItemIndex(cItem) {
if reversedChatItems[i].isRcvNew {
// update current chat
markChatItemRead_(i)
// update preview
decreaseUnreadCounter(cInfo)
func markChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) async {
if chatId == cInfo.id,
let itemIndex = getChatItemIndex(cItem),
let chatIndex = getChatIndex(cInfo.id),
reversedChatItems[itemIndex].isRcvNew {
await MainActor.run {
withTransaction(Transaction()) {
// update current chat
markChatItemRead_(itemIndex)
// update preview
decreaseUnreadCounter(chatIndex)
}
}
}
}
@@ -558,11 +576,9 @@ final class ChatModel: ObservableObject {
}
}
func decreaseUnreadCounter(_ cInfo: ChatInfo) {
if let i = getChatIndex(cInfo.id) {
chats[i].chatStats.unreadCount = chats[i].chatStats.unreadCount - 1
decreaseUnreadCounter(user: currentUser!)
}
func decreaseUnreadCounter(_ chatIndex: Int) {
chats[chatIndex].chatStats.unreadCount = chats[chatIndex].chatStats.unreadCount - 1
decreaseUnreadCounter(user: currentUser!)
}
func increaseUnreadCounter(user: any UserLike) {

View File

@@ -1207,7 +1207,7 @@ func apiMarkChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) async {
do {
logger.debug("apiMarkChatItemRead: \(cItem.id)")
try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId, itemRange: (cItem.id, cItem.id))
await MainActor.run { ChatModel.shared.markChatItemRead(cInfo, cItem) }
await ChatModel.shared.markChatItemRead(cInfo, cItem)
} catch {
logger.error("apiMarkChatItemRead apiChatRead error: \(responseError(error))")
}

View File

@@ -37,7 +37,6 @@ struct ChatView: View {
@State private var searchText: String = ""
@FocusState private var searchFocussed
// opening GroupMemberInfoView on member icon
@State private var membersLoaded = false
@State private var selectedMember: GMember? = nil
// opening GroupLinkView on link button (incognito)
@State private var showGroupLinkSheet: Bool = false
@@ -122,7 +121,7 @@ struct ChatView: View {
chatModel.reversedChatItems = []
chatModel.groupMembers = []
chatModel.groupMembersIndexes.removeAll()
membersLoaded = false
chatModel.membersLoaded = false
}
}
}
@@ -164,7 +163,7 @@ struct ChatView: View {
}
} else if case let .group(groupInfo) = cInfo {
Button {
Task { await loadGroupMembers(groupInfo) { showChatInfoSheet = true } }
Task { await chatModel.loadGroupMembers(groupInfo) { showChatInfoSheet = true } }
} label: {
ChatInfoToolbar(chat: chat)
.tint(theme.colors.primary)
@@ -250,19 +249,7 @@ struct ChatView: View {
}
}
}
private func loadGroupMembers(_ groupInfo: GroupInfo, updateView: @escaping () -> Void = {}) async {
let groupMembers = await apiListMembers(groupInfo.groupId)
await MainActor.run {
if chatModel.chatId == groupInfo.id {
chatModel.groupMembers = groupMembers.map { GMember.init($0) }
chatModel.populateGroupMembersIndexes()
membersLoaded = true
updateView()
}
}
}
private func initChatView() {
let cInfo = chat.chatInfo
// This check prevents the call to apiContactInfo after the app is suspended, and the database is closed.
@@ -477,9 +464,7 @@ struct ChatView: View {
.foregroundColor(theme.colors.primary)
}
.onTapGesture {
if let latestUnreadItem = filtered(chatModel.reversedChatItems).last(where: { $0.isRcvNew }) {
scrollModel.scrollToItem(id: latestUnreadItem.id)
}
scrollModel.scrollToBottom()
}
} else if !counts.isNearBottom {
circleButton {
@@ -534,7 +519,7 @@ struct ChatView: View {
private func addMembersButton() -> some View {
Button {
if case let .group(gInfo) = chat.chatInfo {
Task { await loadGroupMembers(gInfo) { showAddMembersSheet = true } }
Task { await chatModel.loadGroupMembers(gInfo) { showAddMembersSheet = true } }
}
} label: {
Image(systemName: "person.crop.circle.badge.plus")
@@ -604,11 +589,9 @@ struct ChatView: View {
chat: chat,
chatItem: ci,
maxWidth: maxWidth,
itemWidth: maxWidth,
composeState: $composeState,
selectedMember: $selectedMember,
revealedChatItem: $revealedChatItem,
chatView: self
revealedChatItem: $revealedChatItem
)
}
@@ -616,13 +599,11 @@ struct ChatView: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
@ObservedObject var chat: Chat
var chatItem: ChatItem
var maxWidth: CGFloat
@State var itemWidth: CGFloat
let chatItem: ChatItem
let maxWidth: CGFloat
@Binding var composeState: ComposeState
@Binding var selectedMember: GMember?
@Binding var revealedChatItem: ChatItem?
var chatView: ChatView
@State private var deletingItem: ChatItem? = nil
@State private var showDeleteMessage = false
@@ -700,11 +681,11 @@ struct ChatView: View {
HStack(alignment: .top, spacing: 8) {
ProfileImage(imageStr: member.memberProfile.image, size: memberImageSize, backgroundColor: theme.colors.background)
.onTapGesture {
if chatView.membersLoaded {
if m.membersLoaded {
selectedMember = m.getGroupMember(member.groupMemberId)
} else {
Task {
await chatView.loadGroupMembers(groupInfo) {
await m.loadGroupMembers(groupInfo) {
selectedMember = m.getGroupMember(member.groupMemberId)
}
}
@@ -1099,7 +1080,7 @@ struct ChatView: View {
chatItemInfo = ciInfo
}
if case let .group(gInfo) = chat.chatInfo {
await chatView.loadGroupMembers(gInfo)
await m.loadGroupMembers(gInfo)
}
} catch let error {
logger.error("apiGetChatItemInfo error: \(responseError(error))")