From 46b214db965230a33d2a18a4fb6bc32b00e1bfe6 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Fri, 4 Oct 2024 14:23:01 +0200 Subject: [PATCH 1/2] feat(AutocompletionView): Give higher priority to Infomaniak Contacts --- Mail/Views/New Message/AutocompletionView.swift | 16 +++++++++++++++- Mail/Views/Search/SearchViewModel.swift | 6 +++++- .../Cache/ContactManager/ContactManager+DB.swift | 14 +++++++++----- MailCore/Models/Recipient.swift | 2 +- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/Mail/Views/New Message/AutocompletionView.swift b/Mail/Views/New Message/AutocompletionView.swift index 93b303cb1..e941d153d 100644 --- a/Mail/Views/New Message/AutocompletionView.swift +++ b/Mail/Views/New Message/AutocompletionView.swift @@ -24,6 +24,8 @@ import RealmSwift import SwiftUI struct AutocompletionView: View { + private static let maxAutocompleteCount = 10 + @EnvironmentObject private var mailboxManager: MailboxManager @State private var shouldAddUserProposal = false @@ -67,7 +69,11 @@ struct AutocompletionView: View { private func updateAutocompletion(_ search: String) { let trimmedSearch = search.trimmingCharacters(in: .whitespacesAndNewlines) - let autocompleteContacts = mailboxManager.contactManager.frozenContacts(matching: trimmedSearch, fetchLimit: nil) + let autocompleteContacts = mailboxManager.contactManager.frozenContacts( + matching: trimmedSearch, + fetchLimit: Self.maxAutocompleteCount, + sorted: sortByRemoteAndName + ) var autocompleteRecipients = autocompleteContacts.map { Recipient(email: $0.email, name: $0.name) } let realResults = autocompleteRecipients.filter { !addedRecipients.map(\.email).contains($0.email) } @@ -82,6 +88,14 @@ struct AutocompletionView: View { autocompletion = autocompleteRecipients } } + + private func sortByRemoteAndName(lhs: MergedContact, rhs: MergedContact) -> Bool { + if lhs.isRemote != rhs.isRemote { + return lhs.isRemote && !rhs.isRemote + } else { + return lhs.name.localizedStandardCompare(rhs.name) == .orderedAscending + } + } } #Preview { diff --git a/Mail/Views/Search/SearchViewModel.swift b/Mail/Views/Search/SearchViewModel.swift index 2203e440b..25146405e 100644 --- a/Mail/Views/Search/SearchViewModel.swift +++ b/Mail/Views/Search/SearchViewModel.swift @@ -143,7 +143,11 @@ final class SearchViewModel: ObservableObject, ThreadListable { } func updateContactSuggestion() { - let autocompleteContacts = mailboxManager.contactManager.frozenContacts(matching: searchValue, fetchLimit: nil) + let autocompleteContacts = mailboxManager.contactManager.frozenContacts( + matching: searchValue, + fetchLimit: nil, + sorted: nil + ) var autocompleteRecipients = autocompleteContacts.map { Recipient(email: $0.email, name: $0.name).freezeIfNeeded() } // Append typed email diff --git a/MailCore/Cache/ContactManager/ContactManager+DB.swift b/MailCore/Cache/ContactManager/ContactManager+DB.swift index b0d09cd33..0e02c1122 100644 --- a/MailCore/Cache/ContactManager/ContactManager+DB.swift +++ b/MailCore/Cache/ContactManager/ContactManager+DB.swift @@ -26,7 +26,8 @@ public protocol ContactFetchable { /// - string: input string to match against email and name /// - fetchLimit: limit the query by default to limit memory footprint /// - Returns: The collection of matching contacts. - func frozenContacts(matching string: String, fetchLimit: Int?) -> any Collection + func frozenContacts(matching string: String, fetchLimit: Int?, sorted: ((MergedContact, MergedContact) -> Bool)?) + -> any Collection /// Get a contact from a given transactionable func getContact(for correspondent: any Correspondent, transactionable: Transactionable) -> MergedContact? @@ -49,7 +50,8 @@ public extension ContactManager { /// - string: input string to match against email and name /// - fetchLimit: limit the query by default to limit memory footprint /// - Returns: The collection of matching contacts. Frozen. - func frozenContacts(matching string: String, fetchLimit: Int?) -> any Collection { + func frozenContacts(matching string: String, fetchLimit: Int?, + sorted: ((MergedContact, MergedContact) -> Bool)?) -> any Collection { var lazyResults = fetchResults(ofType: MergedContact.self) { partial in partial } @@ -57,10 +59,12 @@ public extension ContactManager { .filter(Self.searchContactInsensitivePredicate, string, string) .freeze() - let fetchLimit = min(lazyResults.count, fetchLimit ?? Self.contactFetchLimit) + var sortedIfNecessary: any Collection = lazyResults + if let sorted { + sortedIfNecessary = sortedIfNecessary.sorted(by: sorted) + } - let limitedResults = lazyResults[0 ..< fetchLimit] - return limitedResults + return sortedIfNecessary.prefix(fetchLimit ?? Self.contactFetchLimit) } func getContact(for correspondent: any Correspondent) -> MergedContact? { diff --git a/MailCore/Models/Recipient.swift b/MailCore/Models/Recipient.swift index e16000d94..affe22ae1 100644 --- a/MailCore/Models/Recipient.swift +++ b/MailCore/Models/Recipient.swift @@ -101,7 +101,7 @@ public final class Recipient: EmbeddedObject, Correspondent, Codable { let isAnAlias = mailboxManager.mailbox.aliases.contains(email) - let isContact = !(mailboxManager.contactManager.frozenContacts(matching: email, fetchLimit: nil)).isEmpty + let isContact = !(mailboxManager.contactManager.frozenContacts(matching: email, fetchLimit: nil, sorted: nil)).isEmpty return !isKnownDomain && !isMailerDeamon && !isAnAlias && !isContact } From c2ae5921ded972dec1929abebd5eb7d0c80f235d Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Fri, 4 Oct 2024 15:03:08 +0200 Subject: [PATCH 2/2] chore: Update tests with new API --- MailTests/Contacts/UTContactManager.swift | 2 +- MailTests/Folders/ITFolderListViewModel.swift | 3 ++- MailTests/Search/ITSearchViewModel.swift | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/MailTests/Contacts/UTContactManager.swift b/MailTests/Contacts/UTContactManager.swift index 157bd0b54..e67e3ebfe 100644 --- a/MailTests/Contacts/UTContactManager.swift +++ b/MailTests/Contacts/UTContactManager.swift @@ -73,7 +73,7 @@ final class UTContactManager: XCTestCase { generateFakeContacts(count: 100_000) measure { // WHEN - let matchingContacts = contactManager.frozenContacts(matching: "mail", fetchLimit: nil) + let matchingContacts = contactManager.frozenContacts(matching: "mail", fetchLimit: nil, sorted: nil) // THEN XCTAssertEqual(matchingContacts.isEmpty, false) } diff --git a/MailTests/Folders/ITFolderListViewModel.swift b/MailTests/Folders/ITFolderListViewModel.swift index 7fa063622..8a9e8a1c2 100644 --- a/MailTests/Folders/ITFolderListViewModel.swift +++ b/MailTests/Folders/ITFolderListViewModel.swift @@ -33,7 +33,8 @@ struct MCKContactManageable_FolderListViewModel: ContactManageable, MCKTransacti let transactionExecutor: Transactionable! - func frozenContacts(matching string: String, fetchLimit: Int?) -> any Collection { [] } + func frozenContacts(matching string: String, fetchLimit: Int?, + sorted: ((MergedContact, MergedContact) -> Bool)?) -> any Collection { [] } func getContact(for correspondent: any MailCore.Correspondent) -> MailCore.MergedContact? { nil } diff --git a/MailTests/Search/ITSearchViewModel.swift b/MailTests/Search/ITSearchViewModel.swift index 9cd281d05..d0d2d3a97 100644 --- a/MailTests/Search/ITSearchViewModel.swift +++ b/MailTests/Search/ITSearchViewModel.swift @@ -39,7 +39,8 @@ struct MCKContactManageable_SearchViewModel: ContactManageable, MCKTransactionab transactionExecutor = TransactionExecutor(realmAccessible: realmAccessor) } - func frozenContacts(matching string: String, fetchLimit: Int?) -> any Collection { [] } + func frozenContacts(matching string: String, fetchLimit: Int?, + sorted: ((MergedContact, MergedContact) -> Bool)?) -> any Collection { [] } func getContact(for correspondent: any MailCore.Correspondent) -> MailCore.MergedContact? { nil }