re-use contact picker for "add to existing"

Required refactor of contact picker to be presented non-modally.

TODO: merge emails, address, display names

// FREEBIE
This commit is contained in:
Michael Kirk 2018-05-09 11:54:10 -04:00
parent 609746abec
commit 0c469764f1
10 changed files with 253 additions and 60 deletions

View File

@ -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 = "<group>"; };
4523149F1F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectionalPanGestureRecognizer.swift; sourceTree = "<group>"; };
4523D015206EDC2B00A2AB51 /* LRUCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LRUCache.swift; sourceTree = "<group>"; };
452B998F20A34B6B006F2F9E /* AddContactShareToExistingContactViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactShareToExistingContactViewController.swift; sourceTree = "<group>"; };
452C468E1E427E200087B011 /* OutboundCallInitiator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutboundCallInitiator.swift; sourceTree = "<group>"; };
452D1AF02081059C00A67F7F /* StringAdditionsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringAdditionsTest.swift; sourceTree = "<group>"; };
452D1AF220810B6F00A67F7F /* String+OWS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+OWS.swift"; sourceTree = "<group>"; };
@ -1681,6 +1683,7 @@
34B3F86E1E8DF1700035BE1A /* SignalsNavigationController.m */,
340FC897204DAC8D007AEB0F /* ThreadSettings */,
34D1F0BE1F8EC1760066283D /* Utils */,
452B998F20A34B6B006F2F9E /* AddContactShareToExistingContactViewController.swift */,
);
path = ViewControllers;
sourceTree = "<group>";
@ -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 */,

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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

View File

@ -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<UIViewControllerAnimatedTransiti
- (void)contactsPickerDidCancel:(ContactsPicker *)contactsPicker
{
DDLogDebug(@"%@ in %s", self.logTag, __PRETTY_FUNCTION__);
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)contactsPicker:(ContactsPicker *)contactsPicker contactFetchDidFail:(NSError *)error
{
DDLogDebug(@"%@ in %s with error %@", self.logTag, __PRETTY_FUNCTION__, error);
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)contactsPicker:(ContactsPicker *)contactsPicker didSelectContact:(Contact *)contact
@ -4992,6 +4995,8 @@ interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransiti
DDLogDebug(@"%@ in %s with contact: %@", self.logTag, __PRETTY_FUNCTION__, contact);
[self dismissViewControllerAnimated:YES completion:nil];
OWSContact *_Nullable contactShareRecord = [OWSContacts contactForSystemContact:contact.cnContact];
if (!contactShareRecord) {
DDLogError(@"%@ Could not convert system contact.", self.logTag);
@ -5029,6 +5034,7 @@ interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransiti
- (void)contactsPicker:(ContactsPicker *)contactsPicker didSelectMultipleContacts:(NSArray<Contact *> *)contacts
{
OWSFail(@"%@ in %s with contacts: %@", self.logTag, __PRETTY_FUNCTION__, contacts);
[self dismissViewControllerAnimated:YES completion:nil];
}
- (BOOL)contactsPicker:(ContactsPicker *)contactsPicker shouldSelectContact:(Contact *)contact

View File

@ -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)

View File

@ -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)

View File

@ -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<String> = 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<CNPhoneNumber>) 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<String> = Set()
// mergedCNContact.emailAddresses.forEach { (existingEmail: CNLabeledValue<NSString>) 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?,

View File

@ -308,7 +308,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)presentContactViewControllerForRecipientId:(NSString *)recipientId
fromViewController:(UIViewController<ContactEditingDelegate> *)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<CNLabeledValue *> *phoneNumbers
= (updatedContact.phoneNumbers ? [updatedContact.phoneNumbers mutableCopy] : [NSMutableArray new]);
// Only add recipientId as a phone number for the existing contact

View File

@ -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)