diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 36c3bf57fd..21539c244c 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -23,7 +23,8 @@ struct ChatView: View { @Environment(\.scenePhase) var scenePhase @State @ObservedObject var chat: Chat @StateObject private var scrollModel = ReverseListScrollModel() - @State private var itemSection: Dictionary = Dictionary() + @State private var itemSection: [Int64: ChatSection] = [:] + @State private var activeSection: ChatSection = .bottom @State private var showChatInfoSheet: Bool = false @State private var showAddMembersSheet: Bool = false @State private var composeState = ComposeState() @@ -188,7 +189,7 @@ struct ChatView: View { if !isLoading, im.reversedChatItems.count <= loadItemsPerPage, filtered(im.reversedChatItems).count < 10 { - loadChatItems(chat.chatInfo, .toOldest) + loadChatItems(chat.chatInfo, .toOldest, .bottom, nil) } } .environmentObject(scrollModel) @@ -419,7 +420,7 @@ struct ChatView: View { let cInfo = chat.chatInfo let mergedItems = filtered(im.reversedChatItems) return GeometryReader { g in - ReverseList(items: mergedItems, scrollState: $scrollModel.state, itemSection: $itemSection ) { ci in + ReverseList(items: mergedItems, scrollState: $scrollModel.state, itemSection: $itemSection, activeSection: $activeSection ) { ci in let voiceNoFrame = voiceWithoutFrame(ci) let maxWidth = cInfo.chatType == .group ? voiceNoFrame @@ -439,8 +440,8 @@ struct ChatView: View { forwardedChatItems: $forwardedChatItems ) .id(ci.id) // Required to trigger `onAppear` on iOS15 - } loadPage: { direction in - loadChatItems(cInfo, direction) + } loadPage: { (direction, section, chatItemId) in + loadChatItems(cInfo, direction, section, chatItemId) } loadItemsAround: { chatItemId in loadItemsAround(cInfo, chatItemId) } @@ -842,7 +843,7 @@ struct ChatView: View { } } - private func loadChatItems(_ cInfo: ChatInfo, _ direction: ChatScrollDirection) { + private func loadChatItems(_ cInfo: ChatInfo, _ direction: ChatScrollDirection, _ section: ChatSection, _ chatItem: ChatItem?) { Task { if loadingItems || firstPage { return } loadingItems = true @@ -852,9 +853,9 @@ struct ChatView: View { // Load additional items until the page is +50 large after merging while chatItemsAvailable && filtered(reversedPage).count < loadItemsPerPage { let pagination: ChatPagination = - if direction == .toOldest, let lastItem = reversedPage.last ?? im.reversedChatItems.last { + if direction == .toOldest, let lastItem = chatItem ?? reversedPage.last ?? im.reversedChatItems.last { .before(chatItemId: lastItem.id, count: loadItemsPerPage) - } else if direction == .toLatest, let firstItem = reversedPage.first ?? im.reversedChatItems.first { + } else if direction == .toLatest, let firstItem = chatItem ?? reversedPage.first ?? im.reversedChatItems.first { // TODO: Replace with anchor. .after(chatItemId: firstItem.id, count: loadItemsPerPage) } else { @@ -870,13 +871,19 @@ struct ChatView: View { reversedPage.append(contentsOf: chatItems.reversed()) } await MainActor.run { - if reversedPage.count == 0 { + if reversedPage.count == 0 { firstPage = true } else { + var reversedPageToAppend = Array() reversedPage.forEach { ci in - itemSection[ci.id] = .bottom + if itemSection[ci.id] == nil { + reversedPageToAppend.append(ci) + itemSection[ci.id] = section + } else { + // TODO: Maybe merge + } } - im.reversedChatItems.append(contentsOf: reversedPage) + im.reversedChatItems.append(contentsOf: reversedPageToAppend) } loadingItems = false } @@ -889,9 +896,9 @@ struct ChatView: View { private func loadItemsAround(_ cInfo: ChatInfo, _ chatItemId: Int64) { Task { - if loadingItems { return } + if loadingItems || itemSection[chatItemId] != nil { return } loadingItems = true - do { + do { var reversedPage = Array() var chatItemsAvailable = true // Load additional items until the page is +50 large after merging @@ -913,7 +920,7 @@ struct ChatView: View { ) chatItems.append(contentsOf: afterItems) } - + chatItemsAvailable = !chatItems.isEmpty reversedPage.append(contentsOf: chatItems.reversed()) } diff --git a/apps/ios/Shared/Views/Chat/ReverseList.swift b/apps/ios/Shared/Views/Chat/ReverseList.swift index 410229c0a0..163bfc13b3 100644 --- a/apps/ios/Shared/Views/Chat/ReverseList.swift +++ b/apps/ios/Shared/Views/Chat/ReverseList.swift @@ -28,11 +28,11 @@ struct ReverseList: UIViewControllerRepresentable { @Binding var scrollState: ReverseListScrollModel.State @Binding var itemSection: Dictionary - + @Binding var activeSection: ChatSection /// Closure, that returns user interface for a given item let content: (ChatItem) -> Content - let loadPage: (ChatScrollDirection) -> Void + let loadPage: (ChatScrollDirection, ChatSection, ChatItem?) -> Void let loadItemsAround: (Int64) -> Void func makeUIViewController(context: Context) -> Controller { @@ -92,8 +92,12 @@ struct ReverseList: UIViewControllerRepresentable { self.dataSource = UITableViewDiffableDataSource( tableView: tableView ) { (tableView, indexPath, item) -> UITableViewCell? in - if self.representer.scrollState == .atDestination, indexPath.item > self.itemCount - 8, self.itemCount > 8 { - self.representer.loadPage(.toOldest) + if let section = self.dataSource.sectionIdentifier(for: indexPath.section) { + let itemCount = self.getTotalItemsInItemSection(indexPath: indexPath) + if self.representer.activeSection == section, indexPath.item > itemCount - 8, itemCount > 8 { + let lastItem = self.getLastItemInItemSection(indexPath: indexPath) + self.representer.loadPage(.toOldest, section, lastItem) + } } let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath) if #available(iOS 16.0, *) { @@ -178,7 +182,7 @@ struct ReverseList: UIViewControllerRepresentable { /// Scrolls up func scrollToItem(id: Int64) { - if let loadedIndex = representer.items.firstIndex(where: { $0.id == id }) { + if let loadedIndex = self.representer.items.firstIndex(where: { $0.id == id }) { let ci = representer.items[loadedIndex] if let indexPath = dataSource.indexPath(for: ci), let section = dataSource.sectionIdentifier(for: indexPath.section) { @@ -186,7 +190,10 @@ struct ReverseList: UIViewControllerRepresentable { } } else { Task { - self.representer.loadItemsAround(id) + if (self.representer.itemSection[id] == nil) { + self.representer.activeSection = .destination + self.representer.loadItemsAround(id) + } } } } @@ -290,6 +297,30 @@ struct ReverseList: UIViewControllerRepresentable { relativeFrame.minY < tableView.frame.height - InvertedTableView.inset } else { false } } + + private func getTotalItemsInItemSection(indexPath: IndexPath) -> Int { + return self.tableView.numberOfRows(inSection: indexPath.section) + } + + private func getLastItemInItemSection(indexPath: IndexPath) -> ChatItem? { + let numberOfRows = self.getTotalItemsInItemSection(indexPath: indexPath) + + return if numberOfRows > 0, + let lastItem = self.dataSource.itemIdentifier(for: IndexPath(row: numberOfRows - 1, section: indexPath.section)) { + lastItem + } else { + nil + } + } + + private func getFirstItemInItemSection(indexPath: IndexPath) -> ChatItem? { + let firstIndexPath = IndexPath(item: 0, section: indexPath.section) + return if let firstItem = self.dataSource.itemIdentifier(for: firstIndexPath) { + firstItem + } else { + nil + } + } } /// `UIHostingConfiguration` back-port for iOS14 and iOS15