diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 04be317e20..36d604602e 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -13,11 +13,6 @@ import Combine private let memberImageSize: CGFloat = 34 -struct PaginationBoundary { - var oldest: Bool - var latest: Bool -} - struct ChatView: View { @EnvironmentObject var chatModel: ChatModel @ObservedObject var im = ItemsModel.shared @@ -37,7 +32,6 @@ struct ChatView: View { @State private var customUserProfile: Profile? @State private var connectionCode: String? @State private var loadingItems = false - @State private var paginationBoundary = PaginationBoundary(oldest: false, latest: false) @State private var revealedChatItem: ChatItem? @State private var searchMode = false @State private var searchText: String = "" @@ -171,10 +165,6 @@ struct ChatView: View { selectedChatItems = nil initChatView() } - .onChange(of: sectionModel.activeSection) { ac in - paginationBoundary.oldest = false - paginationBoundary.latest = false - } .onChange(of: chatModel.chatId) { cId in showChatInfoSheet = false selectedChatItems = nil @@ -868,11 +858,11 @@ struct ChatView: View { } private func boundaryReached(_ direction: ChatScrollDirection) -> Bool { - return (direction == .toLatest && paginationBoundary.latest) || (direction == .toOldest && paginationBoundary.oldest) + return (direction == .toLatest && sectionModel.boundaries.latest) || (direction == .toOldest && sectionModel.boundaries.oldest) } private func setBoundary(_ direction: ChatScrollDirection) { - direction == .toLatest ? (paginationBoundary.latest = true) : (paginationBoundary.oldest = true) + direction == .toLatest ? (sectionModel.boundaries.latest = true) : (sectionModel.boundaries.oldest = true) } private func loadChatItems(_ cInfo: ChatInfo, _ direction: ChatScrollDirection, _ section: ChatSection, _ chatItem: ChatItem?) { @@ -902,6 +892,7 @@ struct ChatView: View { chatItemsAvailable = !chatItems.isEmpty reversedPage.append(contentsOf: chatItems.reversed()) } + await MainActor.run { if reversedPage.count == 0 { setBoundary(direction) diff --git a/apps/ios/Shared/Views/Chat/ReverseList.swift b/apps/ios/Shared/Views/Chat/ReverseList.swift index 057a0c8dd5..e4a68d1e55 100644 --- a/apps/ios/Shared/Views/Chat/ReverseList.swift +++ b/apps/ios/Shared/Views/Chat/ReverseList.swift @@ -101,6 +101,13 @@ struct ReverseList: UIViewControllerRepresentable { let firstItem = self.getFirstItemInItemSection(indexPath: indexPath) self.representer.loadPage(.toLatest, section, firstItem) } + + if (self.scrollDirection == .toOldest && indexPath.item - ReverseListSectionModel.IDEAL_SECTION_SIZE > 8) || + (self.scrollDirection == .toLatest && indexPath.item < ReverseListSectionModel.IDEAL_SECTION_SIZE) { + Task { + self.representer.sectionModel.manageActiveSection(scrollDirection: self.scrollDirection) + } + } } } let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath) @@ -200,8 +207,10 @@ struct ReverseList: UIViewControllerRepresentable { func scroll(to index: Int?, position: UITableView.ScrollPosition, section: ChatSection, shouldTryAnimate: Bool) { let activeSectionBeforeScroll = representer.sectionModel.activeSection - Task { - representer.sectionModel.activeSection = section + if section != activeSectionBeforeScroll { + Task { + representer.sectionModel.changeActiveSection(section) + } } var animated = false if #available(iOS 16.0, *) { @@ -410,10 +419,18 @@ class ReverseListScrollModel: ObservableObject { } } +struct SectionBoundaryReached { + var oldest: Bool + var latest: Bool +} + /// Manages ``ReverseList`` sections class ReverseListSectionModel: ObservableObject { static let shared = ReverseListSectionModel() - @Published var activeSection: ChatSection = .bottom + static let MAX_SECTION_SIZE = 50 + static let IDEAL_SECTION_SIZE = 35 + @Published private(set) var activeSection: ChatSection = .bottom + @Published var boundaries = SectionBoundaryReached(oldest: false, latest: false) @Published private var itemSection: [Int64: ChatSection] = [:] @Published private var sections = Set() @@ -421,6 +438,12 @@ class ReverseListSectionModel: ObservableObject { return itemSection[id] } + func changeActiveSection(_ section: ChatSection) { + activeSection = section + boundaries.latest = false + boundaries.oldest = false + } + func maybeDropSection(_ section: ChatSection) { if section == .bottom { return } let im = ItemsModel.shared @@ -429,6 +452,76 @@ class ReverseListSectionModel: ObservableObject { im.reversedChatItems = im.reversedChatItems.filter { it in itemSection[it.id] != section } } + private func activeSectionItemsCount() -> Int { + // TODO: check + return itemSection.values.filter { $0 == self.activeSection }.count + } + + private func trimSection(scrollDirection: ChatScrollDirection, section: ChatSection) { + let im = ItemsModel.shared + var i = scrollDirection == .toLatest ? 0 : im.reversedChatItems.count - 1 + let moveToNextIndex: () -> Bool = { + if scrollDirection == .toLatest { + if i < im.reversedChatItems.count - 1 { + i += 1 + return false + } else { + return true + } + } else { + if i > 0 { + i -= 1 + return false + } else { + return true + } + } + } + var sectionCount = 0 + var completed = false + var toRemoveIndexes = IndexSet() + + while (!completed) { + let it = im.reversedChatItems[i] + if itemSection[it.id] == section { + if sectionCount >= ReverseListSectionModel.IDEAL_SECTION_SIZE { + toRemoveIndexes.insert(i) + itemSection.removeValue(forKey: it.id) + } else { + sectionCount += 1 + } + } + completed = moveToNextIndex() + } + + if (!toRemoveIndexes.isEmpty) { + im.reversedChatItems.remove(atOffsets: toRemoveIndexes) + } + } + + func manageActiveSection(scrollDirection: ChatScrollDirection) { + let activeSectionCount = activeSectionItemsCount() + + switch self.activeSection { + case .bottom: + if scrollDirection == .toLatest, activeSectionCount > ReverseListSectionModel.MAX_SECTION_SIZE { + // boundaries?, the visible item needs to not be there + boundaries.oldest = false + self.trimSection(scrollDirection: scrollDirection, section: self.activeSection) + } + break; + default: + if activeSectionCount > ReverseListSectionModel.MAX_SECTION_SIZE { + if scrollDirection == .toLatest { + boundaries.oldest = false + } else { + boundaries.latest = false + } + self.trimSection(scrollDirection: scrollDirection, section: self.activeSection) + } + } + } + func getSectionsOrdered() -> [ChatSection] { var orderedSections: [ChatSection] = [.bottom]