From 5587a4ade86cf1e3c9790c10cc9d0e35c53d8c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Wed, 7 Jun 2023 18:16:55 +0200 Subject: [PATCH 1/8] feat: disable observation on filter = .unread --- Mail/Views/Thread List/ThreadListView.swift | 2 +- .../ThreadListViewModel+Observation.swift | 105 ++++++++++++++++++ .../Thread List/ThreadListViewModel.swift | 85 +++----------- 3 files changed, 121 insertions(+), 71 deletions(-) create mode 100644 Mail/Views/Thread List/ThreadListViewModel+Observation.swift diff --git a/Mail/Views/Thread List/ThreadListView.swift b/Mail/Views/Thread List/ThreadListView.swift index aca48d26a..3bc42ff71 100644 --- a/Mail/Views/Thread List/ThreadListView.swift +++ b/Mail/Views/Thread List/ThreadListView.swift @@ -144,7 +144,7 @@ struct ThreadListView: View { ProgressView() .id(UUID()) .frame(maxWidth: .infinity) - } else if displayLoadMoreButton { + } else if displayLoadMoreButton && !viewModel.filterUnreadOn { MailButton(label: MailResourcesStrings.Localizable.buttonLoadMore) { withAnimation { isLoadingMore = true diff --git a/Mail/Views/Thread List/ThreadListViewModel+Observation.swift b/Mail/Views/Thread List/ThreadListViewModel+Observation.swift new file mode 100644 index 000000000..e914b816a --- /dev/null +++ b/Mail/Views/Thread List/ThreadListViewModel+Observation.swift @@ -0,0 +1,105 @@ +// +/* + Infomaniak Mail - iOS App + Copyright (C) 2022 Infomaniak Network SA + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +import MailCore +import RealmSwift +import SwiftUI + +extension ThreadListViewModel { + // MARK: - Observe global changes + + func observeChanges(animateInitialThreadChanges: Bool = false) { + stopObserveChanges() + + guard let folder = folder.thaw() else { + sections = [] + return + } + + let threadResults: Results + if let predicate = filter.predicate { + threadResults = folder.threads.filter(predicate + " OR uid == %@", selectedThread?.uid ?? "") + .sorted(by: \.date, ascending: false) + } else { + threadResults = folder.threads.sorted(by: \.date, ascending: false) + } + + observationThreadToken = threadResults.observe(on: observeQueue) { [weak self] changes in + guard let self = self else { + return + } + + switch changes { + case .initial(let results): + let filteredThreads = Array(results.freezeIfNeeded()) + guard let newSections = self.sortThreadsIntoSections(threads: filteredThreads) else { return } + + DispatchQueue.main.sync { + self.filteredThreads = filteredThreads + withAnimation(animateInitialThreadChanges ? .default : nil) { + self.sections = newSections + } + } + case .update(let results, _, _, _): + let filteredThreads = Array(results.freezeIfNeeded()) + guard let newSections = self.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]) != true { + self.filter = .all + } + withAnimation { + self.sections = newSections + } + } + case .error: + break + } + + // We only apply the first update when in "unread" mode + if self.filter == .unseen { + self.stopObserveChanges() + } + } + observationLastUpdateToken = folder.observe(keyPaths: [\Folder.lastUpdate], on: .main) { [weak self] changes in + switch changes { + case .change(let folder, _): + withAnimation { + self?.lastUpdate = folder.lastUpdate + } + default: + break + } + } + } + + func stopObserveChanges() { + observationThreadToken?.invalidate() + observationLastUpdateToken?.invalidate() + } + + // MARK: - Observe unread count + + func observeUnreadCount() { + // TODO observe the unread count to disable filtering when it reaches 0 + } +} diff --git a/Mail/Views/Thread List/ThreadListViewModel.swift b/Mail/Views/Thread List/ThreadListViewModel.swift index 618de2814..c6580b3c0 100644 --- a/Mail/Views/Thread List/ThreadListViewModel.swift +++ b/Mail/Views/Thread List/ThreadListViewModel.swift @@ -17,6 +17,7 @@ */ import Foundation +import InfomaniakCore import InfomaniakCoreUI import MailCore import MailResources @@ -25,7 +26,7 @@ import SwiftUI typealias Thread = MailCore.Thread -class DateSection: Identifiable { +final class DateSection: Identifiable { enum ReferenceDate { case future, today, yesterday, thisWeek, lastWeek, thisMonth, older(Date) @@ -43,7 +44,7 @@ class DateSection: Identifiable { return .init(start: .lastWeek.startOfWeek, end: .lastWeek.endOfWeek) case .thisMonth: return .init(start: .now.startOfMonth, end: .now.endOfMonth) - case let .older(date): + case .older(let date): return .init(start: date.startOfMonth, end: date.endOfMonth) } } @@ -62,7 +63,7 @@ class DateSection: Identifiable { return MailResourcesStrings.Localizable.threadListSectionLastWeek case .thisMonth: return MailResourcesStrings.Localizable.threadListSectionThisMonth - case let .older(date): + case .older(let date): let formatStyle = Calendar.current.isDate(date, equalTo: .now, toGranularity: .year) ? Constants.shortDateFormatter : Constants.longDateFormatter @@ -123,18 +124,21 @@ class DateSection: Identifiable { var scrollViewProxy: ScrollViewProxy? var isCompact: Bool - private var observationThreadToken: NotificationToken? - private var observationLastUpdateToken: NotificationToken? - private let observeQueue = DispatchQueue(label: "com.infomaniak.thread-results", qos: .userInteractive) + var observationUnreadToken: NotificationToken? + var observationThreadToken: NotificationToken? + var observationLastUpdateToken: NotificationToken? + let observeQueue = DispatchQueue(label: "com.infomaniak.thread-results", qos: .userInteractive) @Published var filter = Filter.all { didSet { Task { observeChanges(animateInitialThreadChanges: true) - if let topThread = sections.first?.threads.first?.id { - withAnimation { - self.scrollViewProxy?.scrollTo(topThread, anchor: .top) - } + + guard let topThread = sections.first?.threads.first?.id else { + return + } + withAnimation { + self.scrollViewProxy?.scrollTo(topThread, anchor: .top) } } } @@ -201,65 +205,6 @@ class DateSection: Identifiable { } } - func observeChanges(animateInitialThreadChanges: Bool = false) { - observationThreadToken?.invalidate() - observationLastUpdateToken?.invalidate() - guard let folder = folder.thaw() else { - sections = [] - return - } - - let threadResults: Results - if let predicate = filter.predicate { - threadResults = folder.threads.filter(predicate + " OR uid == %@", selectedThread?.uid ?? "") - .sorted(by: \.date, ascending: false) - } else { - threadResults = folder.threads.sorted(by: \.date, ascending: false) - } - - observationThreadToken = threadResults.observe(on: observeQueue) { [weak self] changes in - switch changes { - case let .initial(results): - let filteredThreads = Array(results.freezeIfNeeded()) - guard let newSections = self?.sortThreadsIntoSections(threads: filteredThreads) else { return } - - DispatchQueue.main.sync { - self?.filteredThreads = filteredThreads - withAnimation(animateInitialThreadChanges ? .default : nil) { - self?.sections = newSections - } - } - case let .update(results, _, _, _): - let filteredThreads = Array(results.freezeIfNeeded()) - guard let newSections = self?.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]) != true { - self?.filter = .all - } - withAnimation { - self?.sections = newSections - } - } - case .error: - break - } - } - observationLastUpdateToken = folder.observe(keyPaths: [\Folder.lastUpdate], on: .main) { [weak self] changes in - switch changes { - case let .change(folder, _): - withAnimation { - self?.lastUpdate = folder.lastUpdate - } - default: - break - } - } - } - func nextThreadIfNeeded(from threads: [Thread]) { guard !isCompact, !threads.isEmpty, @@ -269,7 +214,7 @@ class DateSection: Identifiable { selectedThread = threads[validIndex] } - private func sortThreadsIntoSections(threads: [Thread]) -> [DateSection]? { + func sortThreadsIntoSections(threads: [Thread]) -> [DateSection]? { var newSections = [DateSection]() var currentSection: DateSection? From 6d55d6f57c82a542277df718d851492cd27e0a4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Thu, 8 Jun 2023 09:20:08 +0200 Subject: [PATCH 2/8] feat: observe unread count to disable filtering when reaching zero --- .../ThreadListViewModel+Observation.swift | 44 +++++++++++++++---- .../Thread List/ThreadListViewModel.swift | 10 +++++ 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/Mail/Views/Thread List/ThreadListViewModel+Observation.swift b/Mail/Views/Thread List/ThreadListViewModel+Observation.swift index e914b816a..1f036b8e8 100644 --- a/Mail/Views/Thread List/ThreadListViewModel+Observation.swift +++ b/Mail/Views/Thread List/ThreadListViewModel+Observation.swift @@ -22,14 +22,11 @@ import RealmSwift import SwiftUI extension ThreadListViewModel { - // MARK: - Observe global changes - - func observeChanges(animateInitialThreadChanges: Bool = false) { - stopObserveChanges() - + + private func threadResults() -> Results? { guard let folder = folder.thaw() else { sections = [] - return + return nil } let threadResults: Results @@ -39,8 +36,16 @@ extension ThreadListViewModel { } else { threadResults = folder.threads.sorted(by: \.date, ascending: false) } + + return threadResults + } + + // MARK: - Observe global changes - observationThreadToken = threadResults.observe(on: observeQueue) { [weak self] changes in + func observeChanges(animateInitialThreadChanges: Bool = false) { + stopObserveChanges() + + observationThreadToken = threadResults()?.observe(on: observeQueue) { [weak self] changes in guard let self = self else { return } @@ -99,7 +104,30 @@ extension ThreadListViewModel { // MARK: - Observe unread count + /// Observe the unread count to disable filtering when it reaches 0 func observeUnreadCount() { - // TODO observe the unread count to disable filtering when it reaches 0 + stopObserveUnread() + + observationUnreadToken = threadResults()?.observe(on: observeQueue) { [weak self] changes in + guard let self = self else { + return + } + + switch changes { + case .initial(let changes), .update(let changes, _, _, _): + let count = changes.where { $0.unseenMessages > 0 }.count + Task { + await MainActor.run { + self.unreadCount = count + } + } + case .error: + break + } + } + } + + func stopObserveUnread() { + observationUnreadToken?.invalidate() } } diff --git a/Mail/Views/Thread List/ThreadListViewModel.swift b/Mail/Views/Thread List/ThreadListViewModel.swift index c6580b3c0..72f94b1cd 100644 --- a/Mail/Views/Thread List/ThreadListViewModel.swift +++ b/Mail/Views/Thread List/ThreadListViewModel.swift @@ -129,6 +129,15 @@ final class DateSection: Identifiable { var observationLastUpdateToken: NotificationToken? let observeQueue = DispatchQueue(label: "com.infomaniak.thread-results", qos: .userInteractive) + @Published var unreadCount: Int = 0 { + didSet { + // Disable filter if we have no unread emails left + if unreadCount == 0 && filterUnreadOn { + filterUnreadOn = false + } + } + } + @Published var filter = Filter.all { didSet { Task { @@ -165,6 +174,7 @@ final class DateSection: Identifiable { lastUpdate = folder.lastUpdate self.isCompact = isCompact observeChanges() + observeUnreadCount() } func fetchThreads() async { From 1add07d211ed5562ba0f8a9aefa38b5e4cfdde9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Thu, 8 Jun 2023 09:29:33 +0200 Subject: [PATCH 3/8] chore: sonarcloud feedback --- Mail/Views/Thread List/ThreadListViewModel+Observation.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Mail/Views/Thread List/ThreadListViewModel+Observation.swift b/Mail/Views/Thread List/ThreadListViewModel+Observation.swift index 1f036b8e8..cf0a28429 100644 --- a/Mail/Views/Thread List/ThreadListViewModel+Observation.swift +++ b/Mail/Views/Thread List/ThreadListViewModel+Observation.swift @@ -68,8 +68,9 @@ extension ThreadListViewModel { DispatchQueue.main.sync { self.nextThreadIfNeeded(from: filteredThreads) self.filteredThreads = filteredThreads - if self.filter != .all && filteredThreads.count == 1 - && self.filter.accepts(thread: filteredThreads[0]) != true { + if self.filter != .all, + filteredThreads.count == 1, + !self.filter.accepts(thread: filteredThreads[0]) { self.filter = .all } withAnimation { From 7bb6bb4ced86a4671307e34c78919b0d69d9f2bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Thu, 8 Jun 2023 17:26:16 +0200 Subject: [PATCH 4/8] feat: working filtered unread as speced (needs cleanup) --- .../ThreadListViewModel+Observation.swift | 107 ++++++++++++++++-- .../Thread List/ThreadListViewModel.swift | 23 +++- 2 files changed, 117 insertions(+), 13 deletions(-) diff --git a/Mail/Views/Thread List/ThreadListViewModel+Observation.swift b/Mail/Views/Thread List/ThreadListViewModel+Observation.swift index cf0a28429..c18d5798a 100644 --- a/Mail/Views/Thread List/ThreadListViewModel+Observation.swift +++ b/Mail/Views/Thread List/ThreadListViewModel+Observation.swift @@ -21,8 +21,8 @@ import MailCore import RealmSwift import SwiftUI + extension ThreadListViewModel { - private func threadResults() -> Results? { guard let folder = folder.thaw() else { sections = [] @@ -31,15 +31,16 @@ extension ThreadListViewModel { let threadResults: Results if let predicate = filter.predicate { - threadResults = folder.threads.filter(predicate + " OR uid == %@", selectedThread?.uid ?? "") + threadResults = folder.threads + .filter(predicate + " OR uid == %@", selectedThread?.uid ?? "") .sorted(by: \.date, ascending: false) } else { threadResults = folder.threads.sorted(by: \.date, ascending: false) } - + return threadResults } - + // MARK: - Observe global changes func observeChanges(animateInitialThreadChanges: Bool = false) { @@ -103,6 +104,92 @@ extension ThreadListViewModel { observationLastUpdateToken?.invalidate() } + // MARK: - Observe filtered results + func observeFilteredResults() { + stopObserveFiltered() + + let allThreadsUIDs = threadResults()?.reduce([String](), { partialResult, thread in + return partialResult + [thread.uid] + }) + + guard let allThreadsUIDs = allThreadsUIDs else { + fatalError("woops") + } + + let containAnyOf = NSPredicate(format: "uid IN %@", allThreadsUIDs) + + let realm = self.mailboxManager.getRealm() + let allThreads = realm.objects(Thread.self).filter(containAnyOf) + + + testToken = allThreads.observe(on: observeQueue) { [weak self] changes in + guard let self = self else { + return + } + + print("change from displayed thread ") + switch changes { + case .initial(let all): + print("aa :\(all.count)") + + case .update(let all, _, _, let modificationIndexes): + print("bb :\(all.count), idx:\(modificationIndexes)") + refreshInFilterMode(all: all, changes: modificationIndexes) + + case .error: + break + } + } + + } + + func stopObserveFiltered() { + testToken?.invalidate() + } + + // TODO clean + /// Refresh filteredThreads when observation is disabled + private func refreshInFilterMode(all: Results, changes: [Int]) { + + for index in changes { + print("index :\(index) self.filteredThreads.count:\(filteredThreads.count)") + let updatedThread = all[index] + + let UID = updatedThread.uid + + let threadToUpdate2: Thread? = self.sections.reduce(nil as Thread?) { partialResult, section in + partialResult ?? section.threads.first(where: { $0.uid == UID }) + } + + guard let threadToUpdate2 = threadToUpdate2 else { + fatalError("woops") + } + + let sectionToUpdate = self.sections.first { section in + ((section.threads.first(where: { $0.uid == UID })) != nil) ? true : false + } + guard let sectionToUpdate = sectionToUpdate else { + fatalError("woops") + } + + let indexSectionToUpdate = self.sections.firstIndex(of: sectionToUpdate) + let threadIndexToUpdate = sectionToUpdate.threads.firstIndex(of: threadToUpdate2) + guard let threadIndexToUpdate = threadIndexToUpdate else { + fatalError("woops") + } + + sectionToUpdate.threads[threadIndexToUpdate] = updatedThread.freeze() + + + Task { + await MainActor.run { + objectWillChange.send() + } + } + + } + } + // MARK: - Observe unread count /// Observe the unread count to disable filtering when it reaches 0 @@ -113,22 +200,24 @@ extension ThreadListViewModel { guard let self = self else { return } - + switch changes { - case .initial(let changes), .update(let changes, _, _, _): - let count = changes.where { $0.unseenMessages > 0 }.count + case .initial(let all), .update(let all, _, _, _): + let unreadCount = all.where { $0.unseenMessages > 0 }.count Task { await MainActor.run { - self.unreadCount = count + self.unreadCount = unreadCount } } + case .error: break } } } - + func stopObserveUnread() { observationUnreadToken?.invalidate() } + } diff --git a/Mail/Views/Thread List/ThreadListViewModel.swift b/Mail/Views/Thread List/ThreadListViewModel.swift index 72f94b1cd..3553445db 100644 --- a/Mail/Views/Thread List/ThreadListViewModel.swift +++ b/Mail/Views/Thread List/ThreadListViewModel.swift @@ -26,7 +26,11 @@ import SwiftUI typealias Thread = MailCore.Thread -final class DateSection: Identifiable { +final class DateSection: Identifiable, Equatable { + static func == (lhs: DateSection, rhs: DateSection) -> Bool { + lhs.id == rhs.id && lhs.title == rhs.title && lhs.threads == rhs.threads + } + enum ReferenceDate { case future, today, yesterday, thisWeek, lastWeek, thisMonth, older(Date) @@ -124,23 +128,34 @@ final class DateSection: Identifiable { var scrollViewProxy: ScrollViewProxy? var isCompact: Bool + var testToken: NotificationToken? + var observationUnreadToken: NotificationToken? var observationThreadToken: NotificationToken? var observationLastUpdateToken: NotificationToken? let observeQueue = DispatchQueue(label: "com.infomaniak.thread-results", qos: .userInteractive) - @Published var unreadCount: Int = 0 { + private let loadNextPageThreshold = 10 + + @Published var unreadCount = 0 { didSet { // Disable filter if we have no unread emails left + print("unreadCount :\(unreadCount)") if unreadCount == 0 && filterUnreadOn { filterUnreadOn = false } } } - + @Published var filter = Filter.all { didSet { Task { + if filter == .unseen { + observeFilteredResults() + } else { + stopObserveFiltered() + } + observeChanges(animateInitialThreadChanges: true) guard let topThread = sections.first?.threads.first?.id else { @@ -162,7 +177,7 @@ final class DateSection: Identifiable { } } - private let loadNextPageThreshold = 10 + // MARK: Init init( mailboxManager: MailboxManager, From 9942b62b5e1cf9f8dffeacf568d66c383fb9a4cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Fri, 9 Jun 2023 09:01:27 +0200 Subject: [PATCH 5/8] chore: cleaned code for production --- Mail/Views/Thread List/ThreadListView.swift | 2 +- .../ThreadListViewModel+Observation.swift | 95 ++++++++----------- .../Thread List/ThreadListViewModel.swift | 10 +- 3 files changed, 47 insertions(+), 60 deletions(-) diff --git a/Mail/Views/Thread List/ThreadListView.swift b/Mail/Views/Thread List/ThreadListView.swift index 3bc42ff71..98c48f0b1 100644 --- a/Mail/Views/Thread List/ThreadListView.swift +++ b/Mail/Views/Thread List/ThreadListView.swift @@ -57,7 +57,7 @@ struct ThreadListView: View { @Binding private var editedMessageDraft: Draft? @Binding private var messageReply: MessageReply? - + private var shouldDisplayEmptyView: Bool { viewModel.folder.lastUpdate != nil && viewModel.sections.isEmpty && !viewModel.isLoadingPage } diff --git a/Mail/Views/Thread List/ThreadListViewModel+Observation.swift b/Mail/Views/Thread List/ThreadListViewModel+Observation.swift index c18d5798a..45cfd43fe 100644 --- a/Mail/Views/Thread List/ThreadListViewModel+Observation.swift +++ b/Mail/Views/Thread List/ThreadListViewModel+Observation.swift @@ -1,4 +1,3 @@ -// /* Infomaniak Mail - iOS App Copyright (C) 2022 Infomaniak Network SA @@ -21,7 +20,6 @@ import MailCore import RealmSwift import SwiftUI - extension ThreadListViewModel { private func threadResults() -> Results? { guard let folder = folder.thaw() else { @@ -105,91 +103,81 @@ extension ThreadListViewModel { } // MARK: - Observe filtered results + + static let containAnyOfUIDs = "uid IN %@" + + /// Observe filtered threads, when global observation is disabled. func observeFilteredResults() { - stopObserveFiltered() - - let allThreadsUIDs = threadResults()?.reduce([String](), { partialResult, thread in - return partialResult + [thread.uid] - }) - + stopObserveFilteredThreads() + + let allThreadsUIDs = threadResults()?.reduce([String]()) { partialResult, thread in + partialResult + [thread.uid] + } + guard let allThreadsUIDs = allThreadsUIDs else { - fatalError("woops") + return } - - let containAnyOf = NSPredicate(format: "uid IN %@", allThreadsUIDs) - - let realm = self.mailboxManager.getRealm() + + let containAnyOf = NSPredicate(format: Self.containAnyOfUIDs, allThreadsUIDs) + let realm = mailboxManager.getRealm() let allThreads = realm.objects(Thread.self).filter(containAnyOf) - - - testToken = allThreads.observe(on: observeQueue) { [weak self] changes in + + observeFilteredThreadsToken = allThreads.observe(on: observeQueue) { [weak self] changes in guard let self = self else { return } - print("change from displayed thread ") switch changes { - case .initial(let all): - print("aa :\(all.count)") - + case .initial(_): + break case .update(let all, _, _, let modificationIndexes): - print("bb :\(all.count), idx:\(modificationIndexes)") refreshInFilterMode(all: all, changes: modificationIndexes) - case .error: break } } - } - - func stopObserveFiltered() { - testToken?.invalidate() + + func stopObserveFilteredThreads() { + observeFilteredThreadsToken?.invalidate() } - - // TODO clean - /// Refresh filteredThreads when observation is disabled - private func refreshInFilterMode(all: Results, changes: [Int]) { + /// Update filtered threads on observation change. + private func refreshInFilterMode(all: Results, changes: [Int]) { for index in changes { - print("index :\(index) self.filteredThreads.count:\(filteredThreads.count)") let updatedThread = all[index] - let UID = updatedThread.uid - - let threadToUpdate2: Thread? = self.sections.reduce(nil as Thread?) { partialResult, section in + + let threadToUpdate: Thread? = sections.reduce(nil as Thread?) { partialResult, section in partialResult ?? section.threads.first(where: { $0.uid == UID }) } - - guard let threadToUpdate2 = threadToUpdate2 else { - fatalError("woops") + + guard let threadToUpdate = threadToUpdate else { + return } - - let sectionToUpdate = self.sections.first { section in - ((section.threads.first(where: { $0.uid == UID })) != nil) ? true : false + + let sectionToUpdate = sections.first { section in + (section.threads.first(where: { $0.uid == UID })) != nil } guard let sectionToUpdate = sectionToUpdate else { - fatalError("woops") + return } - - let indexSectionToUpdate = self.sections.firstIndex(of: sectionToUpdate) - let threadIndexToUpdate = sectionToUpdate.threads.firstIndex(of: threadToUpdate2) - guard let threadIndexToUpdate = threadIndexToUpdate else { - fatalError("woops") + + let threadToUpdateIndex = sectionToUpdate.threads.firstIndex(of: threadToUpdate) + guard let threadToUpdateIndex = threadToUpdateIndex else { + return } - - sectionToUpdate.threads[threadIndexToUpdate] = updatedThread.freeze() - - + + sectionToUpdate.threads[threadToUpdateIndex] = updatedThread.freeze() + Task { await MainActor.run { objectWillChange.send() } } - } } - + // MARK: - Observe unread count /// Observe the unread count to disable filtering when it reaches 0 @@ -209,7 +197,7 @@ extension ThreadListViewModel { self.unreadCount = unreadCount } } - + case .error: break } @@ -219,5 +207,4 @@ extension ThreadListViewModel { func stopObserveUnread() { observationUnreadToken?.invalidate() } - } diff --git a/Mail/Views/Thread List/ThreadListViewModel.swift b/Mail/Views/Thread List/ThreadListViewModel.swift index 3553445db..a70f0ed37 100644 --- a/Mail/Views/Thread List/ThreadListViewModel.swift +++ b/Mail/Views/Thread List/ThreadListViewModel.swift @@ -128,8 +128,9 @@ final class DateSection: Identifiable, Equatable { var scrollViewProxy: ScrollViewProxy? var isCompact: Bool - var testToken: NotificationToken? - + /// Observe a filtered thread + var observeFilteredThreadsToken: NotificationToken? + /// Observe unread count var observationUnreadToken: NotificationToken? var observationThreadToken: NotificationToken? var observationLastUpdateToken: NotificationToken? @@ -140,7 +141,6 @@ final class DateSection: Identifiable, Equatable { @Published var unreadCount = 0 { didSet { // Disable filter if we have no unread emails left - print("unreadCount :\(unreadCount)") if unreadCount == 0 && filterUnreadOn { filterUnreadOn = false } @@ -153,9 +153,9 @@ final class DateSection: Identifiable, Equatable { if filter == .unseen { observeFilteredResults() } else { - stopObserveFiltered() + stopObserveFilteredThreads() } - + observeChanges(animateInitialThreadChanges: true) guard let topThread = sections.first?.threads.first?.id else { From 622cfcfd59cb62e59cb70a01c8e5da99c5f29190 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Fri, 9 Jun 2023 09:04:33 +0200 Subject: [PATCH 6/8] chore: Sonar Feedback --- .../ThreadListViewModel+Observation.swift | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Mail/Views/Thread List/ThreadListViewModel+Observation.swift b/Mail/Views/Thread List/ThreadListViewModel+Observation.swift index 45cfd43fe..fb48d5d9e 100644 --- a/Mail/Views/Thread List/ThreadListViewModel+Observation.swift +++ b/Mail/Views/Thread List/ThreadListViewModel+Observation.swift @@ -105,7 +105,7 @@ extension ThreadListViewModel { // MARK: - Observe filtered results static let containAnyOfUIDs = "uid IN %@" - + /// Observe filtered threads, when global observation is disabled. func observeFilteredResults() { stopObserveFilteredThreads() @@ -128,7 +128,7 @@ extension ThreadListViewModel { } switch changes { - case .initial(_): + case .initial: break case .update(let all, _, _, let modificationIndexes): refreshInFilterMode(all: all, changes: modificationIndexes) @@ -146,20 +146,18 @@ extension ThreadListViewModel { private func refreshInFilterMode(all: Results, changes: [Int]) { for index in changes { let updatedThread = all[index] - let UID = updatedThread.uid + let uid = updatedThread.uid let threadToUpdate: Thread? = sections.reduce(nil as Thread?) { partialResult, section in - partialResult ?? section.threads.first(where: { $0.uid == UID }) - } - - guard let threadToUpdate = threadToUpdate else { - return + partialResult ?? section.threads.first(where: { $0.uid == uid }) } let sectionToUpdate = sections.first { section in - (section.threads.first(where: { $0.uid == UID })) != nil + (section.threads.first(where: { $0.uid == uid })) != nil } - guard let sectionToUpdate = sectionToUpdate else { + + guard let threadToUpdate = threadToUpdate, + let sectionToUpdate = sectionToUpdate else { return } From 1bfae9c3ea782255ae84b1be8749329edca24cd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Fri, 9 Jun 2023 11:07:05 +0200 Subject: [PATCH 7/8] feat: prohibit swiftlint from linting derived data if present --- .swiftlint.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.swiftlint.yml b/.swiftlint.yml index b40c9c77f..98d81262b 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -33,3 +33,5 @@ opt_in_rules: excluded: - .tuist-bin/* - Derived/Sources/* + - DerivedData/ + - Derived/ From d38b8838822baa0a2f5ec3442a986a6e826b807f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Fri, 9 Jun 2023 11:20:32 +0200 Subject: [PATCH 8/8] chore: PR feedback --- Mail/Views/Thread List/ThreadListViewModel+Observation.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mail/Views/Thread List/ThreadListViewModel+Observation.swift b/Mail/Views/Thread List/ThreadListViewModel+Observation.swift index fb48d5d9e..37837d0a3 100644 --- a/Mail/Views/Thread List/ThreadListViewModel+Observation.swift +++ b/Mail/Views/Thread List/ThreadListViewModel+Observation.swift @@ -149,11 +149,11 @@ extension ThreadListViewModel { let uid = updatedThread.uid let threadToUpdate: Thread? = sections.reduce(nil as Thread?) { partialResult, section in - partialResult ?? section.threads.first(where: { $0.uid == uid }) + partialResult ?? section.threads.first { $0.uid == uid } } let sectionToUpdate = sections.first { section in - (section.threads.first(where: { $0.uid == uid })) != nil + section.threads.contains { $0.uid == uid } } guard let threadToUpdate = threadToUpdate,