From e4d381cb19e66135da028fd026d4deb137db2e72 Mon Sep 17 00:00:00 2001 From: Anson Chen Date: Wed, 14 Feb 2024 12:56:32 +0000 Subject: [PATCH] MAILIOS-4144, Device contacts don't be represented in composer suggestion list --- .../ContactPicker/ContactPicker.swift | 11 ++++ .../ComposeContentViewController.swift | 66 +++++++++---------- .../Compose/ViewModel/ComposeViewModel.swift | 44 +++++++------ 3 files changed, 66 insertions(+), 55 deletions(-) diff --git a/ProtonMail/ProtonMail/Components/APP_share/ContactPicker/ContactPicker.swift b/ProtonMail/ProtonMail/Components/APP_share/ContactPicker/ContactPicker.swift index 8577ac1a6..b2825e826 100644 --- a/ProtonMail/ProtonMail/Components/APP_share/ContactPicker/ContactPicker.swift +++ b/ProtonMail/ProtonMail/Components/APP_share/ContactPicker/ContactPicker.swift @@ -222,6 +222,17 @@ class ContactPicker: UIView, AccessibleView { // #pragma mark - Keyboard Notification Handling // + /// Reloads the list of contacts only if there is a query string already set. Call this function if more contacts are + /// available to be listed in the picker. + func reloadContactsList() { + guard let query = searchTableViewController?.queryString else { + return + } + // this forces to read the list of contacts again + let filteredContacts = self.filteredContacts(by: query) + searchTableViewController?.filteredContacts = filteredContacts + } + internal func reloadData() { self.contactCollectionView.selectedContacts.removeAll() diff --git a/ProtonMail/ProtonMail/ViewControllers/APP_share/Compose/ComposeContentView/ComposeContentViewController.swift b/ProtonMail/ProtonMail/ViewControllers/APP_share/Compose/ComposeContentView/ComposeContentViewController.swift index c13764639..fd0e348c9 100644 --- a/ProtonMail/ProtonMail/ViewControllers/APP_share/Compose/ComposeContentView/ComposeContentViewController.swift +++ b/ProtonMail/ProtonMail/ViewControllers/APP_share/Compose/ComposeContentView/ComposeContentViewController.swift @@ -20,6 +20,7 @@ // You should have received a copy of the GNU General Public License // along with Proton Mail. If not, see . +import Combine import MBProgressHUD import PromiseKit import ProtonCoreDataModel @@ -59,6 +60,7 @@ class ComposeContentViewController: HorizontallyScrollableWebViewContainer, Acce var pickedCallback: (([DraftEmailData]) -> Void)? var groupSubSelectionPresenter: ContactGroupSubSelectionActionSheetPresenter? private lazy var schemeHandler: ComposerSchemeHandler = .init(imageProxy: dependencies.imageProxy) + private var cancellables = Set() private let dependencies: Dependencies @@ -106,48 +108,42 @@ class ComposeContentViewController: HorizontallyScrollableWebViewContainer, Acce // load all contacts and groups viewModel.fetchContacts() - // TODO: move to view model - firstly { [weak self] () -> Promise in - self?.retrievePhoneContacts() ?? Promise() - }.done { [weak self] in - guard let self = self else { return } - - self.headerView?.toContactPicker?.reloadData() - self.headerView?.ccContactPicker?.reloadData() - self.headerView?.bccContactPicker?.reloadData() - - self.headerView?.toContactPicker?.contactCollectionView?.layoutIfNeeded() - self.headerView?.bccContactPicker?.contactCollectionView?.layoutIfNeeded() - self.headerView?.ccContactPicker?.contactCollectionView?.layoutIfNeeded() - - delay(0.5) { - // There is a height observer in ComposeContainerViewController - // If the tableview reload, the keyboard will be dismissed - switch self.viewModel.messageAction { - case .openDraft, .reply, .replyAll: - self.headerView?.notifyViewSize(true) - case .forward: - _ = self.headerView?.toContactPicker.becomeFirstResponder() - default: - _ = self.headerView?.toContactPicker.becomeFirstResponder() - } + viewModel.fetchPhoneContacts() + + viewModel + .contactsDidChangePublisher + .receive(on: DispatchQueue.main) + .sink { _ in + self.headerView?.toContactPicker?.reloadContactsList() + self.headerView?.ccContactPicker?.reloadContactsList() + self.headerView?.bccContactPicker?.reloadContactsList() + }.store(in: &cancellables) + + self.headerView?.toContactPicker?.reloadData() + self.headerView?.ccContactPicker?.reloadData() + self.headerView?.bccContactPicker?.reloadData() + + self.headerView?.toContactPicker?.contactCollectionView?.layoutIfNeeded() + self.headerView?.bccContactPicker?.contactCollectionView?.layoutIfNeeded() + self.headerView?.ccContactPicker?.contactCollectionView?.layoutIfNeeded() + + delay(0.5) { + // There is a height observer in ComposeContainerViewController + // If the tableview reload, the keyboard will be dismissed + switch self.viewModel.messageAction { + case .openDraft, .reply, .replyAll: + self.headerView?.notifyViewSize(true) + case .forward: + _ = self.headerView?.toContactPicker.becomeFirstResponder() + default: + _ = self.headerView?.toContactPicker.becomeFirstResponder() } - - }.catch { _ in } self.viewModel.markAsRead() generateAccessibilityIdentifiers() } - private func retrievePhoneContacts() -> Promise { - return Promise { seal in - viewModel.fetchPhoneContacts { - seal.fulfill(()) - } - } - } - @objc func dismiss() { if self.presentingViewController != nil { diff --git a/ProtonMail/ProtonMail/ViewControllers/APP_share/Compose/ViewModel/ComposeViewModel.swift b/ProtonMail/ProtonMail/ViewControllers/APP_share/Compose/ViewModel/ComposeViewModel.swift index 2e875b932..5644e8a38 100644 --- a/ProtonMail/ProtonMail/ViewControllers/APP_share/Compose/ViewModel/ComposeViewModel.swift +++ b/ProtonMail/ProtonMail/ViewControllers/APP_share/Compose/ViewModel/ComposeViewModel.swift @@ -55,12 +55,25 @@ class ComposeViewModel: NSObject { return Set(schemes.map(\.rawValue)) } - private(set) var contacts: [ContactPickerModelProtocol] = [] + var contacts: [ContactPickerModelProtocol] { + // sort the contact group and phone address together + let sortedContacts = phoneContacts.appending(protonGroupContacts).sorted(by: { $0.contactTitle.lowercased() < $1.contactTitle.lowercased() }) + return protonContacts + sortedContacts + } + private var phoneContacts: [ContactPickerModelProtocol] = [] { + didSet { + contactsDidChangePublisher.send() + } + } + private var protonContacts: [ContactPickerModelProtocol] = [] { + didSet { + contactsDidChangePublisher.send() + } + } + private var protonGroupContacts: [ContactPickerModelProtocol] = [] private var emailPublisher: EmailPublisher? private var cancellable: AnyCancellable? - private(set) var phoneContacts: [ContactPickerModelProtocol] = [] - private(set) var messageAction: ComposeMessageAction = .newDraft private(set) var subject: String = .empty var body: String = .empty @@ -89,6 +102,8 @@ class ComposeViewModel: NSObject { dependencies.keychain[.metadataStripping] == .stripMetadata } + let contactsDidChangePublisher = PassthroughSubject() + // For share extension init( subject: String, @@ -1253,35 +1268,24 @@ extension ComposeViewModel { filteredResult.append(contact) } } - self?.contacts = filteredResult - self?.addContactWithPhoneContact() + self?.protonContacts = filteredResult }) emailPublisher?.start() + fetchGroupContacts() } - func fetchPhoneContacts(completion: (() -> Void)?) { + func fetchPhoneContacts() { let service = user.contactService service.getContactVOsFromPhone { contacts, error in DispatchQueue.main.async { self.phoneContacts = contacts - completion?() } } } - private func addContactWithPhoneContact() { - var contactsWithoutLastTimeUsed: [ContactPickerModelProtocol] = phoneContacts - - if user.hasPaidMailPlan { - let contactGroupsToAdd = user.contactGroupService.getAllContactGroupVOs().filter { - $0.contactCount > 0 - } - contactsWithoutLastTimeUsed.append(contentsOf: contactGroupsToAdd) - } - // sort the contact group and phone address together - contactsWithoutLastTimeUsed.sort(by: { $0.contactTitle.lowercased() < $1.contactTitle.lowercased() }) - - self.contacts += contactsWithoutLastTimeUsed + private func fetchGroupContacts() { + guard user.hasPaidMailPlan else { return } + protonGroupContacts = user.contactGroupService.getAllContactGroupVOs().filter { $0.contactCount > 0 } } }