diff --git a/Signal/src/ViewControllers/ContactViewController.swift b/Signal/src/ViewControllers/ContactViewController.swift index 37d02f53e4..4493b57785 100644 --- a/Signal/src/ViewControllers/ContactViewController.swift +++ b/Signal/src/ViewControllers/ContactViewController.swift @@ -77,7 +77,6 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate required init(contact: OWSContact) { contactsManager = Environment.current().contactsManager self.contact = contact - self.scrollView = UIScrollView() super.init(nibName: nil, bundle: nil) @@ -98,15 +97,16 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate // MARK: - View Lifecycle - override func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - } - override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) UIUtil.applySignalAppearence() + if let navigationController = self.navigationController { + owsFail("\(logTag) missing navigationController") + navigationController.isNavigationBarHidden = true + } + contactsManager.requestSystemContactsOnce(completion: { [weak self] _ in guard let strongSelf = self else { return } strongSelf.updateMode() @@ -119,18 +119,24 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate UIUtil.applySignalAppearence() } - private var scrollView: UIScrollView + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + if let navigationController = self.navigationController { + owsFail("\(logTag) missing navigationController") + navigationController.isNavigationBarHidden = false + } + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + } override func loadView() { super.loadView() - self.view.addSubview(scrollView) - scrollView.layoutMargins = .zero - scrollView.autoPinWidthToSuperview() - scrollView.autoPin(toTopLayoutGuideOf: self, withInset: 0) - scrollView.autoPin(toBottomLayoutGuideOf: self, withInset: 0) - - self.view.backgroundColor = UIColor.white + self.view.preservesSuperviewLayoutMargins = false + self.view.backgroundColor = heroBackgroundColor() updateContent() @@ -185,26 +191,90 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate private func updateContent() { SwiftAssertIsOnMainThread(#function) - let rootView = self.scrollView + guard let rootView = self.view else { + owsFail("\(logTag) missing root view.") + return + } for subview in rootView.subviews { subview.removeFromSuperview() } - // TODO: The design calls for no navigation bar, just a back button. - let topView = UIView.container() - topView.backgroundColor = UIColor(rgbHex: 0xefeff4) - topView.preservesSuperviewLayoutMargins = true + let topView = createTopView() rootView.addSubview(topView) - topView.autoPinEdge(toSuperviewEdge: .top) - topView.autoPinEdge(.left, to: .left, of: self.view) - topView.autoPinEdge(.right, to: .right, of: self.view) + topView.autoPin(toTopLayoutGuideOf: self, withInset: 0) + topView.autoPinWidthToSuperview() + + // This view provides a background "below the fold". + let bottomView = UIView.container() + bottomView.backgroundColor = UIColor.white + self.view.addSubview(bottomView) + bottomView.layoutMargins = .zero + bottomView.autoPinWidthToSuperview() + bottomView.autoPinEdge(.top, to: .bottom, of: topView) + bottomView.autoPinEdge(toSuperviewEdge: .bottom) + + let scrollView = UIScrollView() + scrollView.preservesSuperviewLayoutMargins = false + self.view.addSubview(scrollView) + scrollView.layoutMargins = .zero + scrollView.autoPinWidthToSuperview() + scrollView.autoPinEdge(.top, to: .bottom, of: topView) + scrollView.autoPinEdge(toSuperviewEdge: .bottom) + + let fieldsView = createFieldsView() + + scrollView.addSubview(fieldsView) + fieldsView.autoPinLeadingToSuperviewMargin() + fieldsView.autoPinTrailingToSuperviewMargin() + fieldsView.autoPinEdge(toSuperviewEdge: .top) + fieldsView.autoPinEdge(toSuperviewEdge: .bottom) + } + + private func heroBackgroundColor() -> UIColor { + return UIColor(rgbHex: 0xefeff4) + } + + private func createTopView() -> UIView { + SwiftAssertIsOnMainThread(#function) + + let topView = UIView.container() + topView.backgroundColor = heroBackgroundColor() + topView.preservesSuperviewLayoutMargins = false + + // Back Button + let backButtonSize = CGFloat(50) + let backButton = TappableView(actionBlock: { [weak self] _ in + guard let strongSelf = self else { return } + strongSelf.didPressDismiss() + }) + backButton.autoSetDimension(.width, toSize: backButtonSize) + backButton.autoSetDimension(.height, toSize: backButtonSize) + topView.addSubview(backButton) + backButton.autoPin(toTopLayoutGuideOf: self, withInset: 0) + backButton.autoPinLeadingToSuperviewMargin() + + let backIconName = (self.view.isRTL() ? "system_disclosure_indicator" : "system_disclosure_indicator_rtl") + guard let backIconImage = UIImage(named: backIconName) else { + owsFail("\(logTag) missing icon.") + return topView + } + let backIconView = UIImageView(image: backIconImage.withRenderingMode(.alwaysTemplate)) + backIconView.contentMode = .scaleAspectFit + backIconView.tintColor = UIColor.black.withAlphaComponent(0.6) + backButton.addSubview(backIconView) + backIconView.autoCenterInSuperview() // TODO: Use actual avatar. let avatarSize = CGFloat(100) - let avatarView = UIView.container() - avatarView.backgroundColor = UIColor.ows_materialBlue - avatarView.layer.cornerRadius = avatarSize * 0.5 + + let avatarView = AvatarImageView() + // TODO: What's the best colorSeed value to use? + let avatarBuilder = OWSContactAvatarBuilder(nonSignalName: contact.displayName, + colorSeed: contact.displayName, + diameter: UInt(avatarSize), + contactsManager: contactsManager) + avatarView.image = avatarBuilder.build() topView.addSubview(avatarView) avatarView.autoPin(toTopLayoutGuideOf: self, withInset: 20) avatarView.autoHCenterInSuperview() @@ -226,7 +296,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate if let firstPhoneNumber = contact.phoneNumbers.first { let phoneNumberLabel = UILabel() - phoneNumberLabel.text = firstPhoneNumber.phoneNumber + phoneNumberLabel.text = PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: firstPhoneNumber.phoneNumber) phoneNumberLabel.font = UIFont.ows_dynamicTypeCaption2 phoneNumberLabel.textColor = UIColor.black phoneNumberLabel.lineBreakMode = .byTruncatingTail @@ -297,16 +367,15 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate lastView.autoPinEdge(toSuperviewEdge: .bottom, withInset: 15) - let bottomView = UIView.container() - bottomView.backgroundColor = UIColor.white - bottomView.layoutMargins = .zero - bottomView.preservesSuperviewLayoutMargins = false - rootView.addSubview(bottomView) - bottomView.autoPinEdge(.top, to: .bottom, of: topView) - bottomView.autoPinEdge(toSuperviewEdge: .bottom) - bottomView.autoPinEdge(.left, to: .left, of: self.view) - bottomView.autoPinEdge(.right, to: .right, of: self.view) - bottomView.setContentHuggingVerticalLow() + return topView + } + + private func createFieldsView() -> UIView { + SwiftAssertIsOnMainThread(#function) + + let fieldsView = UIView.container() + fieldsView.layoutMargins = .zero + fieldsView.preservesSuperviewLayoutMargins = false var lastRow: UIView? @@ -317,7 +386,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate } let row = UIView() row.backgroundColor = UIColor(rgbHex: 0xdedee1) - bottomView.addSubview(row) + fieldsView.addSubview(row) row.autoSetDimension(.height, toSize: 1) row.autoPinLeadingToSuperviewMargin(withInset: self.hMargin) row.autoPinTrailingToSuperviewMargin() @@ -329,7 +398,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate if lastRow != nil { addSpacerRow() } - bottomView.addSubview(row) + fieldsView.addSubview(row) row.autoPinLeadingToSuperviewMargin() row.autoPinTrailingToSuperviewMargin() if let lastRow = lastRow { @@ -359,9 +428,10 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate // } for phoneNumber in contact.phoneNumbers { - // TODO: Try to format the phone number nicely. + let formattedPhoneNumber = PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: phoneNumber.phoneNumber) + addRow(createNameValueRow(name: phoneNumber.localizedLabel(), - value: phoneNumber.phoneNumber, + value: formattedPhoneNumber, actionBlock: { guard let url = NSURL(string: "tel:\(phoneNumber.phoneNumber)") else { owsFail("\(ContactViewController.logTag) could not open phone number.") @@ -386,6 +456,8 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate // TODO: Should we present addresses here too? How? lastRow?.autoPinEdge(toSuperviewEdge: .bottom) + + return fieldsView } private let hMargin = CGFloat(16) @@ -411,7 +483,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate return row } - private func createNameValueRow(name: String, value: String, actionBlock : @escaping () -> Void) -> UIView { + private func createNameValueRow(name: String, value: String?, actionBlock : @escaping () -> Void) -> UIView { let row = TappableView(actionBlock: actionBlock) row.layoutMargins.left = 0 row.layoutMargins.right = 0 @@ -527,40 +599,38 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate func didPressSendMessage() { Logger.info("\(logTag) \(#function)") - // TODO: We're taking the first Signal account id. We might - // want to let the user select if there's more than one. - guard let recipientId = systemContactsWithSignalAccountsForContact().first else { - owsFail("\(logTag) missing Signal recipient id.") - return - } - - SignalApp.shared().presentConversation(forRecipientId: recipientId, action: .compose) + presentThreadAndPeform(action: .compose) } func didPressAudioCall() { Logger.info("\(logTag) \(#function)") - // TODO: We're taking the first Signal account id. We might - // want to let the user select if there's more than one. - guard let recipientId = systemContactsWithSignalAccountsForContact().first else { - owsFail("\(logTag) missing Signal recipient id.") - return - } - - SignalApp.shared().presentConversation(forRecipientId: recipientId, action: .audioCall) + presentThreadAndPeform(action: .audioCall) } 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. - guard let recipientId = systemContactsWithSignalAccountsForContact().first else { + 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 + } - SignalApp.shared().presentConversation(forRecipientId: recipientId, action: .videoCall) + showPhoneNumberPicker(phoneNumbers: phoneNumbers, completion: { (recipientId) in + SignalApp.shared().presentConversation(forRecipientId: recipientId, action: action) + }) } func didPressInvite() { @@ -582,6 +652,27 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate inviteFlow.sendSMSTo(phoneNumbers: phoneNumbers) } + 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) + } + + func didPressDismiss() { + Logger.info("\(self.logTag) \(#function)") + + self.navigationController?.popViewController(animated: true) + } + // MARK: - private func presentNewContactView() { diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSContactShareView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSContactShareView.m index fad3386770..f56f63bb58 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSContactShareView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSContactShareView.m @@ -3,10 +3,14 @@ // #import "OWSContactShareView.h" +#import "OWSContactAvatarBuilder.h" +#import "Signal-Swift.h" #import "UIColor+JSQMessages.h" #import "UIColor+OWS.h" #import "UIFont+OWS.h" #import "UIView+OWS.h" +#import +#import #import NS_ASSUME_NONNULL_BEGIN @@ -101,22 +105,19 @@ NS_ASSUME_NONNULL_BEGIN [contentView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:self.vMargin]; [contentView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:self.vMargin]; - UIView *iconCircleView = [UIView containerView]; - iconCircleView.backgroundColor = [UIColor colorWithRGBHex:0x00ffff]; - iconCircleView.layer.cornerRadius = self.iconSize * 0.5f; - [iconCircleView autoSetDimension:ALDimensionWidth toSize:self.iconSize]; - [iconCircleView autoSetDimension:ALDimensionHeight toSize:self.iconSize]; - [iconCircleView setCompressionResistanceHigh]; - [iconCircleView setContentHuggingHigh]; - - // TODO: Use avatar, if present and downloaded. else default. - UIImage *image = [UIImage imageNamed:@"attachment_file"]; - OWSAssert(image); - UIImageView *imageView = [UIImageView new]; - imageView.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - imageView.tintColor = self.bubbleBackgroundColor; - [iconCircleView addSubview:imageView]; - [imageView autoCenterInSuperview]; + // TODO: Use the contact's avatar if present and downloaded. + AvatarImageView *avatarView = [AvatarImageView new]; + // TODO: What's the best colorSeed value to use? + OWSAvatarBuilder *avatarBuilder = + [[OWSContactAvatarBuilder alloc] initWithNonSignalName:self.contactShare.displayName + colorSeed:self.contactShare.displayName + diameter:(NSUInteger)self.iconSize + contactsManager:[Environment current].contactsManager]; + avatarView.image = [avatarBuilder build]; + [avatarView autoSetDimension:ALDimensionWidth toSize:self.iconSize]; + [avatarView autoSetDimension:ALDimensionHeight toSize:self.iconSize]; + [avatarView setCompressionResistanceHigh]; + [avatarView setContentHuggingHigh]; UILabel *topLabel = [UILabel new]; topLabel.text = self.contactShare.displayName; @@ -135,7 +136,7 @@ NS_ASSUME_NONNULL_BEGIN NSString *_Nullable firstPhoneNumber = self.contactShare.phoneNumbers.firstObject.phoneNumber; if (firstPhoneNumber.length > 0) { UILabel *bottomLabel = [UILabel new]; - bottomLabel.text = firstPhoneNumber; + bottomLabel.text = [PhoneNumber bestEffortLocalizedPhoneNumberWithE164:firstPhoneNumber]; // TODO: bottomLabel.textColor = [UIColor ows_darkGrayColor]; bottomLabel.lineBreakMode = NSLineBreakByTruncatingTail; @@ -166,7 +167,7 @@ NS_ASSUME_NONNULL_BEGIN [stackView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:0 relation:NSLayoutRelationGreaterThanOrEqual]; [stackView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:0 relation:NSLayoutRelationGreaterThanOrEqual]; - [stackView addArrangedSubview:iconCircleView]; + [stackView addArrangedSubview:avatarView]; [stackView addArrangedSubview:labelsView]; [stackView addArrangedSubview:disclosureImageView]; } diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m index 78c07e0ed2..c9f3c85fbf 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m @@ -3000,17 +3000,14 @@ typedef OWSContact * (^OWSContactBlock)(void); phoneNumber2, ]; - OWSContactEmail *email1 = [OWSContactEmail new]; - email1.emailType = OWSContactEmailType_Home; - email1.email = @"a1@b.com"; - OWSContactEmail *email2 = [OWSContactEmail new]; - email2.emailType = OWSContactEmailType_Custom; - email2.label = @"customer support"; - email2.email = @"a2@b.com"; - contact.emails = @[ - email1, - email2, - ]; + NSMutableArray *emails = [NSMutableArray new]; + for (NSUInteger i = 0; i < 16; i++) { + OWSContactEmail *email = [OWSContactEmail new]; + email.emailType = OWSContactEmailType_Home; + email.email = [NSString stringWithFormat:@"a%zd@b.com", i]; + [emails addObject:email]; + } + contact.emails = emails; OWSContactAddress *address1 = [OWSContactAddress new]; address1.addressType = OWSContactAddressType_Home; @@ -3178,17 +3175,14 @@ typedef OWSContact * (^OWSContactBlock)(void); phoneNumber2, ]; - OWSContactEmail *email1 = [OWSContactEmail new]; - email1.emailType = OWSContactEmailType_Home; - email1.email = @"a@b.com"; - OWSContactEmail *email2 = [OWSContactEmail new]; - email2.emailType = OWSContactEmailType_Custom; - email2.label = @"customer support"; - email2.email = @"a@b.com"; - contact.emails = @[ - email1, - email2, - ]; + NSMutableArray *emails = [NSMutableArray new]; + for (NSUInteger i = 0; i < 16; i++) { + OWSContactEmail *email = [OWSContactEmail new]; + email.emailType = OWSContactEmailType_Home; + email.email = [NSString stringWithFormat:@"a%zd@b.com", i]; + [emails addObject:email]; + } + contact.emails = emails; OWSContactAddress *address1 = [OWSContactAddress new]; address1.addressType = OWSContactAddressType_Home; diff --git a/Signal/src/views/ReminderView.swift b/Signal/src/views/ReminderView.swift index 40a2f86e2d..7102d3ce9f 100644 --- a/Signal/src/views/ReminderView.swift +++ b/Signal/src/views/ReminderView.swift @@ -110,8 +110,11 @@ class ReminderView: UIView { // Icon let iconName = (self.isRTL() ? "system_disclosure_indicator_rtl" : "system_disclosure_indicator") - let iconImage = UIImage(named: iconName)?.withRenderingMode(.alwaysTemplate) - let iconView = UIImageView(image: iconImage) + guard let iconImage = UIImage(named: iconName) else { + owsFail("\(logTag) missing icon.") + return + } + let iconView = UIImageView(image: iconImage.withRenderingMode(.alwaysTemplate)) iconView.contentMode = .scaleAspectFit iconView.tintColor = UIColor.black.withAlphaComponent(0.6) container.addSubview(iconView) diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 131814aa56..6c0fc69347 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -406,6 +406,9 @@ /* Generic button text to proceed with an action */ "CONFIRMATION_TITLE" = "Confirm"; +/* Label for a contact's postal address. */ +"CONTACT_ADDRESS" = "Address"; + /* An indicator that a contact has been blocked. */ "CONTACT_CELL_IS_BLOCKED" = "Blocked"; @@ -415,6 +418,12 @@ /* No comment provided by engineer. */ "CONTACT_DETAIL_COMM_TYPE_INSECURE" = "Unregistered Number"; +/* Label for a contact's email address. */ +"CONTACT_EMAIL" = "Email"; + +/* Label for a contact's phone number. */ +"CONTACT_PHONE" = "Phone"; + /* table cell subtitle when contact card has no email */ "CONTACT_PICKER_NO_EMAILS_AVAILABLE" = "No email available."; @@ -424,6 +433,9 @@ /* navbar title for contact picker when sharing a contact */ "CONTACT_PICKER_TITLE" = "Select Contact"; +/* Indicates that a contact has no name. */ +"CONTACT_WITHOUT_NAME" = "Unnamed Contact"; + /* title for conversation settings screen */ "CONVERSATION_SETTINGS" = "Conversation Settings"; diff --git a/SignalServiceKit/src/Contacts/PhoneNumber.h b/SignalServiceKit/src/Contacts/PhoneNumber.h index 2d717bc9c6..7d89ad3047 100644 --- a/SignalServiceKit/src/Contacts/PhoneNumber.h +++ b/SignalServiceKit/src/Contacts/PhoneNumber.h @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #define COUNTRY_CODE_PREFIX @"+" @@ -30,6 +30,7 @@ + (NSString *)bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:(NSString *)input; + (NSString *)bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:(NSString *)input withSpecifiedCountryCodeString:(NSString *)countryCodeString; ++ (NSString *)bestEffortLocalizedPhoneNumberWithE164:(NSString *)phoneNumber; + (NSString *)regionCodeFromCountryCodeString:(NSString *)countryCodeString; diff --git a/SignalServiceKit/src/Contacts/PhoneNumber.m b/SignalServiceKit/src/Contacts/PhoneNumber.m index ce78fdd19f..2eb0563a7f 100644 --- a/SignalServiceKit/src/Contacts/PhoneNumber.m +++ b/SignalServiceKit/src/Contacts/PhoneNumber.m @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "PhoneNumber.h" @@ -119,6 +119,45 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN return result; } ++ (NSString *)formatIntAsEN:(int)value +{ + static NSNumberFormatter *formatter = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + formatter = [NSNumberFormatter new]; + formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US"]; + }); + return [formatter stringFromNumber:@(value)]; +} + ++ (NSString *)bestEffortLocalizedPhoneNumberWithE164:(NSString *)phoneNumber +{ + OWSAssert(phoneNumber); + + PhoneNumber *_Nullable parsedPhoneNumber = [self tryParsePhoneNumberFromE164:phoneNumber]; + if (!parsedPhoneNumber) { + DDLogWarn(@"%@ could not parse phone number.", self.logTag); + return phoneNumber; + } + NSNumber *_Nullable countryCode = [parsedPhoneNumber getCountryCode]; + if (!countryCode) { + DDLogWarn(@"%@ parsed phone number has no country code.", self.logTag); + return phoneNumber; + } + NSString *countryCodeString = [self formatIntAsEN:countryCode.intValue]; + if (countryCodeString.length < 1) { + DDLogWarn(@"%@ invalid country code.", self.logTag); + return phoneNumber; + } + NSString *_Nullable formattedPhoneNumber = + [self bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:phoneNumber + withSpecifiedRegionCode:countryCodeString]; + if (!countryCode) { + DDLogWarn(@"%@ could not format phone number.", self.logTag); + return phoneNumber; + } + return formattedPhoneNumber; +} + (NSString *)regionCodeFromCountryCodeString:(NSString *)countryCodeString { NBPhoneNumberUtil *phoneUtil = [PhoneNumberUtil sharedUtil].nbPhoneNumberUtil; diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContact+Private.h b/SignalServiceKit/src/Messages/Interactions/OWSContact+Private.h index 5a037eb84b..e8f37c6cb5 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSContact+Private.h +++ b/SignalServiceKit/src/Messages/Interactions/OWSContact+Private.h @@ -55,7 +55,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, nullable) NSString *namePrefix; @property (nonatomic, nullable) NSString *middleName; @property (nonatomic, nullable) NSString *organizationName; -@property (nonatomic, nullable) NSString *displayName; +@property (nonatomic) NSString *displayName; @property (nonatomic) NSArray *phoneNumbers; @property (nonatomic) NSArray *emails; diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContact.h b/SignalServiceKit/src/Messages/Interactions/OWSContact.h index 517debc26a..4a87832e62 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSContact.h +++ b/SignalServiceKit/src/Messages/Interactions/OWSContact.h @@ -109,7 +109,7 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value); @property (nonatomic, readonly, nullable) NSString *namePrefix; @property (nonatomic, readonly, nullable) NSString *middleName; @property (nonatomic, readonly, nullable) NSString *organizationName; -@property (nonatomic, readonly, nullable) NSString *displayName; +@property (nonatomic, readonly) NSString *displayName; @property (nonatomic, readonly) NSArray *phoneNumbers; @property (nonatomic, readonly) NSArray *emails; diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContact.m b/SignalServiceKit/src/Messages/Interactions/OWSContact.m index b7c7f9fb24..a284d1d382 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSContact.m +++ b/SignalServiceKit/src/Messages/Interactions/OWSContact.m @@ -49,18 +49,7 @@ NSString *NSStringForContactPhoneType(OWSContactPhoneType value) DDLogWarn(@"%@ invalid phone number; not e164: %@.", self.logTag, self.phoneNumber); return NO; } - switch (self.phoneType) { - case OWSContactPhoneType_Home: - case OWSContactPhoneType_Mobile: - case OWSContactPhoneType_Work: - return YES; - case OWSContactPhoneType_Custom: - if (self.label.ows_stripped.length < 1) { - DDLogWarn(@"%@ invalid phone number; missing custom label: %@.", self.logTag, self.label); - return NO; - } - return YES; - } + return YES; } - (NSString *)localizedLabel @@ -73,7 +62,10 @@ NSString *NSStringForContactPhoneType(OWSContactPhoneType value) case OWSContactPhoneType_Work: return [CNLabeledValue localizedStringForLabel:CNLabelWork]; default: - return self.label; + if (self.label.ows_stripped.length < 1) { + return NSLocalizedString(@"CONTACT_PHONE", @"Label for a contact's phone number."); + } + return self.label.ows_stripped; } } @@ -130,18 +122,7 @@ NSString *NSStringForContactEmailType(OWSContactEmailType value) DDLogWarn(@"%@ invalid email: %@.", self.logTag, self.email); return NO; } - switch (self.emailType) { - case OWSContactEmailType_Home: - case OWSContactEmailType_Mobile: - case OWSContactEmailType_Work: return YES; - case OWSContactEmailType_Custom: - if (self.label.ows_stripped.length < 1) { - DDLogWarn(@"%@ invalid email; missing custom label: %@.", self.logTag, self.label); - return NO; - } - return YES; - } } - (NSString *)localizedLabel @@ -154,7 +135,10 @@ NSString *NSStringForContactEmailType(OWSContactEmailType value) case OWSContactEmailType_Work: return [CNLabeledValue localizedStringForLabel:CNLabelWork]; default: - return self.label; + if (self.label.ows_stripped.length < 1) { + return NSLocalizedString(@"CONTACT_EMAIL", @"Label for a contact's email address."); + } + return self.label.ows_stripped; } } @@ -217,17 +201,7 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value) DDLogWarn(@"%@ invalid address; empty.", self.logTag); return NO; } - switch (self.addressType) { - case OWSContactAddressType_Home: - case OWSContactAddressType_Work: return YES; - case OWSContactAddressType_Custom: - if (self.label.ows_stripped.length < 1) { - DDLogWarn(@"%@ invalid address; missing custom label: %@.", self.logTag, self.label); - return NO; - } - return YES; - } } - (NSString *)localizedLabel @@ -238,7 +212,10 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value) case OWSContactAddressType_Work: return [CNLabeledValue localizedStringForLabel:CNLabelWork]; default: - return self.label; + if (self.label.ows_stripped.length < 1) { + return NSLocalizedString(@"CONTACT_ADDRESS", @"Label for a contact's postal address."); + } + return self.label.ows_stripped; } } @@ -288,7 +265,7 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value) @property (nonatomic, nullable) NSString *namePrefix; @property (nonatomic, nullable) NSString *middleName; @property (nonatomic, nullable) NSString *organizationName; -@property (nonatomic, nullable) NSString *displayName; +@property (nonatomic) NSString *displayName; @property (nonatomic) NSArray *phoneNumbers; @property (nonatomic) NSArray *emails; @@ -360,10 +337,14 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value) return hasValue; } -- (nullable NSString *)displayName +- (NSString *)displayName { [self ensureDisplayName]; + if (_displayName.length < 1) { + OWSProdLogAndFail(@"%@ could not derive a valid display name.", self.logTag); + return NSLocalizedString(@"CONTACT_WITHOUT_NAME", @"Indicates that a contact has no name."); + } return _displayName; } @@ -377,9 +358,6 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value) // Fall back to using the organization name. _displayName = self.organizationName; } - if (_displayName.length < 1) { - OWSProdLogAndFail(@"%@ could not derive a valid display name.", self.logTag); - } } - (NSString *)debugDescription