diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 34a9dfbc44..624e464424 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -48,6 +48,8 @@ 34330A5C1E787A9800DF2FB9 /* dripicons-v2.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5B1E787A9800DF2FB9 /* dripicons-v2.ttf */; }; 34330A5E1E787BD800DF2FB9 /* ElegantIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5D1E787BD800DF2FB9 /* ElegantIcons.ttf */; }; 34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34330AA21E79686200DF2FB9 /* OWSProgressView.m */; }; + 34382266209A4E400094FEB7 /* ApproveContactShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34382265209A4E3F0094FEB7 /* ApproveContactShareViewController.swift */; }; + 3438226A209B63500094FEB7 /* EditContactShareNameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34382269209B63500094FEB7 /* EditContactShareNameViewController.swift */; }; 34386A51207D0C01009F5D9C /* HomeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34386A4D207D0C01009F5D9C /* HomeViewController.m */; }; 34386A52207D0C01009F5D9C /* HomeViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34386A50207D0C01009F5D9C /* HomeViewCell.m */; }; 34386A54207D271D009F5D9C /* NeverClearView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34386A53207D271C009F5D9C /* NeverClearView.swift */; }; @@ -161,6 +163,7 @@ 3478506C1FD9B78A007B8332 /* NoopNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 347850681FD9B78A007B8332 /* NoopNotificationsManager.swift */; }; 347850711FDAEB17007B8332 /* OWSUserProfile.m in Sources */ = {isa = PBXBuildFile; fileRef = 3478506F1FDAEB16007B8332 /* OWSUserProfile.m */; }; 347850721FDAEB17007B8332 /* OWSUserProfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 347850701FDAEB16007B8332 /* OWSUserProfile.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 348BB254209CD4B80047AEC2 /* ContactFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348BB253209CD4B80047AEC2 /* ContactFieldView.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 */; }; @@ -630,6 +633,8 @@ 34330A5D1E787BD800DF2FB9 /* ElegantIcons.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = ElegantIcons.ttf; sourceTree = ""; }; 34330AA11E79686200DF2FB9 /* OWSProgressView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSProgressView.h; sourceTree = ""; }; 34330AA21E79686200DF2FB9 /* OWSProgressView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSProgressView.m; sourceTree = ""; }; + 34382265209A4E3F0094FEB7 /* ApproveContactShareViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ApproveContactShareViewController.swift; path = SignalMessaging/attachments/ApproveContactShareViewController.swift; sourceTree = SOURCE_ROOT; }; + 34382269209B63500094FEB7 /* EditContactShareNameViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EditContactShareNameViewController.swift; path = SignalMessaging/attachments/EditContactShareNameViewController.swift; sourceTree = SOURCE_ROOT; }; 34386A4D207D0C01009F5D9C /* HomeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HomeViewController.m; sourceTree = ""; }; 34386A4E207D0C01009F5D9C /* HomeViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HomeViewCell.h; sourceTree = ""; }; 34386A4F207D0C01009F5D9C /* HomeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HomeViewController.h; sourceTree = ""; }; @@ -755,6 +760,7 @@ 347850681FD9B78A007B8332 /* NoopNotificationsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoopNotificationsManager.swift; sourceTree = ""; }; 3478506F1FDAEB16007B8332 /* OWSUserProfile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSUserProfile.m; sourceTree = ""; }; 347850701FDAEB16007B8332 /* OWSUserProfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSUserProfile.h; sourceTree = ""; }; + 348BB253209CD4B80047AEC2 /* ContactFieldView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ContactFieldView.swift; path = SignalMessaging/attachments/ContactFieldView.swift; sourceTree = SOURCE_ROOT; }; 348F2EAD1F0D21BC00D4ECE0 /* DeviceSleepManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceSleepManager.swift; sourceTree = ""; }; 3495BC911F1426B800B478F5 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = translations/ar.lproj/Localizable.strings; sourceTree = ""; }; 3496744B2076768600080B5F /* OWSMessageBubbleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageBubbleView.h; sourceTree = ""; }; @@ -1551,12 +1557,12 @@ 346129A11FD1F09100532771 /* contacts */ = { isa = PBXGroup; children = ( - 3400C7941EAF99F4008A8584 /* SelectThreadViewController.h */, - 3400C7951EAF99F4008A8584 /* SelectThreadViewController.m */, 346129A21FD1F09100532771 /* OWSContactsManager.h */, 346129A31FD1F09100532771 /* OWSContactsManager.m */, 34612A041FD7238500532771 /* OWSContactsSyncing.h */, 34612A051FD7238500532771 /* OWSContactsSyncing.m */, + 3400C7941EAF99F4008A8584 /* SelectThreadViewController.h */, + 3400C7951EAF99F4008A8584 /* SelectThreadViewController.m */, 346129AE1FD1F5D900532771 /* SystemContactsFetcher.swift */, ); path = contacts; @@ -1596,9 +1602,12 @@ 346129DB1FD5C02900532771 /* viewControllers */ = { isa = PBXGroup; children = ( + 34382265209A4E3F0094FEB7 /* ApproveContactShareViewController.swift */, 344F2498200FD03200CFB4F4 /* AttachmentApprovalViewController.swift */, + 348BB253209CD4B80047AEC2 /* ContactFieldView.swift */, 344F248220069E9B00CFB4F4 /* CountryCodeViewController.h */, 344F248320069E9B00CFB4F4 /* CountryCodeViewController.m */, + 34382269209B63500094FEB7 /* EditContactShareNameViewController.swift */, 346129DC1FD5C02900532771 /* LockInteractionController.h */, 346129DD1FD5C02900532771 /* LockInteractionController.m */, 344F2496200FD03200CFB4F4 /* MessageApprovalViewController.swift */, @@ -3051,6 +3060,7 @@ 346129E61FD5C0C600532771 /* OWSDatabaseMigrationRunner.m in Sources */, 346129AB1FD1F0EE00532771 /* OWSFormat.m in Sources */, 344F249C200FD03300CFB4F4 /* AttachmentApprovalViewController.swift in Sources */, + 348BB254209CD4B80047AEC2 /* ContactFieldView.swift in Sources */, 451F8A461FD715BA005CB9DA /* OWSGroupAvatarBuilder.m in Sources */, 347850591FD9972E007B8332 /* SwiftSingletons.swift in Sources */, 344F248720069ECB00CFB4F4 /* ModalActivityIndicatorViewController.swift in Sources */, @@ -3093,7 +3103,9 @@ 451F8A3F1FD71455005CB9DA /* OWSTableViewController.m in Sources */, 451F8A351FD710DE005CB9DA /* Searcher.swift in Sources */, 451F8A481FD715BA005CB9DA /* OWSContactAvatarBuilder.m in Sources */, + 34382266209A4E400094FEB7 /* ApproveContactShareViewController.swift in Sources */, 4503F1C3204711D300CEE724 /* OWS107LegacySounds.m in Sources */, + 3438226A209B63500094FEB7 /* EditContactShareNameViewController.swift in Sources */, 346129A61FD1F09100532771 /* OWSContactsManager.m in Sources */, 4598198F204E2F28009414F2 /* OWS108CallLoggingPreference.m in Sources */, 346129D21FD2085A00532771 /* CommonStrings.swift in Sources */, diff --git a/Signal/src/ViewControllers/ContactViewController.swift b/Signal/src/ViewControllers/ContactViewController.swift index b0d2733b72..8c03decff0 100644 --- a/Signal/src/ViewControllers/ContactViewController.swift +++ b/Signal/src/ViewControllers/ContactViewController.swift @@ -14,7 +14,7 @@ class TappableView: UIView { // MARK: - Initializers - @available(*, unavailable, message: "use init(call:) constructor instead.") + @available(*, unavailable, message: "use other constructor instead.") required init?(coder aDecoder: NSCoder) { fatalError("Unimplemented") } @@ -367,48 +367,14 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate private func createFieldsView() -> UIView { SwiftAssertIsOnMainThread(#function) - let fieldsView = UIView.container() - fieldsView.layoutMargins = .zero - fieldsView.preservesSuperviewLayoutMargins = false - - var lastRow: UIView? - - let addSpacerRow = { - guard let prevRow = lastRow else { - owsFail("\(self.logTag) missing last row") - return - } - let row = UIView() - row.backgroundColor = UIColor(rgbHex: 0xdedee1) - fieldsView.addSubview(row) - row.autoSetDimension(.height, toSize: 1) - row.autoPinLeadingToSuperviewMargin(withInset: self.hMargin) - row.autoPinTrailingToSuperviewMargin() - row.autoPinEdge(.top, to: .bottom, of: prevRow) - lastRow = row - } - - let addRow: ((UIView) -> Void) = { (row) in - if lastRow != nil { - addSpacerRow() - } - fieldsView.addSubview(row) - row.autoPinLeadingToSuperviewMargin() - row.autoPinTrailingToSuperviewMargin() - if let lastRow = lastRow { - row.autoPinEdge(.top, to: .bottom, of: lastRow) - } else { - row.autoPinEdge(toSuperviewEdge: .top) - } - lastRow = row - } + var rows = [UIView]() if viewMode == .nonSystemContact { - addRow(createActionRow(labelText: NSLocalizedString("CONVERSATION_SETTINGS_NEW_CONTACT", + rows.append(createActionRow(labelText: NSLocalizedString("CONVERSATION_SETTINGS_NEW_CONTACT", comment: "Label for 'new contact' button in conversation settings view."), action: #selector(didPressCreateNewContact))) - addRow(createActionRow(labelText: NSLocalizedString("CONVERSATION_SETTINGS_ADD_TO_EXISTING_CONTACT", + rows.append(createActionRow(labelText: NSLocalizedString("CONVERSATION_SETTINGS_ADD_TO_EXISTING_CONTACT", comment: "Label for 'new contact' button in conversation settings view."), action: #selector(didPressAddToExistingContact))) } @@ -424,7 +390,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate for phoneNumber in contact.phoneNumbers { let formattedPhoneNumber = PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: phoneNumber.phoneNumber) - addRow(createNameValueRow(name: phoneNumber.localizedLabel(), + rows.append(createNameValueRow(name: phoneNumber.localizedLabel(), value: formattedPhoneNumber, actionBlock: { guard let url = NSURL(string: "tel:\(phoneNumber.phoneNumber)") else { @@ -436,7 +402,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate } for email in contact.emails { - addRow(createNameValueRow(name: email.localizedLabel(), + rows.append(createNameValueRow(name: email.localizedLabel(), value: email.email, actionBlock: { guard let url = NSURL(string: "mailto:\(email.email)") else { @@ -449,9 +415,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate // TODO: Should we present addresses here too? How? - lastRow?.autoPinEdge(toSuperviewEdge: .bottom) - - return fieldsView + return ContactFieldView(rows: rows, hMargin: hMargin) } private let hMargin = CGFloat(16) @@ -690,10 +654,10 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate 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)) + contactViewController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: CommonStrings.cancelButton, + style: .plain, + target: self, + action: #selector(didFinishEditingContact)) self.navigationController?.pushViewController(contactViewController, animated: true) diff --git a/Signal/src/ViewControllers/ContactsPicker.swift b/Signal/src/ViewControllers/ContactsPicker.swift index bc6af9e358..24c49bbd25 100644 --- a/Signal/src/ViewControllers/ContactsPicker.swift +++ b/Signal/src/ViewControllers/ContactsPicker.swift @@ -60,7 +60,8 @@ public class ContactsPicker: OWSViewController, UITableViewDelegate, UITableView CNContactFormatter.descriptorForRequiredKeys(for: .fullName), CNContactThumbnailImageDataKey as CNKeyDescriptor, CNContactPhoneNumbersKey as CNKeyDescriptor, - CNContactEmailAddressesKey as CNKeyDescriptor + CNContactEmailAddressesKey as CNKeyDescriptor, + CNContactPostalAddressesKey as CNKeyDescriptor ] // MARK: - Lifecycle Methods diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 9e9a250adc..b1357f4773 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -117,6 +117,7 @@ typedef enum : NSUInteger { #pragma mark - @interface ConversationViewController () *)contacts @@ -4932,6 +4971,27 @@ interactionControllerForAnimationController:(id String + + func isIncluded() -> Bool + + func setIsIncluded(_ isIncluded: Bool) + + func applyToContact(contact: OWSContact) +} + +// MARK: - + +class ContactShareFieldBase: NSObject, ContactShareField { + + let value: ContactFieldType + + private var isIncludedFlag = true + + required init(_ value: ContactFieldType) { + self.value = value + + super.init() + } + + func localizedLabel() -> String { + return value.localizedLabel() + } + + func isIncluded() -> Bool { + return isIncludedFlag + } + + func setIsIncluded(_ isIncluded: Bool) { + isIncludedFlag = isIncluded + } + + func applyToContact(contact: OWSContact) { + preconditionFailure("This method must be overridden") + } +} + +// MARK: - + +class ContactSharePhoneNumber: ContactShareFieldBase { + + override func applyToContact(contact: OWSContact) { + assert(isIncluded()) + + var values = [OWSContactPhoneNumber]() + values += contact.phoneNumbers + values.append(value) + contact.phoneNumbers = values + } +} + +// MARK: - + +class ContactShareEmail: ContactShareFieldBase { + + override func applyToContact(contact: OWSContact) { + assert(isIncluded()) + + var values = [OWSContactEmail]() + values += contact.emails + values.append(value) + contact.emails = values + } +} + +// MARK: - + +class ContactShareAddress: ContactShareFieldBase { + + override func applyToContact(contact: OWSContact) { + assert(isIncluded()) + + var values = [OWSContactAddress]() + values += contact.addresses + values.append(value) + contact.addresses = values + } +} + +// MARK: - + +class ContactShareFieldView: UIView { + + let field: ContactShareField + + let previewViewBlock : (() -> UIView) + + private var checkbox: UIButton! + + // MARK: - Initializers + + @available(*, unavailable, message: "use init(call:) constructor instead.") + required init?(coder aDecoder: NSCoder) { + fatalError("Unimplemented") + } + + required init(field: ContactShareField, previewViewBlock : @escaping (() -> UIView)) { + self.field = field + self.previewViewBlock = previewViewBlock + + super.init(frame: CGRect.zero) + + self.isUserInteractionEnabled = true + self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(wasTapped))) + + createContents() + } + + let hSpacing = CGFloat(10) + let hMargin = CGFloat(0) + + func createContents() { + self.layoutMargins.left = 0 + self.layoutMargins.right = 0 + + let checkbox = UIButton(type: .custom) + self.checkbox = checkbox + // TODO: Use real assets. + checkbox.setTitle("☐", for: .normal) + checkbox.setTitle("☒", for: .selected) + checkbox.setTitleColor(UIColor.black, for: .normal) + checkbox.setTitleColor(UIColor.black, for: .selected) + checkbox.titleLabel?.font = UIFont.ows_dynamicTypeBody + checkbox.isSelected = field.isIncluded() + // Disable the checkbox; the entire row is hot. + checkbox.isUserInteractionEnabled = false + addSubview(checkbox) + checkbox.autoPinEdge(toSuperviewEdge: .leading, withInset: hMargin) + checkbox.autoVCenterInSuperview() + checkbox.setCompressionResistanceHigh() + checkbox.setContentHuggingHigh() + + let nameLabel = UILabel() + nameLabel.text = field.localizedLabel() + nameLabel.font = UIFont.ows_dynamicTypeCaption1 + nameLabel.textColor = UIColor.black + nameLabel.lineBreakMode = .byTruncatingTail + addSubview(nameLabel) + nameLabel.autoPinTopToSuperviewMargin() + nameLabel.autoPinLeading(toTrailingEdgeOf: checkbox, offset: hSpacing) + nameLabel.autoPinTrailingToSuperviewMargin(withInset: hMargin) + + let previewView = previewViewBlock() + addSubview(previewView) + previewView.autoPinEdge(.top, to: .bottom, of: nameLabel, withOffset: 3) + previewView.autoPinBottomToSuperviewMargin() + previewView.autoPinLeading(toTrailingEdgeOf: checkbox, offset: hSpacing) + previewView.autoPinTrailingToSuperviewMargin(withInset: hMargin) + } + + func wasTapped(sender: UIGestureRecognizer) { + Logger.info("\(self.logTag) \(#function)") + + guard sender.state == .recognized else { + return + } + field.setIsIncluded(!field.isIncluded()) + checkbox.isSelected = field.isIncluded() + } +} + +// MARK: - + +@objc +public class ApproveContactShareViewController: OWSViewController, EditContactShareNameViewControllerDelegate { + weak var delegate: ApproveContactShareViewControllerDelegate? + + let contactsManager: OWSContactsManager + + var contactShare: OWSContact + + var fieldViews = [ContactShareFieldView]() + + var nameLabel: UILabel! + + // MARK: Initializers + + @available(*, unavailable, message:"use other constructor instead.") + required public init?(coder aDecoder: NSCoder) { + fatalError("unimplemented") + } + + @objc + required public init(contactShare: OWSContact, contactsManager: OWSContactsManager, delegate: ApproveContactShareViewControllerDelegate) { + self.contactsManager = contactsManager + self.contactShare = contactShare + self.delegate = delegate + + super.init(nibName: nil, bundle: nil) + + buildFields() + } + + func buildFields() { + var fieldViews = [ContactShareFieldView]() + + // TODO: Avatar + + for phoneNumber in contactShare.phoneNumbers { + let field = ContactSharePhoneNumber(phoneNumber) + let fieldView = ContactShareFieldView(field: field, previewViewBlock: { [weak self] _ in + guard let strongSelf = self else { return UIView() } + return strongSelf.previewView(forPhoneNumber: phoneNumber) + }) + fieldViews.append(fieldView) + } + for email in contactShare.emails { + let field = ContactShareEmail(email) + let fieldView = ContactShareFieldView(field: field, previewViewBlock: { [weak self] _ in + guard let strongSelf = self else { return UIView() } + return strongSelf.previewView(forEmail: email) + }) + fieldViews.append(fieldView) + } + for address in contactShare.addresses { + let field = ContactShareAddress(address) + let fieldView = ContactShareFieldView(field: field, previewViewBlock: { [weak self] _ in + guard let strongSelf = self else { return UIView() } + return strongSelf.previewView(forAddress: address) + }) + fieldViews.append(fieldView) + } + + self.fieldViews = fieldViews + } + + // MARK: - View Lifecycle + + override public func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + updateNavigationBar() + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + } + + override public func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + } + + override public func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + } + + override public func loadView() { + super.loadView() + + self.navigationItem.title = NSLocalizedString("CONTACT_SHARE_APPROVAL_VIEW_TITLE", + comment: "Title for the 'Approve contact share' view.") + + self.view.backgroundColor = UIColor.white + + updateContent() + + updateNavigationBar() + } + + // TODO: Surface error with resolution to user if not. + func canShareContact() -> Bool { + return contactShare.ows_isValid() + } + + func updateNavigationBar() { + self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, + target: self, + action: #selector(didPressCancel)) + + if canShareContact() { + self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: NSLocalizedString("ATTACHMENT_APPROVAL_SEND_BUTTON", + comment: "Label for 'send' button in the 'attachment approval' dialog."), + style: .plain, target: self, action: #selector(didPressSendButton)) + } else { + self.navigationItem.rightBarButtonItem = nil + } + + } + + private func updateContent() { + SwiftAssertIsOnMainThread(#function) + + guard let rootView = self.view else { + owsFail("\(logTag) missing root view.") + return + } + + for subview in rootView.subviews { + subview.removeFromSuperview() + } + + let scrollView = UIScrollView() + scrollView.preservesSuperviewLayoutMargins = false + self.view.addSubview(scrollView) + scrollView.layoutMargins = .zero + scrollView.autoPinWidthToSuperview() + scrollView.autoPin(toTopLayoutGuideOf: self, withInset: 0) + scrollView.autoPinEdge(toSuperviewEdge: .bottom) + + let fieldsView = createFieldsView() + + scrollView.addSubview(fieldsView) + // Use layoutMarginsGuide for views inside UIScrollView + // that should have same width as scroll view. + fieldsView.autoPinLeadingToSuperviewMargin() + fieldsView.autoPinTrailingToSuperviewMargin() + fieldsView.autoPinEdge(toSuperviewEdge: .top) + fieldsView.autoPinEdge(toSuperviewEdge: .bottom) + fieldsView.setContentHuggingHorizontalLow() + } + + private func createFieldsView() -> UIView { + SwiftAssertIsOnMainThread(#function) + + var rows = [UIView]() + + rows.append(createNameRow()) + + for fieldView in fieldViews { + rows.append(fieldView) + } + + return ContactFieldView(rows: rows, hMargin: hMargin) + } + + private let hMargin = CGFloat(16) + + func createNameRow() -> UIView { + let nameVMargin = CGFloat(16) + + let row = UIView() + row.layoutMargins = UIEdgeInsets(top: nameVMargin, left: 0, bottom: nameVMargin, right: 0) + + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.alignment = .center + stackView.layoutMargins = .zero + stackView.spacing = 10 + row.addSubview(stackView) + stackView.autoPinEdgesToSuperviewMargins() + + let nameLabel = UILabel() + self.nameLabel = nameLabel + nameLabel.text = contactShare.displayName + nameLabel.font = UIFont.ows_dynamicTypeBody + nameLabel.textColor = UIColor.ows_materialBlue + nameLabel.lineBreakMode = .byTruncatingTail + stackView.addArrangedSubview(nameLabel) + + let editNameLabel = UILabel() + editNameLabel.text = NSLocalizedString("CONTACT_EDIT_NAME_BUTTON", comment: "Label for the 'edit name' button in the contact share approval view.") + editNameLabel.font = UIFont.ows_dynamicTypeCaption1 + editNameLabel.textColor = UIColor.black + stackView.addArrangedSubview(editNameLabel) + editNameLabel.setContentHuggingHigh() + editNameLabel.setCompressionResistanceHigh() + + // Icon + let iconName = (self.view.isRTL() ? "system_disclosure_indicator_rtl" : "system_disclosure_indicator") + guard let iconImage = UIImage(named: iconName) else { + owsFail("\(logTag) missing icon.") + return row + } + let iconView = UIImageView(image: iconImage.withRenderingMode(.alwaysTemplate)) + iconView.contentMode = .scaleAspectFit + iconView.tintColor = UIColor.black.withAlphaComponent(0.6) + stackView.addArrangedSubview(iconView) + iconView.setContentHuggingHigh() + iconView.setCompressionResistanceHigh() + + row.isUserInteractionEnabled = true + row.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didPressEditName))) + + return row + } + + func previewView(forPhoneNumber phoneNumber: OWSContactPhoneNumber) -> UIView { + let label = UILabel() + label.text = PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: phoneNumber.phoneNumber) + label.font = UIFont.ows_dynamicTypeCaption1 + label.textColor = UIColor.ows_materialBlue + label.lineBreakMode = .byTruncatingTail + return label + } + + func previewView(forEmail email: OWSContactEmail) -> UIView { + let label = UILabel() + label.text = email.email + label.font = UIFont.ows_dynamicTypeCaption1 + label.textColor = UIColor.ows_materialBlue + label.lineBreakMode = .byTruncatingTail + return label + } + + func previewView(forAddress address: OWSContactAddress) -> UIView { + + let stackView = UIStackView() + stackView.axis = .vertical + stackView.alignment = .leading + stackView.spacing = 0 + stackView.layoutMargins = .zero + + let tryToAddNameValue: ((String, String?) -> Void) = { (name, value) in + guard let value = value else { + return + } + guard value.count > 0 else { + return + } + let row = UIView.container() + + let nameLabel = UILabel() + nameLabel.text = name + nameLabel.font = UIFont.ows_dynamicTypeCaption1 + nameLabel.textColor = UIColor.black + nameLabel.lineBreakMode = .byTruncatingTail + row.addSubview(nameLabel) + nameLabel.autoPinLeadingToSuperviewMargin() + nameLabel.autoPinHeightToSuperview() + nameLabel.setContentHuggingHigh() + nameLabel.setCompressionResistanceHigh() + + let valueLabel = UILabel() + valueLabel.text = value + valueLabel.font = UIFont.ows_dynamicTypeCaption1 + valueLabel.textColor = UIColor.ows_materialBlue + valueLabel.lineBreakMode = .byTruncatingTail + row.addSubview(valueLabel) + valueLabel.autoPinLeading(toTrailingEdgeOf: nameLabel, offset: 10) + valueLabel.autoPinTrailingToSuperviewMargin() + valueLabel.autoPinHeightToSuperview() + + stackView.addArrangedSubview(row) + } + + tryToAddNameValue(NSLocalizedString("CONTACT_FIELD_ADDRESS_STREET", comment: "Label for the 'street' field of a contact's address."), + address.street) + tryToAddNameValue(NSLocalizedString("CONTACT_FIELD_ADDRESS_POBOX", comment: "Label for the 'pobox' field of a contact's address."), + address.pobox) + tryToAddNameValue(NSLocalizedString("CONTACT_FIELD_ADDRESS_NEIGHBORHOOD", comment: "Label for the 'neighborhood' field of a contact's address."), + address.neighborhood) + tryToAddNameValue(NSLocalizedString("CONTACT_FIELD_ADDRESS_CITY", comment: "Label for the 'city' field of a contact's address."), + address.city) + tryToAddNameValue(NSLocalizedString("CONTACT_FIELD_ADDRESS_REGION", comment: "Label for the 'region' field of a contact's address."), + address.region) + tryToAddNameValue(NSLocalizedString("CONTACT_FIELD_ADDRESS_POSTCODE", comment: "Label for the 'postcode' field of a contact's address."), + address.postcode) + tryToAddNameValue(NSLocalizedString("CONTACT_FIELD_ADDRESS_COUNTRY", comment: "Label for the 'country' field of a contact's address."), + address.country) + + return stackView + } + + // MARK: - + + func filteredContactShare() -> OWSContact { + let result = self.contactShare.newContact(withNamePrefix: self.contactShare.namePrefix, + givenName: self.contactShare.givenName, + middleName: self.contactShare.middleName, + familyName: self.contactShare.familyName, + nameSuffix: self.contactShare.nameSuffix) + + for fieldView in fieldViews { + if fieldView.field.isIncluded() { + fieldView.field.applyToContact(contact: result) + } + } + + return result + } + + // MARK: - + + func didPressSendButton() { + Logger.info("\(logTag) \(#function)") + + guard let delegate = self.delegate else { + owsFail("\(logTag) missing delegate.") + return + } + + let filteredContactShare = self.filteredContactShare() + assert(filteredContactShare.ows_isValid()) + + delegate.approveContactShare(self, didApproveContactShare: filteredContactShare) + } + + func didPressCancel() { + Logger.info("\(logTag) \(#function)") + + guard let delegate = self.delegate else { + owsFail("\(logTag) missing delegate.") + return + } + + delegate.approveContactShare(self, didCancelContactShare: contactShare) + } + + func didPressEditName() { + Logger.info("\(logTag) \(#function)") + + let view = EditContactShareNameViewController(contactShare: contactShare, delegate: self) + self.navigationController?.pushViewController(view, animated: true) + } + + // MARK: - EditContactShareNameViewControllerDelegate + + public func editContactShareNameView(_ editContactShareNameView: EditContactShareNameViewController, didEditContactShare contactShare: OWSContact) { + self.contactShare = contactShare + + nameLabel.text = contactShare.displayName + + self.updateNavigationBar() + } +} diff --git a/SignalMessaging/attachments/ContactFieldView.swift b/SignalMessaging/attachments/ContactFieldView.swift new file mode 100644 index 0000000000..ac4ef71f1b --- /dev/null +++ b/SignalMessaging/attachments/ContactFieldView.swift @@ -0,0 +1,63 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation + +public class ContactFieldView: UIView { + + @available(*, unavailable, message: "use other constructor instead.") + public required init?(coder aDecoder: NSCoder) { + fatalError("Unimplemented") + } + + public required init(rows: [UIView], hMargin: CGFloat) { + super.init(frame: CGRect.zero) + + self.layoutMargins = .zero + self.preservesSuperviewLayoutMargins = false + + addRows(rows: rows, hMargin: hMargin) + } + + private func addRows(rows: [UIView], hMargin: CGFloat) { + + var lastRow: UIView? + + let addSpacerRow = { + guard let prevRow = lastRow else { + owsFail("\(self.logTag) missing last row") + return + } + let row = UIView() + row.backgroundColor = UIColor(rgbHex: 0xdedee1) + self.addSubview(row) + row.autoSetDimension(.height, toSize: 1) + row.autoPinLeadingToSuperviewMargin(withInset: hMargin) + row.autoPinTrailingToSuperviewMargin() + row.autoPinEdge(.top, to: .bottom, of: prevRow, withOffset: 0) + lastRow = row + } + + let addRow: ((UIView) -> Void) = { (row) in + if lastRow != nil { + addSpacerRow() + } + self.addSubview(row) + row.autoPinLeadingToSuperviewMargin(withInset: hMargin) + row.autoPinTrailingToSuperviewMargin(withInset: hMargin) + if let lastRow = lastRow { + row.autoPinEdge(.top, to: .bottom, of: lastRow, withOffset: 0) + } else { + row.autoPinEdge(toSuperviewEdge: .top, withInset: 0) + } + lastRow = row + } + + for row in rows { + addRow(row) + } + + lastRow?.autoPinEdge(toSuperviewEdge: .bottom, withInset: 0) + } +} diff --git a/SignalMessaging/attachments/EditContactShareNameViewController.swift b/SignalMessaging/attachments/EditContactShareNameViewController.swift new file mode 100644 index 0000000000..b943b60c99 --- /dev/null +++ b/SignalMessaging/attachments/EditContactShareNameViewController.swift @@ -0,0 +1,313 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation +import SignalServiceKit + +@objc +public protocol ContactNameFieldViewDelegate: class { + func nameFieldDidChange() +} + +// MARK: - + +class ContactNameFieldView: UIView { + weak var delegate: ContactNameFieldViewDelegate? + + let name: String + let initialValue: String? + + var valueView: UITextField! + + var hasUnsavedChanges = false + + // MARK: - Initializers + + @available(*, unavailable, message: "use other constructor instead.") + required init?(coder aDecoder: NSCoder) { + fatalError("Unimplemented") + } + + required init(name: String, value: String?, delegate: ContactNameFieldViewDelegate) { + self.name = name + self.initialValue = value + self.delegate = delegate + + super.init(frame: CGRect.zero) + + self.isUserInteractionEnabled = true + self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(wasTapped))) + + createContents() + } + + func createContents() { + let vMargin = CGFloat(10) + self.layoutMargins = UIEdgeInsets(top: vMargin, left: 0, bottom: vMargin, right: 0) + + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.alignment = .center + stackView.layoutMargins = .zero + stackView.spacing = 10 + self.addSubview(stackView) + stackView.autoPinEdgesToSuperviewMargins() + + let nameLabel = UILabel() + nameLabel.text = name + nameLabel.font = UIFont.ows_dynamicTypeBody + nameLabel.textColor = UIColor.ows_materialBlue + nameLabel.lineBreakMode = .byTruncatingTail + stackView.addArrangedSubview(nameLabel) + nameLabel.setContentHuggingHigh() + nameLabel.setCompressionResistanceHigh() + + valueView = UITextField() + if let initialValue = initialValue { + valueView.text = initialValue + } + valueView.font = UIFont.ows_dynamicTypeBody + valueView.textColor = UIColor.black + stackView.addArrangedSubview(valueView) + + valueView.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged) + } + + func wasTapped(sender: UIGestureRecognizer) { + Logger.info("\(self.logTag) \(#function)") + + guard sender.state == .recognized else { + return + } + + valueView.becomeFirstResponder() + } + + func textFieldDidChange(sender: UITextField) { + Logger.info("\(self.logTag) \(#function)") + + hasUnsavedChanges = true + + guard let delegate = self.delegate else { + owsFail("\(logTag) missing delegate.") + return + } + + delegate.nameFieldDidChange() + } + + public func value() -> String { + guard let value = valueView.text else { + return "" + } + return value + } +} + +// MARK: - + +@objc +public protocol EditContactShareNameViewControllerDelegate: class { + func editContactShareNameView(_ editContactShareNameView: EditContactShareNameViewController, didEditContactShare contactShare: OWSContact) +} + +// MARK: - + +@objc +public class EditContactShareNameViewController: OWSViewController, ContactNameFieldViewDelegate { + weak var delegate: EditContactShareNameViewControllerDelegate? + + let contactShare: OWSContact + + var namePrefixView: ContactNameFieldView! + var givenNameView: ContactNameFieldView! + var middleNameView: ContactNameFieldView! + var familyNameView: ContactNameFieldView! + var nameSuffixView: ContactNameFieldView! + + var fieldViews = [ContactNameFieldView]() + + // MARK: Initializers + + @available(*, unavailable, message:"use other constructor instead.") + required public init?(coder aDecoder: NSCoder) { + fatalError("unimplemented") + } + + @objc + required public init(contactShare: OWSContact, delegate: EditContactShareNameViewControllerDelegate) { + self.contactShare = contactShare + self.delegate = delegate + + super.init(nibName: nil, bundle: nil) + + buildFields() + } + + func buildFields() { + namePrefixView = ContactNameFieldView(name: NSLocalizedString("CONTACT_FIELD_NAME_PREFIX", comment: "Label for the 'name prefix' field of a contact."), + value: contactShare.namePrefix, delegate: self) + givenNameView = ContactNameFieldView(name: NSLocalizedString("CONTACT_FIELD_GIVEN_NAME", comment: "Label for the 'given name' field of a contact."), + value: contactShare.givenName, delegate: self) + middleNameView = ContactNameFieldView(name: NSLocalizedString("CONTACT_FIELD_MIDDLE_NAME", comment: "Label for the 'middle name' field of a contact."), + value: contactShare.middleName, delegate: self) + familyNameView = ContactNameFieldView(name: NSLocalizedString("CONTACT_FIELD_FAMILY_NAME", comment: "Label for the 'family name' field of a contact."), + value: contactShare.familyName, delegate: self) + nameSuffixView = ContactNameFieldView(name: NSLocalizedString("CONTACT_FIELD_NAME_SUFFIX", comment: "Label for the 'name suffix' field of a contact."), + value: contactShare.nameSuffix, delegate: self) + fieldViews = [ + namePrefixView , + givenNameView , + middleNameView , + familyNameView , + nameSuffixView + ] + } + + override public var canBecomeFirstResponder: Bool { + return true + } + + // MARK: - View Lifecycle + + override public func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + updateNavigationBar() + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + } + + override public func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + } + + override public func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + } + + override public func loadView() { + super.loadView() + + self.navigationItem.title = NSLocalizedString("CONTACT_SHARE_EDIT_NAME_VIEW_TITLE", + comment: "Title for the 'edit contact share name' view.") + + self.view.preservesSuperviewLayoutMargins = false + self.view.backgroundColor = UIColor.white + + updateContent() + + updateNavigationBar() + } + + func hasUnsavedChanges() -> Bool { + for fieldView in fieldViews { + if fieldView.hasUnsavedChanges { + return true + } + } + return false + } + + func updateNavigationBar() { + self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, + target: self, + action: #selector(didPressCancel)) + + if hasUnsavedChanges() { + self.navigationItem.rightBarButtonItem = + UIBarButtonItem(barButtonSystemItem: .save, + target: self, + action: #selector(didPressSave)) + } else { + self.navigationItem.rightBarButtonItem = nil + } + } + + private func updateContent() { + SwiftAssertIsOnMainThread(#function) + + guard let rootView = self.view else { + owsFail("\(logTag) missing root view.") + return + } + + for subview in rootView.subviews { + subview.removeFromSuperview() + } + + let scrollView = UIScrollView() + scrollView.preservesSuperviewLayoutMargins = false + self.view.addSubview(scrollView) + scrollView.layoutMargins = .zero + scrollView.autoPinWidthToSuperview() + scrollView.autoPin(toTopLayoutGuideOf: self, withInset: 0) + scrollView.autoPinEdge(toSuperviewEdge: .bottom) + + let fieldsView = createFieldsView() + + scrollView.addSubview(fieldsView) + fieldsView.autoPinLeadingToSuperviewMargin() + fieldsView.autoPinTrailingToSuperviewMargin() + fieldsView.autoPinEdge(toSuperviewEdge: .top) + fieldsView.autoPinEdge(toSuperviewEdge: .bottom) + } + + private func createFieldsView() -> UIView { + SwiftAssertIsOnMainThread(#function) + + var rows = [UIView]() + + for fieldView in fieldViews { + rows.append(fieldView) + } + + return ContactFieldView(rows: rows, hMargin: hMargin) + } + + private let hMargin = CGFloat(16) + + // MARK: - + + func didPressSave() { + Logger.info("\(logTag) \(#function)") + + let modifiedContactShare = contactShare.copy(withNamePrefix: namePrefixView.value(), + givenName: givenNameView.value(), + middleName: middleNameView.value(), + familyName: familyNameView.value(), + nameSuffix: nameSuffixView.value()) + + guard let delegate = self.delegate else { + owsFail("\(logTag) missing delegate.") + return + } + + delegate.editContactShareNameView(self, didEditContactShare: modifiedContactShare) + + guard let navigationController = self.navigationController else { + owsFail("\(logTag) Missing navigationController.") + return + } + navigationController.popViewController(animated: true) + } + + func didPressCancel() { + Logger.info("\(logTag) \(#function)") + + guard let navigationController = self.navigationController else { + owsFail("\(logTag) Missing navigationController.") + return + } + navigationController.popViewController(animated: true) + } + + // MARK: - ContactNameFieldViewDelegate + + public func nameFieldDidChange() { + updateNavigationBar() + } +} diff --git a/SignalMessaging/contacts/SystemContactsFetcher.swift b/SignalMessaging/contacts/SystemContactsFetcher.swift index d11be0f6ad..513380e33f 100644 --- a/SignalMessaging/contacts/SystemContactsFetcher.swift +++ b/SignalMessaging/contacts/SystemContactsFetcher.swift @@ -34,6 +34,7 @@ class ContactsFrameworkContactStoreAdaptee: ContactStoreAdaptee { CNContactThumbnailImageDataKey as CNKeyDescriptor, // TODO full image instead of thumbnail? CNContactPhoneNumbersKey as CNKeyDescriptor, CNContactEmailAddressesKey as CNKeyDescriptor, + CNContactPostalAddressesKey as CNKeyDescriptor, CNContactViewController.descriptorForRequiredKeys() ] diff --git a/SignalServiceKit/src/Contacts/PhoneNumber.m b/SignalServiceKit/src/Contacts/PhoneNumber.m index 2eb0563a7f..02982379f5 100644 --- a/SignalServiceKit/src/Contacts/PhoneNumber.m +++ b/SignalServiceKit/src/Contacts/PhoneNumber.m @@ -134,6 +134,10 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN { OWSAssert(phoneNumber); + if (![phoneNumber hasPrefix:COUNTRY_CODE_PREFIX]) { + return phoneNumber; + } + PhoneNumber *_Nullable parsedPhoneNumber = [self tryParsePhoneNumberFromE164:phoneNumber]; if (!parsedPhoneNumber) { DDLogWarn(@"%@ could not parse phone number.", self.logTag); @@ -356,6 +360,9 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN + (PhoneNumber *)tryParsePhoneNumberFromE164:(NSString *)text { OWSAssert(text != nil); + if (![text hasPrefix:COUNTRY_CODE_PREFIX]) { + return nil; + } return [self phoneNumberFromE164:text]; } diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContact.h b/SignalServiceKit/src/Messages/Interactions/OWSContact.h index 4a87832e62..91f4176347 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSContact.h +++ b/SignalServiceKit/src/Messages/Interactions/OWSContact.h @@ -24,13 +24,7 @@ typedef NS_ENUM(NSUInteger, OWSContactPhoneType) { NSString *NSStringForContactPhoneType(OWSContactPhoneType value); -@interface OWSContactPhoneNumber : MTLModel - -@property (nonatomic, readonly) OWSContactPhoneType phoneType; -// Applies in the OWSContactPhoneType_Custom case. -@property (nonatomic, readonly, nullable) NSString *label; - -@property (nonatomic, readonly) NSString *phoneNumber; +@protocol OWSContactField - (BOOL)ows_isValid; @@ -42,6 +36,18 @@ NSString *NSStringForContactPhoneType(OWSContactPhoneType value); #pragma mark - +@interface OWSContactPhoneNumber : MTLModel + +@property (nonatomic, readonly) OWSContactPhoneType phoneType; +// Applies in the OWSContactPhoneType_Custom case. +@property (nonatomic, readonly, nullable) NSString *label; + +@property (nonatomic, readonly) NSString *phoneNumber; + +@end + +#pragma mark - + typedef NS_ENUM(NSUInteger, OWSContactEmailType) { OWSContactEmailType_Home = 1, OWSContactEmailType_Mobile, @@ -51,7 +57,7 @@ typedef NS_ENUM(NSUInteger, OWSContactEmailType) { NSString *NSStringForContactEmailType(OWSContactEmailType value); -@interface OWSContactEmail : MTLModel +@interface OWSContactEmail : MTLModel @property (nonatomic, readonly) OWSContactEmailType emailType; // Applies in the OWSContactEmailType_Custom case. @@ -59,12 +65,6 @@ NSString *NSStringForContactEmailType(OWSContactEmailType value); @property (nonatomic, readonly) NSString *email; -- (BOOL)ows_isValid; - -- (NSString *)localizedLabel; - -- (NSString *)debugDescription; - @end #pragma mark - @@ -77,7 +77,7 @@ typedef NS_ENUM(NSUInteger, OWSContactAddressType) { NSString *NSStringForContactAddressType(OWSContactAddressType value); -@interface OWSContactAddress : MTLModel +@interface OWSContactAddress : MTLModel @property (nonatomic, readonly) OWSContactAddressType addressType; // Applies in the OWSContactAddressType_Custom case. @@ -91,12 +91,6 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value); @property (nonatomic, readonly, nullable) NSString *postcode; @property (nonatomic, readonly, nullable) NSString *country; -- (BOOL)ows_isValid; - -- (NSString *)localizedLabel; - -- (NSString *)debugDescription; - @end #pragma mark - @@ -128,6 +122,20 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value); - (NSString *)debugDescription; +#pragma mark - Creation and Derivation + +- (OWSContact *)newContactWithNamePrefix:(nullable NSString *)namePrefix + givenName:(nullable NSString *)givenName + middleName:(nullable NSString *)middleName + familyName:(nullable NSString *)familyName + nameSuffix:(nullable NSString *)nameSuffix; + +- (OWSContact *)copyContactWithNamePrefix:(nullable NSString *)namePrefix + givenName:(nullable NSString *)givenName + middleName:(nullable NSString *)middleName + familyName:(nullable NSString *)familyName + nameSuffix:(nullable NSString *)nameSuffix; + @end #pragma mark - diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContact.m b/SignalServiceKit/src/Messages/Interactions/OWSContact.m index a284d1d382..5e1130e550 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSContact.m +++ b/SignalServiceKit/src/Messages/Interactions/OWSContact.m @@ -7,6 +7,8 @@ #import "OWSContact+Private.h" #import "OWSSignalServiceProtos.pb.h" #import "PhoneNumber.h" + +//#import "Contact.h" #import "TSAttachment.h" #import @@ -45,8 +47,8 @@ NSString *NSStringForContactPhoneType(OWSContactPhoneType value) - (BOOL)ows_isValid { - if (![PhoneNumber tryParsePhoneNumberFromE164:self.phoneNumber]) { - DDLogWarn(@"%@ invalid phone number; not e164: %@.", self.logTag, self.phoneNumber); + if (self.phoneNumber.ows_stripped.length < 1) { + DDLogWarn(@"%@ invalid phone number: %@.", self.logTag, self.phoneNumber); return NO; } return YES; @@ -122,7 +124,7 @@ NSString *NSStringForContactEmailType(OWSContactEmailType value) DDLogWarn(@"%@ invalid email: %@.", self.logTag, self.email); return NO; } - return YES; + return YES; } - (NSString *)localizedLabel @@ -201,7 +203,7 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value) DDLogWarn(@"%@ invalid address; empty.", self.logTag); return NO; } - return YES; + return YES; } - (NSString *)localizedLabel @@ -360,6 +362,13 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value) } } +- (void)updateDisplayName +{ + _displayName = nil; + + [self ensureDisplayName]; +} + - (NSString *)debugDescription { NSMutableString *result = [NSMutableString new]; @@ -398,6 +407,55 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value) return result; } +- (OWSContact *)newContactWithNamePrefix:(nullable NSString *)namePrefix + givenName:(nullable NSString *)givenName + middleName:(nullable NSString *)middleName + familyName:(nullable NSString *)familyName + nameSuffix:(nullable NSString *)nameSuffix +{ + OWSContact *newContact = [OWSContact new]; + + [newContact setNamePrefix:namePrefix + givenName:givenName + middleName:middleName + familyName:familyName + nameSuffix:nameSuffix]; + + return newContact; +} + +- (OWSContact *)copyContactWithNamePrefix:(nullable NSString *)namePrefix + givenName:(nullable NSString *)givenName + middleName:(nullable NSString *)middleName + familyName:(nullable NSString *)familyName + nameSuffix:(nullable NSString *)nameSuffix +{ + OWSContact *contactCopy = [self copy]; + + [contactCopy setNamePrefix:namePrefix + givenName:givenName + middleName:middleName + familyName:familyName + nameSuffix:nameSuffix]; + + return contactCopy; +} + +- (void)setNamePrefix:(nullable NSString *)namePrefix + givenName:(nullable NSString *)givenName + middleName:(nullable NSString *)middleName + familyName:(nullable NSString *)familyName + nameSuffix:(nullable NSString *)nameSuffix +{ + self.namePrefix = namePrefix.ows_stripped; + self.givenName = givenName.ows_stripped; + self.middleName = middleName.ows_stripped; + self.familyName = familyName.ows_stripped; + self.nameSuffix = nameSuffix.ows_stripped; + + [self updateDisplayName]; +} + @end #pragma mark -