From 0c745dd989164fbbe1bb1eed10e2d839effa8a53 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Thu, 3 May 2018 10:47:42 -0400 Subject: [PATCH 1/8] Add contact share approval view. --- Signal.xcodeproj/project.pbxproj | 8 +- .../src/ViewControllers/ContactsPicker.swift | 3 +- .../ConversationViewController.m | 133 +- .../translations/en.lproj/Localizable.strings | 24 + .../ApproveContactShareViewController.swift | 1133 +++++++++++++++++ SignalMessaging/contacts/OWSContactsManager.m | 8 +- .../contacts/SystemContactsFetcher.swift | 1 + SignalServiceKit/src/Contacts/PhoneNumber.m | 7 + .../src/Messages/Interactions/OWSContact.h | 4 + .../src/Messages/Interactions/OWSContact.m | 76 +- 10 files changed, 1387 insertions(+), 10 deletions(-) create mode 100644 SignalMessaging/attachments/ApproveContactShareViewController.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 34a9dfbc44..144b01541a 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -48,6 +48,7 @@ 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 */; }; 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 */; }; @@ -630,6 +631,7 @@ 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; }; 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 = ""; }; @@ -1551,12 +1553,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,6 +1598,7 @@ 346129DB1FD5C02900532771 /* viewControllers */ = { isa = PBXGroup; children = ( + 34382265209A4E3F0094FEB7 /* ApproveContactShareViewController.swift */, 344F2498200FD03200CFB4F4 /* AttachmentApprovalViewController.swift */, 344F248220069E9B00CFB4F4 /* CountryCodeViewController.h */, 344F248320069E9B00CFB4F4 /* CountryCodeViewController.m */, @@ -3093,6 +3096,7 @@ 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 */, 346129A61FD1F09100532771 /* OWSContactsManager.m in Sources */, 4598198F204E2F28009414F2 /* OWS108CallLoggingPreference.m in Sources */, 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..7874e3011c 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 () *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; + address1.street = @"123 home st."; + address1.neighborhood = @"round the bend."; + address1.city = @"homeville"; + address1.region = @"HO"; + address1.postcode = @"12345"; + address1.country = @"USA"; + OWSContactAddress *address2 = [OWSContactAddress new]; + address2.addressType = OWSContactAddressType_Custom; + address2.label = @"Otra casa"; + address2.pobox = @"caja 123"; + address2.street = @"123 casa calle"; + address2.city = @"barrio norte"; + address2.region = @"AB"; + address2.postcode = @"53421"; + address2.country = @"MX"; + contact.addresses = @[ + address1, + address2, + ]; + + // TODO: Avatar + return contact; +} + - (void)contactsPickerDidCancel:(ContactsPicker *)contactsPicker { DDLogDebug(@"%@ in %s", self.logTag, __PRETTY_FUNCTION__); @@ -4915,10 +5013,32 @@ interactionControllerForAnimationController:(id *)contacts @@ -4932,6 +5052,17 @@ interactionControllerForAnimationController:(id String +// func isIncluded() -> Bool +// func setIsIncluded(isIncluded: Bool) +//} + +// MARK: - + +class ContactShareField: NSObject { + + var isIncludedFlag = true + +// override required init() { +// super.init() +// } + + func localizedLabel() -> String { + preconditionFailure("This method must be overridden") + } + + func isIncluded() -> Bool { + return isIncludedFlag + } + + func setIsIncluded(isIncluded: Bool) { + isIncludedFlag = isIncluded + } +} + +// MARK: - + +class ContactSharePhoneNumber: ContactShareField { + + let value: OWSContactPhoneNumber + + required init(_ value: OWSContactPhoneNumber) { + self.value = value + + super.init() + } + + override func localizedLabel() -> String { + return value.localizedLabel() + } +} + +// MARK: - + +class ContactShareEmail: ContactShareField { + + let value: OWSContactEmail + + required init(_ value: OWSContactEmail) { + self.value = value + + super.init() + } + + override func localizedLabel() -> String { + return value.localizedLabel() + } +} + +// MARK: - + +class ContactShareAddress: ContactShareField { + + let value: OWSContactAddress + + required init(_ value: OWSContactAddress) { + self.value = value + + super.init() + } + + override func localizedLabel() -> String { + return value.localizedLabel() + } +} + +// 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.addRedBorder() + + 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 +// checkbox.addTarget(self, action: #selector(checkboxTapped), for: .touchUpInside) + addSubview(checkbox) + checkbox.autoPinEdge(toSuperviewEdge: .leading, withInset: hMargin) + checkbox.autoVCenterInSuperview() + checkbox.setCompressionResistanceHigh() + checkbox.setContentHuggingHigh() +// checkbox.addRedBorder() + + 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 checkboxTapped(sender: UIButton) { +// field.setIsIncluded(isIncluded: checkbox.isSelected) +// } + + func wasTapped(sender: UIGestureRecognizer) { + Logger.info("\(self.logTag) \(#function)") + + guard sender.state == .recognized else { + return + } + checkbox.isSelected = !checkbox.isSelected + } +} + +// MARK: - + +@objc +public class ApproveContactShareViewController: OWSViewController +//, CaptioningToolbarDelegate, PlayerProgressBarDelegate, OWSVideoPlayerDelegate +{ + weak var delegate: ApproveContactShareViewControllerDelegate? + + let contactsManager: OWSContactsManager + + let contactShare: OWSContact + +// // We sometimes shrink the attachment view so that it remains somewhat visible +// // when the keyboard is presented. +// enum AttachmentViewScale { +// case fullsize, compact +// } +// +// // MARK: Properties +// +// let attachment: SignalAttachment +// private var videoPlayer: OWSVideoPlayer? +// +// private(set) var bottomToolbar: UIView! +// private(set) var mediaMessageView: MediaMessageView! +// private(set) var scrollView: UIScrollView! +// private(set) var contentContainer: UIView! +// private(set) var playVideoButton: UIView? + + var fields = [ContactShareField]() + var fieldViews = [ContactShareFieldView]() + + // 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 fields = [ContactShareField]() + var fieldViews = [ContactShareFieldView]() + + if let phoneNumbers = contactShare.phoneNumbers { + for phoneNumber in phoneNumbers { + let field = ContactSharePhoneNumber(phoneNumber) + fields.append(field) + let fieldView = ContactShareFieldView(field: field, previewViewBlock: { [weak self] _ in + guard let strongSelf = self else { return UIView() } + return strongSelf.previewView(forPhoneNumber: phoneNumber) + }) + fieldViews.append(fieldView) + } + } + + if let emails = contactShare.emails { + for email in emails { + let field = ContactShareEmail(email) + fields.append(field) + let fieldView = ContactShareFieldView(field: field, previewViewBlock: { [weak self] _ in + guard let strongSelf = self else { return UIView() } + return strongSelf.previewView(forEmail: email) + }) + fieldViews.append(fieldView) + } + } + + if let addresses = contactShare.addresses { + for address in addresses { + let field = ContactShareAddress(address) + fields.append(field) + 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.fields = fields + self.fieldViews = fieldViews + } + +// +// +// +// +//// +//// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +//// +// +// +//class TappableView: UIView { +// let actionBlock : (() -> Void) +// +// // MARK: - Initializers +// +// @available(*, unavailable, message: "use init(call:) constructor instead.") +// required init?(coder aDecoder: NSCoder) { +// fatalError("Unimplemented") +// } +// +// required init(actionBlock : @escaping () -> Void) { +// self.actionBlock = actionBlock +// super.init(frame: CGRect.zero) +// +// self.isUserInteractionEnabled = true +// self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(wasTapped))) +// } +// +// func wasTapped(sender: UIGestureRecognizer) { +// Logger.info("\(self.logTag) \(#function)") +// +// guard sender.state == .recognized else { +// return +// } +// actionBlock() +// } +//} +// +//// MARK: - +// +//class ContactViewController: OWSViewController, CNContactViewControllerDelegate { +// +// let TAG = "[ContactView]" + +// enum ContactViewMode { +// case systemContactWithSignal, +// systemContactWithoutSignal, +// nonSystemContact, +// noPhoneNumber, +// unknown +// } +// +// private var hasLoadedView = false +// +// private var viewMode = ContactViewMode.unknown { +// didSet { +// SwiftAssertIsOnMainThread(#function) +// +// if oldValue != viewMode && hasLoadedView { +// updateContent() +// } +// } +// } +// +// var reachability: Reachability? + + override public var canBecomeFirstResponder: Bool { + return true + } + +// private let contact: OWSContact +// +// // MARK: - Initializers +// +// @available(*, unavailable, message: "use init(call:) constructor instead.") +// required init?(coder aDecoder: NSCoder) { +// fatalError("Unimplemented") +// } +// +// required init(contact: OWSContact) { +// contactsManager = Environment.current().contactsManager +// self.contact = contact +// +// super.init(nibName: nil, bundle: nil) +// +// tryToDetermineMode() +// +// NotificationCenter.default.addObserver(forName: .OWSContactsManagerSignalAccountsDidChange, object: nil, queue: nil) { [weak self] _ in +// guard let strongSelf = self else { return } +// strongSelf.tryToDetermineMode() +// } +// +// reachability = Reachability.forInternetConnection() +// +// NotificationCenter.default.addObserver(forName: .reachabilityChanged, object: nil, queue: nil) { [weak self] _ in +// guard let strongSelf = self else { return } +// strongSelf.tryToDetermineMode() +// } +// } + + // MARK: - View Lifecycle + + override public func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + updateNavigationBar() + +// UIUtil.applySignalAppearence() +// +// if let navigationController = self.navigationController { +// navigationController.isNavigationBarHidden = true +// } +// +// self.becomeFirstResponder() +// +// contactsManager.requestSystemContactsOnce(completion: { [weak self] _ in +// guard let strongSelf = self else { return } +// strongSelf.tryToDetermineMode() +// }) + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) +// +// UIUtil.applySignalAppearence() +// +// self.becomeFirstResponder() + } + + override public func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + +// if let navigationController = self.navigationController { +// navigationController.isNavigationBarHidden = false +// } + } + + 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.preservesSuperviewLayoutMargins = false + self.view.backgroundColor = UIColor.white +// self.view.backgroundColor = UIColor(rgbHex: 0xefeff4) + + updateContent() + +// hasLoadedView = true + + updateNavigationBar() + } + + // TODO: Show error. + func canShareContact() -> Bool { + return contactShare.ows_isValid() + } + + func updateNavigationBar() { + 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 + } +// self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, +// target: self, +// action: #selector(donePressed)) + } + +// private func tryToDetermineMode() { +// SwiftAssertIsOnMainThread(#function) +// +// guard phoneNumbersForContact().count > 0 else { +// viewMode = .noPhoneNumber +// return +// } +// if systemContactsWithSignalAccountsForContact().count > 0 { +// viewMode = .systemContactWithSignal +// return +// } +// if systemContactsForContact().count > 0 { +// viewMode = .systemContactWithoutSignal +// return +// } +// +// viewMode = .nonSystemContact +// } + +// private func systemContactsWithSignalAccountsForContact() -> [String] { +// SwiftAssertIsOnMainThread(#function) +// +// return phoneNumbersForContact().filter({ (phoneNumber) -> Bool in +// return contactsManager.hasSignalAccount(forRecipientId: phoneNumber) +// }) +// } +// +// private func systemContactsForContact() -> [String] { +// SwiftAssertIsOnMainThread(#function) +// +// return phoneNumbersForContact().filter({ (phoneNumber) -> Bool in +// return contactsManager.allContactsMap[phoneNumber] != nil +// }) +// } +// +// private func phoneNumbersForContact() -> [String] { +// SwiftAssertIsOnMainThread(#function) +// +// var result = [String]() +// if let phoneNumbers = contact.phoneNumbers { +// for phoneNumber in phoneNumbers { +// result.append(phoneNumber.phoneNumber) +// } +// } +// return result +// } + +// private func tryToDetermineModeRetry() { +// // Try again after a minute. +// DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 60.0) { [weak self] in +// guard let strongSelf = self else { return } +// strongSelf.tryToDetermineMode() +// } +// } + + 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 topView = createTopView() +// rootView.addSubview(topView) +// 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, withOffset: 0) +// bottomView.autoPinEdge(toSuperviewEdge: .bottom) + + let scrollView = UIScrollView() + scrollView.preservesSuperviewLayoutMargins = false + self.view.addSubview(scrollView) + scrollView.layoutMargins = .zero + scrollView.autoPinWidthToSuperview() + scrollView.autoPin(toTopLayoutGuideOf: self, withInset: 0) +// scrollView.autoPinEdge(.top, to: .bottom, of: topView, withOffset: 0) + scrollView.autoPinEdge(toSuperviewEdge: .bottom) + + let fieldsView = createFieldsView() + + // See notes on how to use UIScrollView with iOS Auto Layout: + // + // https://developer.apple.com/library/content/releasenotes/General/RN-iOSSDK-6_0/ + scrollView.addSubview(fieldsView) + fieldsView.autoPinLeadingToSuperviewMargin() + fieldsView.autoPinTrailingToSuperviewMargin() + fieldsView.autoPinEdge(toSuperviewEdge: .top) + fieldsView.autoPinEdge(toSuperviewEdge: .bottom) + } + +// private func createTopView() -> UIView { +// SwiftAssertIsOnMainThread(#function) +// +// let topView = UIView.container() +// topView.backgroundColor = UIColor(rgbHex: 0xefeff4) +// 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.autoPinEdge(toSuperviewEdge: .left) +// +// let backIconName = (self.view.isRTL() ? "system_disclosure_indicator" : "system_disclosure_indicator_rtl") +// let backIconImage = UIImage(named: backIconName)?.withRenderingMode(.alwaysTemplate) +// let backIconView = UIImageView(image: backIconImage) +// 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 = 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() +// avatarView.autoSetDimension(.width, toSize: avatarSize) +// avatarView.autoSetDimension(.height, toSize: avatarSize) +// +// let nameLabel = UILabel() +// nameLabel.text = contact.displayName +// nameLabel.font = UIFont.ows_dynamicTypeTitle2.ows_bold() +// nameLabel.textColor = UIColor.black +// nameLabel.lineBreakMode = .byTruncatingTail +// nameLabel.textAlignment = .center +// topView.addSubview(nameLabel) +// nameLabel.autoPinEdge(.top, to: .bottom, of: avatarView, withOffset: 10) +// nameLabel.autoPinLeadingToSuperviewMargin(withInset: hMargin) +// nameLabel.autoPinTrailingToSuperviewMargin(withInset: hMargin) +// +// var lastView: UIView = nameLabel +// +// if let firstPhoneNumber = contact.phoneNumbers?.first { +// let phoneNumberLabel = UILabel() +// phoneNumberLabel.text = PhoneNumber.bestEffortFormatE164(asLocalizedPhoneNumber: firstPhoneNumber.phoneNumber) +// phoneNumberLabel.font = UIFont.ows_dynamicTypeCaption2 +// phoneNumberLabel.textColor = UIColor.black +// phoneNumberLabel.lineBreakMode = .byTruncatingTail +// phoneNumberLabel.textAlignment = .center +// topView.addSubview(phoneNumberLabel) +// phoneNumberLabel.autoPinEdge(.top, to: .bottom, of: lastView, withOffset: 5) +// phoneNumberLabel.autoPinLeadingToSuperviewMargin(withInset: hMargin) +// phoneNumberLabel.autoPinTrailingToSuperviewMargin(withInset: hMargin) +// lastView = phoneNumberLabel +// } +// +// switch viewMode { +// case .systemContactWithSignal: +// // Show actions buttons for system contacts with a Signal account. +// let stackView = UIStackView() +// stackView.axis = .horizontal +// stackView.distribution = .fillEqually +// stackView.addArrangedSubview(createCircleActionButton(text: NSLocalizedString("ACTION_SEND_MESSAGE", +// comment: "Label for 'sent message' button in contact view."), +// actionBlock: { [weak self] _ in +// guard let strongSelf = self else { return } +// strongSelf.didPressSendMessage() +// })) +// stackView.addArrangedSubview(createCircleActionButton(text: NSLocalizedString("ACTION_AUDIO_CALL", +// comment: "Label for 'audio call' button in contact view."), +// actionBlock: { [weak self] _ in +// guard let strongSelf = self else { return } +// strongSelf.didPressAudioCall() +// })) +// stackView.addArrangedSubview(createCircleActionButton(text: NSLocalizedString("ACTION_VIDEO_CALL", +// comment: "Label for 'video call' button in contact view."), +// actionBlock: { [weak self] _ in +// guard let strongSelf = self else { return } +// strongSelf.didPressVideoCall() +// })) +// topView.addSubview(stackView) +// stackView.autoPinEdge(.top, to: .bottom, of: lastView, withOffset: 20) +// stackView.autoPinLeadingToSuperviewMargin(withInset: hMargin) +// stackView.autoPinTrailingToSuperviewMargin(withInset: hMargin) +// lastView = stackView +// case .systemContactWithoutSignal: +// // Show invite button for system contacts without a Signal account. +// let inviteButton = createLargePillButton(text: NSLocalizedString("ACTION_INVITE", +// comment: "Label for 'invite' button in contact view."), +// actionBlock: { [weak self] _ in +// guard let strongSelf = self else { return } +// strongSelf.didPressInvite() +// }) +// topView.addSubview(inviteButton) +// inviteButton.autoPinEdge(.top, to: .bottom, of: lastView, withOffset: 20) +// inviteButton.autoPinLeadingToSuperviewMargin(withInset: 55) +// inviteButton.autoPinTrailingToSuperviewMargin(withInset: 55) +// lastView = inviteButton +// case .nonSystemContact: +// // Show no action buttons for contacts not in user's device contacts. +// break +// case .noPhoneNumber: +// // Show no action buttons for contacts without a phone number. +// break +// case .unknown: +// let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge) +// topView.addSubview(activityIndicator) +// activityIndicator.autoPinEdge(.top, to: .bottom, of: lastView, withOffset: 10) +// activityIndicator.autoHCenterInSuperview() +// lastView = activityIndicator +// break +// } +// +// lastView.autoPinEdge(toSuperviewEdge: .bottom, withInset: 15) +// +// return topView +// } + + 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, withOffset: 0) + lastRow = row + } + + let addRow: ((UIView) -> Void) = { (row) in + if lastRow != nil { + addSpacerRow() + } + fieldsView.addSubview(row) + row.autoPinLeadingToSuperviewMargin(withInset: self.hMargin) + row.autoPinTrailingToSuperviewMargin(withInset: self.hMargin) + if let lastRow = lastRow { + row.autoPinEdge(.top, to: .bottom, of: lastRow, withOffset: 0) + } else { + row.autoPinEdge(toSuperviewEdge: .top, withInset: 0) + } + lastRow = row + } + + for fieldView in fieldViews { + addRow(fieldView) + } + +// if viewMode == .nonSystemContact { +// addRow(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", +// comment: "Label for 'new contact' button in conversation settings view."), +// action: #selector(didPressAddToExistingContact))) +// } +// +// // TODO: Not designed yet. +// // if viewMode == .systemContactWithSignal || +// // viewMode == .systemContactWithoutSignal { +// // addRow(createActionRow(labelText:NSLocalizedString("ACTION_SHARE_CONTACT", +// // comment:"Label for 'share contact' button."), +// // action:#selector(didPressShareContact))) +// // } +// +// if let phoneNumbers = contact.phoneNumbers { +// for phoneNumber in phoneNumbers { +// // TODO: Try to format the phone number nicely. +// addRow(createNameValueRow(name: phoneNumber.localizedLabel(), +// value: +// PhoneNumber.bestEffortFormatE164(asLocalizedPhoneNumber: phoneNumber.phoneNumber), +// actionBlock: { +// guard let url = NSURL(string: "tel:\(phoneNumber.phoneNumber)") else { +// owsFail("\(ContactViewController.logTag) could not open phone number.") +// return +// } +// UIApplication.shared.openURL(url as URL) +// })) +// } +// } +// +// if let emails = contact.emails { +// for email in emails { +// addRow(createNameValueRow(name: email.localizedLabel(), +// value: email.email, +// actionBlock: { +// guard let url = NSURL(string: "mailto:\(email.email)") else { +// owsFail("\(ContactViewController.logTag) could not open email.") +// return +// } +// UIApplication.shared.openURL(url as URL) +// })) +// } +// } + + // TODO: Should we present addresses here too? How? + + lastRow?.autoPinEdge(toSuperviewEdge: .bottom, withInset: 0) + + return fieldsView + } + + private let hMargin = CGFloat(16) + +// private func createActionRow(labelText: String, action: Selector) -> UIView { +// let row = UIView() +// row.layoutMargins.left = 0 +// row.layoutMargins.right = 0 +// row.isUserInteractionEnabled = true +// row.addGestureRecognizer(UITapGestureRecognizer(target: self, action: action)) +// +// let label = UILabel() +// label.text = labelText +// label.font = UIFont.ows_dynamicTypeBody +// label.textColor = UIColor.ows_materialBlue +// label.lineBreakMode = .byTruncatingTail +// row.addSubview(label) +// label.autoPinTopToSuperviewMargin() +// label.autoPinBottomToSuperviewMargin() +// label.autoPinLeadingToSuperviewMargin(withInset: hMargin) +// label.autoPinTrailingToSuperviewMargin(withInset: hMargin) +// +// return row +// } + +// private func createNameValueRow(name: String, value: String, actionBlock : @escaping () -> Void) -> UIView { +// let row = TappableView(actionBlock: actionBlock) +// row.layoutMargins.left = 0 +// row.layoutMargins.right = 0 +// +// let nameLabel = UILabel() +// nameLabel.text = name +// nameLabel.font = UIFont.ows_dynamicTypeCaption1 +// nameLabel.textColor = UIColor.black +// nameLabel.lineBreakMode = .byTruncatingTail +// row.addSubview(nameLabel) +// nameLabel.autoPinTopToSuperviewMargin() +// nameLabel.autoPinLeadingToSuperviewMargin(withInset: hMargin) +// nameLabel.autoPinTrailingToSuperviewMargin(withInset: hMargin) +// +// let valueLabel = UILabel() +// valueLabel.text = value +// valueLabel.font = UIFont.ows_dynamicTypeCaption1 +// valueLabel.textColor = UIColor.ows_materialBlue +// valueLabel.lineBreakMode = .byTruncatingTail +// row.addSubview(valueLabel) +// valueLabel.autoPinEdge(.top, to: .bottom, of: nameLabel, withOffset: 3) +// valueLabel.autoPinBottomToSuperviewMargin() +// valueLabel.autoPinLeadingToSuperviewMargin(withInset: hMargin) +// valueLabel.autoPinTrailingToSuperviewMargin(withInset: hMargin) +// +// // TODO: Should there be a disclosure icon here? +// +// return row +// } + +// // TODO: Use real assets. +// private func createCircleActionButton(text: String, actionBlock : @escaping () -> Void) -> UIView { +// let buttonSize = CGFloat(50) +// +// let button = TappableView(actionBlock: actionBlock) +// button.layoutMargins = .zero +// button.autoSetDimension(.width, toSize: buttonSize, relation: .greaterThanOrEqual) +// +// let circleView = UIView() +// circleView.backgroundColor = UIColor.white +// circleView.autoSetDimension(.width, toSize: buttonSize) +// circleView.autoSetDimension(.height, toSize: buttonSize) +// circleView.layer.cornerRadius = buttonSize * 0.5 +// button.addSubview(circleView) +// circleView.autoPinEdge(toSuperviewEdge: .top) +// circleView.autoHCenterInSuperview() +// +// let label = UILabel() +// label.text = text +// label.font = UIFont.ows_dynamicTypeCaption2 +// label.textColor = UIColor.black +// label.lineBreakMode = .byTruncatingTail +// label.textAlignment = .center +// button.addSubview(label) +// label.autoPinEdge(.top, to: .bottom, of: circleView, withOffset: 3) +// label.autoPinEdge(toSuperviewEdge: .bottom) +// label.autoPinLeadingToSuperviewMargin() +// label.autoPinTrailingToSuperviewMargin() +// +// return button +// } + +// private func createLargePillButton(text: String, actionBlock : @escaping () -> Void) -> UIView { +// let button = TappableView(actionBlock: actionBlock) +// button.backgroundColor = UIColor.white +// button.layoutMargins = .zero +// button.autoSetDimension(.height, toSize: 45) +// button.layer.cornerRadius = 5 +// +// let label = UILabel() +// label.text = text +// label.font = UIFont.ows_dynamicTypeCaption1 +// label.textColor = UIColor.ows_materialBlue +// label.lineBreakMode = .byTruncatingTail +// label.textAlignment = .center +// button.addSubview(label) +// label.autoPinLeadingToSuperviewMargin(withInset: 20) +// label.autoPinTrailingToSuperviewMargin(withInset: 20) +// label.autoVCenterInSuperview() +// label.autoPinEdge(toSuperviewEdge: .top, withInset: 0, relation: .greaterThanOrEqual) +// label.autoPinEdge(toSuperviewEdge: .bottom, withInset: 0, relation: .greaterThanOrEqual) +// +// return button +// } + +// private func createLabeledFieldRow(name: String, value: String, actionBlock : @escaping () -> Void) -> UIView { +// let row = TappableView(actionBlock: actionBlock) +// row.layoutMargins.left = 0 +// row.layoutMargins.right = 0 +// +// let nameLabel = UILabel() +// nameLabel.text = name +// nameLabel.font = UIFont.ows_dynamicTypeCaption1 +// nameLabel.textColor = UIColor.black +// nameLabel.lineBreakMode = .byTruncatingTail +// row.addSubview(nameLabel) +// nameLabel.autoPinTopToSuperviewMargin() +// nameLabel.autoPinLeadingToSuperviewMargin(withInset: hMargin) +// nameLabel.autoPinTrailingToSuperviewMargin(withInset: hMargin) +// +// let valueLabel = UILabel() +// valueLabel.text = value +// valueLabel.font = UIFont.ows_dynamicTypeCaption1 +// valueLabel.textColor = UIColor.ows_materialBlue +// valueLabel.lineBreakMode = .byTruncatingTail +// row.addSubview(valueLabel) +// valueLabel.autoPinEdge(.top, to: .bottom, of: nameLabel, withOffset: 3) +// valueLabel.autoPinBottomToSuperviewMargin() +// valueLabel.autoPinLeadingToSuperviewMargin(withInset: hMargin) +// valueLabel.autoPinTrailingToSuperviewMargin(withInset: hMargin) +// +// // TODO: Should there be a disclosure icon here? +// +// return row +// } + + func previewView(forPhoneNumber phoneNumber: OWSContactPhoneNumber) -> UIView { + let label = UILabel() + label.text = PhoneNumber.bestEffortFormatE164(asLocalizedPhoneNumber: phoneNumber.phoneNumber) + label.font = UIFont.ows_dynamicTypeCaption1 + label.textColor = UIColor.ows_materialBlue + label.lineBreakMode = .byTruncatingTail + // label.textAlignment = .center + 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 + // label.textAlignment = .center + return label + } + + func previewView(forAddress address: OWSContactAddress) -> UIView { + let previewView = UIView.container() + var lastRow: UIView? + let addRow: ((UIView) -> Void) = { (row) in + previewView.addSubview(row) + row.autoPinLeadingToSuperviewMargin() + row.autoPinTrailingToSuperviewMargin() + if let lastRow = lastRow { + row.autoPinEdge(.top, to: .bottom, of: lastRow, withOffset: 0) + } else { + row.autoPinEdge(toSuperviewEdge: .top, withInset: 0) + } + lastRow = row + } + 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() + + addRow(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) + + lastRow?.autoPinEdge(toSuperviewEdge: .bottom, withInset: 0) + + return previewView + } + + // MARK: - + +// func didPressCreateNewContact(sender: UIGestureRecognizer) { +// Logger.info("\(self.TAG) \(#function)") +// +// guard sender.state == .recognized else { +// return +// } +// presentNewContactView() +// } +// +// func didPressAddToExistingContact(sender: UIGestureRecognizer) { +// Logger.info("\(self.TAG) \(#function)") +// +// guard sender.state == .recognized else { +// return +// } +// presentSelectAddToExistingContactView() +// } +// +// func didPressShareContact(sender: UIGestureRecognizer) { +// Logger.info("\(self.TAG) \(#function)") +// +// guard sender.state == .recognized else { +// return +// } +// // TODO: +// } +// +// func didPressSendMessage() { +// Logger.info("\(self.TAG) \(#function)") +// +// presentThreadAndPeform(action: .compose) +// } +// +// func didPressAudioCall() { +// Logger.info("\(self.TAG) \(#function)") +// +// presentThreadAndPeform(action: .audioCall) +// } +// +// func didPressVideoCall() { +// Logger.info("\(self.TAG) \(#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) +// }) +// } +// +// func didPressInvite() { +// Logger.info("\(self.TAG) \(#function)") +// +// guard MFMessageComposeViewController.canSendText() else { +// Logger.info("\(TAG) Device cannot send text") +// OWSAlerts.showErrorAlert(message: NSLocalizedString("UNSUPPORTED_FEATURE_ERROR", comment: "")) +// return +// } +// let phoneNumbers = +// phoneNumbersForContact() +// guard phoneNumbers.count > 0 else { +// owsFail("\(logTag) no phone numbers.") +// return +// } +// +// let inviteFlow = +// InviteFlow(presentingViewController: self, contactsManager: contactsManager) +// 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.bestEffortFormatE164(asLocalizedPhoneNumber: phoneNumber), +// style: .default) { _ in +// completion(phoneNumber) +// }) +// } +// actionSheet.addAction(OWSAlerts.cancelAction) +// +// self.present(actionSheet, animated: true) +// } + +// func didPressDismiss() { +// Logger.info("\(self.TAG) \(#function)") +// +// self.navigationController?.popViewController(animated: true) +// } + + func didPressSendButton() { + Logger.info("\(logTag) \(#function)") + + // TODO: + } +} diff --git a/SignalMessaging/contacts/OWSContactsManager.m b/SignalMessaging/contacts/OWSContactsManager.m index 7cda1c4faa..1e5fae453e 100644 --- a/SignalMessaging/contacts/OWSContactsManager.m +++ b/SignalMessaging/contacts/OWSContactsManager.m @@ -343,10 +343,10 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification { OWSAssertIsOnMainThread(); - if ([signalAccounts isEqual:self.signalAccounts]) { - DDLogDebug(@"%@ SignalAccounts unchanged.", self.logTag); - return; - } + // if ([signalAccounts isEqual:self.signalAccounts]) { + // DDLogDebug(@"%@ SignalAccounts unchanged.", self.logTag); + // return; + // } NSMutableDictionary *signalAccountMap = [NSMutableDictionary new]; for (SignalAccount *signalAccount in signalAccounts) { 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..d419bce32c 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSContact.h +++ b/SignalServiceKit/src/Messages/Interactions/OWSContact.h @@ -154,6 +154,10 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value); + (nullable OWSSignalServiceProtosDataMessageContact *)protoForContact:(OWSContact *)contact; + (OWSContact *_Nullable)contactForDataMessage:(OWSSignalServiceProtosDataMessage *)dataMessage; +//#pragma mark - Old Contacts +// +//+ (nullable OWSContact *)contactForOldContact:(Contact *)oldContact + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContact.m b/SignalServiceKit/src/Messages/Interactions/OWSContact.m index a284d1d382..a0c4398e36 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; @@ -958,6 +960,76 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value) return result; } +//#pragma mark - Old Contacts +// +//+ (nullable OWSContact *)contactForOldContact:(Contact *)oldContact +//{ +// OWSAssert(oldContact); +// OWSAssert(oldContact.cnContact); +// +//// DDLogDebug(@"%@ in %s with contact: %@", self.logTag, __PRETTY_FUNCTION__, contact); +// +// return [self contactForSystemContact:oldContact.cnContact]; +// +//// OWSContact *newContact = [OWSContact new]; +//// +//// newContact.givenName = oldContact.firstName; +//// newContact.familyName = oldContact.lastName; +//// +//// NSMutableArray< +//// OWSContactPhoneNumber *> *newPhoneNumbers = [NSMutableArray new]; +//// for (NSString *oldPhoneNumber in oldContact.userTextPhoneNumbers) { +//// OWSContactPhoneNumber *newPhoneNumber = [OWSContactPhoneNumber new]; +//// newPhoneNumber.phoneNumber = oldPhoneNumber; +//// newPhoneNumber.phoneType = OWSContactPhoneType_Custom; +//// [newPhoneNumbers addObject:newPhoneNumber]; +//// } +//// newContact.phoneNumbers = newPhoneNumbers; +//// +//// NSMutableArray< +//// OWSContactEmail *> *newEmails = [NSMutableArray new]; +//// for (NSString *oldEmail in oldContact.emails) { +//// OWSContactEmail *newEmail = [OWSContactEmail new]; +//// newEmail.email = oldEmail; +//// newEmail.emailType = OWSContactEmailType_Custom; +//// [newEmails addObject:newEmail]; +//// } +//// newContact.emails = newEmails; +//// +//// @interface Contact : MTLModel +//// +//// @property (nullable, readonly, nonatomic) NSString *firstName; +//// @property (nullable, readonly, nonatomic) NSString *lastName; +//// @property (readonly, nonatomic) NSString *fullName; +//// @property (readonly, nonatomic) NSString *comparableNameFirstLast; +//// @property (readonly, nonatomic) NSString *comparableNameLastFirst; +//// @property (readonly, nonatomic) NSArray *parsedPhoneNumbers; +//// @property (readonly, nonatomic) NSArray *userTextPhoneNumbers; +//// @property (readonly, nonatomic) NSArray *emails; +//// @property (readonly, nonatomic) NSString *uniqueId; +//// @property (nonatomic, readonly) BOOL isSignalContact; +////#if TARGET_OS_IOS +//// @property (nullable, readonly, nonatomic) UIImage *image; +//// @property (nullable, nonatomic, readonly) CNContact *cnContact; +////#endif // TARGET_OS_IOS +//// +//// - (NSArray *)signalRecipientsWithTransaction:(YapDatabaseReadTransaction *)transaction; +//// // TODO: Remove this method. +//// - (NSArray *)textSecureIdentifiers; +//// +////#if TARGET_OS_IOS +//// +//// - (instancetype)initWithSystemContact:(CNContact *)contact NS_AVAILABLE_IOS(9_0); +//// +//// - (NSString *)nameForPhoneNumber:(NSString *)recipientId; +//// +////#endif // TARGET_OS_IOS +//// +//// + (NSComparator)comparatorSortingNamesByFirstThenLast:(BOOL)firstNameOrdering; +//// +//// @end +//} + @end NS_ASSUME_NONNULL_END From 6e18d84a101d08c26f7fde2df56ffb5d87187a36 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Thu, 3 May 2018 11:33:01 -0400 Subject: [PATCH 2/8] Add contact share approval view. --- .../ContactViewController.swift | 8 +- .../ConversationViewController.m | 32 +- .../translations/en.lproj/Localizable.strings | 3 + .../ApproveContactShareViewController.swift | 741 ++---------------- 4 files changed, 116 insertions(+), 668 deletions(-) diff --git a/Signal/src/ViewControllers/ContactViewController.swift b/Signal/src/ViewControllers/ContactViewController.swift index b0d2733b72..ecb87d6949 100644 --- a/Signal/src/ViewControllers/ContactViewController.swift +++ b/Signal/src/ViewControllers/ContactViewController.swift @@ -690,10 +690,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/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 7874e3011c..bb4ba7a831 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -2971,6 +2971,26 @@ typedef enum : NSUInteger { } } +- (void)sendContactShare:(OWSContact *)contactShare +{ + OWSAssertIsOnMainThread(); + OWSAssert(contactShare); + + DDLogVerbose(@"%@ Sending contact share.", self.logTag); + + BOOL didAddToProfileWhitelist = [ThreadUtil addThreadToProfileWhitelistIfEmptyContactThread:self.thread]; + TSOutgoingMessage *message = [ThreadUtil sendMessageWithContactShare:contactShare + inThread:self.thread + messageSender:self.messageSender + completion:nil]; + + [self messageWasSent:message]; + + if (didAddToProfileWhitelist) { + [self ensureDynamicInteractions]; + } +} + - (NSURL *)videoTempFolder { NSString *temporaryDirectory = NSTemporaryDirectory(); @@ -5059,9 +5079,19 @@ interactionControllerForAnimationController:(id String -// func isIncluded() -> Bool -// func setIsIncluded(isIncluded: Bool) -//} - -// MARK: - - class ContactShareField: NSObject { var isIncludedFlag = true -// override required init() { -// super.init() -// } - func localizedLabel() -> String { preconditionFailure("This method must be overridden") } @@ -134,8 +114,6 @@ class ContactShareFieldView: UIView { let hMargin = CGFloat(0) func createContents() { -// self.addRedBorder() - self.layoutMargins.left = 0 self.layoutMargins.right = 0 @@ -150,13 +128,11 @@ class ContactShareFieldView: UIView { checkbox.isSelected = field.isIncluded() // Disable the checkbox; the entire row is hot. checkbox.isUserInteractionEnabled = false -// checkbox.addTarget(self, action: #selector(checkboxTapped), for: .touchUpInside) addSubview(checkbox) checkbox.autoPinEdge(toSuperviewEdge: .leading, withInset: hMargin) checkbox.autoVCenterInSuperview() checkbox.setCompressionResistanceHigh() checkbox.setContentHuggingHigh() -// checkbox.addRedBorder() let nameLabel = UILabel() nameLabel.text = field.localizedLabel() @@ -176,10 +152,6 @@ class ContactShareFieldView: UIView { previewView.autoPinTrailingToSuperviewMargin(withInset: hMargin) } -// func checkboxTapped(sender: UIButton) { -// field.setIsIncluded(isIncluded: checkbox.isSelected) -// } - func wasTapped(sender: UIGestureRecognizer) { Logger.info("\(self.logTag) \(#function)") @@ -193,32 +165,13 @@ class ContactShareFieldView: UIView { // MARK: - @objc -public class ApproveContactShareViewController: OWSViewController -//, CaptioningToolbarDelegate, PlayerProgressBarDelegate, OWSVideoPlayerDelegate -{ +public class ApproveContactShareViewController: OWSViewController { weak var delegate: ApproveContactShareViewControllerDelegate? let contactsManager: OWSContactsManager let contactShare: OWSContact -// // We sometimes shrink the attachment view so that it remains somewhat visible -// // when the keyboard is presented. -// enum AttachmentViewScale { -// case fullsize, compact -// } -// -// // MARK: Properties -// -// let attachment: SignalAttachment -// private var videoPlayer: OWSVideoPlayer? -// -// private(set) var bottomToolbar: UIView! -// private(set) var mediaMessageView: MediaMessageView! -// private(set) var scrollView: UIScrollView! -// private(set) var contentContainer: UIView! -// private(set) var playVideoButton: UIView? - var fields = [ContactShareField]() var fieldViews = [ContactShareFieldView]() @@ -244,6 +197,8 @@ public class ApproveContactShareViewController: OWSViewController var fields = [ContactShareField]() var fieldViews = [ContactShareFieldView]() + // TODO: Avatar + if let phoneNumbers = contactShare.phoneNumbers { for phoneNumber in phoneNumbers { let field = ContactSharePhoneNumber(phoneNumber) @@ -284,140 +239,24 @@ public class ApproveContactShareViewController: OWSViewController self.fieldViews = fieldViews } -// -// -// -// -//// -//// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -//// -// -// -//class TappableView: UIView { -// let actionBlock : (() -> Void) -// -// // MARK: - Initializers -// -// @available(*, unavailable, message: "use init(call:) constructor instead.") -// required init?(coder aDecoder: NSCoder) { -// fatalError("Unimplemented") -// } -// -// required init(actionBlock : @escaping () -> Void) { -// self.actionBlock = actionBlock -// super.init(frame: CGRect.zero) -// -// self.isUserInteractionEnabled = true -// self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(wasTapped))) -// } -// -// func wasTapped(sender: UIGestureRecognizer) { -// Logger.info("\(self.logTag) \(#function)") -// -// guard sender.state == .recognized else { -// return -// } -// actionBlock() -// } -//} -// -//// MARK: - -// -//class ContactViewController: OWSViewController, CNContactViewControllerDelegate { -// -// let TAG = "[ContactView]" - -// enum ContactViewMode { -// case systemContactWithSignal, -// systemContactWithoutSignal, -// nonSystemContact, -// noPhoneNumber, -// unknown -// } -// -// private var hasLoadedView = false -// -// private var viewMode = ContactViewMode.unknown { -// didSet { -// SwiftAssertIsOnMainThread(#function) -// -// if oldValue != viewMode && hasLoadedView { -// updateContent() -// } -// } -// } -// -// var reachability: Reachability? - override public var canBecomeFirstResponder: Bool { return true } -// private let contact: OWSContact -// -// // MARK: - Initializers -// -// @available(*, unavailable, message: "use init(call:) constructor instead.") -// required init?(coder aDecoder: NSCoder) { -// fatalError("Unimplemented") -// } -// -// required init(contact: OWSContact) { -// contactsManager = Environment.current().contactsManager -// self.contact = contact -// -// super.init(nibName: nil, bundle: nil) -// -// tryToDetermineMode() -// -// NotificationCenter.default.addObserver(forName: .OWSContactsManagerSignalAccountsDidChange, object: nil, queue: nil) { [weak self] _ in -// guard let strongSelf = self else { return } -// strongSelf.tryToDetermineMode() -// } -// -// reachability = Reachability.forInternetConnection() -// -// NotificationCenter.default.addObserver(forName: .reachabilityChanged, object: nil, queue: nil) { [weak self] _ in -// guard let strongSelf = self else { return } -// strongSelf.tryToDetermineMode() -// } -// } - // MARK: - View Lifecycle override public func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) updateNavigationBar() - -// UIUtil.applySignalAppearence() -// -// if let navigationController = self.navigationController { -// navigationController.isNavigationBarHidden = true -// } -// -// self.becomeFirstResponder() -// -// contactsManager.requestSystemContactsOnce(completion: { [weak self] _ in -// guard let strongSelf = self else { return } -// strongSelf.tryToDetermineMode() -// }) } override public func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) -// -// UIUtil.applySignalAppearence() -// -// self.becomeFirstResponder() } override public func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - -// if let navigationController = self.navigationController { -// navigationController.isNavigationBarHidden = false -// } } override public func viewDidDisappear(_ animated: Bool) { @@ -432,21 +271,22 @@ public class ApproveContactShareViewController: OWSViewController self.view.preservesSuperviewLayoutMargins = false self.view.backgroundColor = UIColor.white -// self.view.backgroundColor = UIColor(rgbHex: 0xefeff4) updateContent() -// hasLoadedView = true - updateNavigationBar() } - // TODO: Show error. + // 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."), @@ -454,66 +294,9 @@ public class ApproveContactShareViewController: OWSViewController } else { self.navigationItem.rightBarButtonItem = nil } -// self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, -// target: self, -// action: #selector(donePressed)) + } -// private func tryToDetermineMode() { -// SwiftAssertIsOnMainThread(#function) -// -// guard phoneNumbersForContact().count > 0 else { -// viewMode = .noPhoneNumber -// return -// } -// if systemContactsWithSignalAccountsForContact().count > 0 { -// viewMode = .systemContactWithSignal -// return -// } -// if systemContactsForContact().count > 0 { -// viewMode = .systemContactWithoutSignal -// return -// } -// -// viewMode = .nonSystemContact -// } - -// private func systemContactsWithSignalAccountsForContact() -> [String] { -// SwiftAssertIsOnMainThread(#function) -// -// return phoneNumbersForContact().filter({ (phoneNumber) -> Bool in -// return contactsManager.hasSignalAccount(forRecipientId: phoneNumber) -// }) -// } -// -// private func systemContactsForContact() -> [String] { -// SwiftAssertIsOnMainThread(#function) -// -// return phoneNumbersForContact().filter({ (phoneNumber) -> Bool in -// return contactsManager.allContactsMap[phoneNumber] != nil -// }) -// } -// -// private func phoneNumbersForContact() -> [String] { -// SwiftAssertIsOnMainThread(#function) -// -// var result = [String]() -// if let phoneNumbers = contact.phoneNumbers { -// for phoneNumber in phoneNumbers { -// result.append(phoneNumber.phoneNumber) -// } -// } -// return result -// } - -// private func tryToDetermineModeRetry() { -// // Try again after a minute. -// DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 60.0) { [weak self] in -// guard let strongSelf = self else { return } -// strongSelf.tryToDetermineMode() -// } -// } - private func updateContent() { SwiftAssertIsOnMainThread(#function) @@ -526,27 +309,12 @@ public class ApproveContactShareViewController: OWSViewController subview.removeFromSuperview() } -// let topView = createTopView() -// rootView.addSubview(topView) -// 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, withOffset: 0) -// bottomView.autoPinEdge(toSuperviewEdge: .bottom) - let scrollView = UIScrollView() scrollView.preservesSuperviewLayoutMargins = false self.view.addSubview(scrollView) scrollView.layoutMargins = .zero scrollView.autoPinWidthToSuperview() scrollView.autoPin(toTopLayoutGuideOf: self, withInset: 0) -// scrollView.autoPinEdge(.top, to: .bottom, of: topView, withOffset: 0) scrollView.autoPinEdge(toSuperviewEdge: .bottom) let fieldsView = createFieldsView() @@ -561,138 +329,6 @@ public class ApproveContactShareViewController: OWSViewController fieldsView.autoPinEdge(toSuperviewEdge: .bottom) } -// private func createTopView() -> UIView { -// SwiftAssertIsOnMainThread(#function) -// -// let topView = UIView.container() -// topView.backgroundColor = UIColor(rgbHex: 0xefeff4) -// 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.autoPinEdge(toSuperviewEdge: .left) -// -// let backIconName = (self.view.isRTL() ? "system_disclosure_indicator" : "system_disclosure_indicator_rtl") -// let backIconImage = UIImage(named: backIconName)?.withRenderingMode(.alwaysTemplate) -// let backIconView = UIImageView(image: backIconImage) -// 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 = 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() -// avatarView.autoSetDimension(.width, toSize: avatarSize) -// avatarView.autoSetDimension(.height, toSize: avatarSize) -// -// let nameLabel = UILabel() -// nameLabel.text = contact.displayName -// nameLabel.font = UIFont.ows_dynamicTypeTitle2.ows_bold() -// nameLabel.textColor = UIColor.black -// nameLabel.lineBreakMode = .byTruncatingTail -// nameLabel.textAlignment = .center -// topView.addSubview(nameLabel) -// nameLabel.autoPinEdge(.top, to: .bottom, of: avatarView, withOffset: 10) -// nameLabel.autoPinLeadingToSuperviewMargin(withInset: hMargin) -// nameLabel.autoPinTrailingToSuperviewMargin(withInset: hMargin) -// -// var lastView: UIView = nameLabel -// -// if let firstPhoneNumber = contact.phoneNumbers?.first { -// let phoneNumberLabel = UILabel() -// phoneNumberLabel.text = PhoneNumber.bestEffortFormatE164(asLocalizedPhoneNumber: firstPhoneNumber.phoneNumber) -// phoneNumberLabel.font = UIFont.ows_dynamicTypeCaption2 -// phoneNumberLabel.textColor = UIColor.black -// phoneNumberLabel.lineBreakMode = .byTruncatingTail -// phoneNumberLabel.textAlignment = .center -// topView.addSubview(phoneNumberLabel) -// phoneNumberLabel.autoPinEdge(.top, to: .bottom, of: lastView, withOffset: 5) -// phoneNumberLabel.autoPinLeadingToSuperviewMargin(withInset: hMargin) -// phoneNumberLabel.autoPinTrailingToSuperviewMargin(withInset: hMargin) -// lastView = phoneNumberLabel -// } -// -// switch viewMode { -// case .systemContactWithSignal: -// // Show actions buttons for system contacts with a Signal account. -// let stackView = UIStackView() -// stackView.axis = .horizontal -// stackView.distribution = .fillEqually -// stackView.addArrangedSubview(createCircleActionButton(text: NSLocalizedString("ACTION_SEND_MESSAGE", -// comment: "Label for 'sent message' button in contact view."), -// actionBlock: { [weak self] _ in -// guard let strongSelf = self else { return } -// strongSelf.didPressSendMessage() -// })) -// stackView.addArrangedSubview(createCircleActionButton(text: NSLocalizedString("ACTION_AUDIO_CALL", -// comment: "Label for 'audio call' button in contact view."), -// actionBlock: { [weak self] _ in -// guard let strongSelf = self else { return } -// strongSelf.didPressAudioCall() -// })) -// stackView.addArrangedSubview(createCircleActionButton(text: NSLocalizedString("ACTION_VIDEO_CALL", -// comment: "Label for 'video call' button in contact view."), -// actionBlock: { [weak self] _ in -// guard let strongSelf = self else { return } -// strongSelf.didPressVideoCall() -// })) -// topView.addSubview(stackView) -// stackView.autoPinEdge(.top, to: .bottom, of: lastView, withOffset: 20) -// stackView.autoPinLeadingToSuperviewMargin(withInset: hMargin) -// stackView.autoPinTrailingToSuperviewMargin(withInset: hMargin) -// lastView = stackView -// case .systemContactWithoutSignal: -// // Show invite button for system contacts without a Signal account. -// let inviteButton = createLargePillButton(text: NSLocalizedString("ACTION_INVITE", -// comment: "Label for 'invite' button in contact view."), -// actionBlock: { [weak self] _ in -// guard let strongSelf = self else { return } -// strongSelf.didPressInvite() -// }) -// topView.addSubview(inviteButton) -// inviteButton.autoPinEdge(.top, to: .bottom, of: lastView, withOffset: 20) -// inviteButton.autoPinLeadingToSuperviewMargin(withInset: 55) -// inviteButton.autoPinTrailingToSuperviewMargin(withInset: 55) -// lastView = inviteButton -// case .nonSystemContact: -// // Show no action buttons for contacts not in user's device contacts. -// break -// case .noPhoneNumber: -// // Show no action buttons for contacts without a phone number. -// break -// case .unknown: -// let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge) -// topView.addSubview(activityIndicator) -// activityIndicator.autoPinEdge(.top, to: .bottom, of: lastView, withOffset: 10) -// activityIndicator.autoHCenterInSuperview() -// lastView = activityIndicator -// break -// } -// -// lastView.autoPinEdge(toSuperviewEdge: .bottom, withInset: 15) -// -// return topView -// } - private func createFieldsView() -> UIView { SwiftAssertIsOnMainThread(#function) @@ -732,60 +368,12 @@ public class ApproveContactShareViewController: OWSViewController lastRow = row } + addRow(createNameRow()) + for fieldView in fieldViews { addRow(fieldView) } -// if viewMode == .nonSystemContact { -// addRow(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", -// comment: "Label for 'new contact' button in conversation settings view."), -// action: #selector(didPressAddToExistingContact))) -// } -// -// // TODO: Not designed yet. -// // if viewMode == .systemContactWithSignal || -// // viewMode == .systemContactWithoutSignal { -// // addRow(createActionRow(labelText:NSLocalizedString("ACTION_SHARE_CONTACT", -// // comment:"Label for 'share contact' button."), -// // action:#selector(didPressShareContact))) -// // } -// -// if let phoneNumbers = contact.phoneNumbers { -// for phoneNumber in phoneNumbers { -// // TODO: Try to format the phone number nicely. -// addRow(createNameValueRow(name: phoneNumber.localizedLabel(), -// value: -// PhoneNumber.bestEffortFormatE164(asLocalizedPhoneNumber: phoneNumber.phoneNumber), -// actionBlock: { -// guard let url = NSURL(string: "tel:\(phoneNumber.phoneNumber)") else { -// owsFail("\(ContactViewController.logTag) could not open phone number.") -// return -// } -// UIApplication.shared.openURL(url as URL) -// })) -// } -// } -// -// if let emails = contact.emails { -// for email in emails { -// addRow(createNameValueRow(name: email.localizedLabel(), -// value: email.email, -// actionBlock: { -// guard let url = NSURL(string: "mailto:\(email.email)") else { -// owsFail("\(ContactViewController.logTag) could not open email.") -// return -// } -// UIApplication.shared.openURL(url as URL) -// })) -// } -// } - - // TODO: Should we present addresses here too? How? - lastRow?.autoPinEdge(toSuperviewEdge: .bottom, withInset: 0) return fieldsView @@ -793,143 +381,53 @@ public class ApproveContactShareViewController: OWSViewController private let hMargin = CGFloat(16) -// private func createActionRow(labelText: String, action: Selector) -> UIView { -// let row = UIView() -// row.layoutMargins.left = 0 -// row.layoutMargins.right = 0 -// row.isUserInteractionEnabled = true -// row.addGestureRecognizer(UITapGestureRecognizer(target: self, action: action)) -// -// let label = UILabel() -// label.text = labelText -// label.font = UIFont.ows_dynamicTypeBody -// label.textColor = UIColor.ows_materialBlue -// label.lineBreakMode = .byTruncatingTail -// row.addSubview(label) -// label.autoPinTopToSuperviewMargin() -// label.autoPinBottomToSuperviewMargin() -// label.autoPinLeadingToSuperviewMargin(withInset: hMargin) -// label.autoPinTrailingToSuperviewMargin(withInset: hMargin) -// -// return row -// } + func createNameRow() -> UIView { + let nameVMargin = CGFloat(16) -// private func createNameValueRow(name: String, value: String, actionBlock : @escaping () -> Void) -> UIView { -// let row = TappableView(actionBlock: actionBlock) -// row.layoutMargins.left = 0 -// row.layoutMargins.right = 0 -// -// let nameLabel = UILabel() -// nameLabel.text = name -// nameLabel.font = UIFont.ows_dynamicTypeCaption1 -// nameLabel.textColor = UIColor.black -// nameLabel.lineBreakMode = .byTruncatingTail -// row.addSubview(nameLabel) -// nameLabel.autoPinTopToSuperviewMargin() -// nameLabel.autoPinLeadingToSuperviewMargin(withInset: hMargin) -// nameLabel.autoPinTrailingToSuperviewMargin(withInset: hMargin) -// -// let valueLabel = UILabel() -// valueLabel.text = value -// valueLabel.font = UIFont.ows_dynamicTypeCaption1 -// valueLabel.textColor = UIColor.ows_materialBlue -// valueLabel.lineBreakMode = .byTruncatingTail -// row.addSubview(valueLabel) -// valueLabel.autoPinEdge(.top, to: .bottom, of: nameLabel, withOffset: 3) -// valueLabel.autoPinBottomToSuperviewMargin() -// valueLabel.autoPinLeadingToSuperviewMargin(withInset: hMargin) -// valueLabel.autoPinTrailingToSuperviewMargin(withInset: hMargin) -// -// // TODO: Should there be a disclosure icon here? -// -// return row -// } + let row = UIView() + row.layoutMargins = UIEdgeInsets(top: nameVMargin, left: 0, bottom: nameVMargin, right: 0) -// // TODO: Use real assets. -// private func createCircleActionButton(text: String, actionBlock : @escaping () -> Void) -> UIView { -// let buttonSize = CGFloat(50) -// -// let button = TappableView(actionBlock: actionBlock) -// button.layoutMargins = .zero -// button.autoSetDimension(.width, toSize: buttonSize, relation: .greaterThanOrEqual) -// -// let circleView = UIView() -// circleView.backgroundColor = UIColor.white -// circleView.autoSetDimension(.width, toSize: buttonSize) -// circleView.autoSetDimension(.height, toSize: buttonSize) -// circleView.layer.cornerRadius = buttonSize * 0.5 -// button.addSubview(circleView) -// circleView.autoPinEdge(toSuperviewEdge: .top) -// circleView.autoHCenterInSuperview() -// -// let label = UILabel() -// label.text = text -// label.font = UIFont.ows_dynamicTypeCaption2 -// label.textColor = UIColor.black -// label.lineBreakMode = .byTruncatingTail -// label.textAlignment = .center -// button.addSubview(label) -// label.autoPinEdge(.top, to: .bottom, of: circleView, withOffset: 3) -// label.autoPinEdge(toSuperviewEdge: .bottom) -// label.autoPinLeadingToSuperviewMargin() -// label.autoPinTrailingToSuperviewMargin() -// -// return button -// } + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.alignment = .center + stackView.layoutMargins = .zero + row.addSubview(stackView) + stackView.autoPinLeadingToSuperviewMargin() + stackView.autoPinTrailingToSuperviewMargin() + stackView.autoPinTopToSuperviewMargin() + stackView.autoPinBottomToSuperviewMargin() -// private func createLargePillButton(text: String, actionBlock : @escaping () -> Void) -> UIView { -// let button = TappableView(actionBlock: actionBlock) -// button.backgroundColor = UIColor.white -// button.layoutMargins = .zero -// button.autoSetDimension(.height, toSize: 45) -// button.layer.cornerRadius = 5 -// -// let label = UILabel() -// label.text = text -// label.font = UIFont.ows_dynamicTypeCaption1 -// label.textColor = UIColor.ows_materialBlue -// label.lineBreakMode = .byTruncatingTail -// label.textAlignment = .center -// button.addSubview(label) -// label.autoPinLeadingToSuperviewMargin(withInset: 20) -// label.autoPinTrailingToSuperviewMargin(withInset: 20) -// label.autoVCenterInSuperview() -// label.autoPinEdge(toSuperviewEdge: .top, withInset: 0, relation: .greaterThanOrEqual) -// label.autoPinEdge(toSuperviewEdge: .bottom, withInset: 0, relation: .greaterThanOrEqual) -// -// return button -// } + let nameLabel = UILabel() + nameLabel.text = contactShare.displayName + nameLabel.font = UIFont.ows_dynamicTypeBody + nameLabel.textColor = UIColor.ows_materialBlue + nameLabel.lineBreakMode = .byTruncatingTail + stackView.addArrangedSubview(nameLabel) + nameLabel.setContentHuggingHigh() -// private func createLabeledFieldRow(name: String, value: String, actionBlock : @escaping () -> Void) -> UIView { -// let row = TappableView(actionBlock: actionBlock) -// row.layoutMargins.left = 0 -// row.layoutMargins.right = 0 -// -// let nameLabel = UILabel() -// nameLabel.text = name -// nameLabel.font = UIFont.ows_dynamicTypeCaption1 -// nameLabel.textColor = UIColor.black -// nameLabel.lineBreakMode = .byTruncatingTail -// row.addSubview(nameLabel) -// nameLabel.autoPinTopToSuperviewMargin() -// nameLabel.autoPinLeadingToSuperviewMargin(withInset: hMargin) -// nameLabel.autoPinTrailingToSuperviewMargin(withInset: hMargin) -// -// let valueLabel = UILabel() -// valueLabel.text = value -// valueLabel.font = UIFont.ows_dynamicTypeCaption1 -// valueLabel.textColor = UIColor.ows_materialBlue -// valueLabel.lineBreakMode = .byTruncatingTail -// row.addSubview(valueLabel) -// valueLabel.autoPinEdge(.top, to: .bottom, of: nameLabel, withOffset: 3) -// valueLabel.autoPinBottomToSuperviewMargin() -// valueLabel.autoPinLeadingToSuperviewMargin(withInset: hMargin) -// valueLabel.autoPinTrailingToSuperviewMargin(withInset: hMargin) -// -// // TODO: Should there be a disclosure icon here? -// -// return row -// } + 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") + let iconImage = UIImage(named: iconName)?.withRenderingMode(.alwaysTemplate) + let iconView = UIImageView(image: iconImage) + 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() @@ -1020,113 +518,30 @@ public class ApproveContactShareViewController: OWSViewController // MARK: - -// func didPressCreateNewContact(sender: UIGestureRecognizer) { -// Logger.info("\(self.TAG) \(#function)") -// -// guard sender.state == .recognized else { -// return -// } -// presentNewContactView() -// } -// -// func didPressAddToExistingContact(sender: UIGestureRecognizer) { -// Logger.info("\(self.TAG) \(#function)") -// -// guard sender.state == .recognized else { -// return -// } -// presentSelectAddToExistingContactView() -// } -// -// func didPressShareContact(sender: UIGestureRecognizer) { -// Logger.info("\(self.TAG) \(#function)") -// -// guard sender.state == .recognized else { -// return -// } -// // TODO: -// } -// -// func didPressSendMessage() { -// Logger.info("\(self.TAG) \(#function)") -// -// presentThreadAndPeform(action: .compose) -// } -// -// func didPressAudioCall() { -// Logger.info("\(self.TAG) \(#function)") -// -// presentThreadAndPeform(action: .audioCall) -// } -// -// func didPressVideoCall() { -// Logger.info("\(self.TAG) \(#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) -// }) -// } -// -// func didPressInvite() { -// Logger.info("\(self.TAG) \(#function)") -// -// guard MFMessageComposeViewController.canSendText() else { -// Logger.info("\(TAG) Device cannot send text") -// OWSAlerts.showErrorAlert(message: NSLocalizedString("UNSUPPORTED_FEATURE_ERROR", comment: "")) -// return -// } -// let phoneNumbers = -// phoneNumbersForContact() -// guard phoneNumbers.count > 0 else { -// owsFail("\(logTag) no phone numbers.") -// return -// } -// -// let inviteFlow = -// InviteFlow(presentingViewController: self, contactsManager: contactsManager) -// 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.bestEffortFormatE164(asLocalizedPhoneNumber: phoneNumber), -// style: .default) { _ in -// completion(phoneNumber) -// }) -// } -// actionSheet.addAction(OWSAlerts.cancelAction) -// -// self.present(actionSheet, animated: true) -// } - -// func didPressDismiss() { -// Logger.info("\(self.TAG) \(#function)") -// -// self.navigationController?.popViewController(animated: true) -// } - func didPressSendButton() { - Logger.info("\(logTag) \(#function)") + Logger.info("\(logTag) \(#function)") + + guard let delegate = self.delegate else { + owsFail("\(logTag) missing delegate.") + return + } + + delegate.approveContactShare(self, didApproveContactShare: contactShare) + } + + 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)") // TODO: } From 7a9acce504ac6a7fb19257be9fa29b328d24e23c Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Thu, 3 May 2018 14:07:49 -0400 Subject: [PATCH 3/8] Add contact share approval view. --- Signal.xcodeproj/project.pbxproj | 4 + .../ContactViewController.swift | 2 +- .../translations/en.lproj/Localizable.strings | 18 + .../ApproveContactShareViewController.swift | 98 ++++- .../EditContactShareNameViewController.swift | 347 ++++++++++++++++++ .../src/Messages/Interactions/OWSContact.h | 18 +- .../src/Messages/Interactions/OWSContact.m | 130 +++---- 7 files changed, 525 insertions(+), 92 deletions(-) create mode 100644 SignalMessaging/attachments/EditContactShareNameViewController.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 144b01541a..6aad516aa2 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -49,6 +49,7 @@ 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 */; }; @@ -632,6 +633,7 @@ 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 = ""; }; @@ -1602,6 +1604,7 @@ 344F2498200FD03200CFB4F4 /* AttachmentApprovalViewController.swift */, 344F248220069E9B00CFB4F4 /* CountryCodeViewController.h */, 344F248320069E9B00CFB4F4 /* CountryCodeViewController.m */, + 34382269209B63500094FEB7 /* EditContactShareNameViewController.swift */, 346129DC1FD5C02900532771 /* LockInteractionController.h */, 346129DD1FD5C02900532771 /* LockInteractionController.m */, 344F2496200FD03200CFB4F4 /* MessageApprovalViewController.swift */, @@ -3098,6 +3101,7 @@ 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 ecb87d6949..e0c1e6ee54 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") } diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 8de7b1a91a..a9c36920c6 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -445,6 +445,21 @@ /* Label for the 'street' field of a contact's address. */ "CONTACT_FIELD_ADDRESS_STREET" = "Street"; +/* Label for the 'family name' field of a contact. */ +"CONTACT_FIELD_FAMILY_NAME" = "Family Name"; + +/* Label for the 'given name' field of a contact. */ +"CONTACT_FIELD_GIVEN_NAME" = "Given Name"; + +/* Label for the 'middle name' field of a contact. */ +"CONTACT_FIELD_MIDDLE_NAME" = "Middle Name"; + +/* Label for the 'name prefix' field of a contact. */ +"CONTACT_FIELD_NAME_PREFIX" = "Prefix"; + +/* Label for the 'name suffix' field of a contact. */ +"CONTACT_FIELD_NAME_SUFFIX" = "Suffix"; + /* Label for a contact's phone number. */ "CONTACT_PHONE" = "Phone"; @@ -460,6 +475,9 @@ /* Title for the 'Approve contact share' view. */ "CONTACT_SHARE_APPROVAL_VIEW_TITLE" = "Share Contact"; +/* Title for the 'edit contact share name' view. */ +"CONTACT_SHARE_EDIT_NAME_VIEW_TITLE" = "Edit Name"; + /* Indicates that a contact has no name. */ "CONTACT_WITHOUT_NAME" = "Unnamed Contact"; diff --git a/SignalMessaging/attachments/ApproveContactShareViewController.swift b/SignalMessaging/attachments/ApproveContactShareViewController.swift index d3ea3d45ed..d44f4faec5 100644 --- a/SignalMessaging/attachments/ApproveContactShareViewController.swift +++ b/SignalMessaging/attachments/ApproveContactShareViewController.swift @@ -15,7 +15,7 @@ public protocol ApproveContactShareViewControllerDelegate: class { class ContactShareField: NSObject { - var isIncludedFlag = true + private var isIncludedFlag = true func localizedLabel() -> String { preconditionFailure("This method must be overridden") @@ -25,9 +25,13 @@ class ContactShareField: NSObject { return isIncludedFlag } - func setIsIncluded(isIncluded: Bool) { + func setIsIncluded(_ isIncluded: Bool) { isIncludedFlag = isIncluded } + + func applyToContact(contact: OWSContact) { + preconditionFailure("This method must be overridden") + } } // MARK: - @@ -45,6 +49,17 @@ class ContactSharePhoneNumber: ContactShareField { override func localizedLabel() -> String { return value.localizedLabel() } + + override func applyToContact(contact: OWSContact) { + assert(isIncluded()) + + var values = [OWSContactPhoneNumber]() + if let oldValues = contact.phoneNumbers { + values += oldValues + } + values.append(value) + contact.phoneNumbers = values + } } // MARK: - @@ -62,6 +77,17 @@ class ContactShareEmail: ContactShareField { override func localizedLabel() -> String { return value.localizedLabel() } + + override func applyToContact(contact: OWSContact) { + assert(isIncluded()) + + var values = [OWSContactEmail]() + if let oldValues = contact.emails { + values += oldValues + } + values.append(value) + contact.emails = values + } } // MARK: - @@ -79,6 +105,17 @@ class ContactShareAddress: ContactShareField { override func localizedLabel() -> String { return value.localizedLabel() } + + override func applyToContact(contact: OWSContact) { + assert(isIncluded()) + + var values = [OWSContactAddress]() + if let oldValues = contact.addresses { + values += oldValues + } + values.append(value) + contact.addresses = values + } } // MARK: - @@ -158,23 +195,25 @@ class ContactShareFieldView: UIView { guard sender.state == .recognized else { return } - checkbox.isSelected = !checkbox.isSelected + field.setIsIncluded(!field.isIncluded()) + checkbox.isSelected = field.isIncluded() } } // MARK: - @objc -public class ApproveContactShareViewController: OWSViewController { +public class ApproveContactShareViewController: OWSViewController, EditContactShareNameViewControllerDelegate { weak var delegate: ApproveContactShareViewControllerDelegate? - let contactsManager: OWSContactsManager + let contactsManager: OWSContactsManager - let contactShare: OWSContact + var contactShare: OWSContact - var fields = [ContactShareField]() var fieldViews = [ContactShareFieldView]() + var nameLabel: UILabel! + // MARK: Initializers @available(*, unavailable, message:"use other constructor instead.") @@ -194,7 +233,6 @@ public class ApproveContactShareViewController: OWSViewController { } func buildFields() { - var fields = [ContactShareField]() var fieldViews = [ContactShareFieldView]() // TODO: Avatar @@ -202,7 +240,6 @@ public class ApproveContactShareViewController: OWSViewController { if let phoneNumbers = contactShare.phoneNumbers { for phoneNumber in phoneNumbers { let field = ContactSharePhoneNumber(phoneNumber) - fields.append(field) let fieldView = ContactShareFieldView(field: field, previewViewBlock: { [weak self] _ in guard let strongSelf = self else { return UIView() } return strongSelf.previewView(forPhoneNumber: phoneNumber) @@ -214,7 +251,6 @@ public class ApproveContactShareViewController: OWSViewController { if let emails = contactShare.emails { for email in emails { let field = ContactShareEmail(email) - fields.append(field) let fieldView = ContactShareFieldView(field: field, previewViewBlock: { [weak self] _ in guard let strongSelf = self else { return UIView() } return strongSelf.previewView(forEmail: email) @@ -226,7 +262,6 @@ public class ApproveContactShareViewController: OWSViewController { if let addresses = contactShare.addresses { for address in addresses { let field = ContactShareAddress(address) - fields.append(field) let fieldView = ContactShareFieldView(field: field, previewViewBlock: { [weak self] _ in guard let strongSelf = self else { return UIView() } return strongSelf.previewView(forAddress: address) @@ -235,7 +270,6 @@ public class ApproveContactShareViewController: OWSViewController { } } - self.fields = fields self.fieldViews = fieldViews } @@ -290,7 +324,7 @@ public class ApproveContactShareViewController: OWSViewController { 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)) + style: .plain, target: self, action: #selector(didPressSendButton)) } else { self.navigationItem.rightBarButtonItem = nil } @@ -391,6 +425,7 @@ public class ApproveContactShareViewController: OWSViewController { stackView.axis = .horizontal stackView.alignment = .center stackView.layoutMargins = .zero + stackView.spacing = 10 row.addSubview(stackView) stackView.autoPinLeadingToSuperviewMargin() stackView.autoPinTrailingToSuperviewMargin() @@ -398,6 +433,7 @@ public class ApproveContactShareViewController: OWSViewController { stackView.autoPinBottomToSuperviewMargin() let nameLabel = UILabel() + self.nameLabel = nameLabel nameLabel.text = contactShare.displayName nameLabel.font = UIFont.ows_dynamicTypeBody nameLabel.textColor = UIColor.ows_materialBlue @@ -518,6 +554,24 @@ public class ApproveContactShareViewController: OWSViewController { // 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)") @@ -526,7 +580,10 @@ public class ApproveContactShareViewController: OWSViewController { return } - delegate.approveContactShare(self, didApproveContactShare: contactShare) + let filteredContactShare = self.filteredContactShare() + assert(filteredContactShare.ows_isValid()) + + delegate.approveContactShare(self, didApproveContactShare: filteredContactShare) } func didPressCancel() { @@ -543,6 +600,17 @@ public class ApproveContactShareViewController: OWSViewController { func didPressEditName() { Logger.info("\(logTag) \(#function)") - // TODO: + 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/EditContactShareNameViewController.swift b/SignalMessaging/attachments/EditContactShareNameViewController.swift new file mode 100644 index 0000000000..496efc4ff2 --- /dev/null +++ b/SignalMessaging/attachments/EditContactShareNameViewController.swift @@ -0,0 +1,347 @@ +// +// 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.autoPinTopToSuperviewMargin() + stackView.autoPinBottomToSuperviewMargin() + stackView.autoPinLeadingToSuperviewMargin() + stackView.autoPinTrailingToSuperviewMargin() + + 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() + + // See notes on how to use UIScrollView with iOS Auto Layout: + // + // https://developer.apple.com/library/content/releasenotes/General/RN-iOSSDK-6_0/ + scrollView.addSubview(fieldsView) + fieldsView.autoPinLeadingToSuperviewMargin() + fieldsView.autoPinTrailingToSuperviewMargin() + fieldsView.autoPinEdge(toSuperviewEdge: .top) + fieldsView.autoPinEdge(toSuperviewEdge: .bottom) + } + + 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, withOffset: 0) + lastRow = row + } + + let addRow: ((UIView) -> Void) = { (row) in + if lastRow != nil { + addSpacerRow() + } + fieldsView.addSubview(row) + row.autoPinLeadingToSuperviewMargin(withInset: self.hMargin) + row.autoPinTrailingToSuperviewMargin(withInset: self.hMargin) + if let lastRow = lastRow { + row.autoPinEdge(.top, to: .bottom, of: lastRow, withOffset: 0) + } else { + row.autoPinEdge(toSuperviewEdge: .top, withInset: 0) + } + lastRow = row + } + + for fieldView in fieldViews { + addRow(fieldView) + } + + lastRow?.autoPinEdge(toSuperviewEdge: .bottom, withInset: 0) + + return fieldsView + } + + 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) + + self.navigationController?.popViewController(animated: true) + } + + func didPressCancel() { + Logger.info("\(logTag) \(#function)") + + self.navigationController?.popViewController(animated: true) + } + + // MARK: - ContactNameFieldViewDelegate + + public func nameFieldDidChange() { + updateNavigationBar() + } +} diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContact.h b/SignalServiceKit/src/Messages/Interactions/OWSContact.h index d419bce32c..ecd8fc5d04 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSContact.h +++ b/SignalServiceKit/src/Messages/Interactions/OWSContact.h @@ -128,6 +128,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 - @@ -154,10 +168,6 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value); + (nullable OWSSignalServiceProtosDataMessageContact *)protoForContact:(OWSContact *)contact; + (OWSContact *_Nullable)contactForDataMessage:(OWSSignalServiceProtosDataMessage *)dataMessage; -//#pragma mark - Old Contacts -// -//+ (nullable OWSContact *)contactForOldContact:(Contact *)oldContact - @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContact.m b/SignalServiceKit/src/Messages/Interactions/OWSContact.m index a0c4398e36..5e1130e550 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSContact.m +++ b/SignalServiceKit/src/Messages/Interactions/OWSContact.m @@ -124,7 +124,7 @@ NSString *NSStringForContactEmailType(OWSContactEmailType value) DDLogWarn(@"%@ invalid email: %@.", self.logTag, self.email); return NO; } - return YES; + return YES; } - (NSString *)localizedLabel @@ -203,7 +203,7 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value) DDLogWarn(@"%@ invalid address; empty.", self.logTag); return NO; } - return YES; + return YES; } - (NSString *)localizedLabel @@ -362,6 +362,13 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value) } } +- (void)updateDisplayName +{ + _displayName = nil; + + [self ensureDisplayName]; +} + - (NSString *)debugDescription { NSMutableString *result = [NSMutableString new]; @@ -400,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 - @@ -960,76 +1016,6 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value) return result; } -//#pragma mark - Old Contacts -// -//+ (nullable OWSContact *)contactForOldContact:(Contact *)oldContact -//{ -// OWSAssert(oldContact); -// OWSAssert(oldContact.cnContact); -// -//// DDLogDebug(@"%@ in %s with contact: %@", self.logTag, __PRETTY_FUNCTION__, contact); -// -// return [self contactForSystemContact:oldContact.cnContact]; -// -//// OWSContact *newContact = [OWSContact new]; -//// -//// newContact.givenName = oldContact.firstName; -//// newContact.familyName = oldContact.lastName; -//// -//// NSMutableArray< -//// OWSContactPhoneNumber *> *newPhoneNumbers = [NSMutableArray new]; -//// for (NSString *oldPhoneNumber in oldContact.userTextPhoneNumbers) { -//// OWSContactPhoneNumber *newPhoneNumber = [OWSContactPhoneNumber new]; -//// newPhoneNumber.phoneNumber = oldPhoneNumber; -//// newPhoneNumber.phoneType = OWSContactPhoneType_Custom; -//// [newPhoneNumbers addObject:newPhoneNumber]; -//// } -//// newContact.phoneNumbers = newPhoneNumbers; -//// -//// NSMutableArray< -//// OWSContactEmail *> *newEmails = [NSMutableArray new]; -//// for (NSString *oldEmail in oldContact.emails) { -//// OWSContactEmail *newEmail = [OWSContactEmail new]; -//// newEmail.email = oldEmail; -//// newEmail.emailType = OWSContactEmailType_Custom; -//// [newEmails addObject:newEmail]; -//// } -//// newContact.emails = newEmails; -//// -//// @interface Contact : MTLModel -//// -//// @property (nullable, readonly, nonatomic) NSString *firstName; -//// @property (nullable, readonly, nonatomic) NSString *lastName; -//// @property (readonly, nonatomic) NSString *fullName; -//// @property (readonly, nonatomic) NSString *comparableNameFirstLast; -//// @property (readonly, nonatomic) NSString *comparableNameLastFirst; -//// @property (readonly, nonatomic) NSArray *parsedPhoneNumbers; -//// @property (readonly, nonatomic) NSArray *userTextPhoneNumbers; -//// @property (readonly, nonatomic) NSArray *emails; -//// @property (readonly, nonatomic) NSString *uniqueId; -//// @property (nonatomic, readonly) BOOL isSignalContact; -////#if TARGET_OS_IOS -//// @property (nullable, readonly, nonatomic) UIImage *image; -//// @property (nullable, nonatomic, readonly) CNContact *cnContact; -////#endif // TARGET_OS_IOS -//// -//// - (NSArray *)signalRecipientsWithTransaction:(YapDatabaseReadTransaction *)transaction; -//// // TODO: Remove this method. -//// - (NSArray *)textSecureIdentifiers; -//// -////#if TARGET_OS_IOS -//// -//// - (instancetype)initWithSystemContact:(CNContact *)contact NS_AVAILABLE_IOS(9_0); -//// -//// - (NSString *)nameForPhoneNumber:(NSString *)recipientId; -//// -////#endif // TARGET_OS_IOS -//// -//// + (NSComparator)comparatorSortingNamesByFirstThenLast:(BOOL)firstNameOrdering; -//// -//// @end -//} - @end NS_ASSUME_NONNULL_END From fd93bf677e38ff1b330feacc0c7ec9d77fb771eb Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Fri, 4 May 2018 13:41:24 -0400 Subject: [PATCH 4/8] Clean up ahead of PR. --- .../ConversationViewController.m | 101 ------------------ .../ApproveContactShareViewController.swift | 64 +++++------ 2 files changed, 25 insertions(+), 140 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index bb4ba7a831..b1357f4773 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -338,26 +338,8 @@ typedef enum : NSUInteger { OWSAssertIsOnMainThread(); [self ensureDynamicInteractions]; - - // [self tryToShowContactShareUI]; } -//- (void)tryToShowContactShareUI -//{ -// OWSAssertIsOnMainThread(); -// -// Contact *_Nullable firstContact = self.contactsManager.allContacts.firstObject; -// DDLogInfo(@"%@ firstContact: %@ %d", self.logTag, firstContact, firstContact.cnContact != nil); -// [DDLog flushLog]; -// if (!firstContact.cnContact) { -// return; -// } -// static dispatch_once_t onceToken; -// dispatch_once(&onceToken, ^{ -// [self contactsPicker:nil didSelectContact:firstContact]; -// }); -//} - - (void)otherUsersProfileDidChange:(NSNotification *)notification { OWSAssertIsOnMainThread(); @@ -508,11 +490,6 @@ typedef enum : NSUInteger { [self addNotificationListeners]; [self loadDraftInCompose]; - - dispatch_async(dispatch_get_main_queue(), ^{ - [self showPhonyContactShareApproval]; - // [self tryToShowContactShareUI]; - }); } - (void)loadView @@ -4948,79 +4925,6 @@ interactionControllerForAnimationController:(id *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; - address1.street = @"123 home st."; - address1.neighborhood = @"round the bend."; - address1.city = @"homeville"; - address1.region = @"HO"; - address1.postcode = @"12345"; - address1.country = @"USA"; - OWSContactAddress *address2 = [OWSContactAddress new]; - address2.addressType = OWSContactAddressType_Custom; - address2.label = @"Otra casa"; - address2.pobox = @"caja 123"; - address2.street = @"123 casa calle"; - address2.city = @"barrio norte"; - address2.region = @"AB"; - address2.postcode = @"53421"; - address2.country = @"MX"; - contact.addresses = @[ - address1, - address2, - ]; - - // TODO: Avatar - return contact; -} - - (void)contactsPickerDidCancel:(ContactsPicker *)contactsPicker { DDLogDebug(@"%@ in %s", self.logTag, __PRETTY_FUNCTION__); @@ -5044,16 +4948,11 @@ interactionControllerForAnimationController:(id UIView { let label = UILabel() - label.text = PhoneNumber.bestEffortFormatE164(asLocalizedPhoneNumber: phoneNumber.phoneNumber) + label.text = PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: phoneNumber.phoneNumber) label.font = UIFont.ows_dynamicTypeCaption1 label.textColor = UIColor.ows_materialBlue label.lineBreakMode = .byTruncatingTail From 60c3764525d1033dc76c6fc9333ebea0d0c41e52 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Fri, 4 May 2018 13:48:58 -0400 Subject: [PATCH 5/8] Clean up ahead of PR. --- .../ApproveContactShareViewController.swift | 38 +++++++------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/SignalMessaging/attachments/ApproveContactShareViewController.swift b/SignalMessaging/attachments/ApproveContactShareViewController.swift index 57e11f7cc5..571259750a 100644 --- a/SignalMessaging/attachments/ApproveContactShareViewController.swift +++ b/SignalMessaging/attachments/ApproveContactShareViewController.swift @@ -339,9 +339,6 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh let fieldsView = createFieldsView() - // See notes on how to use UIScrollView with iOS Auto Layout: - // - // https://developer.apple.com/library/content/releasenotes/General/RN-iOSSDK-6_0/ scrollView.addSubview(fieldsView) fieldsView.autoPinLeadingToSuperviewMargin() fieldsView.autoPinTrailingToSuperviewMargin() @@ -437,8 +434,11 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh // Icon let iconName = (self.view.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 row + } + let iconView = UIImageView(image: iconImage.withRenderingMode(.alwaysTemplate)) iconView.contentMode = .scaleAspectFit iconView.tintColor = UIColor.black.withAlphaComponent(0.6) stackView.addArrangedSubview(iconView) @@ -457,7 +457,6 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh label.font = UIFont.ows_dynamicTypeCaption1 label.textColor = UIColor.ows_materialBlue label.lineBreakMode = .byTruncatingTail - // label.textAlignment = .center return label } @@ -467,24 +466,17 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh label.font = UIFont.ows_dynamicTypeCaption1 label.textColor = UIColor.ows_materialBlue label.lineBreakMode = .byTruncatingTail - // label.textAlignment = .center return label } func previewView(forAddress address: OWSContactAddress) -> UIView { - let previewView = UIView.container() - var lastRow: UIView? - let addRow: ((UIView) -> Void) = { (row) in - previewView.addSubview(row) - row.autoPinLeadingToSuperviewMargin() - row.autoPinTrailingToSuperviewMargin() - if let lastRow = lastRow { - row.autoPinEdge(.top, to: .bottom, of: lastRow, withOffset: 0) - } else { - row.autoPinEdge(toSuperviewEdge: .top, withInset: 0) - } - lastRow = row - } + + 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 @@ -515,7 +507,7 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh valueLabel.autoPinTrailingToSuperviewMargin() valueLabel.autoPinHeightToSuperview() - addRow(row) + stackView.addArrangedSubview(row) } tryToAddNameValue(NSLocalizedString("CONTACT_FIELD_ADDRESS_STREET", comment: "Label for the 'street' field of a contact's address."), @@ -533,9 +525,7 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh tryToAddNameValue(NSLocalizedString("CONTACT_FIELD_ADDRESS_COUNTRY", comment: "Label for the 'country' field of a contact's address."), address.country) - lastRow?.autoPinEdge(toSuperviewEdge: .bottom, withInset: 0) - - return previewView + return stackView } // MARK: - From 2c6f18fa6ebc5d31bde889dfcc50ee204d36f542 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Fri, 4 May 2018 13:57:29 -0400 Subject: [PATCH 6/8] Clean up ahead of PR. --- Signal.xcodeproj/project.pbxproj | 4 ++ .../ContactViewController.swift | 48 ++------------ .../ApproveContactShareViewController.swift | 44 ++----------- .../attachments/ContactFieldView.swift | 63 +++++++++++++++++++ .../EditContactShareNameViewController.swift | 42 +------------ 5 files changed, 80 insertions(+), 121 deletions(-) create mode 100644 SignalMessaging/attachments/ContactFieldView.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 6aad516aa2..624e464424 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -163,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 */; }; @@ -759,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 = ""; }; @@ -1602,6 +1604,7 @@ children = ( 34382265209A4E3F0094FEB7 /* ApproveContactShareViewController.swift */, 344F2498200FD03200CFB4F4 /* AttachmentApprovalViewController.swift */, + 348BB253209CD4B80047AEC2 /* ContactFieldView.swift */, 344F248220069E9B00CFB4F4 /* CountryCodeViewController.h */, 344F248320069E9B00CFB4F4 /* CountryCodeViewController.m */, 34382269209B63500094FEB7 /* EditContactShareNameViewController.swift */, @@ -3057,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 */, diff --git a/Signal/src/ViewControllers/ContactViewController.swift b/Signal/src/ViewControllers/ContactViewController.swift index e0c1e6ee54..8c03decff0 100644 --- a/Signal/src/ViewControllers/ContactViewController.swift +++ b/Signal/src/ViewControllers/ContactViewController.swift @@ -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) diff --git a/SignalMessaging/attachments/ApproveContactShareViewController.swift b/SignalMessaging/attachments/ApproveContactShareViewController.swift index 571259750a..b99c396d88 100644 --- a/SignalMessaging/attachments/ApproveContactShareViewController.swift +++ b/SignalMessaging/attachments/ApproveContactShareViewController.swift @@ -349,51 +349,15 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh private func createFieldsView() -> UIView { SwiftAssertIsOnMainThread(#function) - let fieldsView = UIView.container() - fieldsView.layoutMargins = .zero - fieldsView.preservesSuperviewLayoutMargins = false + var rows = [UIView]() - 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, withOffset: 0) - lastRow = row - } - - let addRow: ((UIView) -> Void) = { (row) in - if lastRow != nil { - addSpacerRow() - } - fieldsView.addSubview(row) - row.autoPinLeadingToSuperviewMargin(withInset: self.hMargin) - row.autoPinTrailingToSuperviewMargin(withInset: self.hMargin) - if let lastRow = lastRow { - row.autoPinEdge(.top, to: .bottom, of: lastRow, withOffset: 0) - } else { - row.autoPinEdge(toSuperviewEdge: .top, withInset: 0) - } - lastRow = row - } - - addRow(createNameRow()) + rows.append(createNameRow()) for fieldView in fieldViews { - addRow(fieldView) + rows.append(fieldView) } - lastRow?.autoPinEdge(toSuperviewEdge: .bottom, withInset: 0) - - return fieldsView + return ContactFieldView(rows: rows, hMargin: hMargin) } private let hMargin = CGFloat(16) 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 index 496efc4ff2..14111331b1 100644 --- a/SignalMessaging/attachments/EditContactShareNameViewController.swift +++ b/SignalMessaging/attachments/EditContactShareNameViewController.swift @@ -265,49 +265,13 @@ public class EditContactShareNameViewController: OWSViewController, ContactNameF 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, withOffset: 0) - lastRow = row - } - - let addRow: ((UIView) -> Void) = { (row) in - if lastRow != nil { - addSpacerRow() - } - fieldsView.addSubview(row) - row.autoPinLeadingToSuperviewMargin(withInset: self.hMargin) - row.autoPinTrailingToSuperviewMargin(withInset: self.hMargin) - if let lastRow = lastRow { - row.autoPinEdge(.top, to: .bottom, of: lastRow, withOffset: 0) - } else { - row.autoPinEdge(toSuperviewEdge: .top, withInset: 0) - } - lastRow = row - } + var rows = [UIView]() for fieldView in fieldViews { - addRow(fieldView) + rows.append(fieldView) } - lastRow?.autoPinEdge(toSuperviewEdge: .bottom, withInset: 0) - - return fieldsView + return ContactFieldView(rows: rows, hMargin: hMargin) } private let hMargin = CGFloat(16) From ba74e3857a096468f81ac3922d5e4c836c0b3bda Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Fri, 4 May 2018 14:04:23 -0400 Subject: [PATCH 7/8] Clean up ahead of PR. --- .../src/ViewControllers/MessageDetailViewController.swift | 3 --- .../attachments/EditContactShareNameViewController.swift | 3 --- SignalMessaging/contacts/OWSContactsManager.m | 8 ++++---- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/Signal/src/ViewControllers/MessageDetailViewController.swift b/Signal/src/ViewControllers/MessageDetailViewController.swift index 81de50e244..f9c42ba7c4 100644 --- a/Signal/src/ViewControllers/MessageDetailViewController.swift +++ b/Signal/src/ViewControllers/MessageDetailViewController.swift @@ -120,9 +120,6 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele scrollView.autoPinWidthToSuperview(withMargin: 0) scrollView.autoPin(toTopLayoutGuideOf: self, withInset: 0) - // See notes on how to use UIScrollView with iOS Auto Layout: - // - // https://developer.apple.com/library/content/releasenotes/General/RN-iOSSDK-6_0/ let contentView = UIView.container() self.contentView = contentView scrollView.addSubview(contentView) diff --git a/SignalMessaging/attachments/EditContactShareNameViewController.swift b/SignalMessaging/attachments/EditContactShareNameViewController.swift index 14111331b1..da6ff149e1 100644 --- a/SignalMessaging/attachments/EditContactShareNameViewController.swift +++ b/SignalMessaging/attachments/EditContactShareNameViewController.swift @@ -252,9 +252,6 @@ public class EditContactShareNameViewController: OWSViewController, ContactNameF let fieldsView = createFieldsView() - // See notes on how to use UIScrollView with iOS Auto Layout: - // - // https://developer.apple.com/library/content/releasenotes/General/RN-iOSSDK-6_0/ scrollView.addSubview(fieldsView) fieldsView.autoPinLeadingToSuperviewMargin() fieldsView.autoPinTrailingToSuperviewMargin() diff --git a/SignalMessaging/contacts/OWSContactsManager.m b/SignalMessaging/contacts/OWSContactsManager.m index 1e5fae453e..7cda1c4faa 100644 --- a/SignalMessaging/contacts/OWSContactsManager.m +++ b/SignalMessaging/contacts/OWSContactsManager.m @@ -343,10 +343,10 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification { OWSAssertIsOnMainThread(); - // if ([signalAccounts isEqual:self.signalAccounts]) { - // DDLogDebug(@"%@ SignalAccounts unchanged.", self.logTag); - // return; - // } + if ([signalAccounts isEqual:self.signalAccounts]) { + DDLogDebug(@"%@ SignalAccounts unchanged.", self.logTag); + return; + } NSMutableDictionary *signalAccountMap = [NSMutableDictionary new]; for (SignalAccount *signalAccount in signalAccounts) { From 7c3991ebd8bf8ffd886292e5d75981a9e1320b81 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 7 May 2018 09:47:22 -0400 Subject: [PATCH 8/8] Respond to CR. --- .../ApproveContactShareViewController.swift | 79 +++++++------------ .../EditContactShareNameViewController.swift | 17 ++-- .../src/Messages/Interactions/OWSContact.h | 36 ++++----- 3 files changed, 54 insertions(+), 78 deletions(-) diff --git a/SignalMessaging/attachments/ApproveContactShareViewController.swift b/SignalMessaging/attachments/ApproveContactShareViewController.swift index b99c396d88..5bddec249c 100644 --- a/SignalMessaging/attachments/ApproveContactShareViewController.swift +++ b/SignalMessaging/attachments/ApproveContactShareViewController.swift @@ -11,14 +11,33 @@ public protocol ApproveContactShareViewControllerDelegate: class { func approveContactShare(_ approveContactShare: ApproveContactShareViewController, didCancelContactShare contactShare: OWSContact) } +protocol ContactShareField: class { + + func localizedLabel() -> String + + func isIncluded() -> Bool + + func setIsIncluded(_ isIncluded: Bool) + + func applyToContact(contact: OWSContact) +} + // MARK: - -class ContactShareField: NSObject { +class ContactShareFieldBase: NSObject, ContactShareField { + + let value: ContactFieldType private var isIncludedFlag = true + required init(_ value: ContactFieldType) { + self.value = value + + super.init() + } + func localizedLabel() -> String { - preconditionFailure("This method must be overridden") + return value.localizedLabel() } func isIncluded() -> Bool { @@ -36,19 +55,7 @@ class ContactShareField: NSObject { // MARK: - -class ContactSharePhoneNumber: ContactShareField { - - let value: OWSContactPhoneNumber - - required init(_ value: OWSContactPhoneNumber) { - self.value = value - - super.init() - } - - override func localizedLabel() -> String { - return value.localizedLabel() - } +class ContactSharePhoneNumber: ContactShareFieldBase { override func applyToContact(contact: OWSContact) { assert(isIncluded()) @@ -62,19 +69,7 @@ class ContactSharePhoneNumber: ContactShareField { // MARK: - -class ContactShareEmail: ContactShareField { - - let value: OWSContactEmail - - required init(_ value: OWSContactEmail) { - self.value = value - - super.init() - } - - override func localizedLabel() -> String { - return value.localizedLabel() - } +class ContactShareEmail: ContactShareFieldBase { override func applyToContact(contact: OWSContact) { assert(isIncluded()) @@ -88,19 +83,7 @@ class ContactShareEmail: ContactShareField { // MARK: - -class ContactShareAddress: ContactShareField { - - let value: OWSContactAddress - - required init(_ value: OWSContactAddress) { - self.value = value - - super.init() - } - - override func localizedLabel() -> String { - return value.localizedLabel() - } +class ContactShareAddress: ContactShareFieldBase { override func applyToContact(contact: OWSContact) { assert(isIncluded()) @@ -259,10 +242,6 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh self.fieldViews = fieldViews } - override public var canBecomeFirstResponder: Bool { - return true - } - // MARK: - View Lifecycle override public func viewWillAppear(_ animated: Bool) { @@ -289,7 +268,6 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh self.navigationItem.title = NSLocalizedString("CONTACT_SHARE_APPROVAL_VIEW_TITLE", comment: "Title for the 'Approve contact share' view.") - self.view.preservesSuperviewLayoutMargins = false self.view.backgroundColor = UIColor.white updateContent() @@ -340,10 +318,13 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh 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 { @@ -374,10 +355,7 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh stackView.layoutMargins = .zero stackView.spacing = 10 row.addSubview(stackView) - stackView.autoPinLeadingToSuperviewMargin() - stackView.autoPinTrailingToSuperviewMargin() - stackView.autoPinTopToSuperviewMargin() - stackView.autoPinBottomToSuperviewMargin() + stackView.autoPinEdgesToSuperviewMargins() let nameLabel = UILabel() self.nameLabel = nameLabel @@ -386,7 +364,6 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh nameLabel.textColor = UIColor.ows_materialBlue nameLabel.lineBreakMode = .byTruncatingTail stackView.addArrangedSubview(nameLabel) - nameLabel.setContentHuggingHigh() let editNameLabel = UILabel() editNameLabel.text = NSLocalizedString("CONTACT_EDIT_NAME_BUTTON", comment: "Label for the 'edit name' button in the contact share approval view.") diff --git a/SignalMessaging/attachments/EditContactShareNameViewController.swift b/SignalMessaging/attachments/EditContactShareNameViewController.swift index da6ff149e1..b943b60c99 100644 --- a/SignalMessaging/attachments/EditContactShareNameViewController.swift +++ b/SignalMessaging/attachments/EditContactShareNameViewController.swift @@ -52,10 +52,7 @@ class ContactNameFieldView: UIView { stackView.layoutMargins = .zero stackView.spacing = 10 self.addSubview(stackView) - stackView.autoPinTopToSuperviewMargin() - stackView.autoPinBottomToSuperviewMargin() - stackView.autoPinLeadingToSuperviewMargin() - stackView.autoPinTrailingToSuperviewMargin() + stackView.autoPinEdgesToSuperviewMargins() let nameLabel = UILabel() nameLabel.text = name @@ -291,13 +288,21 @@ public class EditContactShareNameViewController: OWSViewController, ContactNameF delegate.editContactShareNameView(self, didEditContactShare: modifiedContactShare) - self.navigationController?.popViewController(animated: true) + guard let navigationController = self.navigationController else { + owsFail("\(logTag) Missing navigationController.") + return + } + navigationController.popViewController(animated: true) } func didPressCancel() { Logger.info("\(logTag) \(#function)") - self.navigationController?.popViewController(animated: true) + guard let navigationController = self.navigationController else { + owsFail("\(logTag) Missing navigationController.") + return + } + navigationController.popViewController(animated: true) } // MARK: - ContactNameFieldViewDelegate diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContact.h b/SignalServiceKit/src/Messages/Interactions/OWSContact.h index ecd8fc5d04..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 -