From 1e419b87902ca50de0937e6a9ba1c23a8a2e607b Mon Sep 17 00:00:00 2001 From: Diogo Date: Wed, 16 Oct 2024 15:39:41 +0100 Subject: [PATCH] refactor --- .../Views/Chat/ChatItem/FramedItemView.swift | 1 + apps/ios/Shared/Views/Chat/ChatView.swift | 54 ++++------- apps/ios/Shared/Views/Chat/ReverseList.swift | 95 ++++++++++++++----- 3 files changed, 87 insertions(+), 63 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift index 123f37b885..d82bcf0065 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift @@ -13,6 +13,7 @@ struct FramedItemView: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme @EnvironmentObject var scrollModel: ReverseListScrollModel + @EnvironmentObject var sectionModel: ReverseListSectionModel @ObservedObject var chat: Chat var chatItem: ChatItem var preview: UIImage? diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index bdacacc5d0..913e66f7f4 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -28,8 +28,7 @@ struct ChatView: View { @Environment(\.scenePhase) var scenePhase @State @ObservedObject var chat: Chat @StateObject private var scrollModel = ReverseListScrollModel() - @State private var itemSection: [Int64: ChatSection] = [:] - @State private var activeSection: ChatSection = .bottom + @StateObject private var sectionModel = ReverseListSectionModel() @State private var showChatInfoSheet: Bool = false @State private var showAddMembersSheet: Bool = false @State private var composeState = ComposeState() @@ -370,14 +369,7 @@ struct ChatView: View { } } ChatView.FloatingButtonModel.shared.totalUnread = chat.chatStats.unreadCount - resetSections() - } - - private func resetSections() { - itemSection.removeAll() - im.reversedChatItems.forEach { - itemSection[$0.id] = .bottom - } + sectionModel.resetSections(items: im.reversedChatItems) } private func searchToolbar() -> some View { @@ -433,7 +425,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, activeSection: $activeSection ) { ci in + ReverseList(items: mergedItems, scrollState: $scrollModel.state, sectionModel: sectionModel) { ci in let voiceNoFrame = voiceWithoutFrame(ci) let maxWidth = cInfo.chatType == .group ? voiceNoFrame @@ -464,7 +456,7 @@ struct ChatView: View { .onChange(of: searchText) { _ in Task { await loadChat(chat: chat, search: searchText) - resetSections() + sectionModel.resetSections(items: im.reversedChatItems) } } .onChange(of: im.itemAdded) { added in @@ -897,27 +889,11 @@ struct ChatView: View { if reversedPage.count == 0 { setBoundary(direction) } else { - var reversedPageToAppend = Array() - var targetSection = section - - if let sectionIntersectionItem = reversedPage.first(where: { itemSection[$0.id] != nil && itemSection[$0.id] != section }) { - let sectionToDrop = itemSection[sectionIntersectionItem.id] == .bottom ? section : (itemSection[sectionIntersectionItem.id] ?? .destination) - targetSection = itemSection[sectionIntersectionItem.id] == .bottom ? .bottom : section - im.reversedChatItems.forEach { - if itemSection[$0.id] == sectionToDrop { - itemSection[$0.id] = targetSection - } - } - } - - reversedPage.forEach { ci in - if itemSection[ci.id] == nil { - reversedPageToAppend.append(ci) - itemSection[ci.id] = targetSection - } - } - - self.activeSection = targetSection + let reversedPageToAppend = self.sectionModel.handleSectionInsertion( + candidateSection: section, + reversedPage: reversedPage, + allItems: im.reversedChatItems + ) if direction == .toLatest { let at = if let chatItemId = chatItem?.id, let index = im.reversedChatItems.firstIndex(where: { $0.id == chatItemId }) { @@ -941,7 +917,7 @@ struct ChatView: View { private func loadItemsAround(_ cInfo: ChatInfo, _ chatItemId: Int64) { Task { - if loadingItems || itemSection[chatItemId] != nil { return } + if loadingItems || sectionModel.getItemSection(chatItemId) != nil { return } loadingItems = true do { var reversedPage = Array() @@ -970,10 +946,12 @@ struct ChatView: View { reversedPage.append(contentsOf: chatItems.reversed()) } await MainActor.run { - reversedPage.forEach { ci in - itemSection[ci.id] = .destination - } - im.reversedChatItems.append(contentsOf: reversedPage) + let reversedPageToAppend = self.sectionModel.handleSectionInsertion( + candidateSection: .destination, + reversedPage: reversedPage, + allItems: im.reversedChatItems + ) + im.reversedChatItems.append(contentsOf: reversedPageToAppend) loadingItems = false } } catch let error { diff --git a/apps/ios/Shared/Views/Chat/ReverseList.swift b/apps/ios/Shared/Views/Chat/ReverseList.swift index 79bf05b6e0..471511d987 100644 --- a/apps/ios/Shared/Views/Chat/ReverseList.swift +++ b/apps/ios/Shared/Views/Chat/ReverseList.swift @@ -27,8 +27,7 @@ struct ReverseList: UIViewControllerRepresentable { let items: Array @Binding var scrollState: ReverseListScrollModel.State - @Binding var itemSection: Dictionary - @Binding var activeSection: ChatSection + @ObservedObject var sectionModel: ReverseListSectionModel /// Closure, that returns user interface for a given item let content: (ChatItem) -> Content @@ -96,7 +95,7 @@ struct ReverseList: UIViewControllerRepresentable { ) { (tableView, indexPath, item) -> UITableViewCell? in if let section = self.dataSource.sectionIdentifier(for: indexPath.section) { let itemCount = self.getTotalItemsInItemSection(indexPath: indexPath) - if self.representer.activeSection == section { + if self.representer.sectionModel.activeSection == section { if self.scrollDirection == .toOldest, indexPath.item > itemCount - 8, itemCount > 8 { let lastItem = self.getLastItemInItemSection(indexPath: indexPath) self.representer.loadPage(.toOldest, section, lastItem) @@ -197,8 +196,8 @@ struct ReverseList: UIViewControllerRepresentable { } } else { Task { - if (self.representer.itemSection[id] == nil) { - self.representer.activeSection = .destination + if (self.representer.sectionModel.getItemSection(id) == nil) { + self.representer.sectionModel.activeSection = .destination self.representer.loadItemsAround(id) } } @@ -229,28 +228,14 @@ struct ReverseList: UIViewControllerRepresentable { func update(items: [ChatItem]) { var snapshot = NSDiffableDataSourceSnapshot() - var itemsBySection: Dictionary = Dictionary() - items.forEach { ci in - var section: ChatSection = .bottom - if let sec = representer.itemSection[ci.id] { - section = sec - } - - if var existingItems = itemsBySection[section] { - existingItems.append(ci) - itemsBySection[section] = existingItems - } else { - itemsBySection[section] = [ci] + let sections = self.representer.sectionModel.getSectionsOrdered() + let itemsBySection = self.itemsBySection(items: items) + snapshot.appendSections(sections) + sections.forEach { sec in + if let sectionItems = itemsBySection[sec] { + snapshot.appendItems(sectionItems, toSection: sec) } } - snapshot.appendSections([.bottom, .destination]) - if let bottomItems = itemsBySection[.bottom] { - snapshot.appendItems(bottomItems, toSection: .bottom) - } - if let destinationItems = itemsBySection[.destination] { - snapshot.appendItems(destinationItems, toSection: .destination) - } - dataSource.defaultRowAnimation = .none dataSource.apply( snapshot, @@ -337,6 +322,16 @@ struct ReverseList: UIViewControllerRepresentable { nil } } + + private func itemsBySection(items: [ChatItem]) -> [ChatSection: [ChatItem]] { + let itemsBySection = items.reduce(into: [ChatSection: [ChatItem]]()) { result, ci in + if let sec = self.representer.sectionModel.getItemSection(ci.id) { + result[sec, default: []].append(ci) + } + } + + return itemsBySection + } } /// `UIHostingConfiguration` back-port for iOS14 and iOS15 @@ -414,6 +409,56 @@ class ReverseListScrollModel: ObservableObject { } } +/// Manages ``ReverseList`` sections +class ReverseListSectionModel: ObservableObject { + @Published var activeSection: ChatSection = .bottom + @Published private var itemSection: [Int64: ChatSection] = [:] + @Published private var sections = Set() + + func getItemSection(_ id: Int64) -> ChatSection? { + return itemSection[id] + } + + func getSectionsOrdered() -> [ChatSection] { + var orderedSections: [ChatSection] = [.bottom] + + if (sections.contains(.destination)) { + orderedSections.append(.destination) + } + + return orderedSections + } + + func resetSections(items: [ChatItem]) { + itemSection = Dictionary(uniqueKeysWithValues: items.map { ($0.id, .bottom) }) + } + + func handleSectionInsertion(candidateSection: ChatSection, reversedPage: [ChatItem], allItems: [ChatItem]) -> [ChatItem] { + var reversedPageToAppend = Array() + var targetSection = candidateSection + + if let sectionIntersectionItem = reversedPage.first(where: { itemSection[$0.id] != nil && itemSection[$0.id] != candidateSection }) { + let sectionToDrop = itemSection[sectionIntersectionItem.id] == .bottom ? candidateSection : (itemSection[sectionIntersectionItem.id] ?? .destination) + targetSection = itemSection[sectionIntersectionItem.id] == .bottom ? .bottom : candidateSection + itemSection = itemSection.mapValues { section in + section == sectionToDrop ? targetSection : section + } + sections.remove(targetSection) + } + + reversedPage.forEach { ci in + if itemSection[ci.id] == nil { + reversedPageToAppend.append(ci) + itemSection[ci.id] = targetSection + } + } + sections.insert(targetSection) + self.activeSection = targetSection + + return reversedPageToAppend + } +} + fileprivate let cellReuseId = "hostingCell" fileprivate let notificationName = NSNotification.Name(rawValue: "reverseListNeedsLayout")