Create contact view.

This commit is contained in:
Matthew Chen 2018-05-01 15:53:51 -04:00
parent 2738bcbc58
commit fa5577eece
8 changed files with 407 additions and 74 deletions

View File

@ -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"

View File

@ -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()
}
}

View File

@ -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,

View File

@ -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

View File

@ -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 <NSObject>
@ -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

View File

@ -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

View File

@ -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 -

View File

@ -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 -