diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index ff09cf586..12c4fdc79 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -294,6 +294,7 @@ 4523149E1F7E916B003A428C /* SlideOffAnimatedTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4523149D1F7E916B003A428C /* SlideOffAnimatedTransition.swift */; }; 452314A01F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4523149F1F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift */; }; 4523D016206EDC2B00A2AB51 /* LRUCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4523D015206EDC2B00A2AB51 /* LRUCache.swift */; }; + 452B999020A34B6B006F2F9E /* AddContactShareToExistingContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452B998F20A34B6B006F2F9E /* AddContactShareToExistingContactViewController.swift */; }; 452C468F1E427E200087B011 /* OutboundCallInitiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452C468E1E427E200087B011 /* OutboundCallInitiator.swift */; }; 452C7CA72037628B003D51A5 /* Weak.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170D51E315310003FC1F2 /* Weak.swift */; }; 452D1AF12081059C00A67F7F /* StringAdditionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452D1AF02081059C00A67F7F /* StringAdditionsTest.swift */; }; @@ -926,6 +927,7 @@ 4523149D1F7E916B003A428C /* SlideOffAnimatedTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SlideOffAnimatedTransition.swift; path = UserInterface/SlideOffAnimatedTransition.swift; sourceTree = ""; }; 4523149F1F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectionalPanGestureRecognizer.swift; sourceTree = ""; }; 4523D015206EDC2B00A2AB51 /* LRUCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LRUCache.swift; sourceTree = ""; }; + 452B998F20A34B6B006F2F9E /* AddContactShareToExistingContactViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactShareToExistingContactViewController.swift; sourceTree = ""; }; 452C468E1E427E200087B011 /* OutboundCallInitiator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutboundCallInitiator.swift; sourceTree = ""; }; 452D1AF02081059C00A67F7F /* StringAdditionsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringAdditionsTest.swift; sourceTree = ""; }; 452D1AF220810B6F00A67F7F /* String+OWS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+OWS.swift"; sourceTree = ""; }; @@ -1681,6 +1683,7 @@ 34B3F86E1E8DF1700035BE1A /* SignalsNavigationController.m */, 340FC897204DAC8D007AEB0F /* ThreadSettings */, 34D1F0BE1F8EC1760066283D /* Utils */, + 452B998F20A34B6B006F2F9E /* AddContactShareToExistingContactViewController.swift */, ); path = ViewControllers; sourceTree = ""; @@ -3238,6 +3241,7 @@ 340FC8BC204DAC8D007AEB0F /* FingerprintViewController.m in Sources */, 450DF2051E0D74AC003D14BE /* Platform.swift in Sources */, 340FC8B2204DAC8D007AEB0F /* AdvancedSettingsTableViewController.m in Sources */, + 452B999020A34B6B006F2F9E /* AddContactShareToExistingContactViewController.swift in Sources */, 346129991FD1E4DA00532771 /* SignalApp.m in Sources */, 34BECE301F7ABCF800D7438D /* GifPickerLayout.swift in Sources */, 343A65951FC47D5E000477A1 /* DebugUISyncMessages.m in Sources */, diff --git a/Signal/src/ViewControllers/AddContactShareToExistingContactViewController.swift b/Signal/src/ViewControllers/AddContactShareToExistingContactViewController.swift new file mode 100644 index 000000000..c53eba4e6 --- /dev/null +++ b/Signal/src/ViewControllers/AddContactShareToExistingContactViewController.swift @@ -0,0 +1,121 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation +import UIKit +import ContactsUI + +class AddContactShareToExistingContactViewController: ContactsPicker, ContactsPickerDelegate, CNContactViewControllerDelegate { + + // TODO actual protocol? + weak var addToExistingContactDelegate: UIViewController? + + let contactShare: ContactShareViewModel + + required init(contactShare: ContactShareViewModel) { + self.contactShare = contactShare + super.init(allowsMultipleSelection: false, subtitleCellType: .none) + + self.contactsPickerDelegate = self + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc required public init(allowsMultipleSelection: Bool, subtitleCellType: SubtitleCellValue) { + fatalError("init(allowsMultipleSelection:subtitleCellType:) has not been implemented") + } + + // MARK: - ContactsPickerDelegate + + func contactsPicker(_: ContactsPicker, contactFetchDidFail error: NSError) { + owsFail("\(logTag) in \(#function) with error: \(error)") + + guard let navigationController = self.navigationController else { + owsFail("\(logTag) in \(#function) navigationController was unexpectedly nil") + return + } + + navigationController.popViewController(animated: true) + } + + func contactsPickerDidCancel(_: ContactsPicker) { + Logger.debug("\(self.logTag) in \(#function)") + guard let navigationController = self.navigationController else { + owsFail("\(logTag) in \(#function) navigationController was unexpectedly nil") + return + } + + navigationController.popViewController(animated: true) + } + + func contactsPicker(_: ContactsPicker, didSelectContact contact: Contact) { + Logger.debug("\(self.logTag) in \(#function)") + + guard let mergedContact: CNContact = self.contactShare.cnContact(mergedWithExistingContact: contact) else { + // TODO maybe this should not be optional and return a blank contact so we can still save the (not-actually merged) contact + owsFail("\(logTag) in \(#function) navigationController was unexpectedly nil") + return + } + + // Not actually a "new" contact, but this brings up the edit form rather than the "Read" form + // saving our users a tap in some cases when we already know they want to edit. + let contactViewController: CNContactViewController = CNContactViewController(forNewContact: mergedContact) + + // Default title is "New Contact". We could give a more descriptive title, but anything + // seems redundant - the context is sufficiently clear. + contactViewController.title = "" + contactViewController.allowsActions = false + contactViewController.allowsEditing = true + contactViewController.delegate = self + + guard let navigationController = self.navigationController else { + owsFail("\(logTag) in \(#function) navigationController was unexpectedly nil") + return + } + navigationController.pushViewController(contactViewController, animated: true) + } + + func contactsPicker(_: ContactsPicker, didSelectMultipleContacts contacts: [Contact]) { + Logger.debug("\(self.logTag) in \(#function)") + owsFail("\(logTag) only supports single contact select") + + guard let navigationController = self.navigationController else { + owsFail("\(logTag) in \(#function) navigationController was unexpectedly nil") + return + } + + navigationController.popViewController(animated: true) + } + + func contactsPicker(_: ContactsPicker, shouldSelectContact contact: Contact) -> Bool { + return true + } + + // MARK: - CNContactViewControllerDelegate + + public func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) { + Logger.debug("\(self.logTag) in \(#function)") + + guard let navigationController = self.navigationController else { + owsFail("\(logTag) in \(#function) navigationController was unexpectedly nil") + return + } + + // We want to pop *this* view and the still presented CNContactViewController in a single animation. + // Note this happens for *cancel* and for *done*. Unfortunately, I don't know of a way to detect the difference + // between the two, since both just call this method. + guard let myIndex = navigationController.viewControllers.index(of: self) else { + owsFail("\(logTag) in \(#function) myIndex was unexpectedly nil") + navigationController.popViewController(animated: true) + navigationController.popViewController(animated: true) + return + } + + let previousViewControllerIndex = navigationController.viewControllers.index(before: myIndex) + let previousViewController = navigationController.viewControllers[previousViewControllerIndex] + navigationController.popToViewController(previousViewController, animated: true) + } +} diff --git a/Signal/src/ViewControllers/ContactShareViewHelper.swift b/Signal/src/ViewControllers/ContactShareViewHelper.swift index 64e53b1aa..b995c7f75 100644 --- a/Signal/src/ViewControllers/ContactShareViewHelper.swift +++ b/Signal/src/ViewControllers/ContactShareViewHelper.swift @@ -177,6 +177,7 @@ public class ContactShareViewHelper: NSObject, CNContactViewControllerDelegate { UIUtil.applyDefaultSystemAppearence() } + // MJK TODO fromNavigationController? private func presentSelectAddToExistingContactView(contactShare: ContactShareViewModel, fromViewController: UIViewController) { guard contactsManager.supportsContactEditing else { owsFail("\(logTag) Contact editing not supported") @@ -187,23 +188,26 @@ public class ContactShareViewHelper: NSObject, CNContactViewControllerDelegate { ContactsViewHelper.presentMissingContactAccessAlertController(from: fromViewController) return } - - // TODO: Revisit this. - guard let firstPhoneNumber = contactShare.e164PhoneNumbers().first else { - owsFail("\(logTag) Missing phone number.") - return - } - - // TODO: We need to modify OWSAddToContactViewController to take a OWSContact - // and merge it with an existing CNContact. - let viewController = OWSAddToContactViewController() - viewController.configure(withRecipientId: firstPhoneNumber) +// +// // TODO: Revisit this. +// guard let firstPhoneNumber = contactShare.e164PhoneNumbers().first else { +// owsFail("\(logTag) Missing phone number.") +// return +// } +// +// // TODO: We need to modify OWSAddToContactViewController to take a OWSContact +// // and merge it with an existing CNContact. +// let viewController = OWSAddToContactViewController() +// viewController.configure(withRecipientId: firstPhoneNumber) guard let navigationController = fromViewController.navigationController else { owsFail("\(logTag) missing navigationController") return } + let viewController = AddContactShareToExistingContactViewController(contactShare: contactShare) +// viewController.addToExistingContactDelegate = fromViewController + navigationController.pushViewController(viewController, animated: true) } diff --git a/Signal/src/ViewControllers/ContactsPicker.swift b/Signal/src/ViewControllers/ContactsPicker.swift index 1cdfb030b..db22a57e0 100644 --- a/Signal/src/ViewControllers/ContactsPicker.swift +++ b/Signal/src/ViewControllers/ContactsPicker.swift @@ -20,10 +20,6 @@ public protocol ContactsPickerDelegate: class { func contactsPicker(_: ContactsPicker, shouldSelectContact contact: Contact) -> Bool } -public extension ContactsPickerDelegate { - func contactsPicker(_: ContactsPicker, shouldSelectContact contact: Contact) -> Bool { return true } -} - @objc public enum SubtitleCellValue: Int { case phoneNumber, email, none @@ -72,15 +68,22 @@ public class ContactsPicker: OWSViewController, UITableViewDelegate, UITableView // Configuration public weak var contactsPickerDelegate: ContactsPickerDelegate? - private let subtitleCellValue: SubtitleCellValue - private let multiSelectEnabled: Bool - private let allowedContactKeys: [CNKeyDescriptor] = [ - CNContactFormatter.descriptorForRequiredKeys(for: .fullName), - CNContactThumbnailImageDataKey as CNKeyDescriptor, - CNContactPhoneNumbersKey as CNKeyDescriptor, - CNContactEmailAddressesKey as CNKeyDescriptor, - CNContactPostalAddressesKey as CNKeyDescriptor - ] + private let subtitleCellType: SubtitleCellValue + private let allowsMultipleSelection: Bool + private let allowedContactKeys: [CNKeyDescriptor] = ContactsFrameworkContactStoreAdaptee.allowedContactKeys + + // MARK: - Initializers + + @objc + required public init(allowsMultipleSelection: Bool, subtitleCellType: SubtitleCellValue) { + self.allowsMultipleSelection = allowsMultipleSelection + self.subtitleCellType = subtitleCellType + super.init(nibName: "ContactsPicker", bundle: nil) + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } // MARK: - Lifecycle Methods @@ -95,7 +98,7 @@ public class ContactsPicker: OWSViewController, UITableViewDelegate, UITableView tableView.estimatedRowHeight = 60.0 tableView.rowHeight = UITableViewAutomaticDimension - tableView.allowsMultipleSelection = multiSelectEnabled + tableView.allowsMultipleSelection = allowsMultipleSelection tableView.separatorInset = UIEdgeInsets(top: 0, left: ContactCell.kSeparatorHInset, bottom: 0, right: 16) @@ -116,7 +119,7 @@ public class ContactsPicker: OWSViewController, UITableViewDelegate, UITableView let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(onTouchCancelButton)) self.navigationItem.leftBarButtonItem = cancelButton - if multiSelectEnabled { + if allowsMultipleSelection { let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(onTouchDoneButton)) self.navigationItem.rightBarButtonItem = doneButton } @@ -126,20 +129,6 @@ public class ContactsPicker: OWSViewController, UITableViewDelegate, UITableView tableView.register(ContactCell.self, forCellReuseIdentifier: contactCellReuseIdentifier) } - // MARK: - Initializers - - @objc - required public init(delegate: ContactsPickerDelegate?, multiSelection: Bool, subtitleCellType: SubtitleCellValue) { - multiSelectEnabled = multiSelection - subtitleCellValue = subtitleCellType - super.init(nibName: nil, bundle: nil) - contactsPickerDelegate = delegate - } - - required public init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - // MARK: - Contact Operations private func reloadContacts() { @@ -162,7 +151,6 @@ public class ContactsPicker: OWSViewController, UITableViewDelegate, UITableView let error = NSError(domain: "contactsPickerErrorDomain", code: 1, userInfo: [NSLocalizedDescriptionKey: "No Contacts Access"]) self.contactsPickerDelegate?.contactsPicker(self, contactFetchDidFail: error) errorHandler(error) - self.dismiss(animated: true, completion: nil) }) alert.addAction(cancelAction) @@ -231,13 +219,16 @@ public class ContactsPicker: OWSViewController, UITableViewDelegate, UITableView // MARK: - Table View Delegates open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: contactCellReuseIdentifier, for: indexPath) as! ContactCell + guard let cell = tableView.dequeueReusableCell(withIdentifier: contactCellReuseIdentifier, for: indexPath) as? ContactCell else { + owsFail("\(logTag) in \(#function) cell had unexpected type") + return UITableViewCell() + } let dataSource = filteredSections let cnContact = dataSource[indexPath.section][indexPath.row] let contact = Contact(systemContact: cnContact) - cell.configure(contact: contact, subtitleType: subtitleCellValue, contactsManager: self.contactsManager) + cell.configure(contact: contact, subtitleType: subtitleCellType, showsWhenSelected: self.allowsMultipleSelection, contactsManager: self.contactsManager) let isSelected = selectedContacts.contains(where: { $0.uniqueId == contact.uniqueId }) cell.isSelected = isSelected @@ -274,11 +265,9 @@ public class ContactsPicker: OWSViewController, UITableViewDelegate, UITableView selectedContacts.append(selectedContact) - if !multiSelectEnabled { - //Single selection code - self.dismiss(animated: true) { - self.contactsPickerDelegate?.contactsPicker(self, didSelectContact: selectedContact) - } + if !allowsMultipleSelection { + // Single selection code + self.contactsPickerDelegate?.contactsPicker(self, didSelectContact: selectedContact) } } @@ -313,12 +302,10 @@ public class ContactsPicker: OWSViewController, UITableViewDelegate, UITableView func onTouchCancelButton() { contactsPickerDelegate?.contactsPickerDidCancel(self) - dismiss(animated: true, completion: nil) } func onTouchDoneButton() { contactsPickerDelegate?.contactsPicker(self, didSelectMultipleContacts: selectedContacts) - dismiss(animated: true, completion: nil) } // MARK: - Search Actions diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index c3fad83c1..c976699fe 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -2571,7 +2571,8 @@ typedef enum : NSUInteger { - (void)chooseContactForSending { ContactsPicker *contactsPicker = - [[ContactsPicker alloc] initWithDelegate:self multiSelection:NO subtitleCellType:SubtitleCellValueNone]; + [[ContactsPicker alloc] initWithAllowsMultipleSelection:NO subtitleCellType:SubtitleCellValueNone]; + contactsPicker.contactsPickerDelegate = self; contactsPicker.title = NSLocalizedString(@"CONTACT_PICKER_TITLE", @"navbar title for contact picker when sharing a contact"); @@ -4978,11 +4979,13 @@ interactionControllerForAnimationController:(id *)contacts { OWSFail(@"%@ in %s with contacts: %@", self.logTag, __PRETTY_FUNCTION__, contacts); + [self dismissViewControllerAnimated:YES completion:nil]; } - (BOOL)contactsPicker:(ContactsPicker *)contactsPicker shouldSelectContact:(Contact *)contact diff --git a/Signal/src/ViewControllers/InviteFlow.swift b/Signal/src/ViewControllers/InviteFlow.swift index 075fa0d66..323167a7c 100644 --- a/Signal/src/ViewControllers/InviteFlow.swift +++ b/Signal/src/ViewControllers/InviteFlow.swift @@ -88,6 +88,8 @@ class InviteFlow: NSObject, MFMessageComposeViewControllerDelegate, MFMailCompos func contactsPicker(_: ContactsPicker, didSelectMultipleContacts contacts: [Contact]) { Logger.debug("\(TAG) didSelectContacts:\(contacts)") + self.presentingViewController.dismiss(animated: true) + guard let inviteChannel = channel else { Logger.error("\(TAG) unexpected nil channel after returning from contact picker.") return @@ -124,14 +126,17 @@ class InviteFlow: NSObject, MFMessageComposeViewControllerDelegate, MFMailCompos func contactsPicker(_: ContactsPicker, contactFetchDidFail error: NSError) { Logger.error("\(self.logTag) in \(#function) with error: \(error)") + self.presentingViewController.dismiss(animated: true) } func contactsPickerDidCancel(_: ContactsPicker) { Logger.debug("\(self.logTag) in \(#function)") + self.presentingViewController.dismiss(animated: true) } func contactsPicker(_: ContactsPicker, didSelectContact contact: Contact) { owsFail("\(logTag) in \(#function) InviteFlow only supports multi-select") + self.presentingViewController.dismiss(animated: true) } // MARK: SMS @@ -146,7 +151,8 @@ class InviteFlow: NSObject, MFMessageComposeViewControllerDelegate, MFMailCompos return UIAlertAction(title: messageTitle, style: .default) { _ in Logger.debug("\(self.TAG) Chose message.") self.channel = .message - let picker = ContactsPicker(delegate: self, multiSelection: true, subtitleCellType: .phoneNumber) + let picker = ContactsPicker(allowsMultipleSelection: true, subtitleCellType: .phoneNumber) + picker.contactsPickerDelegate = self picker.title = NSLocalizedString("INVITE_FRIENDS_PICKER_TITLE", comment: "Navbar title") let navigationController = UINavigationController(rootViewController: picker) self.presentingViewController.present(navigationController, animated: true) @@ -209,7 +215,8 @@ class InviteFlow: NSObject, MFMessageComposeViewControllerDelegate, MFMailCompos Logger.debug("\(self.TAG) Chose mail.") self.channel = .mail - let picker = ContactsPicker(delegate: self, multiSelection: true, subtitleCellType: .email) + let picker = ContactsPicker(allowsMultipleSelection: true, subtitleCellType: .email) + picker.contactsPickerDelegate = self picker.title = NSLocalizedString("INVITE_FRIENDS_PICKER_TITLE", comment: "Navbar title") let navigationController = UINavigationController(rootViewController: picker) self.presentingViewController.present(navigationController, animated: true) diff --git a/Signal/src/views/ContactCell.swift b/Signal/src/views/ContactCell.swift index aa520ef95..df015109e 100644 --- a/Signal/src/views/ContactCell.swift +++ b/Signal/src/views/ContactCell.swift @@ -19,6 +19,7 @@ class ContactCell: UITableViewCell { var subtitleLabel: UILabel var contact: Contact? + var showsWhenSelected: Bool = false override init(style: UITableViewCellStyle, reuseIdentifier: String?) { self.contactImageView = AvatarImageView() @@ -59,7 +60,9 @@ class ContactCell: UITableViewCell { override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) - accessoryType = selected ? .checkmark : .none + if showsWhenSelected { + accessoryType = selected ? .checkmark : .none + } } func didChangePreferredContentSize() { @@ -67,8 +70,9 @@ class ContactCell: UITableViewCell { self.subtitleLabel.font = UIFont.ows_dynamicTypeSubheadline } - func configure(contact: Contact, subtitleType: SubtitleCellValue, contactsManager: OWSContactsManager) { + func configure(contact: Contact, subtitleType: SubtitleCellValue, showsWhenSelected: Bool, contactsManager: OWSContactsManager) { self.contact = contact + self.showsWhenSelected = showsWhenSelected titleLabel.attributedText = contact.cnContact?.formattedFullName(font: titleLabel.font) updateSubtitle(subtitleType: subtitleType, contact: contact) diff --git a/SignalMessaging/ViewModels/ContactShareViewModel.swift b/SignalMessaging/ViewModels/ContactShareViewModel.swift index da6b3f087..210c84117 100644 --- a/SignalMessaging/ViewModels/ContactShareViewModel.swift +++ b/SignalMessaging/ViewModels/ContactShareViewModel.swift @@ -109,6 +109,65 @@ public class ContactShareViewModel: NSObject { return dbRecord.isProfileAvatar } + public func cnContact(mergedWithExistingContact existingContact: Contact) -> CNContact? { + + guard let mergedCNContact: CNMutableContact = existingContact.cnContact?.mutableCopy() as? CNMutableContact else { + owsFail("\(logTag) in \(#function) mergedCNContact was unexpectedly nil") + return nil + } + + guard let newCNContact = OWSContacts.systemContact(for: self.dbRecord) else { + owsFail("\(logTag) in \(#function) newCNContact was unexpectedly nil") + return nil + } + + let normalize = { (input: String) -> String in + return (input as NSString).ows_stripped() + } + + // TODO display name - but only if existing is blank + + var existingPhoneNumberSet: Set = Set() + mergedCNContact.phoneNumbers.map { $0.value }.forEach { (existingPhoneNumber: CNPhoneNumber) in + // compare e164? + let normalizedValue = normalize(existingPhoneNumber.stringValue as String) + existingPhoneNumberSet.insert(normalizedValue) + } + + newCNContact.phoneNumbers.forEach { (newLabeledPhoneNumber: CNLabeledValue) in + let normalizedValue = normalize(newLabeledPhoneNumber.value.stringValue as String) + guard !existingPhoneNumberSet.contains(normalizedValue) else { + Logger.debug("\(self.logTag) in \(#function) ignoring matching phone number: \(normalizedValue)") + return + } + + Logger.debug("\(self.logTag) in \(#function) adding new phone number: \(normalizedValue)") + mergedCNContact.phoneNumbers.append(newLabeledPhoneNumber) + } + +// // TODO emails +// var existingEmailSet: Set = Set() +// mergedCNContact.emailAddresses.forEach { (existingEmail: CNLabeledValue) in +// existingEmailSet.insert(normalize(existingEmail.value as String)) +// } + + // TODO address + +// newCNContact.emailAddresses.forEach { newEmail in +// let match = mergedCNContact.emailAddresses.first { existingEmail in +// return normalize(existingEmail) == normalize(newEmail) +// } +// if match == nil { +// mergedCNContact.emailAddresses.add +// } +// } +// +// +// + + return mergedCNContact.copy() as? CNContact + } + public func copy(withNamePrefix namePrefix: String?, givenName: String?, middleName: String?, diff --git a/SignalMessaging/Views/ContactsViewHelper.m b/SignalMessaging/Views/ContactsViewHelper.m index 17c38ef4d..d1afa28db 100644 --- a/SignalMessaging/Views/ContactsViewHelper.m +++ b/SignalMessaging/Views/ContactsViewHelper.m @@ -308,7 +308,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)presentContactViewControllerForRecipientId:(NSString *)recipientId fromViewController:(UIViewController *)fromViewController editImmediately:(BOOL)shouldEditImmediately - addToExistingCnContact:(CNContact *_Nullable)addToExistingCnContact + addToExistingCnContact:(CNContact *_Nullable)existingContact { SignalAccount *signalAccount = [self signalAccountForRecipientId:recipientId]; @@ -325,8 +325,8 @@ NS_ASSUME_NONNULL_BEGIN CNContactViewController *_Nullable contactViewController; CNContact *_Nullable cnContact = nil; - if (addToExistingCnContact) { - CNMutableContact *updatedContact = [addToExistingCnContact mutableCopy]; + if (existingContact) { + CNMutableContact *updatedContact = [existingContact mutableCopy]; NSMutableArray *phoneNumbers = (updatedContact.phoneNumbers ? [updatedContact.phoneNumbers mutableCopy] : [NSMutableArray new]); // Only add recipientId as a phone number for the existing contact diff --git a/SignalMessaging/contacts/SystemContactsFetcher.swift b/SignalMessaging/contacts/SystemContactsFetcher.swift index 513380e33..33a3900f6 100644 --- a/SignalMessaging/contacts/SystemContactsFetcher.swift +++ b/SignalMessaging/contacts/SystemContactsFetcher.swift @@ -20,6 +20,7 @@ protocol ContactStoreAdaptee { func startObservingChanges(changeHandler: @escaping () -> Void) } +public class ContactsFrameworkContactStoreAdaptee: ContactStoreAdaptee { let TAG = "[ContactsFrameworkContactStoreAdaptee]" private let contactStore = CNContactStore() @@ -29,7 +30,7 @@ class ContactsFrameworkContactStoreAdaptee: ContactStoreAdaptee { let supportsContactEditing = true - private let allowedContactKeys: [CNKeyDescriptor] = [ + public static let allowedContactKeys: [CNKeyDescriptor] = [ CNContactFormatter.descriptorForRequiredKeys(for: .fullName), CNContactThumbnailImageDataKey as CNKeyDescriptor, // TODO full image instead of thumbnail? CNContactPhoneNumbersKey as CNKeyDescriptor, @@ -92,7 +93,7 @@ class ContactsFrameworkContactStoreAdaptee: ContactStoreAdaptee { func fetchContacts() -> Result<[Contact], Error> { var systemContacts = [CNContact]() do { - let contactFetchRequest = CNContactFetchRequest(keysToFetch: self.allowedContactKeys) + let contactFetchRequest = CNContactFetchRequest(keysToFetch: ContactsFrameworkContactStoreAdaptee.allowedContactKeys) contactFetchRequest.sortOrder = .userDefault try self.contactStore.enumerateContacts(with: contactFetchRequest) { (contact, _) -> Void in systemContacts.append(contact)