From 2738bcbc583ea049de71260f0354013695ba24ec Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 1 May 2018 13:39:48 -0400 Subject: [PATCH 1/5] Create contact view. --- Signal.xcodeproj/project.pbxproj | 6 +- .../ContactViewController.swift | 303 ++++++++++++++++++ .../Cells/OWSMessageBubbleView.h | 2 + .../Cells/OWSMessageBubbleView.m | 2 +- .../ConversationViewController.m | 11 + .../MessageDetailViewController.swift | 9 + 6 files changed, 331 insertions(+), 2 deletions(-) create mode 100644 Signal/src/ViewControllers/ContactViewController.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index c466642a5..34a9dfbc4 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -230,6 +230,7 @@ 34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E3E5671EC4B19400495BAC /* AudioProgressView.swift */; }; 34E3EF0D1EFC235B007F6822 /* DebugUIDiskUsage.m in Sources */ = {isa = PBXBuildFile; fileRef = 34E3EF0C1EFC235B007F6822 /* DebugUIDiskUsage.m */; }; 34E3EF101EFC2684007F6822 /* DebugUIPage.m in Sources */ = {isa = PBXBuildFile; fileRef = 34E3EF0F1EFC2684007F6822 /* DebugUIPage.m */; }; + 34E88D262098C5AE00A608F4 /* ContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E88D252098C5AE00A608F4 /* ContactViewController.swift */; }; 34E8A8D12085238A00B272B1 /* ProtoParsingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 34E8A8D02085238900B272B1 /* ProtoParsingTest.m */; }; 34F308A21ECB469700BB7697 /* OWSBezierPathView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F308A11ECB469700BB7697 /* OWSBezierPathView.m */; }; 34FD93701E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 34FD936F1E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m */; }; @@ -881,6 +882,7 @@ 34E3EF0C1EFC235B007F6822 /* DebugUIDiskUsage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIDiskUsage.m; sourceTree = ""; }; 34E3EF0E1EFC2684007F6822 /* DebugUIPage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIPage.h; sourceTree = ""; }; 34E3EF0F1EFC2684007F6822 /* DebugUIPage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIPage.m; sourceTree = ""; }; + 34E88D252098C5AE00A608F4 /* ContactViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactViewController.swift; sourceTree = ""; }; 34E8A8D02085238900B272B1 /* ProtoParsingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ProtoParsingTest.m; sourceTree = ""; }; 34F308A01ECB469700BB7697 /* OWSBezierPathView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBezierPathView.h; sourceTree = ""; }; 34F308A11ECB469700BB7697 /* OWSBezierPathView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBezierPathView.m; sourceTree = ""; }; @@ -1624,6 +1626,7 @@ 34B3F83B1E8DF1700035BE1A /* CallViewController.swift */, 34B3F83E1E8DF1700035BE1A /* ContactsPicker.swift */, 34B3F83F1E8DF1700035BE1A /* ContactsPicker.xib */, + 34E88D252098C5AE00A608F4 /* ContactViewController.swift */, 3448BFC01EDF0EA7005B2D69 /* ConversationView */, 346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */, 34D8C0221ED3673300188D7C /* DebugUI */, @@ -1631,6 +1634,7 @@ 34BECE2C1F7ABCE000D7438D /* GifPicker */, 34386A4C207D0C01009F5D9C /* HomeView */, 34B3F84C1E8DF1700035BE1A /* InviteFlow.swift */, + 3496744E2076ACCE00080B5F /* LongTextViewController.swift */, 45B9EE9A200E91FB005D2F2D /* MediaDetailViewController.h */, 45B9EE9B200E91FB005D2F2D /* MediaDetailViewController.m */, 452EC6DE205E9E30000E787C /* MediaGalleryViewController.swift */, @@ -1641,7 +1645,6 @@ 34B3F8501E8DF1700035BE1A /* NewContactThreadViewController.m */, 34B3F8541E8DF1700035BE1A /* NewGroupViewController.h */, 34B3F8551E8DF1700035BE1A /* NewGroupViewController.m */, - 3496744E2076ACCE00080B5F /* LongTextViewController.swift */, 34A55F3620485464002CC6DE /* OWS2FARegistrationViewController.h */, 34A55F3520485464002CC6DE /* OWS2FARegistrationViewController.m */, 45D2AC01204885170033C692 /* OWS2FAReminderViewController.swift */, @@ -3255,6 +3258,7 @@ 34D1F0841F8678AA0066283D /* ConversationInputToolbar.m in Sources */, 457F671B20746193000EABCD /* QuotedReplyPreview.swift in Sources */, 34DBF004206BD5A500025978 /* OWSBubbleView.m in Sources */, + 34E88D262098C5AE00A608F4 /* ContactViewController.swift in Sources */, FCC81A981A44558300DFEC7D /* UIDevice+TSHardwareVersion.m in Sources */, 76EB054018170B33006006FC /* AppDelegate.m in Sources */, 34D1F0831F8678AA0066283D /* ConversationInputTextView.m in Sources */, diff --git a/Signal/src/ViewControllers/ContactViewController.swift b/Signal/src/ViewControllers/ContactViewController.swift new file mode 100644 index 000000000..605220570 --- /dev/null +++ b/Signal/src/ViewControllers/ContactViewController.swift @@ -0,0 +1,303 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation +import SignalServiceKit +import SignalMessaging +import Reachability + +class ContactViewController: OWSViewController { + + let TAG = "[ContactView]" + + enum ContactViewMode { + case systemContactWithSignal, + systemContactWithoutSignal, + nonSystemContactWithSignal, + nonSystemContactWithoutSignal, + noPhoneNumber, + unknown + } + + enum ContactLookupMode { + case notLookingUp, + lookingUp, + lookedUpNoAccount, + lookedUpHasAccount + } + + private var hasLoadedView = false + + private var viewMode = ContactViewMode.unknown { + didSet { + SwiftAssertIsOnMainThread(#function) + + if oldValue != viewMode && hasLoadedView { + updateContent() + } + } + } + + private var lookupMode = ContactLookupMode.notLookingUp { + didSet { + SwiftAssertIsOnMainThread(#function) + + if oldValue != lookupMode && hasLoadedView { + updateContent() + } + } + } + + let contactsManager: OWSContactsManager + + 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 func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + self.becomeFirstResponder() + + contactsManager.requestSystemContactsOnce(completion: { [weak self] _ in + guard let strongSelf = self else { return } + strongSelf.tryToDetermineMode() + }) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + self.becomeFirstResponder() + } + + override func loadView() { + super.loadView() + self.view.backgroundColor = UIColor.white + + updateContent() + + hasLoadedView = true + } + + private func tryToDetermineMode() { + SwiftAssertIsOnMainThread(#function) + + guard let firstPhoneNumber = contact.phoneNumbers?.first else { + viewMode = .noPhoneNumber + return + } + if contactsManager.hasSignalAccount(forRecipientId: firstPhoneNumber.phoneNumber) { + viewMode = .systemContactWithSignal + return + } + if contactsManager.allContactsMap[firstPhoneNumber.phoneNumber] != nil { + // We can infer that this is _not_ a signal user because + // all contacts in contactsManager.allContactsMap have + // already been looked up. + viewMode = .systemContactWithoutSignal + return + } + + switch lookupMode { + case .notLookingUp: + lookupMode = .lookingUp + viewMode = .unknown + ContactsUpdater.shared().lookupIdentifiers([firstPhoneNumber.phoneNumber], success: { [weak self] (signalRecipients) in + guard let strongSelf = self else { return } + + let hasSignalAccount = signalRecipients.filter({ (signalRecipient) -> Bool in + return signalRecipient.recipientId() == firstPhoneNumber.phoneNumber + }).count > 0 + + if hasSignalAccount { + strongSelf.lookupMode = .lookedUpHasAccount + strongSelf.tryToDetermineMode() + } else { + strongSelf.lookupMode = .lookedUpNoAccount + strongSelf.tryToDetermineMode() + } + }) { [weak self] (error) in + guard let strongSelf = self else { return } + Logger.error("\(strongSelf.logTag) error looking up contact: \(error)") + strongSelf.lookupMode = .notLookingUp + strongSelf.tryToDetermineModeRetry() + } + return + case .lookingUp: + viewMode = .unknown + return + case .lookedUpNoAccount: + viewMode = .nonSystemContactWithoutSignal + return + case .lookedUpHasAccount: + viewMode = .nonSystemContactWithSignal + return + } + } + + 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) + + for subview in self.view.subviews { + subview.removeFromSuperview() + } + + // TODO: The design calls for no navigation bar, just a back button. + let topView = UIView.container() + topView.backgroundColor = UIColor(rgbHex: 0xefeff4) + topView.preservesSuperviewLayoutMargins = true + self.view.addSubview(topView) + topView.autoPinEdge(toSuperviewEdge: .top) + topView.autoPinWidthToSuperview() + + // TODO: Use actual avatar. + let avatarSize = CGFloat(100) + let avatarView = UIView.container() + avatarView.backgroundColor = UIColor.ows_materialBlue + avatarView.layer.cornerRadius = avatarSize * 0.5 + topView.addSubview(avatarView) + avatarView.autoPin(toTopLayoutGuideOf: self, withInset: 0) + avatarView.autoHCenterInSuperview() + avatarView.autoSetDimension(.width, toSize: avatarSize) + avatarView.autoSetDimension(.height, toSize: avatarSize) + + let nameLabel = UILabel() + nameLabel.text = contact.displayName + nameLabel.font = UIFont.ows_dynamicTypeTitle3 + nameLabel.textColor = UIColor.black + nameLabel.lineBreakMode = .byTruncatingTail + nameLabel.textAlignment = .center + topView.addSubview(nameLabel) + nameLabel.autoPinEdge(.top, to: .bottom, of: avatarView, withOffset: 10) + nameLabel.autoPinLeadingToSuperviewMargin() + nameLabel.autoPinTrailingToSuperviewMargin() + + var lastView: UIView = nameLabel + + if let firstPhoneNumber = contact.phoneNumbers?.first { + let phoneNumberLabel = UILabel() + phoneNumberLabel.text = firstPhoneNumber.phoneNumber + phoneNumberLabel.font = UIFont.ows_dynamicTypeCaption1 + phoneNumberLabel.textColor = UIColor.black + phoneNumberLabel.lineBreakMode = .byTruncatingTail + phoneNumberLabel.textAlignment = .center + topView.addSubview(phoneNumberLabel) + phoneNumberLabel.autoPinEdge(.top, to: .bottom, of: lastView, withOffset: 10) + phoneNumberLabel.autoPinLeadingToSuperviewMargin() + phoneNumberLabel.autoPinTrailingToSuperviewMargin() + lastView = phoneNumberLabel + } + + switch viewMode { + case .systemContactWithSignal: + break + case .systemContactWithoutSignal: + break + case .nonSystemContactWithSignal: + break + case .nonSystemContactWithoutSignal: + break + case .noPhoneNumber: + 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: 10) + } + +// acceptIncomingButton = createButton(image: #imageLiteral(resourceName: "call-active-wide"), +// action: #selector(didPressAnswerCall)) +// acceptIncomingButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_ACCEPT_INCOMING_CALL_LABEL", +// comment: "Accessibility label for accepting incoming calls") + +// func createButton(image: UIImage, action: Selector) -> UIButton { +// let button = UIButton() +// button.setImage(image, for: .normal) +// button.imageEdgeInsets = UIEdgeInsets(top: buttonInset(), +// left: buttonInset(), +// bottom: buttonInset(), +// right: buttonInset()) +// button.addTarget(self, action: action, for: .touchUpInside) +// button.autoSetDimension(.width, toSize: buttonSize()) +// button.autoSetDimension(.height, toSize: buttonSize()) +// return button +// } +// +// // MARK: - Layout +// + +// +// func didPressFlipCamera(sender: UIButton) { +// // toggle value +// sender.isSelected = !sender.isSelected +// +// let useBackCamera = sender.isSelected +// Logger.info("\(TAG) in \(#function) with useBackCamera: \(useBackCamera)") +// +// callUIAdapter.setCameraSource(call: call, useBackCamera: useBackCamera) +// } +// +// internal func dismissImmediately(completion: (() -> Void)?) { +// if ContactView.kShowCallViewOnSeparateWindow { +// OWSWindowManager.shared().endCall(self) +// completion?() +// } else { +// self.dismiss(animated: true, completion: completion) +// } +// } +// +} diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.h b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.h index 19323d81f..fedb9a51e 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.h +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.h @@ -40,6 +40,8 @@ typedef NS_ENUM(NSUInteger, OWSMessageGestureLocation) { quotedReply:(OWSQuotedReplyModel *)quotedReply failedThumbnailDownloadAttachmentPointer:(TSAttachmentPointer *)attachmentPointer; +- (void)didTapContactShareViewItem:(ConversationViewItem *)viewItem; + @end @interface OWSMessageBubbleView : UIView diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m index 50cc326ce..413133b60 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m @@ -1203,7 +1203,7 @@ NS_ASSUME_NONNULL_BEGIN break; } case OWSMessageCellType_ContactShare: - // TODO: + [self.delegate didTapContactShareViewItem:self.viewItem]; break; } } diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 0d1c0801a..828c00de2 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -2073,6 +2073,17 @@ typedef enum : NSUInteger { [self.navigationController pushViewController:view animated:YES]; } +- (void)didTapContactShareViewItem:(ConversationViewItem *)conversationItem +{ + OWSAssertIsOnMainThread(); + OWSAssert(conversationItem); + OWSAssert(conversationItem.contactShare); + OWSAssert([conversationItem.interaction isKindOfClass:[TSMessage class]]); + + ContactViewController *view = [[ContactViewController alloc] initWithContact:conversationItem.contactShare]; + [self.navigationController pushViewController:view animated:YES]; +} + - (void)didTapFailedIncomingAttachment:(ConversationViewItem *)viewItem attachmentPointer:(TSAttachmentPointer *)attachmentPointer { diff --git a/Signal/src/ViewControllers/MessageDetailViewController.swift b/Signal/src/ViewControllers/MessageDetailViewController.swift index 3c77d3433..81de50e24 100644 --- a/Signal/src/ViewControllers/MessageDetailViewController.swift +++ b/Signal/src/ViewControllers/MessageDetailViewController.swift @@ -615,6 +615,15 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele mediaGalleryViewController.presentDetailView(fromViewController: self, mediaMessage: self.message, replacingView: imageView) } + func didTapContactShare(_ viewItem: ConversationViewItem) { + guard let contact = viewItem.contactShare() else { + owsFail("\(logTag) missing contact.") + return + } + let contactViewController = ContactViewController(contact: contact) + self.navigationController?.pushViewController(contactViewController, animated: true) + } + var audioAttachmentPlayer: OWSAudioPlayer? func didTapAudioViewItem(_ viewItem: ConversationViewItem, attachmentStream: TSAttachmentStream) { From fa5577eece388f32196501aae3129a21883731f2 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 1 May 2018 15:53:51 -0400 Subject: [PATCH 2/5] Create contact view. --- Signal/src/Signal-Bridging-Header.h | 1 + .../ContactViewController.swift | 406 +++++++++++++++--- .../ViewControllers/DebugUI/DebugUIMessages.m | 8 +- .../HomeView/HomeViewController.m | 4 + SignalMessaging/Views/ContactsViewHelper.h | 11 +- SignalMessaging/Views/ContactsViewHelper.m | 5 + .../src/Messages/Interactions/OWSContact.h | 6 + .../src/Messages/Interactions/OWSContact.m | 40 ++ 8 files changed, 407 insertions(+), 74 deletions(-) diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h index b1cc323eb..e8a428a91 100644 --- a/Signal/src/Signal-Bridging-Header.h +++ b/Signal/src/Signal-Bridging-Header.h @@ -16,6 +16,7 @@ #import "MediaDetailViewController.h" #import "NotificationSettingsViewController.h" #import "NotificationsManager.h" +#import "OWSAddToContactViewController.h" #import "OWSAnyTouchGestureRecognizer.h" #import "OWSAudioPlayer.h" #import "OWSBackup.h" diff --git a/Signal/src/ViewControllers/ContactViewController.swift b/Signal/src/ViewControllers/ContactViewController.swift index 605220570..3ba1e5738 100644 --- a/Signal/src/ViewControllers/ContactViewController.swift +++ b/Signal/src/ViewControllers/ContactViewController.swift @@ -6,27 +6,52 @@ import Foundation import SignalServiceKit import SignalMessaging import Reachability +import ContactsUI -class ContactViewController: OWSViewController { +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 +//, ContactsViewHelperDelegate +{ let TAG = "[ContactView]" enum ContactViewMode { case systemContactWithSignal, systemContactWithoutSignal, - nonSystemContactWithSignal, - nonSystemContactWithoutSignal, + nonSystemContact, noPhoneNumber, unknown } - enum ContactLookupMode { - case notLookingUp, - lookingUp, - lookedUpNoAccount, - lookedUpHasAccount - } - private var hasLoadedView = false private var viewMode = ContactViewMode.unknown { @@ -39,16 +64,6 @@ class ContactViewController: OWSViewController { } } - private var lookupMode = ContactLookupMode.notLookingUp { - didSet { - SwiftAssertIsOnMainThread(#function) - - if oldValue != lookupMode && hasLoadedView { - updateContent() - } - } - } - let contactsManager: OWSContactsManager var reachability: Reachability? @@ -59,6 +74,8 @@ class ContactViewController: OWSViewController { private let contact: OWSContact +// private var contactsViewHelper : ContactsViewHelper! + // MARK: - Initializers @available(*, unavailable, message: "use init(call:) constructor instead.") @@ -69,9 +86,12 @@ class ContactViewController: OWSViewController { required init(contact: OWSContact) { contactsManager = Environment.current().contactsManager self.contact = contact + self.scrollView = UIScrollView() super.init(nibName: nil, bundle: nil) +// contactsViewHelper = ContactsViewHelper(delegate:self) + tryToDetermineMode() NotificationCenter.default.addObserver(forName: .OWSContactsManagerSignalAccountsDidChange, object: nil, queue: nil) { [weak self] _ in @@ -96,6 +116,8 @@ class ContactViewController: OWSViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + UIUtil.applySignalAppearence() + self.becomeFirstResponder() contactsManager.requestSystemContactsOnce(completion: { [weak self] _ in @@ -107,11 +129,28 @@ class ContactViewController: OWSViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) + UIUtil.applySignalAppearence() + self.becomeFirstResponder() } + private var scrollView: UIScrollView + override func loadView() { +// scrollView.dir +// self.view = scrollView +// let rootView = UIView() super.loadView() +// self.view.layoutMargins = .zero + +// self.scrollView = scrollView +// scrollView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + self.view.addSubview(scrollView) + scrollView.layoutMargins = .zero + scrollView.autoPinWidthToSuperview() + scrollView.autoPin(toTopLayoutGuideOf: self, withInset: 0) + scrollView.autoPin(toBottomLayoutGuideOf: self, withInset: 0) + self.view.backgroundColor = UIColor.white updateContent() @@ -138,41 +177,7 @@ class ContactViewController: OWSViewController { return } - switch lookupMode { - case .notLookingUp: - lookupMode = .lookingUp - viewMode = .unknown - ContactsUpdater.shared().lookupIdentifiers([firstPhoneNumber.phoneNumber], success: { [weak self] (signalRecipients) in - guard let strongSelf = self else { return } - - let hasSignalAccount = signalRecipients.filter({ (signalRecipient) -> Bool in - return signalRecipient.recipientId() == firstPhoneNumber.phoneNumber - }).count > 0 - - if hasSignalAccount { - strongSelf.lookupMode = .lookedUpHasAccount - strongSelf.tryToDetermineMode() - } else { - strongSelf.lookupMode = .lookedUpNoAccount - strongSelf.tryToDetermineMode() - } - }) { [weak self] (error) in - guard let strongSelf = self else { return } - Logger.error("\(strongSelf.logTag) error looking up contact: \(error)") - strongSelf.lookupMode = .notLookingUp - strongSelf.tryToDetermineModeRetry() - } - return - case .lookingUp: - viewMode = .unknown - return - case .lookedUpNoAccount: - viewMode = .nonSystemContactWithoutSignal - return - case .lookedUpHasAccount: - viewMode = .nonSystemContactWithSignal - return - } + viewMode = .nonSystemContact } private func tryToDetermineModeRetry() { @@ -186,7 +191,9 @@ class ContactViewController: OWSViewController { private func updateContent() { SwiftAssertIsOnMainThread(#function) - for subview in self.view.subviews { + let rootView = self.scrollView + + for subview in rootView.subviews { subview.removeFromSuperview() } @@ -194,9 +201,10 @@ class ContactViewController: OWSViewController { let topView = UIView.container() topView.backgroundColor = UIColor(rgbHex: 0xefeff4) topView.preservesSuperviewLayoutMargins = true - self.view.addSubview(topView) + rootView.addSubview(topView) topView.autoPinEdge(toSuperviewEdge: .top) - topView.autoPinWidthToSuperview() + topView.autoPinEdge(.left, to: .left, of: self.view, withOffset: 0) + topView.autoPinEdge(.right, to: .right, of: self.view, withOffset: 0) // TODO: Use actual avatar. let avatarSize = CGFloat(100) @@ -204,14 +212,14 @@ class ContactViewController: OWSViewController { avatarView.backgroundColor = UIColor.ows_materialBlue avatarView.layer.cornerRadius = avatarSize * 0.5 topView.addSubview(avatarView) - avatarView.autoPin(toTopLayoutGuideOf: self, withInset: 0) + avatarView.autoPin(toTopLayoutGuideOf: self, withInset: 10) avatarView.autoHCenterInSuperview() avatarView.autoSetDimension(.width, toSize: avatarSize) avatarView.autoSetDimension(.height, toSize: avatarSize) let nameLabel = UILabel() nameLabel.text = contact.displayName - nameLabel.font = UIFont.ows_dynamicTypeTitle3 + nameLabel.font = UIFont.ows_dynamicTypeTitle1 nameLabel.textColor = UIColor.black nameLabel.lineBreakMode = .byTruncatingTail nameLabel.textAlignment = .center @@ -225,12 +233,12 @@ class ContactViewController: OWSViewController { if let firstPhoneNumber = contact.phoneNumbers?.first { let phoneNumberLabel = UILabel() phoneNumberLabel.text = firstPhoneNumber.phoneNumber - phoneNumberLabel.font = UIFont.ows_dynamicTypeCaption1 + 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: 10) + phoneNumberLabel.autoPinEdge(.top, to: .bottom, of: lastView, withOffset: 5) phoneNumberLabel.autoPinLeadingToSuperviewMargin() phoneNumberLabel.autoPinTrailingToSuperviewMargin() lastView = phoneNumberLabel @@ -238,14 +246,16 @@ class ContactViewController: OWSViewController { switch viewMode { case .systemContactWithSignal: + // Show actions buttons for system contacts with a Signal account. break case .systemContactWithoutSignal: + // Show invite button for system contacts without a Signal account. break - case .nonSystemContactWithSignal: - break - case .nonSystemContactWithoutSignal: + 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) @@ -256,7 +266,149 @@ class ContactViewController: OWSViewController { break } - lastView.autoPinEdge(toSuperviewEdge: .bottom, withInset: 10) + lastView.autoPinEdge(toSuperviewEdge: .bottom, withInset: 15) + + let bottomView = UIView.container() + bottomView.backgroundColor = UIColor.white + bottomView.layoutMargins = .zero + bottomView.preservesSuperviewLayoutMargins = false + rootView.addSubview(bottomView) + bottomView.autoPinEdge(.top, to: .bottom, of: topView, withOffset: 0) + bottomView.autoPinEdge(toSuperviewEdge: .bottom) + bottomView.autoPinEdge(.left, to: .left, of: self.view, withOffset: 0) + bottomView.autoPinEdge(.right, to: .right, of: self.view, withOffset: 0) + bottomView.setContentHuggingVerticalLow() + + 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) + bottomView.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() + } + bottomView.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 + } + + 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.labelString(), + value: 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.labelString(), + 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) + } + + private let hMargin = CGFloat(10) + + private func createActionRow(labelText: String, action: Selector) -> UIView { + let row = UIView() + 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) + + 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 } // acceptIncomingButton = createButton(image: #imageLiteral(resourceName: "call-active-wide"), @@ -299,5 +451,127 @@ class ContactViewController: OWSViewController { // self.dismiss(animated: true, completion: completion) // } // } + + 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: + } + + // MARK: - ContactsViewHelperDelegate + +// @objc +// public func contactsViewHelperDidUpdateContacts() { +// Logger.info("\(self.TAG) \(#function)") // +// // Do nothing +// } + + // MARK: - + + private func presentNewContactView() { + guard contactsManager.supportsContactEditing else { + owsFail("\(self.logTag) Contact editing not supported") + return + } + // contactsViewHelper.presentContactViewController + // let contactsViewHelper = ContactsViewHelper(delegate:self) + // + // TSContactThread *contactThread = (TSContactThread *)self.thread; + // [self.contactsViewHelper presentContactViewControllerForRecipientId:contactThread.contactIdentifier + // fromViewController:self + // editImmediately:YES]; + + guard let systemContact = OWSContacts.systemContact(for: contact) else { + owsFail("\(self.logTag) Could not derive system contact.") + return + } + + guard contactsManager.isSystemContactsAuthorized else { + ContactsViewHelper.presentMissingContactAccessAlertController(from: self) + return + } + + let contactViewController = CNContactViewController(forNewContact: systemContact) + contactViewController.delegate = self + contactViewController.allowsActions = false + contactViewController.allowsEditing = true + contactViewController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: CommonStrings.cancelButton, style: .plain, target: self, action: #selector(didFinishEditingContact)) + contactViewController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: CommonStrings.cancelButton, + style: .plain, + target: self, + action: #selector(didFinishEditingContact)) + + self.navigationController?.pushViewController(contactViewController, animated: true) + + // HACK otherwise CNContactViewController Navbar is shown as black. + // RADAR rdar://28433898 http://www.openradar.me/28433898 + // CNContactViewController incompatible with opaque navigation bar + UIUtil.applyDefaultSystemAppearence() + } + + private func presentSelectAddToExistingContactView() { + guard contactsManager.supportsContactEditing else { + owsFail("\(self.logTag) Contact editing not supported") + return + } + + guard let systemContact = OWSContacts.systemContact(for: contact) else { + owsFail("\(self.logTag) Could not derive system contact.") + return + } + + guard contactsManager.isSystemContactsAuthorized else { + ContactsViewHelper.presentMissingContactAccessAlertController(from: self) + return + } + + guard let firstPhoneNumber = contact.phoneNumbers?.first else { + owsFail("\(self.logTag) Missing phone number.") + return + } + + let viewController = OWSAddToContactViewController() + viewController.configure(withRecipientId: firstPhoneNumber.phoneNumber) + self.navigationController?.pushViewController(viewController, animated: true) + } + + // MARK: - CNContactViewControllerDelegate + + @objc public func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) { + Logger.info("\(self.TAG) \(#function)") + + self.navigationController?.popToViewController(self, animated: true) + + updateContent() + } + + @objc public func didFinishEditingContact() { + Logger.info("\(self.TAG) \(#function)") + + self.navigationController?.popToViewController(self, animated: true) + + updateContent() + } } diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m index 181c97cca..108e8e9a0 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m @@ -2976,11 +2976,11 @@ typedef OWSContact * (^OWSContactBlock)(void); OWSContactPhoneNumber *phoneNumber1 = [OWSContactPhoneNumber new]; phoneNumber1.phoneType = OWSContactPhoneType_Home; - phoneNumber1.phoneNumber = @"+13213214321"; + phoneNumber1.phoneNumber = @"+13213215555"; OWSContactPhoneNumber *phoneNumber2 = [OWSContactPhoneNumber new]; phoneNumber2.phoneType = OWSContactPhoneType_Custom; phoneNumber2.label = @"Carphone"; - phoneNumber2.phoneNumber = @"+13332221111"; + phoneNumber2.phoneNumber = @"+13332226666"; contact.phoneNumbers = @[ phoneNumber1, phoneNumber2, @@ -2988,11 +2988,11 @@ typedef OWSContact * (^OWSContactBlock)(void); OWSContactEmail *email1 = [OWSContactEmail new]; email1.emailType = OWSContactEmailType_Home; - email1.email = @"a@b.com"; + email1.email = @"a1@b.com"; OWSContactEmail *email2 = [OWSContactEmail new]; email2.emailType = OWSContactEmailType_Custom; email2.label = @"customer support"; - email2.email = @"a@b.com"; + email2.email = @"a2@b.com"; contact.emails = @[ email1, email2, diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.m b/Signal/src/ViewControllers/HomeView/HomeViewController.m index 08d1adffa..efd90e5c1 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewController.m @@ -286,6 +286,10 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations } [self updateBarButtonItems]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [self tableView:self.tableView didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]; + }); } - (void)applyDefaultBackButton diff --git a/SignalMessaging/Views/ContactsViewHelper.h b/SignalMessaging/Views/ContactsViewHelper.h index e963ba687..e48b1675a 100644 --- a/SignalMessaging/Views/ContactsViewHelper.h +++ b/SignalMessaging/Views/ContactsViewHelper.h @@ -1,12 +1,13 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // NS_ASSUME_NONNULL_BEGIN -@class ContactsViewHelper; @class Contact; +@class ContactsViewHelper; @class SignalAccount; + @protocol CNContactViewControllerDelegate; @protocol ContactsViewHelperDelegate @@ -27,9 +28,9 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - -@class OWSContactsManager; -@class OWSBlockingManager; @class CNContact; +@class OWSBlockingManager; +@class OWSContactsManager; @interface ContactsViewHelper : NSObject @@ -81,6 +82,8 @@ NS_ASSUME_NONNULL_BEGIN editImmediately:(BOOL)shouldEditImmediately addToExistingCnContact:(CNContact *_Nullable)cnContact; ++ (void)presentMissingContactAccessAlertControllerFromViewController:(UIViewController *)viewController; + @end NS_ASSUME_NONNULL_END diff --git a/SignalMessaging/Views/ContactsViewHelper.m b/SignalMessaging/Views/ContactsViewHelper.m index af2ae1829..17c38ef4d 100644 --- a/SignalMessaging/Views/ContactsViewHelper.m +++ b/SignalMessaging/Views/ContactsViewHelper.m @@ -264,6 +264,11 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Editing - (void)presentMissingContactAccessAlertControllerFromViewController:(UIViewController *)viewController +{ + [ContactsViewHelper presentMissingContactAccessAlertControllerFromViewController:viewController]; +} + ++ (void)presentMissingContactAccessAlertControllerFromViewController:(UIViewController *)viewController { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"EDIT_CONTACT_WITHOUT_CONTACTS_PERMISSION_ALERT_TITLE", comment diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContact.h b/SignalServiceKit/src/Messages/Interactions/OWSContact.h index 8f3f5c07f..bd83eff04 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSContact.h +++ b/SignalServiceKit/src/Messages/Interactions/OWSContact.h @@ -30,6 +30,8 @@ typedef NS_ENUM(NSUInteger, OWSContactPhoneType) { - (BOOL)ows_isValid; +- (NSString *)labelString; + @end #pragma mark - @@ -51,6 +53,8 @@ typedef NS_ENUM(NSUInteger, OWSContactEmailType) { - (BOOL)ows_isValid; +- (NSString *)labelString; + @end #pragma mark - @@ -77,6 +81,8 @@ typedef NS_ENUM(NSUInteger, OWSContactAddressType) { - (BOOL)ows_isValid; +- (NSString *)labelString; + @end #pragma mark - diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContact.m b/SignalServiceKit/src/Messages/Interactions/OWSContact.m index aa15b985e..f97cb6473 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSContact.m +++ b/SignalServiceKit/src/Messages/Interactions/OWSContact.m @@ -42,6 +42,20 @@ NS_ASSUME_NONNULL_BEGIN } } +- (NSString *)labelString +{ + switch (self.phoneType) { + case OWSContactPhoneType_Home: + return [CNLabeledValue localizedStringForLabel:CNLabelHome]; + case OWSContactPhoneType_Mobile: + return [CNLabeledValue localizedStringForLabel:CNLabelPhoneNumberMobile]; + case OWSContactPhoneType_Work: + return [CNLabeledValue localizedStringForLabel:CNLabelWork]; + default: + return self.label; + } +} + @end #pragma mark - @@ -74,6 +88,20 @@ NS_ASSUME_NONNULL_BEGIN } } +- (NSString *)labelString +{ + switch (self.emailType) { + case OWSContactEmailType_Home: + return [CNLabeledValue localizedStringForLabel:CNLabelHome]; + case OWSContactEmailType_Mobile: + return [CNLabeledValue localizedStringForLabel:CNLabelPhoneNumberMobile]; + case OWSContactEmailType_Work: + return [CNLabeledValue localizedStringForLabel:CNLabelWork]; + default: + return self.label; + } +} + @end #pragma mark - @@ -114,6 +142,18 @@ NS_ASSUME_NONNULL_BEGIN } } +- (NSString *)labelString +{ + switch (self.addressType) { + case OWSContactAddressType_Home: + return [CNLabeledValue localizedStringForLabel:CNLabelHome]; + case OWSContactAddressType_Work: + return [CNLabeledValue localizedStringForLabel:CNLabelWork]; + default: + return self.label; + } +} + @end #pragma mark - From 91d54360ba0a35de349d1c7999a144ff8e4b5591 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 2 May 2018 10:08:47 -0400 Subject: [PATCH 3/5] Add contact view. --- .../ContactViewController.swift | 281 ++++++++++++------ .../ConversationViewController.h | 11 +- .../ConversationViewController.m | 62 ++-- .../ViewControllers/DebugUI/DebugUIMessages.m | 40 +++ .../HomeView/HomeViewController.h | 5 +- .../HomeView/HomeViewController.m | 16 +- .../NewContactThreadViewController.m | 3 +- .../ShowGroupMembersViewController.m | 4 +- Signal/src/call/OutboundCallInitiator.swift | 9 +- .../call/UserInterface/CallUIAdapter.swift | 9 +- Signal/src/environment/SignalApp.h | 9 +- Signal/src/environment/SignalApp.m | 40 +-- Signal/src/network/PushManager.m | 2 +- .../translations/en.lproj/Localizable.strings | 15 + 14 files changed, 324 insertions(+), 182 deletions(-) diff --git a/Signal/src/ViewControllers/ContactViewController.swift b/Signal/src/ViewControllers/ContactViewController.swift index 3ba1e5738..901b9327b 100644 --- a/Signal/src/ViewControllers/ContactViewController.swift +++ b/Signal/src/ViewControllers/ContactViewController.swift @@ -7,6 +7,7 @@ import SignalServiceKit import SignalMessaging import Reachability import ContactsUI +import MessageUI class TappableView: UIView { let actionBlock : (() -> Void) @@ -38,9 +39,7 @@ class TappableView: UIView { // MARK: - -class ContactViewController: OWSViewController, CNContactViewControllerDelegate -//, ContactsViewHelperDelegate -{ +class ContactViewController: OWSViewController, CNContactViewControllerDelegate { let TAG = "[ContactView]" @@ -74,8 +73,6 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate private let contact: OWSContact -// private var contactsViewHelper : ContactsViewHelper! - // MARK: - Initializers @available(*, unavailable, message: "use init(call:) constructor instead.") @@ -90,8 +87,6 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate super.init(nibName: nil, bundle: nil) -// contactsViewHelper = ContactsViewHelper(delegate:self) - tryToDetermineMode() NotificationCenter.default.addObserver(forName: .OWSContactsManagerSignalAccountsDidChange, object: nil, queue: nil) { [weak self] _ in @@ -137,14 +132,8 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate private var scrollView: UIScrollView override func loadView() { -// scrollView.dir -// self.view = scrollView -// let rootView = UIView() super.loadView() -// self.view.layoutMargins = .zero -// self.scrollView = scrollView -// scrollView.autoresizingMask = [.flexibleWidth, .flexibleHeight] self.view.addSubview(scrollView) scrollView.layoutMargins = .zero scrollView.autoPinWidthToSuperview() @@ -161,18 +150,15 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate private func tryToDetermineMode() { SwiftAssertIsOnMainThread(#function) - guard let firstPhoneNumber = contact.phoneNumbers?.first else { + guard phoneNumbersForContact().count > 0 else { viewMode = .noPhoneNumber return } - if contactsManager.hasSignalAccount(forRecipientId: firstPhoneNumber.phoneNumber) { + if systemContactsWithSignalAccountsForContact().count > 0 { viewMode = .systemContactWithSignal return } - if contactsManager.allContactsMap[firstPhoneNumber.phoneNumber] != nil { - // We can infer that this is _not_ a signal user because - // all contacts in contactsManager.allContactsMap have - // already been looked up. + if systemContactsForContact().count > 0 { viewMode = .systemContactWithoutSignal return } @@ -180,6 +166,34 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate 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 @@ -212,21 +226,21 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate avatarView.backgroundColor = UIColor.ows_materialBlue avatarView.layer.cornerRadius = avatarSize * 0.5 topView.addSubview(avatarView) - avatarView.autoPin(toTopLayoutGuideOf: self, withInset: 10) + 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_dynamicTypeTitle1 + 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() - nameLabel.autoPinTrailingToSuperviewMargin() + nameLabel.autoPinLeadingToSuperviewMargin(withInset: hMargin) + nameLabel.autoPinTrailingToSuperviewMargin(withInset: hMargin) var lastView: UIView = nameLabel @@ -239,18 +253,53 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate phoneNumberLabel.textAlignment = .center topView.addSubview(phoneNumberLabel) phoneNumberLabel.autoPinEdge(.top, to: .bottom, of: lastView, withOffset: 5) - phoneNumberLabel.autoPinLeadingToSuperviewMargin() - phoneNumberLabel.autoPinTrailingToSuperviewMargin() + phoneNumberLabel.autoPinLeadingToSuperviewMargin(withInset: hMargin) + phoneNumberLabel.autoPinTrailingToSuperviewMargin(withInset: hMargin) lastView = phoneNumberLabel } switch viewMode { case .systemContactWithSignal: // Show actions buttons for system contacts with a Signal account. - break + 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. - break + 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 @@ -363,12 +412,15 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate lastRow?.autoPinEdge(toSuperviewEdge: .bottom, withInset: 0) } - private let hMargin = CGFloat(10) + 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 @@ -379,11 +431,14 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate 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 @@ -411,46 +466,60 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate return row } -// acceptIncomingButton = createButton(image: #imageLiteral(resourceName: "call-active-wide"), -// action: #selector(didPressAnswerCall)) -// acceptIncomingButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_ACCEPT_INCOMING_CALL_LABEL", -// comment: "Accessibility label for accepting incoming calls") - -// func createButton(image: UIImage, action: Selector) -> UIButton { -// let button = UIButton() -// button.setImage(image, for: .normal) -// button.imageEdgeInsets = UIEdgeInsets(top: buttonInset(), -// left: buttonInset(), -// bottom: buttonInset(), -// right: buttonInset()) -// button.addTarget(self, action: action, for: .touchUpInside) -// button.autoSetDimension(.width, toSize: buttonSize()) -// button.autoSetDimension(.height, toSize: buttonSize()) -// return button -// } -// -// // MARK: - Layout -// + // TODO: Use real assets. + private func createCircleActionButton(text: String, actionBlock : @escaping () -> Void) -> UIView { + let buttonSize = CGFloat(50) -// -// func didPressFlipCamera(sender: UIButton) { -// // toggle value -// sender.isSelected = !sender.isSelected -// -// let useBackCamera = sender.isSelected -// Logger.info("\(TAG) in \(#function) with useBackCamera: \(useBackCamera)") -// -// callUIAdapter.setCameraSource(call: call, useBackCamera: useBackCamera) -// } -// -// internal func dismissImmediately(completion: (() -> Void)?) { -// if ContactView.kShowCallViewOnSeparateWindow { -// OWSWindowManager.shared().endCall(self) -// completion?() -// } else { -// self.dismiss(animated: true, completion: completion) -// } -// } + 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 + } func didPressCreateNewContact(sender: UIGestureRecognizer) { Logger.info("\(self.TAG) \(#function)") @@ -479,14 +548,64 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate // TODO: } - // MARK: - ContactsViewHelperDelegate + func didPressSendMessage() { + Logger.info("\(self.TAG) \(#function)") -// @objc -// public func contactsViewHelperDidUpdateContacts() { -// Logger.info("\(self.TAG) \(#function)") -// -// // Do nothing -// } + // TODO: We're taking the first Signal account id. We might + // want to let the user select if there's more than one. + guard let recipientId = systemContactsWithSignalAccountsForContact().first else { + owsFail("\(logTag) missing Signal recipient id.") + return + } + + SignalApp.shared().presentConversation(forRecipientId: recipientId, action: .compose) + } + + func didPressAudioCall() { + Logger.info("\(self.TAG) \(#function)") + + // TODO: We're taking the first Signal account id. We might + // want to let the user select if there's more than one. + guard let recipientId = systemContactsWithSignalAccountsForContact().first else { + owsFail("\(logTag) missing Signal recipient id.") + return + } + + SignalApp.shared().presentConversation(forRecipientId: recipientId, action: .audioCall) + } + + func didPressVideoCall() { + Logger.info("\(self.TAG) \(#function)") + + // TODO: We're taking the first Signal account id. We might + // want to let the user select if there's more than one. + guard let recipientId = systemContactsWithSignalAccountsForContact().first else { + owsFail("\(logTag) missing Signal recipient id.") + return + } + + SignalApp.shared().presentConversation(forRecipientId: recipientId, action: .videoCall) + } + + 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) + } // MARK: - @@ -495,13 +614,6 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate owsFail("\(self.logTag) Contact editing not supported") return } - // contactsViewHelper.presentContactViewController - // let contactsViewHelper = ContactsViewHelper(delegate:self) - // - // TSContactThread *contactThread = (TSContactThread *)self.thread; - // [self.contactsViewHelper presentContactViewControllerForRecipientId:contactThread.contactIdentifier - // fromViewController:self - // editImmediately:YES]; guard let systemContact = OWSContacts.systemContact(for: contact) else { owsFail("\(self.logTag) Could not derive system contact.") @@ -537,11 +649,6 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate return } - guard let systemContact = OWSContacts.systemContact(for: contact) else { - owsFail("\(self.logTag) Could not derive system contact.") - return - } - guard contactsManager.isSystemContactsAuthorized else { ContactsViewHelper.presentMissingContactAccessAlertController(from: self) return @@ -552,6 +659,8 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate return } + // TODO: We need to modify OWSAddToContactViewController to take a OWSContact + // and merge it with an existing CNContact. let viewController = OWSAddToContactViewController() viewController.configure(withRecipientId: firstPhoneNumber.phoneNumber) self.navigationController?.pushViewController(viewController, animated: true) diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.h b/Signal/src/ViewControllers/ConversationView/ConversationViewController.h index ec7610a4a..002a95218 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.h +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.h @@ -6,15 +6,20 @@ NS_ASSUME_NONNULL_BEGIN +typedef NS_ENUM(NSUInteger, ConversationViewAction) { + ConversationViewActionNone, + ConversationViewActionCompose, + ConversationViewActionAudioCall, + ConversationViewActionVideoCall, +}; + @class TSThread; @interface ConversationViewController : OWSViewController @property (nonatomic, readonly) TSThread *thread; -- (void)configureForThread:(TSThread *)thread - keyboardOnViewAppearing:(BOOL)keyboardAppearing - callOnViewAppearing:(BOOL)callOnViewAppearing; +- (void)configureForThread:(TSThread *)thread action:(ConversationViewAction)action; - (void)popKeyBoard; diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 828c00de2..3161621f4 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -189,8 +189,7 @@ typedef enum : NSUInteger { @property (nonatomic, readonly) NSUInteger backButtonUnreadCount; @property (nonatomic) NSUInteger lastRangeLength; -@property (nonatomic) BOOL composeOnOpen; -@property (nonatomic) BOOL callOnOpen; +@property (nonatomic) ConversationViewAction actionOnOpen; @property (nonatomic) BOOL peek; @property (nonatomic, readonly) OWSContactsManager *contactsManager; @@ -396,7 +395,7 @@ typedef enum : NSUInteger { - (void)peekSetup { _peek = YES; - [self setComposeOnOpen:NO]; + self.actionOnOpen = ConversationViewActionNone; } - (void)popped @@ -405,21 +404,11 @@ typedef enum : NSUInteger { [self hideInputIfNeeded]; } -- (void)configureForThread:(TSThread *)thread - keyboardOnViewAppearing:(BOOL)keyboardOnViewAppearing - callOnViewAppearing:(BOOL)callOnViewAppearing +- (void)configureForThread:(TSThread *)thread action:(ConversationViewAction)action { - // At most one. - OWSAssert(!keyboardOnViewAppearing || !callOnViewAppearing); - - if (callOnViewAppearing) { - keyboardOnViewAppearing = NO; - } - _thread = thread; _isGroupConversation = [self.thread isKindOfClass:[TSGroupThread class]]; - _composeOnOpen = keyboardOnViewAppearing; - _callOnOpen = callOnViewAppearing; + self.actionOnOpen = action; _cellMediaCache = [NSCache new]; // Cache the cell media for ~24 cells. self.cellMediaCache.countLimit = 24; @@ -1032,15 +1021,22 @@ typedef enum : NSUInteger { [self updateNavigationBarSubtitleLabel]; [self updateBackButtonUnreadCount]; - if (_composeOnOpen && !self.inputToolbar.hidden) { - [self popKeyBoard]; - _composeOnOpen = NO; - } - if (_callOnOpen) { - [self callAction]; - _callOnOpen = NO; + switch (self.actionOnOpen) { + case ConversationViewActionNone: + break; + case ConversationViewActionCompose: + [self popKeyBoard]; + break; + case ConversationViewActionAudioCall: + [self audioCallAction]; + break; + case ConversationViewActionVideoCall: + [self videoCallAction]; + break; } + self.actionOnOpen = ConversationViewActionNone; + self.isViewCompletelyAppeared = YES; self.viewHasEverAppeared = YES; @@ -1227,7 +1223,7 @@ typedef enum : NSUInteger { imageEdgeInsets.bottom = round(kBarButtonSize - (image.size.height + imageEdgeInsets.top)); callButton.imageEdgeInsets = imageEdgeInsets; callButton.accessibilityLabel = NSLocalizedString(@"CALL_LABEL", "Accessibility label for placing call button"); - [callButton addTarget:self action:@selector(callAction) forControlEvents:UIControlEventTouchUpInside]; + [callButton addTarget:self action:@selector(audioCallAction) forControlEvents:UIControlEventTouchUpInside]; callButton.frame = CGRectMake(0, 0, round(image.size.width + imageEdgeInsets.left + imageEdgeInsets.right), @@ -1362,7 +1358,17 @@ typedef enum : NSUInteger { #pragma mark - Calls -- (void)callAction +- (void)audioCallAction +{ + [self callWithVideo:NO]; +} + +- (void)videoCallAction +{ + [self callWithVideo:YES]; +} + +- (void)callWithVideo:(BOOL)isVideo { OWSAssert([self.thread isKindOfClass:[TSContactThread class]]); @@ -1375,7 +1381,7 @@ typedef enum : NSUInteger { if ([self isBlockedContactConversation]) { [self showUnblockContactUI:^(BOOL isBlocked) { if (!isBlocked) { - [weakSelf callAction]; + [weakSelf callWithVideo:isVideo]; } }]; return; @@ -1385,14 +1391,14 @@ typedef enum : NSUInteger { [self showSafetyNumberConfirmationIfNecessaryWithConfirmationText:[CallStrings confirmAndCallButtonTitle] completion:^(BOOL didConfirmIdentity) { if (didConfirmIdentity) { - [weakSelf callAction]; + [weakSelf callWithVideo:isVideo]; } }]; if (didShowSNAlert) { return; } - [self.outboundCallInitiator initiateCallWithRecipientId:self.thread.contactIdentifier]; + [self.outboundCallInitiator initiateCallWithRecipientId:self.thread.contactIdentifier isVideo:isVideo]; } - (BOOL)canCall @@ -1875,7 +1881,7 @@ typedef enum : NSUInteger { UIAlertAction *callAction = [UIAlertAction actionWithTitle:[CallStrings callBackAlertCallButton] style:UIAlertActionStyleDefault handler:^(UIAlertAction *_Nonnull action) { - [weakSelf callAction]; + [weakSelf audioCallAction]; }]; [alertController addAction:callAction]; [alertController addAction:[OWSAlerts cancelAction]]; diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m index 108e8e9a0..cc98249a1 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m @@ -3023,6 +3023,46 @@ typedef OWSContact * (^OWSContactBlock)(void); // TODO: Avatar return contact; }]]; + [actions addObject:[self fakeShareContactMessageAction:thread + label:@"Long values" + contactBlock:^{ + OWSContact *contact = [OWSContact new]; + contact.givenName = @"Bobasdjasdlkjasldkjas"; + contact.familyName = @"Bobasdjasdlkjasldkjas"; + OWSContactEmail *email = [OWSContactEmail new]; + email.emailType = OWSContactEmailType_Mobile; + email.email = @"asdlakjsaldkjasldkjasdlkjasdlkjasdlkajsa@b.com"; + contact.emails = @[ + email, + ]; + return contact; + }]]; + [actions addObject:[self fakeShareContactMessageAction:thread + label:@"System Contact w/o Signal" + contactBlock:^{ + OWSContact *contact = [OWSContact new]; + contact.givenName = @"Add Me To Your Contacts"; + OWSContactPhoneNumber *phoneNumber = [OWSContactPhoneNumber new]; + phoneNumber.phoneType = OWSContactPhoneType_Work; + phoneNumber.phoneNumber = @"+324602053911"; + contact.phoneNumbers = @[ + phoneNumber, + ]; + return contact; + }]]; + [actions addObject:[self fakeShareContactMessageAction:thread + label:@"System Contact w. Signal" + contactBlock:^{ + OWSContact *contact = [OWSContact new]; + contact.givenName = @"Add Me To Your Contacts"; + OWSContactPhoneNumber *phoneNumber = [OWSContactPhoneNumber new]; + phoneNumber.phoneType = OWSContactPhoneType_Work; + phoneNumber.phoneNumber = @"+32460205392"; + contact.phoneNumbers = @[ + phoneNumber, + ]; + return contact; + }]]; return actions; } diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.h b/Signal/src/ViewControllers/HomeView/HomeViewController.h index 632419ab0..a19c94488 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.h +++ b/Signal/src/ViewControllers/HomeView/HomeViewController.h @@ -2,6 +2,7 @@ // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // +#import "ConversationViewController.h" #import #import @@ -9,9 +10,7 @@ @interface HomeViewController : OWSViewController -- (void)presentThread:(TSThread *)thread - keyboardOnViewAppearing:(BOOL)keyboardOnViewAppearing - callOnViewAppearing:(BOOL)callOnViewAppearing; +- (void)presentThread:(TSThread *)thread action:(ConversationViewAction)action; - (void)showNewConversationView; diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.m b/Signal/src/ViewControllers/HomeView/HomeViewController.m index efd90e5c1..b1d1d2544 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewController.m @@ -5,7 +5,6 @@ #import "HomeViewController.h" #import "AppDelegate.h" #import "AppSettingsViewController.h" -#import "ConversationViewController.h" #import "HomeViewCell.h" #import "NewContactThreadViewController.h" #import "OWSNavigationController.h" @@ -387,7 +386,7 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations ConversationViewController *vc = [ConversationViewController new]; TSThread *thread = [self threadForIndexPath:indexPath]; self.lastThread = thread; - [vc configureForThread:thread keyboardOnViewAppearing:NO callOnViewAppearing:NO]; + [vc configureForThread:thread action:ConversationViewActionNone]; [vc peekSetup]; return vc; @@ -916,17 +915,12 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations } TSThread *thread = [self threadForIndexPath:indexPath]; - [self presentThread:thread keyboardOnViewAppearing:NO callOnViewAppearing:NO]; + [self presentThread:thread action:ConversationViewActionNone]; [tableView deselectRowAtIndexPath:indexPath animated:YES]; } -- (void)presentThread:(TSThread *)thread - keyboardOnViewAppearing:(BOOL)keyboardOnViewAppearing - callOnViewAppearing:(BOOL)callOnViewAppearing +- (void)presentThread:(TSThread *)thread action:(ConversationViewAction)action { - // At most one. - OWSAssert(!keyboardOnViewAppearing || !callOnViewAppearing); - if (thread == nil) { OWSFail(@"Thread unexpectedly nil"); return; @@ -935,9 +929,7 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations // We do this synchronously if we're already on the main thread. DispatchMainThreadSafe(^{ ConversationViewController *mvc = [ConversationViewController new]; - [mvc configureForThread:thread - keyboardOnViewAppearing:keyboardOnViewAppearing - callOnViewAppearing:callOnViewAppearing]; + [mvc configureForThread:thread action:action]; self.lastThread = thread; [self pushTopLevelViewController:mvc animateDismissal:YES animatePresentation:YES]; diff --git a/Signal/src/ViewControllers/NewContactThreadViewController.m b/Signal/src/ViewControllers/NewContactThreadViewController.m index 8ff55a48b..bf1c145c7 100644 --- a/Signal/src/ViewControllers/NewContactThreadViewController.m +++ b/Signal/src/ViewControllers/NewContactThreadViewController.m @@ -809,7 +809,8 @@ NS_ASSUME_NONNULL_BEGIN OWSAssert(thread != nil); [self dismissViewControllerAnimated:YES completion:^() { - [SignalApp.sharedApp presentConversationForThread:thread withCompose:YES]; + [SignalApp.sharedApp presentConversationForThread:thread + action:ConversationViewActionCompose]; }]; } diff --git a/Signal/src/ViewControllers/ThreadSettings/ShowGroupMembersViewController.m b/Signal/src/ViewControllers/ThreadSettings/ShowGroupMembersViewController.m index b5fa90582..bb3b8bbdb 100644 --- a/Signal/src/ViewControllers/ThreadSettings/ShowGroupMembersViewController.m +++ b/Signal/src/ViewControllers/ThreadSettings/ShowGroupMembersViewController.m @@ -407,12 +407,12 @@ NS_ASSUME_NONNULL_BEGIN { OWSAssert(recipientId.length > 0); - [SignalApp.sharedApp presentConversationForRecipientId:recipientId withCompose:YES]; + [SignalApp.sharedApp presentConversationForRecipientId:recipientId action:ConversationViewActionCompose]; } - (void)callMember:(NSString *)recipientId { - [SignalApp.sharedApp callRecipientId:recipientId]; + [SignalApp.sharedApp presentConversationForRecipientId:recipientId action:ConversationViewActionAudioCall]; } - (void)showSafetyNumberView:(NSString *)recipientId diff --git a/Signal/src/call/OutboundCallInitiator.swift b/Signal/src/call/OutboundCallInitiator.swift index 1d411ff45..c081b4e0f 100644 --- a/Signal/src/call/OutboundCallInitiator.swift +++ b/Signal/src/call/OutboundCallInitiator.swift @@ -35,13 +35,14 @@ import SignalMessaging return false } - return initiateCall(recipientId: recipientId) + return initiateCall(recipientId: recipientId, isVideo: false) } /** * |recipientId| is a e164 formatted phone number. */ - public func initiateCall(recipientId: String) -> Bool { + public func initiateCall(recipientId: String, + isVideo: Bool) -> Bool { // Rather than an init-assigned dependency property, we access `callUIAdapter` via Environment // because it can change after app launch due to user settings guard let callUIAdapter = SignalApp.shared().callUIAdapter else { @@ -58,7 +59,7 @@ import SignalMessaging contactsManager: self.contactsManager, completion: { didConfirmIdentity in if didConfirmIdentity { - _ = self.initiateCall(recipientId: recipientId) + _ = self.initiateCall(recipientId: recipientId, isVideo: isVideo) } }) guard !showedAlert else { @@ -81,7 +82,7 @@ import SignalMessaging OWSAlerts.showNoMicrophonePermissionAlert() return } - callUIAdapter.startAndShowOutgoingCall(recipientId: recipientId) + callUIAdapter.startAndShowOutgoingCall(recipientId: recipientId, hasLocalVideo: isVideo) }) return true diff --git a/Signal/src/call/UserInterface/CallUIAdapter.swift b/Signal/src/call/UserInterface/CallUIAdapter.swift index d280ec797..67865be3b 100644 --- a/Signal/src/call/UserInterface/CallUIAdapter.swift +++ b/Signal/src/call/UserInterface/CallUIAdapter.swift @@ -28,7 +28,7 @@ protocol CallUIAdaptee { func failCall(_ call: SignalCall, error: CallError) func setIsMuted(call: SignalCall, isMuted: Bool) func setHasLocalVideo(call: SignalCall, hasLocalVideo: Bool) - func startAndShowOutgoingCall(recipientId: String) + func startAndShowOutgoingCall(recipientId: String, hasLocalVideo: Bool) } // Shared default implementations @@ -63,7 +63,7 @@ extension CallUIAdaptee { notificationsAdapter.presentMissedCall(call, callerName: callerName) } - internal func startAndShowOutgoingCall(recipientId: String) { + internal func startAndShowOutgoingCall(recipientId: String, hasLocalVideo: Bool) { SwiftAssertIsOnMainThread(#function) guard self.callService.call == nil else { @@ -73,6 +73,7 @@ extension CallUIAdaptee { } let call = self.startOutgoingCall(handle: recipientId) + call.hasLocalVideo = hasLocalVideo self.showCall(call) } } @@ -186,10 +187,10 @@ extension CallUIAdaptee { } } - internal func startAndShowOutgoingCall(recipientId: String) { + internal func startAndShowOutgoingCall(recipientId: String, hasLocalVideo: Bool) { SwiftAssertIsOnMainThread(#function) - adaptee.startAndShowOutgoingCall(recipientId: recipientId) + adaptee.startAndShowOutgoingCall(recipientId: recipientId, hasLocalVideo: hasLocalVideo) } internal func recipientAcceptedCall(_ call: SignalCall) { diff --git a/Signal/src/environment/SignalApp.h b/Signal/src/environment/SignalApp.h index f5480119f..e3116c9e4 100644 --- a/Signal/src/environment/SignalApp.h +++ b/Signal/src/environment/SignalApp.h @@ -1,7 +1,9 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // +#import "ConversationViewController.h" + @class AccountManager; @class CallService; @class CallUIAdapter; @@ -33,11 +35,10 @@ #pragma mark - View Convenience Methods - (void)presentConversationForRecipientId:(NSString *)recipientId; -- (void)presentConversationForRecipientId:(NSString *)recipientId withCompose:(BOOL)compose; -- (void)callRecipientId:(NSString *)recipientId; +- (void)presentConversationForRecipientId:(NSString *)recipientId action:(ConversationViewAction)action; - (void)presentConversationForThreadId:(NSString *)threadId; - (void)presentConversationForThread:(TSThread *)thread; -- (void)presentConversationForThread:(TSThread *)thread withCompose:(BOOL)compose; +- (void)presentConversationForThread:(TSThread *)thread action:(ConversationViewAction)action; #pragma mark - Methods diff --git a/Signal/src/environment/SignalApp.m b/Signal/src/environment/SignalApp.m index 9fed45388..49198f316 100644 --- a/Signal/src/environment/SignalApp.m +++ b/Signal/src/environment/SignalApp.m @@ -152,35 +152,18 @@ - (void)presentConversationForRecipientId:(NSString *)recipientId { - [self presentConversationForRecipientId:recipientId keyboardOnViewAppearing:YES callOnViewAppearing:NO]; + [self presentConversationForRecipientId:recipientId action:ConversationViewActionNone]; } -- (void)presentConversationForRecipientId:(NSString *)recipientId withCompose:(BOOL)compose +- (void)presentConversationForRecipientId:(NSString *)recipientId action:(ConversationViewAction)action { - [self presentConversationForRecipientId:recipientId keyboardOnViewAppearing:compose callOnViewAppearing:NO]; -} - -- (void)callRecipientId:(NSString *)recipientId -{ - [self presentConversationForRecipientId:recipientId keyboardOnViewAppearing:NO callOnViewAppearing:YES]; -} - -- (void)presentConversationForRecipientId:(NSString *)recipientId - keyboardOnViewAppearing:(BOOL)keyboardOnViewAppearing - callOnViewAppearing:(BOOL)callOnViewAppearing -{ - // At most one. - OWSAssert(!keyboardOnViewAppearing || !callOnViewAppearing); - DispatchMainThreadSafe(^{ __block TSThread *thread = nil; [OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { thread = [TSContactThread getOrCreateThreadWithContactId:recipientId transaction:transaction]; }]; - [self presentConversationForThread:thread - keyboardOnViewAppearing:keyboardOnViewAppearing - callOnViewAppearing:callOnViewAppearing]; + [self presentConversationForThread:thread action:action]; }); } @@ -199,21 +182,12 @@ - (void)presentConversationForThread:(TSThread *)thread { - [self presentConversationForThread:thread withCompose:YES]; -} - -- (void)presentConversationForThread:(TSThread *)thread withCompose:(BOOL)compose -{ - [self presentConversationForThread:thread keyboardOnViewAppearing:compose callOnViewAppearing:NO]; + [self presentConversationForThread:thread action:ConversationViewActionNone]; } -- (void)presentConversationForThread:(TSThread *)thread - keyboardOnViewAppearing:(BOOL)keyboardOnViewAppearing - callOnViewAppearing:(BOOL)callOnViewAppearing +- (void)presentConversationForThread:(TSThread *)thread action:(ConversationViewAction)action { OWSAssertIsOnMainThread(); - // At most one. - OWSAssert(!keyboardOnViewAppearing || !callOnViewAppearing); DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); @@ -233,9 +207,7 @@ } } - [self.homeViewController presentThread:thread - keyboardOnViewAppearing:keyboardOnViewAppearing - callOnViewAppearing:callOnViewAppearing]; + [self.homeViewController presentThread:thread action:action]; }); } diff --git a/Signal/src/network/PushManager.m b/Signal/src/network/PushManager.m index 42abb5c7f..37859c532 100644 --- a/Signal/src/network/PushManager.m +++ b/Signal/src/network/PushManager.m @@ -253,7 +253,7 @@ NSString *const Signal_Message_MarkAsRead_Identifier = @"Signal_Message_MarkAsRe return; } - [self.callUIAdapter startAndShowOutgoingCallWithRecipientId:recipientId]; + [self.callUIAdapter startAndShowOutgoingCallWithRecipientId:recipientId hasLocalVideo:NO]; completionHandler(); } else if ([identifier isEqualToString:PushManagerActionsShowThread]) { NSString *threadId = notification.userInfo[Signal_Thread_UserInfo_Key]; diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 4dc1145ad..131814aa5 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -4,6 +4,21 @@ /* Action sheet item */ "ACCEPT_NEW_IDENTITY_ACTION" = "Accept New Safety Number"; +/* Label for 'audio call' button in contact view. */ +"ACTION_AUDIO_CALL" = "Signal Call"; + +/* Label for 'invite' button in contact view. */ +"ACTION_INVITE" = "Invite to Signal"; + +/* Label for 'sent message' button in contact view. */ +"ACTION_SEND_MESSAGE" = "Send Message"; + +/* Label for 'share contact' button. */ +"ACTION_SHARE_CONTACT" = "Share Contact"; + +/* Label for 'video call' button in contact view. */ +"ACTION_VIDEO_CALL" = "Video Call"; + /* A label for the 'add by phone number' button in the 'add group member' view */ "ADD_GROUP_MEMBER_VIEW_BUTTON" = "Add"; From 1d56d56ca2de57d500b7bcad78367c896e6e6c0a Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 2 May 2018 11:22:11 -0400 Subject: [PATCH 4/5] Clean up ahead of PR. --- Signal/src/ViewControllers/HomeView/HomeViewController.m | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.m b/Signal/src/ViewControllers/HomeView/HomeViewController.m index b1d1d2544..8974bd59a 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewController.m @@ -285,10 +285,6 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations } [self updateBarButtonItems]; - - dispatch_async(dispatch_get_main_queue(), ^{ - [self tableView:self.tableView didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]; - }); } - (void)applyDefaultBackButton From ff6b45abeee1c732f469b644f37bbbc0678c5201 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Thu, 3 May 2018 14:31:11 -0400 Subject: [PATCH 5/5] Respond to CR. --- .../ContactViewController.swift | 135 +++++++----------- .../ConversationViewController.m | 12 +- .../ViewControllers/DebugUI/DebugUIMessages.m | 6 +- .../Interactions/OWSContact+Private.h | 6 +- .../src/Messages/Interactions/OWSContact.h | 6 +- .../src/Messages/Interactions/OWSContact.m | 17 ++- 6 files changed, 84 insertions(+), 98 deletions(-) diff --git a/Signal/src/ViewControllers/ContactViewController.swift b/Signal/src/ViewControllers/ContactViewController.swift index 901b9327b..0465fa42a 100644 --- a/Signal/src/ViewControllers/ContactViewController.swift +++ b/Signal/src/ViewControllers/ContactViewController.swift @@ -28,7 +28,7 @@ class TappableView: UIView { } func wasTapped(sender: UIGestureRecognizer) { - Logger.info("\(self.logTag) \(#function)") + Logger.info("\(logTag) \(#function)") guard sender.state == .recognized else { return @@ -41,8 +41,6 @@ class TappableView: UIView { class ContactViewController: OWSViewController, CNContactViewControllerDelegate { - let TAG = "[ContactView]" - enum ContactViewMode { case systemContactWithSignal, systemContactWithoutSignal, @@ -67,10 +65,6 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate var reachability: Reachability? - override public var canBecomeFirstResponder: Bool { - return true - } - private let contact: OWSContact // MARK: - Initializers @@ -87,18 +81,18 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate super.init(nibName: nil, bundle: nil) - tryToDetermineMode() + updateMode() NotificationCenter.default.addObserver(forName: .OWSContactsManagerSignalAccountsDidChange, object: nil, queue: nil) { [weak self] _ in guard let strongSelf = self else { return } - strongSelf.tryToDetermineMode() + strongSelf.updateMode() } reachability = Reachability.forInternetConnection() NotificationCenter.default.addObserver(forName: .reachabilityChanged, object: nil, queue: nil) { [weak self] _ in guard let strongSelf = self else { return } - strongSelf.tryToDetermineMode() + strongSelf.updateMode() } } @@ -113,11 +107,9 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate UIUtil.applySignalAppearence() - self.becomeFirstResponder() - contactsManager.requestSystemContactsOnce(completion: { [weak self] _ in guard let strongSelf = self else { return } - strongSelf.tryToDetermineMode() + strongSelf.updateMode() }) } @@ -125,8 +117,6 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate super.viewDidAppear(animated) UIUtil.applySignalAppearence() - - self.becomeFirstResponder() } private var scrollView: UIScrollView @@ -147,7 +137,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate hasLoadedView = true } - private func tryToDetermineMode() { + private func updateMode() { SwiftAssertIsOnMainThread(#function) guard phoneNumbersForContact().count > 0 else { @@ -186,22 +176,12 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate SwiftAssertIsOnMainThread(#function) var result = [String]() - if let phoneNumbers = contact.phoneNumbers { - for phoneNumber in phoneNumbers { - result.append(phoneNumber.phoneNumber) - } + for phoneNumber in contact.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) @@ -217,8 +197,8 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate topView.preservesSuperviewLayoutMargins = true rootView.addSubview(topView) topView.autoPinEdge(toSuperviewEdge: .top) - topView.autoPinEdge(.left, to: .left, of: self.view, withOffset: 0) - topView.autoPinEdge(.right, to: .right, of: self.view, withOffset: 0) + topView.autoPinEdge(.left, to: .left, of: self.view) + topView.autoPinEdge(.right, to: .right, of: self.view) // TODO: Use actual avatar. let avatarSize = CGFloat(100) @@ -244,7 +224,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate var lastView: UIView = nameLabel - if let firstPhoneNumber = contact.phoneNumbers?.first { + if let firstPhoneNumber = contact.phoneNumbers.first { let phoneNumberLabel = UILabel() phoneNumberLabel.text = firstPhoneNumber.phoneNumber phoneNumberLabel.font = UIFont.ows_dynamicTypeCaption2 @@ -322,10 +302,10 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate bottomView.layoutMargins = .zero bottomView.preservesSuperviewLayoutMargins = false rootView.addSubview(bottomView) - bottomView.autoPinEdge(.top, to: .bottom, of: topView, withOffset: 0) + bottomView.autoPinEdge(.top, to: .bottom, of: topView) bottomView.autoPinEdge(toSuperviewEdge: .bottom) - bottomView.autoPinEdge(.left, to: .left, of: self.view, withOffset: 0) - bottomView.autoPinEdge(.right, to: .right, of: self.view, withOffset: 0) + bottomView.autoPinEdge(.left, to: .left, of: self.view) + bottomView.autoPinEdge(.right, to: .right, of: self.view) bottomView.setContentHuggingVerticalLow() var lastRow: UIView? @@ -341,7 +321,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate row.autoSetDimension(.height, toSize: 1) row.autoPinLeadingToSuperviewMargin(withInset: self.hMargin) row.autoPinTrailingToSuperviewMargin() - row.autoPinEdge(.top, to: .bottom, of: prevRow, withOffset: 0) + row.autoPinEdge(.top, to: .bottom, of: prevRow) lastRow = row } @@ -353,9 +333,9 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate row.autoPinLeadingToSuperviewMargin() row.autoPinTrailingToSuperviewMargin() if let lastRow = lastRow { - row.autoPinEdge(.top, to: .bottom, of: lastRow, withOffset: 0) + row.autoPinEdge(.top, to: .bottom, of: lastRow) } else { - row.autoPinEdge(toSuperviewEdge: .top, withInset: 0) + row.autoPinEdge(toSuperviewEdge: .top) } lastRow = row } @@ -378,38 +358,34 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate // action:#selector(didPressShareContact))) // } - if let phoneNumbers = contact.phoneNumbers { - for phoneNumber in phoneNumbers { - // TODO: Try to format the phone number nicely. - addRow(createNameValueRow(name: phoneNumber.labelString(), - value: 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) - })) - } + for phoneNumber in contact.phoneNumbers { + // TODO: Try to format the phone number nicely. + addRow(createNameValueRow(name: phoneNumber.labelString(), + value: 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.labelString(), - 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) - })) - } + for email in contact.emails { + addRow(createNameValueRow(name: email.labelString(), + 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) + lastRow?.autoPinEdge(toSuperviewEdge: .bottom) } private let hMargin = CGFloat(16) @@ -522,7 +498,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate } func didPressCreateNewContact(sender: UIGestureRecognizer) { - Logger.info("\(self.TAG) \(#function)") + Logger.info("\(logTag) \(#function)") guard sender.state == .recognized else { return @@ -531,7 +507,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate } func didPressAddToExistingContact(sender: UIGestureRecognizer) { - Logger.info("\(self.TAG) \(#function)") + Logger.info("\(logTag) \(#function)") guard sender.state == .recognized else { return @@ -540,7 +516,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate } func didPressShareContact(sender: UIGestureRecognizer) { - Logger.info("\(self.TAG) \(#function)") + Logger.info("\(logTag) \(#function)") guard sender.state == .recognized else { return @@ -549,7 +525,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate } func didPressSendMessage() { - Logger.info("\(self.TAG) \(#function)") + Logger.info("\(logTag) \(#function)") // TODO: We're taking the first Signal account id. We might // want to let the user select if there's more than one. @@ -562,7 +538,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate } func didPressAudioCall() { - Logger.info("\(self.TAG) \(#function)") + Logger.info("\(logTag) \(#function)") // TODO: We're taking the first Signal account id. We might // want to let the user select if there's more than one. @@ -575,7 +551,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate } func didPressVideoCall() { - Logger.info("\(self.TAG) \(#function)") + Logger.info("\(logTag) \(#function)") // TODO: We're taking the first Signal account id. We might // want to let the user select if there's more than one. @@ -588,15 +564,14 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate } func didPressInvite() { - Logger.info("\(self.TAG) \(#function)") + Logger.info("\(logTag) \(#function)") guard MFMessageComposeViewController.canSendText() else { - Logger.info("\(TAG) Device cannot send text") + Logger.info("\(logTag) Device cannot send text") OWSAlerts.showErrorAlert(message: NSLocalizedString("UNSUPPORTED_FEATURE_ERROR", comment: "")) return } - let phoneNumbers = - phoneNumbersForContact() + let phoneNumbers = phoneNumbersForContact() guard phoneNumbers.count > 0 else { owsFail("\(logTag) no phone numbers.") return @@ -611,12 +586,12 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate private func presentNewContactView() { guard contactsManager.supportsContactEditing else { - owsFail("\(self.logTag) Contact editing not supported") + owsFail("\(logTag) Contact editing not supported") return } guard let systemContact = OWSContacts.systemContact(for: contact) else { - owsFail("\(self.logTag) Could not derive system contact.") + owsFail("\(logTag) Could not derive system contact.") return } @@ -645,7 +620,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate private func presentSelectAddToExistingContactView() { guard contactsManager.supportsContactEditing else { - owsFail("\(self.logTag) Contact editing not supported") + owsFail("\(logTag) Contact editing not supported") return } @@ -654,8 +629,8 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate return } - guard let firstPhoneNumber = contact.phoneNumbers?.first else { - owsFail("\(self.logTag) Missing phone number.") + guard let firstPhoneNumber = contact.phoneNumbers.first else { + owsFail("\(logTag) Missing phone number.") return } @@ -669,7 +644,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate // MARK: - CNContactViewControllerDelegate @objc public func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) { - Logger.info("\(self.TAG) \(#function)") + Logger.info("\(logTag) \(#function)") self.navigationController?.popToViewController(self, animated: true) @@ -677,7 +652,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate } @objc public func didFinishEditingContact() { - Logger.info("\(self.TAG) \(#function)") + Logger.info("\(logTag) \(#function)") self.navigationController?.popToViewController(self, animated: true) diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 3161621f4..0bc937072 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -1028,10 +1028,10 @@ typedef enum : NSUInteger { [self popKeyBoard]; break; case ConversationViewActionAudioCall: - [self audioCallAction]; + [self startAudioCall]; break; case ConversationViewActionVideoCall: - [self videoCallAction]; + [self startVideoCall]; break; } @@ -1223,7 +1223,7 @@ typedef enum : NSUInteger { imageEdgeInsets.bottom = round(kBarButtonSize - (image.size.height + imageEdgeInsets.top)); callButton.imageEdgeInsets = imageEdgeInsets; callButton.accessibilityLabel = NSLocalizedString(@"CALL_LABEL", "Accessibility label for placing call button"); - [callButton addTarget:self action:@selector(audioCallAction) forControlEvents:UIControlEventTouchUpInside]; + [callButton addTarget:self action:@selector(startAudioCall) forControlEvents:UIControlEventTouchUpInside]; callButton.frame = CGRectMake(0, 0, round(image.size.width + imageEdgeInsets.left + imageEdgeInsets.right), @@ -1358,12 +1358,12 @@ typedef enum : NSUInteger { #pragma mark - Calls -- (void)audioCallAction +- (void)startAudioCall { [self callWithVideo:NO]; } -- (void)videoCallAction +- (void)startVideoCall { [self callWithVideo:YES]; } @@ -1881,7 +1881,7 @@ typedef enum : NSUInteger { UIAlertAction *callAction = [UIAlertAction actionWithTitle:[CallStrings callBackAlertCallButton] style:UIAlertActionStyleDefault handler:^(UIAlertAction *_Nonnull action) { - [weakSelf audioCallAction]; + [weakSelf startAudioCall]; }]; [alertController addAction:callAction]; [alertController addAction:[OWSAlerts cancelAction]]; diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m index cc98249a1..c4b13a2e0 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m @@ -3023,7 +3023,7 @@ typedef OWSContact * (^OWSContactBlock)(void); // TODO: Avatar return contact; }]]; - [actions addObject:[self fakeShareContactMessageAction:thread + [actions addObject:[self fakeContactShareMessageAction:thread label:@"Long values" contactBlock:^{ OWSContact *contact = [OWSContact new]; @@ -3037,7 +3037,7 @@ typedef OWSContact * (^OWSContactBlock)(void); ]; return contact; }]]; - [actions addObject:[self fakeShareContactMessageAction:thread + [actions addObject:[self fakeContactShareMessageAction:thread label:@"System Contact w/o Signal" contactBlock:^{ OWSContact *contact = [OWSContact new]; @@ -3050,7 +3050,7 @@ typedef OWSContact * (^OWSContactBlock)(void); ]; return contact; }]]; - [actions addObject:[self fakeShareContactMessageAction:thread + [actions addObject:[self fakeContactShareMessageAction:thread label:@"System Contact w. Signal" contactBlock:^{ OWSContact *contact = [OWSContact new]; diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContact+Private.h b/SignalServiceKit/src/Messages/Interactions/OWSContact+Private.h index 9c2a4e7c0..5a037eb84 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSContact+Private.h +++ b/SignalServiceKit/src/Messages/Interactions/OWSContact+Private.h @@ -57,9 +57,9 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, nullable) NSString *organizationName; @property (nonatomic, nullable) NSString *displayName; -@property (nonatomic, nullable) NSArray *phoneNumbers; -@property (nonatomic, nullable) NSArray *emails; -@property (nonatomic, nullable) NSArray *addresses; +@property (nonatomic) NSArray *phoneNumbers; +@property (nonatomic) NSArray *emails; +@property (nonatomic) NSArray *addresses; @property (nonatomic, nullable) TSAttachment *avatar; @property (nonatomic) BOOL isProfileAvatar; diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContact.h b/SignalServiceKit/src/Messages/Interactions/OWSContact.h index bd83eff04..46e796931 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSContact.h +++ b/SignalServiceKit/src/Messages/Interactions/OWSContact.h @@ -97,9 +97,9 @@ typedef NS_ENUM(NSUInteger, OWSContactAddressType) { @property (nonatomic, readonly, nullable) NSString *organizationName; @property (nonatomic, readonly, nullable) NSString *displayName; -@property (nonatomic, readonly, nullable) NSArray *phoneNumbers; -@property (nonatomic, readonly, nullable) NSArray *emails; -@property (nonatomic, readonly, nullable) NSArray *addresses; +@property (nonatomic, readonly) NSArray *phoneNumbers; +@property (nonatomic, readonly) NSArray *emails; +@property (nonatomic, readonly) NSArray *addresses; // TODO: This is provisional. @property (nonatomic, readonly, nullable) TSAttachment *avatar; diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContact.m b/SignalServiceKit/src/Messages/Interactions/OWSContact.m index f97cb6473..0c4800b01 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSContact.m +++ b/SignalServiceKit/src/Messages/Interactions/OWSContact.m @@ -168,9 +168,9 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, nullable) NSString *organizationName; @property (nonatomic, nullable) NSString *displayName; -@property (nonatomic, nullable) NSArray *phoneNumbers; -@property (nonatomic, nullable) NSArray *emails; -@property (nonatomic, nullable) NSArray *addresses; +@property (nonatomic) NSArray *phoneNumbers; +@property (nonatomic) NSArray *emails; +@property (nonatomic) NSArray *addresses; @property (nonatomic, nullable) TSAttachment *avatar; @property (nonatomic) BOOL isProfileAvatar; @@ -181,6 +181,17 @@ NS_ASSUME_NONNULL_BEGIN @implementation OWSContact +- (instancetype)init +{ + if (self = [super init]) { + _phoneNumbers = @[]; + _emails = @[]; + _addresses = @[]; + } + + return self; +} + - (void)normalize { self.phoneNumbers = [self.phoneNumbers