From 6c120ff4778ee793f0b9ceaa2fd89342929c92ca Mon Sep 17 00:00:00 2001 From: Ambroise Decouttere Date: Mon, 24 Jul 2023 14:17:23 +0200 Subject: [PATCH 1/4] fix(UnreadFilter): Delete thread correctly --- .../ThreadListViewModel+Observation.swift | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/Mail/Views/Thread List/ThreadListViewModel+Observation.swift b/Mail/Views/Thread List/ThreadListViewModel+Observation.swift index 3f7d8ebb7..c28fe6622 100644 --- a/Mail/Views/Thread List/ThreadListViewModel+Observation.swift +++ b/Mail/Views/Thread List/ThreadListViewModel+Observation.swift @@ -126,6 +126,7 @@ extension ThreadListViewModel { let containAnyOf = NSPredicate(format: Self.containAnyOfUIDs, allThreadsUIDs) let realm = mailboxManager.getRealm() let allThreads = realm.objects(Thread.self).filter(containAnyOf) + let oldThreads = allThreads.freezeIfNeeded() observeFilteredThreadsToken = allThreads.observe(on: observeQueue) { [weak self] changes in guard let self else { @@ -135,8 +136,13 @@ extension ThreadListViewModel { switch changes { case .initial: break - case .update(let all, _, _, let modificationIndexes): - refreshInFilterMode(all: all, changes: modificationIndexes) + case .update(let all, let deletionIndexes, _, let modificationIndexes): + refreshInFilterMode( + all: all, + old: oldThreads, + deletions: deletionIndexes, + changes: modificationIndexes + ) case .error: break } @@ -148,7 +154,7 @@ extension ThreadListViewModel { } /// Update filtered threads on observation change. - private func refreshInFilterMode(all: Results, changes: [Int]) { + private func refreshInFilterMode(all: Results, old: Results, deletions: [Int], changes: [Int]) { for index in changes { let updatedThread = all[index] let uid = updatedThread.uid @@ -173,6 +179,28 @@ extension ThreadListViewModel { sectionToUpdate.threads[threadToUpdateIndex] = updatedThread.freeze() + Task { + await MainActor.run { + objectWillChange.send() + } + } + } + for index in deletions { + let deletedThread = old[index] + let uid = deletedThread.uid + + let sectionToUpdate = sections.first { section in + section.threads.contains { $0.uid == uid } + } + + guard let sectionToUpdate else { + continue + } + + sectionToUpdate.threads.removeAll { + $0.uid == uid + } + Task { await MainActor.run { objectWillChange.send() From 9c7535db07f744dbb7d31d06232b1a05d1c11f25 Mon Sep 17 00:00:00 2001 From: Ambroise Decouttere Date: Thu, 27 Jul 2023 13:00:20 +0200 Subject: [PATCH 2/4] fix(iPad): Read thread on change --- Mail/Views/SplitView.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Mail/Views/SplitView.swift b/Mail/Views/SplitView.swift index 693cf344b..b57b328e5 100644 --- a/Mail/Views/SplitView.swift +++ b/Mail/Views/SplitView.swift @@ -78,6 +78,14 @@ struct SplitView: View { if let thread = navigationState.threadPath.last { ThreadView(thread: thread) + .onChange(of: thread) { newValue in + if newValue.hasUnseenMessages { + Task { + try? await mailboxManager + .toggleRead(threads: [newValue]) + } + } + } } else { EmptyStateView.emptyThread(from: splitViewManager.selectedFolder) } From 1d64fc2a9df7f217fa38475d95b448985632a1b6 Mon Sep 17 00:00:00 2001 From: Ambroise Decouttere Date: Thu, 3 Aug 2023 09:53:35 +0200 Subject: [PATCH 3/4] fix(UnreadFilter): Simplify unread filter --- .../ThreadListViewModel+Observation.swift | 83 ++++--------------- 1 file changed, 18 insertions(+), 65 deletions(-) diff --git a/Mail/Views/Thread List/ThreadListViewModel+Observation.swift b/Mail/Views/Thread List/ThreadListViewModel+Observation.swift index c28fe6622..1dd75c60d 100644 --- a/Mail/Views/Thread List/ThreadListViewModel+Observation.swift +++ b/Mail/Views/Thread List/ThreadListViewModel+Observation.swift @@ -125,8 +125,7 @@ extension ThreadListViewModel { let containAnyOf = NSPredicate(format: Self.containAnyOfUIDs, allThreadsUIDs) let realm = mailboxManager.getRealm() - let allThreads = realm.objects(Thread.self).filter(containAnyOf) - let oldThreads = allThreads.freezeIfNeeded() + let allThreads = realm.objects(Thread.self).filter(containAnyOf).sorted(by: \.date, ascending: false) observeFilteredThreadsToken = allThreads.observe(on: observeQueue) { [weak self] changes in guard let self else { @@ -136,13 +135,23 @@ extension ThreadListViewModel { switch changes { case .initial: break - case .update(let all, let deletionIndexes, _, let modificationIndexes): - refreshInFilterMode( - all: all, - old: oldThreads, - deletions: deletionIndexes, - changes: modificationIndexes - ) + case .update(let all, _, _, _): + let filteredThreads = Array(all.freezeIfNeeded()) + guard let newSections = sortThreadsIntoSections(threads: filteredThreads) + else { return } + + DispatchQueue.main.sync { + self.nextThreadIfNeeded(from: filteredThreads) + self.filteredThreads = filteredThreads + if self.filter != .all, + filteredThreads.count == 1, + !self.filter.accepts(thread: filteredThreads[0]) { + self.filter = .all + } + withAnimation { + self.sections = newSections + } + } case .error: break } @@ -153,62 +162,6 @@ extension ThreadListViewModel { observeFilteredThreadsToken?.invalidate() } - /// Update filtered threads on observation change. - private func refreshInFilterMode(all: Results, old: Results, deletions: [Int], changes: [Int]) { - for index in changes { - let updatedThread = all[index] - let uid = updatedThread.uid - - let threadToUpdate: Thread? = sections.reduce(nil as Thread?) { partialResult, section in - partialResult ?? section.threads.first { $0.uid == uid } - } - - let sectionToUpdate = sections.first { section in - section.threads.contains { $0.uid == uid } - } - - guard let threadToUpdate, - let sectionToUpdate else { - continue - } - - let threadToUpdateIndex = sectionToUpdate.threads.firstIndex(of: threadToUpdate) - guard let threadToUpdateIndex else { - continue - } - - sectionToUpdate.threads[threadToUpdateIndex] = updatedThread.freeze() - - Task { - await MainActor.run { - objectWillChange.send() - } - } - } - for index in deletions { - let deletedThread = old[index] - let uid = deletedThread.uid - - let sectionToUpdate = sections.first { section in - section.threads.contains { $0.uid == uid } - } - - guard let sectionToUpdate else { - continue - } - - sectionToUpdate.threads.removeAll { - $0.uid == uid - } - - Task { - await MainActor.run { - objectWillChange.send() - } - } - } - } - // MARK: - Observe unread count /// Observe the unread count to disable filtering when it reaches 0 From 89579ab863c2d84ac175ac4bd0725f1e1bc74cab Mon Sep 17 00:00:00 2001 From: Ambroise Decouttere Date: Thu, 3 Aug 2023 10:08:50 +0200 Subject: [PATCH 4/4] fix(UnreadFilter): Clean code --- .../ThreadListViewModel+Observation.swift | 54 ++++++++----------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/Mail/Views/Thread List/ThreadListViewModel+Observation.swift b/Mail/Views/Thread List/ThreadListViewModel+Observation.swift index 1dd75c60d..9136e6d39 100644 --- a/Mail/Views/Thread List/ThreadListViewModel+Observation.swift +++ b/Mail/Views/Thread List/ThreadListViewModel+Observation.swift @@ -61,21 +61,7 @@ extension ThreadListViewModel { } } case .update(let results, _, _, _): - let filteredThreads = Array(results.freezeIfNeeded()) - guard let newSections = sortThreadsIntoSections(threads: filteredThreads) else { return } - - DispatchQueue.main.sync { - self.nextThreadIfNeeded(from: filteredThreads) - self.filteredThreads = filteredThreads - if self.filter != .all, - filteredThreads.count == 1, - !self.filter.accepts(thread: filteredThreads[0]) { - self.filter = .all - } - withAnimation { - self.sections = newSections - } - } + updateThreadResults(results: results) case .error: break } @@ -107,6 +93,25 @@ extension ThreadListViewModel { observationLastUpdateToken?.invalidate() } + private func updateThreadResults(results: Results) { + let filteredThreads = Array(results.freezeIfNeeded()) + guard let newSections = sortThreadsIntoSections(threads: filteredThreads) + else { return } + + DispatchQueue.main.sync { + self.nextThreadIfNeeded(from: filteredThreads) + self.filteredThreads = filteredThreads + if self.filter != .all, + filteredThreads.count == 1, + !self.filter.accepts(thread: filteredThreads[0]) { + self.filter = .all + } + withAnimation { + self.sections = newSections + } + } + } + // MARK: - Observe filtered results static let containAnyOfUIDs = "uid IN %@" @@ -135,23 +140,8 @@ extension ThreadListViewModel { switch changes { case .initial: break - case .update(let all, _, _, _): - let filteredThreads = Array(all.freezeIfNeeded()) - guard let newSections = sortThreadsIntoSections(threads: filteredThreads) - else { return } - - DispatchQueue.main.sync { - self.nextThreadIfNeeded(from: filteredThreads) - self.filteredThreads = filteredThreads - if self.filter != .all, - filteredThreads.count == 1, - !self.filter.accepts(thread: filteredThreads[0]) { - self.filter = .all - } - withAnimation { - self.sections = newSections - } - } + case .update(let results, _, _, _): + updateThreadResults(results: results) case .error: break }