mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
refactor
This commit is contained in:
@@ -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?
|
||||
|
||||
@@ -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<ChatItem>()
|
||||
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<ChatItem>()
|
||||
@@ -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 {
|
||||
|
||||
@@ -27,8 +27,7 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
|
||||
let items: Array<ChatItem>
|
||||
|
||||
@Binding var scrollState: ReverseListScrollModel.State
|
||||
@Binding var itemSection: Dictionary<Int64, ChatSection>
|
||||
@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<Content: View>: 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<Content: View>: 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<Content: View>: UIViewControllerRepresentable {
|
||||
|
||||
func update(items: [ChatItem]) {
|
||||
var snapshot = NSDiffableDataSourceSnapshot<ChatSection, ChatItem>()
|
||||
var itemsBySection: Dictionary<ChatSection, [ChatItem]> = 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<Content: View>: 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<ChatSection>()
|
||||
|
||||
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<ChatItem>()
|
||||
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")
|
||||
|
||||
Reference in New Issue
Block a user