session-ios/Signal/src/ViewControllers/ContactViewController.swift

680 lines
29 KiB
Swift
Raw Normal View History

2018-05-01 19:39:48 +02:00
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
2018-05-01 19:39:48 +02:00
//
import Foundation
2020-06-05 02:38:44 +02:00
import SessionServiceKit
2018-05-01 19:39:48 +02:00
import SignalMessaging
import Reachability
2018-05-01 21:53:51 +02:00
import ContactsUI
2018-05-02 16:08:47 +02:00
import MessageUI
2018-05-01 19:39:48 +02:00
class ContactViewController: OWSViewController, ContactShareViewHelperDelegate {
2018-05-01 19:39:48 +02:00
enum ContactViewMode {
case systemContactWithSignal,
systemContactWithoutSignal,
2018-05-01 21:53:51 +02:00
nonSystemContact,
2018-05-01 19:39:48 +02:00
noPhoneNumber,
unknown
}
private var hasLoadedView = false
private var viewMode = ContactViewMode.unknown {
didSet {
AssertIsOnMainThread()
2018-05-01 19:39:48 +02:00
if oldValue != viewMode && hasLoadedView {
updateContent()
}
}
}
private let contactsManager: OWSContactsManager
2018-05-01 19:39:48 +02:00
private var reachability: Reachability?
2018-05-01 19:39:48 +02:00
2018-05-05 04:32:29 +02:00
private let contactShare: ContactShareViewModel
2018-05-01 19:39:48 +02:00
private var contactShareViewHelper: ContactShareViewHelper
private weak var postDismissNavigationController: UINavigationController?
2018-05-01 19:39:48 +02:00
// MARK: - Initializers
@available(*, unavailable, message: "use init(call:) constructor instead.")
required init?(coder aDecoder: NSCoder) {
2018-08-27 16:21:03 +02:00
notImplemented()
2018-05-01 19:39:48 +02:00
}
2018-05-25 23:28:36 +02:00
@objc
required init(contactShare: ContactShareViewModel) {
contactsManager = Environment.shared.contactsManager
2018-05-05 04:32:29 +02:00
self.contactShare = contactShare
self.contactShareViewHelper = ContactShareViewHelper(contactsManager: contactsManager)
2018-05-01 19:39:48 +02:00
super.init(nibName: nil, bundle: nil)
contactShareViewHelper.delegate = self
2018-05-03 20:31:11 +02:00
updateMode()
2018-05-01 19:39:48 +02:00
NotificationCenter.default.addObserver(forName: .OWSContactsManagerSignalAccountsDidChange, object: nil, queue: nil) { [weak self] _ in
guard let strongSelf = self else { return }
2018-05-03 20:31:11 +02:00
strongSelf.updateMode()
2018-05-01 19:39:48 +02:00
}
reachability = Reachability.forInternetConnection()
NotificationCenter.default.addObserver(forName: .reachabilityChanged, object: nil, queue: nil) { [weak self] _ in
guard let strongSelf = self else { return }
2018-05-03 20:31:11 +02:00
strongSelf.updateMode()
2018-05-01 19:39:48 +02:00
}
}
// MARK: - View Lifecycle
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let navigationController = self.navigationController else {
2018-08-27 16:27:48 +02:00
owsFailDebug("navigationController was unexpectedly nil")
return
}
// self.navigationController is nil in viewWillDisappear when transition via message/call buttons
// so we maintain our own reference to restore the navigation bars.
postDismissNavigationController = navigationController
navigationController.isNavigationBarHidden = true
2018-05-01 19:39:48 +02:00
contactsManager.requestSystemContactsOnce(completion: { [weak self] _ in
guard let strongSelf = self else { return }
2018-05-03 20:31:11 +02:00
strongSelf.updateMode()
2018-05-01 19:39:48 +02:00
})
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.presentedViewController == nil {
// No need to do this when we're disappearing due to a modal presentation.
// We'll eventually return to to this view and need to hide again. But also, there is a visible
// animation glitch where the navigation bar for this view controller starts to appear while
// the whole nav stack is about to be obscured by the modal we are presenting.
guard let postDismissNavigationController = self.postDismissNavigationController else {
2018-08-27 16:27:48 +02:00
owsFailDebug("postDismissNavigationController was unexpectedly nil")
return
}
postDismissNavigationController.setNavigationBarHidden(false, animated: animated)
}
}
2018-05-01 21:53:51 +02:00
2018-05-01 19:39:48 +02:00
override func loadView() {
super.loadView()
2018-05-01 21:53:51 +02:00
self.view.preservesSuperviewLayoutMargins = false
2018-05-04 16:30:49 +02:00
self.view.backgroundColor = heroBackgroundColor()
2018-05-01 19:39:48 +02:00
updateContent()
hasLoadedView = true
}
2018-05-03 20:31:11 +02:00
private func updateMode() {
AssertIsOnMainThread()
2018-05-01 19:39:48 +02:00
guard contactShare.e164PhoneNumbers().count > 0 else {
2018-05-01 19:39:48 +02:00
viewMode = .noPhoneNumber
return
}
2018-05-02 16:08:47 +02:00
if systemContactsWithSignalAccountsForContact().count > 0 {
2018-05-01 19:39:48 +02:00
viewMode = .systemContactWithSignal
return
}
2018-05-02 16:08:47 +02:00
if systemContactsForContact().count > 0 {
2018-05-01 19:39:48 +02:00
viewMode = .systemContactWithoutSignal
return
}
2018-05-01 21:53:51 +02:00
viewMode = .nonSystemContact
2018-05-01 19:39:48 +02:00
}
2018-05-02 16:08:47 +02:00
private func systemContactsWithSignalAccountsForContact() -> [String] {
AssertIsOnMainThread()
2018-05-02 16:08:47 +02:00
return contactShare.systemContactsWithSignalAccountPhoneNumbers(contactsManager)
2018-05-02 16:08:47 +02:00
}
private func systemContactsForContact() -> [String] {
AssertIsOnMainThread()
2018-05-02 16:08:47 +02:00
return contactShare.systemContactPhoneNumbers(contactsManager)
}
2018-05-01 19:39:48 +02:00
private func updateContent() {
AssertIsOnMainThread()
2018-05-01 19:39:48 +02:00
guard let rootView = self.view else {
2018-08-27 16:27:48 +02:00
owsFailDebug("missing root view.")
return
}
2018-05-01 21:53:51 +02:00
for subview in rootView.subviews {
2018-05-01 19:39:48 +02:00
subview.removeFromSuperview()
}
let topView = createTopView()
rootView.addSubview(topView)
topView.autoPin(toTopLayoutGuideOf: self, withInset: 0)
topView.autoPinWidthToSuperview()
// This view provides a background "below the fold".
let bottomView = UIView.container()
2018-08-07 23:29:48 +02:00
bottomView.backgroundColor = Theme.backgroundColor
self.view.addSubview(bottomView)
bottomView.layoutMargins = .zero
bottomView.autoPinWidthToSuperview()
bottomView.autoPinEdge(.top, to: .bottom, of: topView)
bottomView.autoPinEdge(toSuperviewEdge: .bottom)
let scrollView = UIScrollView()
scrollView.preservesSuperviewLayoutMargins = false
self.view.addSubview(scrollView)
scrollView.layoutMargins = .zero
scrollView.autoPinWidthToSuperview()
scrollView.autoPinEdge(.top, to: .bottom, of: topView)
scrollView.autoPinEdge(toSuperviewEdge: .bottom)
let fieldsView = createFieldsView()
scrollView.addSubview(fieldsView)
fieldsView.autoPinLeadingToSuperviewMargin()
fieldsView.autoPinTrailingToSuperviewMargin()
fieldsView.autoPinEdge(toSuperviewEdge: .top)
fieldsView.autoPinEdge(toSuperviewEdge: .bottom)
}
2018-05-04 16:30:49 +02:00
private func heroBackgroundColor() -> UIColor {
2018-08-08 15:29:23 +02:00
return (Theme.isDarkThemeEnabled
2018-08-07 23:29:48 +02:00
? UIColor(rgbHex: 0x272727)
: UIColor(rgbHex: 0xefeff4))
2018-05-04 16:30:49 +02:00
}
private func createTopView() -> UIView {
AssertIsOnMainThread()
2018-05-01 19:39:48 +02:00
let topView = UIView.container()
2018-05-04 16:30:49 +02:00
topView.backgroundColor = heroBackgroundColor()
topView.preservesSuperviewLayoutMargins = false
// Back Button
let backButtonSize = CGFloat(50)
2018-05-25 20:59:36 +02:00
let backButton = TappableView(actionBlock: { [weak self] in
guard let strongSelf = self else { return }
strongSelf.didPressDismiss()
})
backButton.autoSetDimension(.width, toSize: backButtonSize)
backButton.autoSetDimension(.height, toSize: backButtonSize)
topView.addSubview(backButton)
backButton.autoPinEdge(toSuperviewEdge: .top)
backButton.autoPinLeadingToSuperviewMargin()
2018-06-29 23:00:22 +02:00
let backIconName = (CurrentAppContext().isRTL ? "system_disclosure_indicator" : "system_disclosure_indicator_rtl")
2018-05-04 19:34:11 +02:00
guard let backIconImage = UIImage(named: backIconName) else {
2018-08-27 16:27:48 +02:00
owsFailDebug("missing icon.")
2018-05-04 19:34:11 +02:00
return topView
}
let backIconView = UIImageView(image: backIconImage.withRenderingMode(.alwaysTemplate))
backIconView.contentMode = .scaleAspectFit
2018-08-07 23:29:48 +02:00
backIconView.tintColor = Theme.primaryColor.withAlphaComponent(0.6)
backButton.addSubview(backIconView)
backIconView.autoCenterInSuperview()
2018-05-01 19:39:48 +02:00
let avatarSize: CGFloat = 100
let avatarView = AvatarImageView()
avatarView.image = contactShare.getAvatarImage(diameter: avatarSize, contactsManager: contactsManager)
2018-05-01 19:39:48 +02:00
topView.addSubview(avatarView)
avatarView.autoPinEdge(toSuperviewEdge: .top, withInset: 20)
2018-05-01 19:39:48 +02:00
avatarView.autoHCenterInSuperview()
avatarView.autoSetDimension(.width, toSize: avatarSize)
avatarView.autoSetDimension(.height, toSize: avatarSize)
let nameLabel = UILabel()
2018-05-05 04:32:29 +02:00
nameLabel.text = contactShare.displayName
2018-05-04 19:06:26 +02:00
nameLabel.font = UIFont.ows_dynamicTypeTitle1
2018-08-07 23:29:48 +02:00
nameLabel.textColor = Theme.primaryColor
2018-05-01 19:39:48 +02:00
nameLabel.lineBreakMode = .byTruncatingTail
nameLabel.textAlignment = .center
topView.addSubview(nameLabel)
nameLabel.autoPinEdge(.top, to: .bottom, of: avatarView, withOffset: 10)
2018-05-02 16:08:47 +02:00
nameLabel.autoPinLeadingToSuperviewMargin(withInset: hMargin)
nameLabel.autoPinTrailingToSuperviewMargin(withInset: hMargin)
2018-05-01 19:39:48 +02:00
var lastView: UIView = nameLabel
2018-05-04 19:06:26 +02:00
for phoneNumber in systemContactsWithSignalAccountsForContact() {
2018-05-01 19:39:48 +02:00
let phoneNumberLabel = UILabel()
2018-05-04 19:06:26 +02:00
phoneNumberLabel.text = PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: phoneNumber)
phoneNumberLabel.font = UIFont.ows_dynamicTypeFootnote
2018-08-07 23:29:48 +02:00
phoneNumberLabel.textColor = Theme.primaryColor
2018-05-01 19:39:48 +02:00
phoneNumberLabel.lineBreakMode = .byTruncatingTail
phoneNumberLabel.textAlignment = .center
topView.addSubview(phoneNumberLabel)
2018-05-01 21:53:51 +02:00
phoneNumberLabel.autoPinEdge(.top, to: .bottom, of: lastView, withOffset: 5)
2018-05-02 16:08:47 +02:00
phoneNumberLabel.autoPinLeadingToSuperviewMargin(withInset: hMargin)
phoneNumberLabel.autoPinTrailingToSuperviewMargin(withInset: hMargin)
2018-05-01 19:39:48 +02:00
lastView = phoneNumberLabel
}
switch viewMode {
case .systemContactWithSignal:
2018-05-01 21:53:51 +02:00
// Show actions buttons for system contacts with a Signal account.
2018-05-02 16:08:47 +02:00
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.distribution = .fillEqually
stackView.addArrangedSubview(createCircleActionButton(text: NSLocalizedString("ACTION_SEND_MESSAGE",
2018-07-05 23:27:37 +02:00
comment: "Label for 'send message' button in contact view."),
2018-05-08 21:52:19 +02:00
imageName: "contact_view_message",
2018-05-25 20:59:36 +02:00
actionBlock: { [weak self] in
2018-05-02 16:08:47 +02:00
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."),
2018-05-08 21:52:19 +02:00
imageName: "contact_view_audio_call",
2018-05-25 20:59:36 +02:00
actionBlock: { [weak self] in
2018-05-02 16:08:47 +02:00
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."),
2018-05-08 21:52:19 +02:00
imageName: "contact_view_video_call",
2018-05-25 20:59:36 +02:00
actionBlock: { [weak self] in
2018-05-02 16:08:47 +02:00
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
2018-05-01 19:39:48 +02:00
case .systemContactWithoutSignal:
2018-05-01 21:53:51 +02:00
// Show invite button for system contacts without a Signal account.
2018-05-02 16:08:47 +02:00
let inviteButton = createLargePillButton(text: NSLocalizedString("ACTION_INVITE",
2018-05-04 19:06:26 +02:00
comment: "Label for 'invite' button in contact view."),
2018-05-25 20:59:36 +02:00
actionBlock: { [weak self] in
2018-05-02 16:08:47 +02:00
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
2018-05-01 21:53:51 +02:00
case .nonSystemContact:
2018-05-10 19:14:30 +02:00
// Show no action buttons for non-system contacts.
2018-05-01 19:39:48 +02:00
break
case .noPhoneNumber:
2018-05-01 21:53:51 +02:00
// Show no action buttons for contacts without a phone number.
2018-05-01 19:39:48 +02:00
break
case .unknown:
2019-03-30 14:22:31 +01:00
let activityIndicator = UIActivityIndicatorView(style: .whiteLarge)
2018-05-01 19:39:48 +02:00
topView.addSubview(activityIndicator)
activityIndicator.autoPinEdge(.top, to: .bottom, of: lastView, withOffset: 10)
activityIndicator.autoHCenterInSuperview()
lastView = activityIndicator
break
}
2018-05-10 19:14:30 +02:00
// Always show "add to contacts" button.
let addToContactsButton = createLargePillButton(text: NSLocalizedString("CONVERSATION_VIEW_ADD_TO_CONTACTS_OFFER",
comment: "Message shown in conversation view that offers to add an unknown user to your phone's contacts."),
2018-05-25 20:59:36 +02:00
actionBlock: { [weak self] in
2018-05-10 19:14:30 +02:00
guard let strongSelf = self else { return }
strongSelf.didPressAddToContacts()
})
topView.addSubview(addToContactsButton)
addToContactsButton.autoPinEdge(.top, to: .bottom, of: lastView, withOffset: 20)
addToContactsButton.autoPinLeadingToSuperviewMargin(withInset: 55)
addToContactsButton.autoPinTrailingToSuperviewMargin(withInset: 55)
lastView = addToContactsButton
2018-05-01 21:53:51 +02:00
lastView.autoPinEdge(toSuperviewEdge: .bottom, withInset: 15)
return topView
}
private func createFieldsView() -> UIView {
AssertIsOnMainThread()
2018-05-04 19:57:29 +02:00
var rows = [UIView]()
2018-05-01 21:53:51 +02:00
// 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 organizationName = contactShare.name.organizationName?.ows_stripped() {
if (contactShare.name.hasAnyNamePart() &&
organizationName.count > 0) {
rows.append(ContactFieldView.contactFieldView(forOrganizationName: organizationName,
layoutMargins: UIEdgeInsets(top: 5, left: hMargin, bottom: 5, right: hMargin)))
}
}
2018-05-05 04:32:29 +02:00
for phoneNumber in contactShare.phoneNumbers {
rows.append(ContactFieldView.contactFieldView(forPhoneNumber: phoneNumber,
layoutMargins: UIEdgeInsets(top: 5, left: hMargin, bottom: 5, right: hMargin),
2018-05-25 20:59:36 +02:00
actionBlock: { [weak self] in
2018-05-09 20:26:51 +02:00
guard let strongSelf = self else { return }
strongSelf.didPressPhoneNumber(phoneNumber: phoneNumber)
2018-05-03 20:31:11 +02:00
}))
2018-05-01 21:53:51 +02:00
}
2018-05-05 04:32:29 +02:00
for email in contactShare.emails {
rows.append(ContactFieldView.contactFieldView(forEmail: email,
layoutMargins: UIEdgeInsets(top: 5, left: hMargin, bottom: 5, right: hMargin),
2018-05-25 20:59:36 +02:00
actionBlock: { [weak self] in
2018-05-09 20:26:51 +02:00
guard let strongSelf = self else { return }
strongSelf.didPressEmail(email: email)
2018-05-03 20:31:11 +02:00
}))
2018-05-01 21:53:51 +02:00
}
2018-05-04 19:06:26 +02:00
for address in contactShare.addresses {
rows.append(ContactFieldView.contactFieldView(forAddress: address,
layoutMargins: UIEdgeInsets(top: 5, left: hMargin, bottom: 5, right: hMargin),
2018-05-25 20:59:36 +02:00
actionBlock: { [weak self] in
guard let strongSelf = self else { return }
strongSelf.didPressAddress(address: address)
2018-05-04 19:06:26 +02:00
}))
}
2018-05-01 21:53:51 +02:00
2018-05-04 19:57:29 +02:00
return ContactFieldView(rows: rows, hMargin: hMargin)
2018-05-01 21:53:51 +02:00
}
2018-05-02 16:08:47 +02:00
private let hMargin = CGFloat(16)
2018-05-01 21:53:51 +02:00
private func createActionRow(labelText: String, action: Selector) -> UIView {
let row = UIView()
2018-05-02 16:08:47 +02:00
row.layoutMargins.left = 0
row.layoutMargins.right = 0
2018-05-01 21:53:51 +02:00
row.isUserInteractionEnabled = true
row.addGestureRecognizer(UITapGestureRecognizer(target: self, action: action))
2018-05-02 16:08:47 +02:00
2018-05-01 21:53:51 +02:00
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)
2018-05-02 16:08:47 +02:00
2018-05-01 21:53:51 +02:00
return row
}
2018-05-02 16:08:47 +02:00
// TODO: Use real assets.
2018-05-08 21:52:19 +02:00
private func createCircleActionButton(text: String, imageName: String, actionBlock : @escaping () -> Void) -> UIView {
2018-05-02 16:08:47 +02:00
let buttonSize = CGFloat(50)
2018-05-01 19:39:48 +02:00
2018-05-02 16:08:47 +02:00
let button = TappableView(actionBlock: actionBlock)
button.layoutMargins = .zero
button.autoSetDimension(.width, toSize: buttonSize, relation: .greaterThanOrEqual)
let circleView = UIView()
2018-08-07 23:29:48 +02:00
circleView.backgroundColor = Theme.backgroundColor
2018-05-02 16:08:47 +02:00
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()
2018-05-08 21:52:19 +02:00
guard let image = UIImage(named: imageName) else {
2018-08-27 16:27:48 +02:00
owsFailDebug("missing image.")
2018-05-08 21:52:19 +02:00
return button
}
2018-08-07 23:29:48 +02:00
let imageView = UIImageView(image: image.withRenderingMode(.alwaysTemplate))
imageView.tintColor = Theme.primaryColor.withAlphaComponent(0.6)
2018-05-08 21:52:19 +02:00
circleView.addSubview(imageView)
imageView.autoCenterInSuperview()
2018-05-02 16:08:47 +02:00
let label = UILabel()
label.text = text
label.font = UIFont.ows_dynamicTypeCaption2
2018-08-07 23:29:48 +02:00
label.textColor = Theme.primaryColor
2018-05-02 16:08:47 +02:00
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)
2018-08-07 23:29:48 +02:00
button.backgroundColor = Theme.backgroundColor
2018-05-02 16:08:47 +02:00
button.layoutMargins = .zero
button.autoSetDimension(.height, toSize: 45)
button.layer.cornerRadius = 5
let label = UILabel()
label.text = text
2018-05-04 19:06:26 +02:00
label.font = UIFont.ows_dynamicTypeBody
2018-05-02 16:08:47 +02:00
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
}
2018-05-01 21:53:51 +02:00
func didPressShareContact(sender: UIGestureRecognizer) {
2018-08-23 16:37:34 +02:00
Logger.info("")
2018-05-01 21:53:51 +02:00
guard sender.state == .recognized else {
return
}
// TODO:
}
2018-05-02 16:08:47 +02:00
func didPressSendMessage() {
2018-08-23 16:37:34 +02:00
Logger.info("")
2018-05-01 21:53:51 +02:00
self.contactShareViewHelper.sendMessage(contactShare: self.contactShare, fromViewController: self)
2018-05-02 16:08:47 +02:00
}
func didPressAudioCall() {
2018-08-23 16:37:34 +02:00
Logger.info("")
2018-05-02 16:08:47 +02:00
self.contactShareViewHelper.audioCall(contactShare: self.contactShare, fromViewController: self)
2018-05-02 16:08:47 +02:00
}
func didPressVideoCall() {
2018-08-23 16:37:34 +02:00
Logger.info("")
2018-05-02 16:08:47 +02:00
self.contactShareViewHelper.videoCall(contactShare: self.contactShare, fromViewController: self)
2018-05-02 16:08:47 +02:00
}
func didPressInvite() {
2018-08-23 16:37:34 +02:00
Logger.info("")
2018-05-02 16:08:47 +02:00
2018-05-09 17:13:29 +02:00
self.contactShareViewHelper.showInviteContact(contactShare: self.contactShare, fromViewController: self)
2018-05-02 16:08:47 +02:00
}
2018-05-01 21:53:51 +02:00
2018-05-04 19:06:26 +02:00
func didPressAddToContacts() {
2018-08-23 16:37:34 +02:00
Logger.info("")
2018-05-04 19:06:26 +02:00
2018-05-09 17:13:29 +02:00
self.contactShareViewHelper.showAddToContacts(contactShare: self.contactShare, fromViewController: self)
}
func didPressDismiss() {
2018-08-23 16:37:34 +02:00
Logger.info("")
guard let navigationController = self.navigationController else {
2018-08-27 16:27:48 +02:00
owsFailDebug("navigationController was unexpectedly nil")
return
}
navigationController.popViewController(animated: true)
}
2018-05-09 20:26:51 +02:00
func didPressPhoneNumber(phoneNumber: OWSContactPhoneNumber) {
2018-08-23 16:37:34 +02:00
Logger.info("")
2018-05-09 20:26:51 +02:00
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
if let e164 = phoneNumber.tryToConvertToE164() {
if contactShare.systemContactsWithSignalAccountPhoneNumbers(contactsManager).contains(e164) {
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("ACTION_SEND_MESSAGE",
2018-07-05 23:27:37 +02:00
comment: "Label for 'send message' button in contact view."),
2018-05-09 20:26:51 +02:00
style: .default) { _ in
Faster conversation presentation. There are multiple places in the codebase we present a conversation. We used to have some very conservative machinery around how this was done, for fear of failing to present the call view controller, which would have left a hidden call in the background. We've since addressed that concern more thoroughly via the separate calling UIWindow. As such, the remaining presentation machinery is overly complex and inflexible for what we need. Sometimes we want to animate-push the conversation. (tap on home, tap on "send message" in contact card/group members) Sometimes we want to dismiss a modal, to reveal the conversation behind it (contact picker, group creation) Sometimes we want to present the conversation with no animation (becoming active from a notification) We also want to ensure that we're never pushing more than one conversation view controller, which was previously a problem since we were "pushing" a newly constructed VC in response to these myriad actions. It turned out there were certain code paths that caused multiple actions to be fired in rapid succession which pushed multiple ConversationVC's. The built-in method: `setViewControllers:animated` easily ensures we only have one ConversationVC on the stack, while being composable enough to faciliate the various more efficient animations we desire. The only thing lost with the complex methods is that the naive `presentViewController:` can fail, e.g. if another view is already presented. E.g. if an alert appears *just* before the user taps compose, the contact picker will fail to present. Since we no longer depend on this for presenting the CallViewController, this isn't catostrophic, and in fact, arguable preferable, since we want the user to read and dismiss any alert explicitly. // FREEBIE
2018-08-18 22:54:35 +02:00
SignalApp.shared().presentConversation(forRecipientId: e164, action: .compose, animated: true)
2018-05-09 20:26:51 +02:00
})
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("ACTION_AUDIO_CALL",
comment: "Label for 'audio call' button in contact view."),
style: .default) { _ in
Faster conversation presentation. There are multiple places in the codebase we present a conversation. We used to have some very conservative machinery around how this was done, for fear of failing to present the call view controller, which would have left a hidden call in the background. We've since addressed that concern more thoroughly via the separate calling UIWindow. As such, the remaining presentation machinery is overly complex and inflexible for what we need. Sometimes we want to animate-push the conversation. (tap on home, tap on "send message" in contact card/group members) Sometimes we want to dismiss a modal, to reveal the conversation behind it (contact picker, group creation) Sometimes we want to present the conversation with no animation (becoming active from a notification) We also want to ensure that we're never pushing more than one conversation view controller, which was previously a problem since we were "pushing" a newly constructed VC in response to these myriad actions. It turned out there were certain code paths that caused multiple actions to be fired in rapid succession which pushed multiple ConversationVC's. The built-in method: `setViewControllers:animated` easily ensures we only have one ConversationVC on the stack, while being composable enough to faciliate the various more efficient animations we desire. The only thing lost with the complex methods is that the naive `presentViewController:` can fail, e.g. if another view is already presented. E.g. if an alert appears *just* before the user taps compose, the contact picker will fail to present. Since we no longer depend on this for presenting the CallViewController, this isn't catostrophic, and in fact, arguable preferable, since we want the user to read and dismiss any alert explicitly. // FREEBIE
2018-08-18 22:54:35 +02:00
SignalApp.shared().presentConversation(forRecipientId: e164, action: .audioCall, animated: true)
2018-05-09 20:26:51 +02:00
})
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("ACTION_VIDEO_CALL",
comment: "Label for 'video call' button in contact view."),
style: .default) { _ in
Faster conversation presentation. There are multiple places in the codebase we present a conversation. We used to have some very conservative machinery around how this was done, for fear of failing to present the call view controller, which would have left a hidden call in the background. We've since addressed that concern more thoroughly via the separate calling UIWindow. As such, the remaining presentation machinery is overly complex and inflexible for what we need. Sometimes we want to animate-push the conversation. (tap on home, tap on "send message" in contact card/group members) Sometimes we want to dismiss a modal, to reveal the conversation behind it (contact picker, group creation) Sometimes we want to present the conversation with no animation (becoming active from a notification) We also want to ensure that we're never pushing more than one conversation view controller, which was previously a problem since we were "pushing" a newly constructed VC in response to these myriad actions. It turned out there were certain code paths that caused multiple actions to be fired in rapid succession which pushed multiple ConversationVC's. The built-in method: `setViewControllers:animated` easily ensures we only have one ConversationVC on the stack, while being composable enough to faciliate the various more efficient animations we desire. The only thing lost with the complex methods is that the naive `presentViewController:` can fail, e.g. if another view is already presented. E.g. if an alert appears *just* before the user taps compose, the contact picker will fail to present. Since we no longer depend on this for presenting the CallViewController, this isn't catostrophic, and in fact, arguable preferable, since we want the user to read and dismiss any alert explicitly. // FREEBIE
2018-08-18 22:54:35 +02:00
SignalApp.shared().presentConversation(forRecipientId: e164, action: .videoCall, animated: true)
2018-05-09 20:26:51 +02:00
})
} else {
// TODO: We could offer callPhoneNumberWithSystemCall.
}
}
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("EDIT_ITEM_COPY_ACTION",
comment: "Short name for edit menu item to copy contents of media message."),
style: .default) { _ in
UIPasteboard.general.string = phoneNumber.phoneNumber
})
actionSheet.addAction(OWSAlerts.cancelAction)
presentAlert(actionSheet)
2018-05-09 20:26:51 +02:00
}
func callPhoneNumberWithSystemCall(phoneNumber: OWSContactPhoneNumber) {
2018-08-23 16:37:34 +02:00
Logger.info("")
2018-05-09 20:26:51 +02:00
guard let url = NSURL(string: "tel:\(phoneNumber.phoneNumber)") else {
2018-08-27 16:27:48 +02:00
owsFailDebug("could not open phone number.")
2018-05-09 20:26:51 +02:00
return
}
UIApplication.shared.openURL(url as URL)
}
func didPressEmail(email: OWSContactEmail) {
2018-08-23 16:37:34 +02:00
Logger.info("")
2018-05-09 20:26:51 +02:00
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
2018-05-09 20:36:05 +02:00
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("CONTACT_VIEW_OPEN_EMAIL_IN_EMAIL_APP",
2018-05-09 20:26:51 +02:00
comment: "Label for 'open email in email app' button in contact view."),
style: .default) { [weak self] _ in
self?.openEmailInEmailApp(email: email)
})
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("EDIT_ITEM_COPY_ACTION",
comment: "Short name for edit menu item to copy contents of media message."),
style: .default) { _ in
UIPasteboard.general.string = email.email
})
actionSheet.addAction(OWSAlerts.cancelAction)
presentAlert(actionSheet)
2018-05-09 20:26:51 +02:00
}
func openEmailInEmailApp(email: OWSContactEmail) {
2018-08-23 16:37:34 +02:00
Logger.info("")
2018-05-09 20:26:51 +02:00
guard let url = NSURL(string: "mailto:\(email.email)") else {
2018-08-27 16:27:48 +02:00
owsFailDebug("could not open email.")
2018-05-09 20:26:51 +02:00
return
}
UIApplication.shared.openURL(url as URL)
}
2018-05-04 19:06:26 +02:00
func didPressAddress(address: OWSContactAddress) {
2018-08-23 16:37:34 +02:00
Logger.info("")
2018-05-04 19:06:26 +02:00
2018-05-09 20:26:51 +02:00
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("CONTACT_VIEW_OPEN_ADDRESS_IN_MAPS_APP",
comment: "Label for 'open address in maps app' button in contact view."),
style: .default) { [weak self] _ in
self?.openAddressInMaps(address: address)
})
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("EDIT_ITEM_COPY_ACTION",
comment: "Short name for edit menu item to copy contents of media message."),
style: .default) { [weak self] _ in
guard let strongSelf = self else { return }
UIPasteboard.general.string = strongSelf.formatAddressForQuery(address: address)
})
actionSheet.addAction(OWSAlerts.cancelAction)
presentAlert(actionSheet)
2018-05-09 20:26:51 +02:00
}
func openAddressInMaps(address: OWSContactAddress) {
2018-08-23 16:37:34 +02:00
Logger.info("")
2018-05-09 20:26:51 +02:00
let mapAddress = formatAddressForQuery(address: address)
guard let escapedMapAddress = mapAddress.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
2018-08-27 16:27:48 +02:00
owsFailDebug("could not open address.")
2018-05-09 20:26:51 +02:00
return
}
// Note that we use "q" (i.e. query) rather than "address" since we can't assume
// this is a well-formed address.
guard let url = URL(string: "http://maps.apple.com/?q=\(escapedMapAddress)") else {
2018-08-27 16:27:48 +02:00
owsFailDebug("could not open address.")
2018-05-09 20:26:51 +02:00
return
}
UIApplication.shared.openURL(url as URL)
}
func formatAddressForQuery(address: OWSContactAddress) -> String {
2018-08-23 16:37:34 +02:00
Logger.info("")
2018-05-09 20:26:51 +02:00
2018-05-04 19:06:26 +02:00
// Open address in Apple Maps app.
var addressParts = [String]()
let addAddressPart: ((String?) -> Void) = { (part) in
guard let part = part else {
return
}
guard part.count > 0 else {
return
}
addressParts.append(part)
}
addAddressPart(address.street)
addAddressPart(address.neighborhood)
addAddressPart(address.city)
addAddressPart(address.region)
addAddressPart(address.postcode)
addAddressPart(address.country)
2018-05-09 20:26:51 +02:00
return addressParts.joined(separator: ", ")
2018-05-04 19:06:26 +02:00
}
// MARK: - ContactShareViewHelperDelegate
2018-05-01 21:53:51 +02:00
public func didCreateOrEditContact() {
2018-08-23 16:37:34 +02:00
Logger.info("")
2018-05-01 21:53:51 +02:00
updateContent()
self.dismiss(animated: true)
2018-05-01 21:53:51 +02:00
}
2018-05-01 19:39:48 +02:00
}