Add contact share actions to conversation view and improve phone number parsing.

This commit is contained in:
Matthew Chen 2018-05-07 15:20:34 -04:00
parent 445c10a7ea
commit 44ceee5849
20 changed files with 707 additions and 191 deletions

View File

@ -166,6 +166,7 @@
348BB254209CD4B80047AEC2 /* ContactFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348BB253209CD4B80047AEC2 /* ContactFieldView.swift */; };
348BB25A209CF8E50047AEC2 /* TappableStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348BB258209CF8E40047AEC2 /* TappableStackView.swift */; };
348BB25B209CF8E50047AEC2 /* TappableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348BB259209CF8E50047AEC2 /* TappableView.swift */; };
348BB25D20A0C5530047AEC2 /* ContactShareViewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348BB25C20A0C5530047AEC2 /* ContactShareViewHelper.swift */; };
3496744D2076768700080B5F /* OWSMessageBubbleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3496744C2076768700080B5F /* OWSMessageBubbleView.m */; };
3496744F2076ACD000080B5F /* LongTextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3496744E2076ACCE00080B5F /* LongTextViewController.swift */; };
34A55F3720485465002CC6DE /* OWS2FARegistrationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A55F3520485464002CC6DE /* OWS2FARegistrationViewController.m */; };
@ -766,6 +767,7 @@
348BB253209CD4B80047AEC2 /* ContactFieldView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ContactFieldView.swift; path = SignalMessaging/attachments/ContactFieldView.swift; sourceTree = SOURCE_ROOT; };
348BB258209CF8E40047AEC2 /* TappableStackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TappableStackView.swift; path = SignalMessaging/Views/TappableStackView.swift; sourceTree = SOURCE_ROOT; };
348BB259209CF8E50047AEC2 /* TappableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TappableView.swift; path = SignalMessaging/Views/TappableView.swift; sourceTree = SOURCE_ROOT; };
348BB25C20A0C5530047AEC2 /* ContactShareViewHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactShareViewHelper.swift; sourceTree = "<group>"; };
348F2EAD1F0D21BC00D4ECE0 /* DeviceSleepManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceSleepManager.swift; sourceTree = "<group>"; };
3495BC911F1426B800B478F5 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = translations/ar.lproj/Localizable.strings; sourceTree = "<group>"; };
3496744B2076768600080B5F /* OWSMessageBubbleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageBubbleView.h; sourceTree = "<group>"; };
@ -1641,6 +1643,7 @@
34D5CCA71EAE3D30005515DB /* AvatarViewHelper.h */,
34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */,
34B3F83B1E8DF1700035BE1A /* CallViewController.swift */,
348BB25C20A0C5530047AEC2 /* ContactShareViewHelper.swift */,
34B3F83E1E8DF1700035BE1A /* ContactsPicker.swift */,
34B3F83F1E8DF1700035BE1A /* ContactsPicker.xib */,
34E88D252098C5AE00A608F4 /* ContactViewController.swift */,
@ -1651,6 +1654,7 @@
34BECE2C1F7ABCE000D7438D /* GifPicker */,
34386A4C207D0C01009F5D9C /* HomeView */,
34B3F84C1E8DF1700035BE1A /* InviteFlow.swift */,
4542DF53208D40AC007B4E76 /* LoadingViewController.swift */,
3496744E2076ACCE00080B5F /* LongTextViewController.swift */,
45B9EE9A200E91FB005D2F2D /* MediaDetailViewController.h */,
45B9EE9B200E91FB005D2F2D /* MediaDetailViewController.m */,
@ -1677,7 +1681,6 @@
34B3F86E1E8DF1700035BE1A /* SignalsNavigationController.m */,
340FC897204DAC8D007AEB0F /* ThreadSettings */,
34D1F0BE1F8EC1760066283D /* Utils */,
4542DF53208D40AC007B4E76 /* LoadingViewController.swift */,
);
path = ViewControllers;
sourceTree = "<group>";
@ -3249,6 +3252,7 @@
4542DF54208D40AC007B4E76 /* LoadingViewController.swift in Sources */,
34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */,
34D1F0B71F87F8850066283D /* OWSGenericAttachmentView.m in Sources */,
348BB25D20A0C5530047AEC2 /* ContactShareViewHelper.swift in Sources */,
34B3F8801E8DF1700035BE1A /* InviteFlow.swift in Sources */,
457C87B82032645C008D52D6 /* DebugUINotifications.swift in Sources */,
340FC8D0205BF2FA007AEB0F /* OWSBackupIO.m in Sources */,

View File

@ -0,0 +1,289 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
import SignalServiceKit
import ContactsUI
import MessageUI
@objc
public protocol ContactShareViewHelperDelegate: class {
func didCreateOrEditContact()
}
@objc
public class ContactShareViewHelper: NSObject, CNContactViewControllerDelegate {
weak var delegate: ContactShareViewHelperDelegate?
let contactShare: ContactShareViewModel
let contactsManager: OWSContactsManager
weak var fromViewController: UIViewController?
public required init(contactShare: ContactShareViewModel, contactsManager: OWSContactsManager, fromViewController: UIViewController, delegate: ContactShareViewHelperDelegate) {
SwiftAssertIsOnMainThread(#function)
self.contactShare = contactShare
self.contactsManager = contactsManager
self.fromViewController = fromViewController
self.delegate = delegate
super.init()
}
// MARK: Actions
@objc
public func sendMessageToContact() {
Logger.info("\(logTag) \(#function)")
presentThreadAndPeform(action: .compose)
}
@objc
public func audioCallToContact() {
Logger.info("\(logTag) \(#function)")
presentThreadAndPeform(action: .audioCall)
}
@objc
public func videoCallToContact() {
Logger.info("\(logTag) \(#function)")
presentThreadAndPeform(action: .videoCall)
}
private func presentThreadAndPeform(action: ConversationViewAction) {
// TODO: We're taking the first Signal account id. We might
// want to let the user select if there's more than one.
let phoneNumbers = contactShare.systemContactsWithSignalAccountPhoneNumbers(contactsManager)
guard phoneNumbers.count > 0 else {
owsFail("\(logTag) missing Signal recipient id.")
return
}
guard phoneNumbers.count > 1 else {
let recipientId = phoneNumbers.first!
SignalApp.shared().presentConversation(forRecipientId: recipientId, action: action)
return
}
showPhoneNumberPicker(phoneNumbers: phoneNumbers, completion: { (recipientId) in
SignalApp.shared().presentConversation(forRecipientId: recipientId, action: action)
})
}
@objc
public func inviteContact() {
Logger.info("\(logTag) \(#function)")
guard let fromViewController = fromViewController else {
owsFail("\(logTag) missing fromViewController")
return
}
guard MFMessageComposeViewController.canSendText() else {
Logger.info("\(logTag) Device cannot send text")
OWSAlerts.showErrorAlert(message: NSLocalizedString("UNSUPPORTED_FEATURE_ERROR", comment: ""))
return
}
let phoneNumbers = contactShare.e164PhoneNumbers()
guard phoneNumbers.count > 0 else {
owsFail("\(logTag) no phone numbers.")
return
}
let inviteFlow =
InviteFlow(presentingViewController: fromViewController, contactsManager: contactsManager)
inviteFlow.sendSMSTo(phoneNumbers: phoneNumbers)
}
func addToContacts() {
Logger.info("\(logTag) \(#function)")
guard let fromViewController = fromViewController else {
owsFail("\(logTag) missing fromViewController")
return
}
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("CONVERSATION_SETTINGS_NEW_CONTACT",
comment: "Label for 'new contact' button in conversation settings view."),
style: .default) { _ in
self.didPressCreateNewContact()
})
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("CONVERSATION_SETTINGS_ADD_TO_EXISTING_CONTACT",
comment: "Label for 'new contact' button in conversation settings view."),
style: .default) { _ in
self.didPressAddToExistingContact()
})
actionSheet.addAction(OWSAlerts.cancelAction)
fromViewController.present(actionSheet, animated: true)
}
private func showPhoneNumberPicker(phoneNumbers: [String], completion :@escaping ((String) -> Void)) {
guard let fromViewController = fromViewController else {
owsFail("\(logTag) missing fromViewController")
return
}
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
for phoneNumber in phoneNumbers {
actionSheet.addAction(UIAlertAction(title: PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: phoneNumber),
style: .default) { _ in
completion(phoneNumber)
})
}
actionSheet.addAction(OWSAlerts.cancelAction)
fromViewController.present(actionSheet, animated: true)
}
func didPressCreateNewContact() {
Logger.info("\(logTag) \(#function)")
presentNewContactView()
}
func didPressAddToExistingContact() {
Logger.info("\(logTag) \(#function)")
presentSelectAddToExistingContactView()
}
// MARK: -
private func presentNewContactView() {
guard let fromViewController = fromViewController else {
owsFail("\(logTag) missing fromViewController")
return
}
guard contactsManager.supportsContactEditing else {
owsFail("\(logTag) Contact editing not supported")
return
}
guard let systemContact = OWSContacts.systemContact(for: contactShare.dbRecord) else {
owsFail("\(logTag) Could not derive system contact.")
return
}
guard contactsManager.isSystemContactsAuthorized else {
ContactsViewHelper.presentMissingContactAccessAlertController(from: fromViewController)
return
}
let contactViewController = CNContactViewController(forNewContact: systemContact)
contactViewController.delegate = self
contactViewController.allowsActions = false
contactViewController.allowsEditing = true
contactViewController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: CommonStrings.cancelButton, style: .plain, target: self, action: #selector(didFinishEditingContact))
contactViewController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: CommonStrings.cancelButton,
style: .plain,
target: self,
action: #selector(didFinishEditingContact))
guard let navigationController = fromViewController.navigationController else {
owsFail("\(logTag) missing navigationController")
return
}
navigationController.pushViewController(contactViewController, animated: true)
// HACK otherwise CNContactViewController Navbar is shown as black.
// RADAR rdar://28433898 http://www.openradar.me/28433898
// CNContactViewController incompatible with opaque navigation bar
UIUtil.applyDefaultSystemAppearence()
}
private func presentSelectAddToExistingContactView() {
guard let fromViewController = fromViewController else {
owsFail("\(logTag) missing fromViewController")
return
}
guard contactsManager.supportsContactEditing else {
owsFail("\(logTag) Contact editing not supported")
return
}
guard contactsManager.isSystemContactsAuthorized else {
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)
guard let navigationController = fromViewController.navigationController else {
owsFail("\(logTag) missing navigationController")
return
}
navigationController.pushViewController(viewController, animated: true)
}
// MARK: - CNContactViewControllerDelegate
@objc public func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
Logger.info("\(logTag) \(#function)")
guard let fromViewController = fromViewController else {
owsFail("\(logTag) missing fromViewController")
return
}
guard let navigationController = fromViewController.navigationController else {
owsFail("\(logTag) missing navigationController")
return
}
guard let delegate = delegate else {
owsFail("\(logTag) missing delegate")
return
}
navigationController.popToViewController(fromViewController, animated: true)
delegate.didCreateOrEditContact()
}
@objc public func didFinishEditingContact() {
Logger.info("\(logTag) \(#function)")
guard let fromViewController = fromViewController else {
owsFail("\(logTag) missing fromViewController")
return
}
guard let navigationController = fromViewController.navigationController else {
owsFail("\(logTag) missing navigationController")
return
}
guard let delegate = delegate else {
owsFail("\(logTag) missing delegate")
return
}
navigationController.popToViewController(fromViewController, animated: true)
delegate.didCreateOrEditContact()
}
}

View File

@ -9,7 +9,7 @@ import Reachability
import ContactsUI
import MessageUI
class ContactViewController: OWSViewController, CNContactViewControllerDelegate {
class ContactViewController: OWSViewController, ContactShareViewHelperDelegate {
enum ContactViewMode {
case systemContactWithSignal,
@ -37,6 +37,8 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate
private let contactShare: ContactShareViewModel
private var helper: ContactShareViewHelper!
// MARK: - Initializers
@available(*, unavailable, message: "use init(call:) constructor instead.")
@ -50,6 +52,8 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate
super.init(nibName: nil, bundle: nil)
self.helper = ContactShareViewHelper(contactShare: contactShare, contactsManager: contactsManager, fromViewController: self, delegate: self)
updateMode()
NotificationCenter.default.addObserver(forName: .OWSContactsManagerSignalAccountsDidChange, object: nil, queue: nil) { [weak self] _ in
@ -110,7 +114,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate
private func updateMode() {
SwiftAssertIsOnMainThread(#function)
guard contactShare.phoneNumberStrings.count > 0 else {
guard contactShare.e164PhoneNumbers().count > 0 else {
viewMode = .noPhoneNumber
return
}
@ -129,17 +133,19 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate
private func systemContactsWithSignalAccountsForContact() -> [String] {
SwiftAssertIsOnMainThread(#function)
return contactShare.phoneNumberStrings.filter({ (phoneNumber) -> Bool in
return contactsManager.hasSignalAccount(forRecipientId: phoneNumber)
})
return contactShare.systemContactsWithSignalAccountPhoneNumbers(contactsManager)
}
private func systemContactsForContact() -> [String] {
SwiftAssertIsOnMainThread(#function)
return contactShare.phoneNumberStrings.filter({ (phoneNumber) -> Bool in
return contactsManager.allContactsMap[phoneNumber] != nil
})
return contactShare.systemContactPhoneNumbers(contactsManager)
}
private func phoneNumbersForContact() -> [String] {
SwiftAssertIsOnMainThread(#function)
return contactShare.e164PhoneNumbers()
}
private func updateContent() {
@ -466,18 +472,6 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate
return button
}
func didPressCreateNewContact() {
Logger.info("\(logTag) \(#function)")
presentNewContactView()
}
func didPressAddToExistingContact() {
Logger.info("\(logTag) \(#function)")
presentSelectAddToExistingContactView()
}
func didPressShareContact(sender: UIGestureRecognizer) {
Logger.info("\(logTag) \(#function)")
@ -490,92 +484,31 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate
func didPressSendMessage() {
Logger.info("\(logTag) \(#function)")
presentThreadAndPeform(action: .compose)
self.helper.sendMessageToContact()
}
func didPressAudioCall() {
Logger.info("\(logTag) \(#function)")
presentThreadAndPeform(action: .audioCall)
self.helper.audioCallToContact()
}
func didPressVideoCall() {
Logger.info("\(logTag) \(#function)")
presentThreadAndPeform(action: .videoCall)
}
func presentThreadAndPeform(action: ConversationViewAction) {
// TODO: We're taking the first Signal account id. We might
// want to let the user select if there's more than one.
let phoneNumbers = systemContactsWithSignalAccountsForContact()
guard phoneNumbers.count > 0 else {
owsFail("\(logTag) missing Signal recipient id.")
return
}
guard phoneNumbers.count > 1 else {
let recipientId = systemContactsWithSignalAccountsForContact().first!
SignalApp.shared().presentConversation(forRecipientId: recipientId, action: action)
return
}
showPhoneNumberPicker(phoneNumbers: phoneNumbers, completion: { (recipientId) in
SignalApp.shared().presentConversation(forRecipientId: recipientId, action: action)
})
self.helper.videoCallToContact()
}
func didPressInvite() {
Logger.info("\(logTag) \(#function)")
guard MFMessageComposeViewController.canSendText() else {
Logger.info("\(logTag) Device cannot send text")
OWSAlerts.showErrorAlert(message: NSLocalizedString("UNSUPPORTED_FEATURE_ERROR", comment: ""))
return
}
let phoneNumbers = contactShare.phoneNumberStrings
guard phoneNumbers.count > 0 else {
owsFail("\(logTag) no phone numbers.")
return
}
let inviteFlow =
InviteFlow(presentingViewController: self, contactsManager: contactsManager)
inviteFlow.sendSMSTo(phoneNumbers: phoneNumbers)
self.helper.inviteContact()
}
func didPressAddToContacts() {
Logger.info("\(logTag) \(#function)")
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("CONVERSATION_SETTINGS_NEW_CONTACT",
comment: "Label for 'new contact' button in conversation settings view."),
style: .default) { _ in
self.didPressCreateNewContact()
})
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("CONVERSATION_SETTINGS_ADD_TO_EXISTING_CONTACT",
comment: "Label for 'new contact' button in conversation settings view."),
style: .default) { _ in
self.didPressAddToExistingContact()
})
actionSheet.addAction(OWSAlerts.cancelAction)
self.present(actionSheet, animated: true)
}
private func showPhoneNumberPicker(phoneNumbers: [String], completion :@escaping ((String) -> Void)) {
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
for phoneNumber in phoneNumbers {
actionSheet.addAction(UIAlertAction(title: PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: phoneNumber),
style: .default) { _ in
completion(phoneNumber)
})
}
actionSheet.addAction(OWSAlerts.cancelAction)
self.present(actionSheet, animated: true)
self.helper.addToContacts()
}
func didPressDismiss() {
@ -619,80 +552,11 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate
UIApplication.shared.openURL(url as URL)
}
// MARK: -
// MARK: - ContactShareViewHelperDelegate
private func presentNewContactView() {
guard contactsManager.supportsContactEditing else {
owsFail("\(logTag) Contact editing not supported")
return
}
guard let systemContact = OWSContacts.systemContact(for: contactShare.dbRecord) else {
owsFail("\(logTag) Could not derive system contactShare.")
return
}
guard contactsManager.isSystemContactsAuthorized else {
ContactsViewHelper.presentMissingContactAccessAlertController(from: self)
return
}
let contactViewController = CNContactViewController(forNewContact: systemContact)
contactViewController.delegate = self
contactViewController.allowsActions = false
contactViewController.allowsEditing = true
contactViewController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: CommonStrings.cancelButton, style: .plain, target: self, action: #selector(didFinishEditingContact))
contactViewController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: CommonStrings.cancelButton,
style: .plain,
target: self,
action: #selector(didFinishEditingContact))
self.navigationController?.pushViewController(contactViewController, animated: true)
// HACK otherwise CNContactViewController Navbar is shown as black.
// RADAR rdar://28433898 http://www.openradar.me/28433898
// CNContactViewController incompatible with opaque navigation bar
UIUtil.applyDefaultSystemAppearence()
}
private func presentSelectAddToExistingContactView() {
guard contactsManager.supportsContactEditing else {
owsFail("\(logTag) Contact editing not supported")
return
}
guard contactsManager.isSystemContactsAuthorized else {
ContactsViewHelper.presentMissingContactAccessAlertController(from: self)
return
}
guard let firstPhoneNumber = contactShare.phoneNumbers.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.phoneNumber)
self.navigationController?.pushViewController(viewController, animated: true)
}
// MARK: - CNContactViewControllerDelegate
@objc public func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
public func didCreateOrEditContact() {
Logger.info("\(logTag) \(#function)")
self.navigationController?.popToViewController(self, animated: true)
updateContent()
}
@objc public func didFinishEditingContact() {
Logger.info("\(logTag) \(#function)")
self.navigationController?.popToViewController(self, animated: true)
updateContent()
}
}

View File

@ -5,14 +5,31 @@
NS_ASSUME_NONNULL_BEGIN
@class ContactShareViewModel;
@class OWSContact;
@class OWSContactsManager;
@protocol OWSContactShareViewDelegate <NSObject>
- (void)sendMessageToContactShare:(ContactShareViewModel *)contactShare;
- (void)sendInviteToContactShare:(ContactShareViewModel *)contactShare;
- (void)showAddToContactUIForContactShare:(ContactShareViewModel *)contactShare;
@end
#pragma mark -
@interface OWSContactShareView : UIView
- (instancetype)initWithContactShare:(ContactShareViewModel *)contactShare isIncoming:(BOOL)isIncoming;
- (instancetype)initWithContactShare:(ContactShareViewModel *)contactShare
isIncoming:(BOOL)isIncoming
delegate:(id<OWSContactShareViewDelegate>)delegate;
- (void)createContents;
+ (CGFloat)bubbleHeight;
+ (CGFloat)bubbleHeightForContactShare:(ContactShareViewModel *)contactShare;
// Returns YES IFF the tap was handled.
- (BOOL)handleTapGesture:(UITapGestureRecognizer *)sender;
@end

View File

@ -18,7 +18,12 @@ NS_ASSUME_NONNULL_BEGIN
@interface OWSContactShareView ()
@property (nonatomic) ContactShareViewModel *contactShare;
@property (nonatomic, weak) id<OWSContactShareViewDelegate> delegate;
@property (nonatomic) BOOL isIncoming;
@property (nonatomic) OWSContactsManager *contactsManager;
@property (nonatomic, nullable) UIView *buttonView;
@end
@ -26,13 +31,17 @@ NS_ASSUME_NONNULL_BEGIN
@implementation OWSContactShareView
- (instancetype)initWithContactShare:(ContactShareViewModel *)contactShare isIncoming:(BOOL)isIncoming
- (instancetype)initWithContactShare:(ContactShareViewModel *)contactShare
isIncoming:(BOOL)isIncoming
delegate:(id<OWSContactShareViewDelegate>)delegate
{
self = [super init];
if (self) {
_contactShare = contactShare;
_isIncoming = isIncoming;
self.delegate = delegate;
self.contactShare = contactShare;
self.isIncoming = isIncoming;
self.contactsManager = [Environment current].contactsManager;
}
return self;
@ -65,14 +74,60 @@ NS_ASSUME_NONNULL_BEGIN
return [OWSContactShareView iconVMargin];
}
+ (CGFloat)bubbleHeight
+ (BOOL)hasSendTextButton:(ContactShareViewModel *)contactShare contactsManager:(OWSContactsManager *)contactsManager
{
OWSAssert(contactShare);
OWSAssert(contactsManager);
return [contactShare systemContactsWithSignalAccountPhoneNumbers:contactsManager].count > 0;
}
+ (BOOL)hasInviteButton:(ContactShareViewModel *)contactShare contactsManager:(OWSContactsManager *)contactsManager
{
OWSAssert(contactShare);
OWSAssert(contactsManager);
return [contactShare systemContactPhoneNumbers:contactsManager].count > 0;
}
+ (BOOL)hasAddToContactsButton:(ContactShareViewModel *)contactShare
{
OWSAssert(contactShare);
return [contactShare e164PhoneNumbers].count > 0;
}
+ (BOOL)hasAnyButton:(ContactShareViewModel *)contactShare contactsManager:(OWSContactsManager *)contactsManager
{
OWSAssert(contactShare);
return ([self hasSendTextButton:contactShare contactsManager:contactsManager] ||
[self hasInviteButton:contactShare contactsManager:contactsManager] ||
[self hasAddToContactsButton:contactShare]);
}
+ (CGFloat)bubbleHeightForContactShare:(ContactShareViewModel *)contactShare
{
OWSAssert(contactShare);
OWSContactsManager *contactsManager = [Environment current].contactsManager;
if ([self hasAnyButton:contactShare contactsManager:contactsManager]) {
return self.contentHeight + self.buttonHeight;
} else {
return self.contentHeight;
}
}
+ (CGFloat)contentHeight
{
return self.iconSize + self.iconVMargin * 2;
}
- (CGFloat)bubbleHeight
+ (CGFloat)buttonHeight
{
return [OWSContactShareView bubbleHeight];
return 44.f;
}
+ (CGFloat)iconSize
@ -108,7 +163,6 @@ NS_ASSUME_NONNULL_BEGIN
[contentView autoPinLeadingToSuperviewMarginWithInset:self.isIncoming ? kBubbleTailWidth : 0.f];
[contentView autoPinTrailingToSuperviewMarginWithInset:self.isIncoming ? 0.f : kBubbleTailWidth];
[contentView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:self.vMargin];
[contentView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:self.vMargin];
AvatarImageView *avatarView = [AvatarImageView new];
avatarView.image =
@ -130,14 +184,11 @@ NS_ASSUME_NONNULL_BEGIN
labelsView.spacing = 2;
[labelsView addArrangedSubview:topLabel];
// TODO: Should we just try to show the _first_ phone number?
// What about email?
// What if the second phone number is a signal account?
NSString *_Nullable firstPhoneNumber = self.contactShare.phoneNumbers.firstObject.phoneNumber;
NSString *_Nullable firstPhoneNumber =
[self.contactShare systemContactsWithSignalAccountPhoneNumbers:self.contactsManager].firstObject;
if (firstPhoneNumber.length > 0) {
UILabel *bottomLabel = [UILabel new];
bottomLabel.text = [PhoneNumber bestEffortLocalizedPhoneNumberWithE164:firstPhoneNumber];
// TODO:
bottomLabel.textColor = [UIColor ows_darkGrayColor];
bottomLabel.lineBreakMode = NSLineBreakByTruncatingTail;
bottomLabel.font = [UIFont ows_dynamicTypeCaption1Font];
@ -170,6 +221,64 @@ NS_ASSUME_NONNULL_BEGIN
[stackView addArrangedSubview:avatarView];
[stackView addArrangedSubview:labelsView];
[stackView addArrangedSubview:disclosureImageView];
if ([OWSContactShareView hasAnyButton:self.contactShare contactsManager:self.contactsManager]) {
UIStackView *buttonView = [UIStackView new];
self.buttonView = buttonView;
buttonView.layoutMargins = UIEdgeInsetsZero;
[buttonView addBackgroundViewWithBackgroundColor:[UIColor whiteColor]];
buttonView.axis = UILayoutConstraintAxisHorizontal;
buttonView.alignment = UIStackViewAlignmentCenter;
[self addSubview:buttonView];
[buttonView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:contentView withOffset:self.vMargin];
[buttonView autoPinWidthToSuperview];
[buttonView autoPinEdgeToSuperviewEdge:ALEdgeBottom];
[buttonView autoSetDimension:ALDimensionHeight toSize:OWSContactShareView.buttonHeight];
UILabel *label = [UILabel new];
if ([OWSContactShareView hasSendTextButton:self.contactShare contactsManager:self.contactsManager]) {
label.text = NSLocalizedString(@"ACTION_SEND_MESSAGE", @"Label for 'sent message' button in contact view.");
} else if ([OWSContactShareView hasInviteButton:self.contactShare contactsManager:self.contactsManager]) {
label.text = NSLocalizedString(@"ACTION_INVITE", @"Label for 'invite' button in contact view.");
} else if ([OWSContactShareView hasAddToContactsButton:self.contactShare]) {
label.text = NSLocalizedString(@"CONVERSATION_VIEW_ADD_TO_CONTACTS_OFFER",
@"Message shown in conversation view that offers to add an unknown user to your phone's contacts.");
} else {
OWSFail(@"%@ unexpected button state.", self.logTag);
}
label.font = [UIFont ows_dynamicTypeBodyFont];
label.textColor = UIColor.ows_materialBlueColor;
label.textAlignment = NSTextAlignmentCenter;
[buttonView addArrangedSubview:label];
[buttonView logFrameLaterWithLabel:@"buttonView"];
[label logFrameLaterWithLabel:@"label"];
} else {
[contentView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:self.vMargin];
}
}
- (BOOL)handleTapGesture:(UITapGestureRecognizer *)sender
{
if (!self.buttonView) {
return NO;
}
CGPoint location = [sender locationInView:self.buttonView];
if (!CGRectContainsPoint(self.buttonView.bounds, location)) {
return NO;
}
if ([OWSContactShareView hasSendTextButton:self.contactShare contactsManager:self.contactsManager]) {
[self.delegate sendMessageToContactShare:self.contactShare];
} else if ([OWSContactShareView hasInviteButton:self.contactShare contactsManager:self.contactsManager]) {
[self.delegate sendInviteToContactShare:self.contactShare];
} else if ([OWSContactShareView hasAddToContactsButton:self.contactShare]) {
[self.delegate showAddToContactUIForContactShare:self.contactShare];
} else {
OWSFail(@"%@ unexpected button tap.", self.logTag);
}
return YES;
}
@end

View File

@ -4,7 +4,9 @@
NS_ASSUME_NONNULL_BEGIN
@class ContactShareViewModel;
@class ConversationViewItem;
@class OWSContact;
@class OWSQuotedReplyModel;
@class TSAttachmentPointer;
@class TSAttachmentStream;
@ -42,6 +44,11 @@ typedef NS_ENUM(NSUInteger, OWSMessageGestureLocation) {
- (void)didTapContactShareViewItem:(ConversationViewItem *)viewItem;
- (void)sendMessageToContactShare:(ContactShareViewModel *)contactShare NS_SWIFT_NAME(sendMessage(toContactShare:));
- (void)sendInviteToContactShare:(ContactShareViewModel *)contactShare NS_SWIFT_NAME(sendInvite(toContactShare:));
- (void)showAddToContactUIForContactShare:(ContactShareViewModel *)contactShare
NS_SWIFT_NAME(showAddToContactUI(forContactShare:));
@end
@interface OWSMessageBubbleView : UIView

View File

@ -18,7 +18,7 @@
NS_ASSUME_NONNULL_BEGIN
@interface OWSMessageBubbleView () <OWSQuotedMessageViewDelegate>
@interface OWSMessageBubbleView () <OWSQuotedMessageViewDelegate, OWSContactShareViewDelegate>
@property (nonatomic) OWSBubbleView *bubbleView;
@ -378,7 +378,8 @@ NS_ASSUME_NONNULL_BEGIN
lastSubview = bodyMediaView;
bottomMargin = 0;
BOOL shouldStrokeMediaView = [bodyMediaView isKindOfClass:[UIImageView class]];
BOOL shouldStrokeMediaView = ([bodyMediaView isKindOfClass:[UIImageView class]] ||
[bodyMediaView isKindOfClass:[OWSContactShareView class]]);
if (shouldStrokeMediaView) {
OWSBubbleStrokeView *bubbleStrokeView = [OWSBubbleStrokeView new];
bubbleStrokeView.strokeThickness = 1.f;
@ -796,9 +797,9 @@ NS_ASSUME_NONNULL_BEGIN
{
OWSAssert(self.viewItem.contactShare);
OWSContactShareView *contactShareView =
[[OWSContactShareView alloc] initWithContactShare:self.viewItem.contactShare
isIncoming:self.isIncoming];
OWSContactShareView *contactShareView = [[OWSContactShareView alloc] initWithContactShare:self.viewItem.contactShare
isIncoming:self.isIncoming
delegate:self];
[contactShareView createContents];
// TODO: Should we change appearance if contact avatar is uploading?
@ -931,7 +932,10 @@ NS_ASSUME_NONNULL_BEGIN
case OWSMessageCellType_DownloadingAttachment:
return CGSizeMake(200, 90);
case OWSMessageCellType_ContactShare:
return CGSizeMake(maxMessageWidth, [OWSContactShareView bubbleHeight]);
OWSAssert(self.viewItem.contactShare);
return CGSizeMake(
maxMessageWidth, [OWSContactShareView bubbleHeightForContactShare:self.viewItem.contactShare]);
}
}
@ -1127,6 +1131,13 @@ NS_ASSUME_NONNULL_BEGIN
}
}
if ([self.bodyMediaView isKindOfClass:[OWSContactShareView class]]) {
OWSContactShareView *contactShareView = (OWSContactShareView *)self.bodyMediaView;
if ([contactShareView handleTapGesture:sender]) {
return;
}
}
CGPoint locationInMessageBubble = [sender locationInView:self];
switch ([self gestureLocationForLocation:locationInMessageBubble]) {
case OWSMessageGestureLocation_Default:
@ -1247,6 +1258,32 @@ NS_ASSUME_NONNULL_BEGIN
failedThumbnailDownloadAttachmentPointer:attachmentPointer];
}
#pragma mark - OWSContactShareViewDelegate
- (void)sendMessageToContactShare:(ContactShareViewModel *)contactShare
{
OWSAssertIsOnMainThread();
OWSAssert(contactShare);
[self.delegate sendMessageToContactShare:contactShare];
}
- (void)sendInviteToContactShare:(ContactShareViewModel *)contactShare
{
OWSAssertIsOnMainThread();
OWSAssert(contactShare);
[self.delegate sendInviteToContactShare:contactShare];
}
- (void)showAddToContactUIForContactShare:(ContactShareViewModel *)contactShare
{
OWSAssertIsOnMainThread();
OWSAssert(contactShare);
[self.delegate showAddToContactUIForContactShare:contactShare];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -123,6 +123,7 @@ typedef enum : NSUInteger {
CNContactViewControllerDelegate,
ContactEditingDelegate,
ContactsPickerDelegate,
ContactShareViewHelperDelegate,
ContactsViewHelperDelegate,
DisappearingTimerConfigurationViewDelegate,
OWSConversationSettingsViewDelegate,
@ -231,6 +232,7 @@ typedef enum : NSUInteger {
@property (nonatomic) BOOL isPickingMediaAsDocument;
@property (nonatomic, nullable) NSNumber *previousLastTimestamp;
@property (nonatomic, nullable) NSNumber *viewHorizonTimestamp;
@property (nonatomic) ContactShareViewHelper *contactShareViewHelper;
@end
@ -2106,6 +2108,42 @@ typedef enum : NSUInteger {
[self.navigationController pushViewController:view animated:YES];
}
- (void)sendMessageToContactShare:(ContactShareViewModel *)contactShare
{
OWSAssertIsOnMainThread();
OWSAssert(contactShare);
self.contactShareViewHelper = [[ContactShareViewHelper alloc] initWithContactShare:contactShare
contactsManager:self.contactsManager
fromViewController:self
delegate:self];
[self.contactShareViewHelper sendMessageToContact];
}
- (void)sendInviteToContactShare:(ContactShareViewModel *)contactShare
{
OWSAssertIsOnMainThread();
OWSAssert(contactShare);
self.contactShareViewHelper = [[ContactShareViewHelper alloc] initWithContactShare:contactShare
contactsManager:self.contactsManager
fromViewController:self
delegate:self];
[self.contactShareViewHelper inviteContact];
}
- (void)showAddToContactUIForContactShare:(ContactShareViewModel *)contactShare
{
OWSAssertIsOnMainThread();
OWSAssert(contactShare);
self.contactShareViewHelper = [[ContactShareViewHelper alloc] initWithContactShare:contactShare
contactsManager:self.contactsManager
fromViewController:self
delegate:self];
[self.contactShareViewHelper addToContacts];
}
- (void)didTapFailedIncomingAttachment:(ConversationViewItem *)viewItem
attachmentPointer:(TSAttachmentPointer *)attachmentPointer
{
@ -5030,6 +5068,15 @@ interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransiti
[self dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - ContactShareViewHelperDelegate
- (void)didCreateOrEditContact
{
DDLogInfo(@"%@ in %s", self.logTag, __PRETTY_FUNCTION__);
// Do nothing.
}
@end
NS_ASSUME_NONNULL_END

View File

@ -12,7 +12,7 @@ enum MessageMetadataViewMode: UInt {
case focusOnMetadata
}
class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDelegate, OWSMessageBubbleViewDelegate {
class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDelegate, OWSMessageBubbleViewDelegate, ContactShareViewHelperDelegate {
// MARK: Properties
@ -40,6 +40,8 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
var attachmentStream: TSAttachmentStream?
var messageBody: String?
private var contactShareViewHelper: ContactShareViewHelper?
// MARK: Initializers
@available(*, unavailable, message:"use other constructor instead.")
@ -621,6 +623,30 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
self.navigationController?.pushViewController(contactViewController, animated: true)
}
func sendMessage(toContactShare contactShare: ContactShareViewModel) {
contactShareViewHelper = ContactShareViewHelper(contactShare: contactShare,
contactsManager: contactsManager,
fromViewController: self,
delegate: self)
contactShareViewHelper?.sendMessageToContact()
}
func sendInvite(toContactShare contactShare: ContactShareViewModel) {
contactShareViewHelper = ContactShareViewHelper(contactShare: contactShare,
contactsManager: contactsManager,
fromViewController: self,
delegate: self)
contactShareViewHelper?.inviteContact()
}
func showAddToContactUI(forContactShare contactShare: ContactShareViewModel) {
contactShareViewHelper = ContactShareViewHelper(contactShare: contactShare,
contactsManager: contactsManager,
fromViewController: self,
delegate: self)
contactShareViewHelper?.addToContacts()
}
var audioAttachmentPlayer: OWSAudioPlayer?
func didTapAudioViewItem(_ viewItem: ConversationViewItem, attachmentStream: TSAttachmentStream) {
@ -700,4 +726,10 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
self.navigationController?.popViewController(animated: true)
}
}
// MARK: - ContactShareViewHelperDelegate
public func didCreateOrEditContact() {
updateContent()
}
}

View File

@ -65,8 +65,16 @@ public class ContactShareViewModel: NSObject {
}
}
public var phoneNumberStrings: [String] {
return phoneNumbers.map { $0.phoneNumber }
public func systemContactsWithSignalAccountPhoneNumbers(_ contactsManager: ContactsManagerProtocol) -> [String] {
return dbRecord.systemContactsWithSignalAccountPhoneNumbers(contactsManager)
}
public func systemContactPhoneNumbers(_ contactsManager: ContactsManagerProtocol) -> [String] {
return dbRecord.systemContactPhoneNumbers(contactsManager)
}
public func e164PhoneNumbers() -> [String] {
return dbRecord.e164PhoneNumbers()
}
public var displayName: String {

View File

@ -4,6 +4,7 @@
import Foundation
@objc
public class TappableStackView: UIStackView {
let actionBlock : (() -> Void)

View File

@ -135,6 +135,14 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value);
@end
#pragma mark -
@interface UIStackView (OWS)
- (void)addBackgroundViewWithBackgroundColor:(UIColor *)backgroundColor;
@end
#pragma mark - Macros
CG_INLINE CGSize CGSizeCeil(CGSize size)

View File

@ -516,4 +516,18 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value)
@end
#pragma mark -
@implementation UIStackView (OWS)
- (void)addBackgroundViewWithBackgroundColor:(UIColor *)backgroundColor
{
UIView *subview = [UIView new];
subview.backgroundColor = backgroundColor;
[self addSubview:subview];
[subview autoPinEdgesToSuperviewEdges];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -60,6 +60,8 @@ extern NSString *const OWSContactsManagerSignalAccountsDidChangeNotification;
#pragma mark - Util
- (BOOL)isSystemContact:(NSString *)recipientId;
- (BOOL)isSystemContactWithSignalAccount:(NSString *)recipientId;
- (BOOL)hasNameInSystemContactsForRecipientId:(NSString *)recipientId;
- (NSString *)displayNameForPhoneIdentifier:(nullable NSString *)identifier;
- (NSString *)displayNameForSignalAccount:(SignalAccount *)signalAccount;

View File

@ -459,6 +459,20 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
#pragma mark - Whisper User Management
- (BOOL)isSystemContact:(NSString *)recipientId
{
OWSAssert(recipientId.length > 0);
return self.allContactsMap[recipientId] != nil;
}
- (BOOL)isSystemContactWithSignalAccount:(NSString *)recipientId
{
OWSAssert(recipientId.length > 0);
return [self hasSignalAccountForRecipientId:recipientId];
}
- (BOOL)hasNameInSystemContactsForRecipientId:(NSString *)recipientId
{
return [self cachedContactNameForRecipientId:recipientId].length > 0;

View File

@ -14,7 +14,6 @@
+ (PhoneNumber *)phoneNumberFromE164:(NSString *)text;
+ (PhoneNumber *)tryParsePhoneNumberFromText:(NSString *)text fromRegion:(NSString *)regionCode;
+ (PhoneNumber *)tryParsePhoneNumberFromUserSpecifiedText:(NSString *)text;
+ (PhoneNumber *)tryParsePhoneNumberFromE164:(NSString *)text;

View File

@ -170,14 +170,6 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
return regionCode;
}
+ (PhoneNumber *)tryParsePhoneNumberFromText:(NSString *)text fromRegion:(NSString *)regionCode {
OWSAssert(text != nil);
OWSAssert(regionCode != nil);
return [self phoneNumberFromText:text andRegion:regionCode];
}
+ (PhoneNumber *)tryParsePhoneNumberFromUserSpecifiedText:(NSString *)text {
OWSAssert(text != nil);

View File

@ -3,6 +3,7 @@
//
#import <Mantle/MTLModel.h>
#import <SignalServiceKit/ContactsManagerProtocol.h>
NS_ASSUME_NONNULL_BEGIN
@ -140,10 +141,19 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value);
familyName:(nullable NSString *)familyName
nameSuffix:(nullable NSString *)nameSuffix;
#pragma mark - Phone Numbers and Recipient IDs
- (NSArray<NSString *> *)systemContactsWithSignalAccountPhoneNumbers:(id<ContactsManagerProtocol>)contactsManager
NS_SWIFT_NAME(systemContactsWithSignalAccountPhoneNumbers(_:));
- (NSArray<NSString *> *)systemContactPhoneNumbers:(id<ContactsManagerProtocol>)contactsManager
NS_SWIFT_NAME(systemContactPhoneNumbers(_:));
- (NSArray<NSString *> *)e164PhoneNumbers NS_SWIFT_NAME(e164PhoneNumbers());
@end
#pragma mark -
// TODO: Move to separate source file, rename to OWSContactConversion.
@interface OWSContacts : NSObject
#pragma mark - VCard Serialization

View File

@ -279,6 +279,8 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value)
@property (nonatomic, nullable) NSString *avatarAttachmentId;
@property (nonatomic) BOOL isProfileAvatar;
@property (nonatomic, nullable) NSArray<NSString *> *e164PhoneNumbersCached;
@end
#pragma mark -
@ -483,6 +485,50 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value)
self.avatarAttachmentId = attachmentStream.uniqueId;
}
#pragma mark - Phone Numbers and Recipient IDs
- (NSArray<NSString *> *)systemContactsWithSignalAccountPhoneNumbers:(id<ContactsManagerProtocol>)contactsManager
{
OWSAssert(contactsManager);
return [self.e164PhoneNumbers
filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSString *_Nullable recipientId,
NSDictionary<NSString *, id> *_Nullable bindings) {
return [contactsManager isSystemContactWithSignalAccount:recipientId];
}]];
}
- (NSArray<NSString *> *)systemContactPhoneNumbers:(id<ContactsManagerProtocol>)contactsManager
{
OWSAssert(contactsManager);
return [self.e164PhoneNumbers
filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSString *_Nullable recipientId,
NSDictionary<NSString *, id> *_Nullable bindings) {
return [contactsManager isSystemContact:recipientId];
}]];
}
- (NSArray<NSString *> *)e164PhoneNumbers
{
if (self.e164PhoneNumbersCached) {
return self.e164PhoneNumbersCached;
}
NSMutableArray<NSString *> *e164PhoneNumbers = [NSMutableArray new];
for (OWSContactPhoneNumber *phoneNumber in self.phoneNumbers) {
PhoneNumber *_Nullable parsedPhoneNumber;
parsedPhoneNumber = [PhoneNumber tryParsePhoneNumberFromE164:phoneNumber.phoneNumber];
if (!parsedPhoneNumber) {
parsedPhoneNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:phoneNumber.phoneNumber];
}
if (parsedPhoneNumber) {
[e164PhoneNumbers addObject:parsedPhoneNumber.toE164];
}
}
self.e164PhoneNumbersCached = e164PhoneNumbers;
return e164PhoneNumbers;
}
@end
#pragma mark -
@ -553,7 +599,20 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value)
NSMutableArray<OWSContactPhoneNumber *> *phoneNumbers = [NSMutableArray new];
for (CNLabeledValue<CNPhoneNumber *> *phoneNumberField in systemContact.phoneNumbers) {
OWSContactPhoneNumber *phoneNumber = [OWSContactPhoneNumber new];
phoneNumber.phoneNumber = phoneNumberField.value.stringValue;
// Make a best effort to parse the phone number to e164.
NSString *unparsedPhoneNumber = phoneNumberField.value.stringValue;
PhoneNumber *_Nullable parsedPhoneNumber;
parsedPhoneNumber = [PhoneNumber tryParsePhoneNumberFromE164:unparsedPhoneNumber];
if (!parsedPhoneNumber) {
parsedPhoneNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:unparsedPhoneNumber];
}
if (parsedPhoneNumber) {
phoneNumber.phoneNumber = parsedPhoneNumber.toE164;
} else {
phoneNumber.phoneNumber = unparsedPhoneNumber;
}
if ([phoneNumberField.label isEqualToString:CNLabelHome]) {
phoneNumber.phoneType = OWSContactPhoneType_Home;
} else if ([phoneNumberField.label isEqualToString:CNLabelWork]) {

View File

@ -12,4 +12,7 @@
- (NSString * _Nonnull)displayNameForPhoneIdentifier:(NSString * _Nullable)phoneNumber;
- (NSArray<SignalAccount *> * _Nonnull)signalAccounts;
- (BOOL)isSystemContact:(NSString *)recipientId;
- (BOOL)isSystemContactWithSignalAccount:(NSString *)recipientId;
@end