mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Add contact share actions to conversation view and improve phone number parsing.
This commit is contained in:
parent
445c10a7ea
commit
44ceee5849
20 changed files with 707 additions and 191 deletions
|
@ -166,6 +166,7 @@
|
|||
348BB254209CD4B80047AEC2 /* ContactFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348BB253209CD4B80047AEC2 /* ContactFieldView.swift */; };
|
||||
348BB25A209CF8E50047AEC2 /* TappableStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348BB258209CF8E40047AEC2 /* TappableStackView.swift */; };
|
||||
348BB25B209CF8E50047AEC2 /* TappableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348BB259209CF8E50047AEC2 /* TappableView.swift */; };
|
||||
348BB25D20A0C5530047AEC2 /* ContactShareViewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348BB25C20A0C5530047AEC2 /* ContactShareViewHelper.swift */; };
|
||||
3496744D2076768700080B5F /* OWSMessageBubbleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3496744C2076768700080B5F /* OWSMessageBubbleView.m */; };
|
||||
3496744F2076ACD000080B5F /* LongTextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3496744E2076ACCE00080B5F /* LongTextViewController.swift */; };
|
||||
34A55F3720485465002CC6DE /* OWS2FARegistrationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A55F3520485464002CC6DE /* OWS2FARegistrationViewController.m */; };
|
||||
|
@ -766,6 +767,7 @@
|
|||
348BB253209CD4B80047AEC2 /* ContactFieldView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ContactFieldView.swift; path = SignalMessaging/attachments/ContactFieldView.swift; sourceTree = SOURCE_ROOT; };
|
||||
348BB258209CF8E40047AEC2 /* TappableStackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TappableStackView.swift; path = SignalMessaging/Views/TappableStackView.swift; sourceTree = SOURCE_ROOT; };
|
||||
348BB259209CF8E50047AEC2 /* TappableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TappableView.swift; path = SignalMessaging/Views/TappableView.swift; sourceTree = SOURCE_ROOT; };
|
||||
348BB25C20A0C5530047AEC2 /* ContactShareViewHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactShareViewHelper.swift; sourceTree = "<group>"; };
|
||||
348F2EAD1F0D21BC00D4ECE0 /* DeviceSleepManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceSleepManager.swift; sourceTree = "<group>"; };
|
||||
3495BC911F1426B800B478F5 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = translations/ar.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
3496744B2076768600080B5F /* OWSMessageBubbleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageBubbleView.h; sourceTree = "<group>"; };
|
||||
|
@ -1641,6 +1643,7 @@
|
|||
34D5CCA71EAE3D30005515DB /* AvatarViewHelper.h */,
|
||||
34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */,
|
||||
34B3F83B1E8DF1700035BE1A /* CallViewController.swift */,
|
||||
348BB25C20A0C5530047AEC2 /* ContactShareViewHelper.swift */,
|
||||
34B3F83E1E8DF1700035BE1A /* ContactsPicker.swift */,
|
||||
34B3F83F1E8DF1700035BE1A /* ContactsPicker.xib */,
|
||||
34E88D252098C5AE00A608F4 /* ContactViewController.swift */,
|
||||
|
@ -1651,6 +1654,7 @@
|
|||
34BECE2C1F7ABCE000D7438D /* GifPicker */,
|
||||
34386A4C207D0C01009F5D9C /* HomeView */,
|
||||
34B3F84C1E8DF1700035BE1A /* InviteFlow.swift */,
|
||||
4542DF53208D40AC007B4E76 /* LoadingViewController.swift */,
|
||||
3496744E2076ACCE00080B5F /* LongTextViewController.swift */,
|
||||
45B9EE9A200E91FB005D2F2D /* MediaDetailViewController.h */,
|
||||
45B9EE9B200E91FB005D2F2D /* MediaDetailViewController.m */,
|
||||
|
@ -1677,7 +1681,6 @@
|
|||
34B3F86E1E8DF1700035BE1A /* SignalsNavigationController.m */,
|
||||
340FC897204DAC8D007AEB0F /* ThreadSettings */,
|
||||
34D1F0BE1F8EC1760066283D /* Utils */,
|
||||
4542DF53208D40AC007B4E76 /* LoadingViewController.swift */,
|
||||
);
|
||||
path = ViewControllers;
|
||||
sourceTree = "<group>";
|
||||
|
@ -3249,6 +3252,7 @@
|
|||
4542DF54208D40AC007B4E76 /* LoadingViewController.swift in Sources */,
|
||||
34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */,
|
||||
34D1F0B71F87F8850066283D /* OWSGenericAttachmentView.m in Sources */,
|
||||
348BB25D20A0C5530047AEC2 /* ContactShareViewHelper.swift in Sources */,
|
||||
34B3F8801E8DF1700035BE1A /* InviteFlow.swift in Sources */,
|
||||
457C87B82032645C008D52D6 /* DebugUINotifications.swift in Sources */,
|
||||
340FC8D0205BF2FA007AEB0F /* OWSBackupIO.m in Sources */,
|
||||
|
|
289
Signal/src/ViewControllers/ContactShareViewHelper.swift
Normal file
289
Signal/src/ViewControllers/ContactShareViewHelper.swift
Normal file
|
@ -0,0 +1,289 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SignalServiceKit
|
||||
import ContactsUI
|
||||
import MessageUI
|
||||
|
||||
@objc
|
||||
public protocol ContactShareViewHelperDelegate: class {
|
||||
func didCreateOrEditContact()
|
||||
}
|
||||
|
||||
@objc
|
||||
public class ContactShareViewHelper: NSObject, CNContactViewControllerDelegate {
|
||||
|
||||
weak var delegate: ContactShareViewHelperDelegate?
|
||||
|
||||
let contactShare: ContactShareViewModel
|
||||
let contactsManager: OWSContactsManager
|
||||
weak var fromViewController: UIViewController?
|
||||
|
||||
public required init(contactShare: ContactShareViewModel, contactsManager: OWSContactsManager, fromViewController: UIViewController, delegate: ContactShareViewHelperDelegate) {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
self.contactShare = contactShare
|
||||
self.contactsManager = contactsManager
|
||||
self.fromViewController = fromViewController
|
||||
self.delegate = delegate
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
// MARK: Actions
|
||||
|
||||
@objc
|
||||
public func sendMessageToContact() {
|
||||
Logger.info("\(logTag) \(#function)")
|
||||
|
||||
presentThreadAndPeform(action: .compose)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func audioCallToContact() {
|
||||
Logger.info("\(logTag) \(#function)")
|
||||
|
||||
presentThreadAndPeform(action: .audioCall)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func videoCallToContact() {
|
||||
Logger.info("\(logTag) \(#function)")
|
||||
|
||||
presentThreadAndPeform(action: .videoCall)
|
||||
}
|
||||
|
||||
private func presentThreadAndPeform(action: ConversationViewAction) {
|
||||
// TODO: We're taking the first Signal account id. We might
|
||||
// want to let the user select if there's more than one.
|
||||
let phoneNumbers = contactShare.systemContactsWithSignalAccountPhoneNumbers(contactsManager)
|
||||
guard phoneNumbers.count > 0 else {
|
||||
owsFail("\(logTag) missing Signal recipient id.")
|
||||
return
|
||||
}
|
||||
guard phoneNumbers.count > 1 else {
|
||||
let recipientId = phoneNumbers.first!
|
||||
SignalApp.shared().presentConversation(forRecipientId: recipientId, action: action)
|
||||
return
|
||||
}
|
||||
|
||||
showPhoneNumberPicker(phoneNumbers: phoneNumbers, completion: { (recipientId) in
|
||||
SignalApp.shared().presentConversation(forRecipientId: recipientId, action: action)
|
||||
})
|
||||
}
|
||||
|
||||
@objc
|
||||
public func inviteContact() {
|
||||
Logger.info("\(logTag) \(#function)")
|
||||
|
||||
guard let fromViewController = fromViewController else {
|
||||
owsFail("\(logTag) missing fromViewController")
|
||||
return
|
||||
}
|
||||
|
||||
guard MFMessageComposeViewController.canSendText() else {
|
||||
Logger.info("\(logTag) Device cannot send text")
|
||||
OWSAlerts.showErrorAlert(message: NSLocalizedString("UNSUPPORTED_FEATURE_ERROR", comment: ""))
|
||||
return
|
||||
}
|
||||
let phoneNumbers = contactShare.e164PhoneNumbers()
|
||||
guard phoneNumbers.count > 0 else {
|
||||
owsFail("\(logTag) no phone numbers.")
|
||||
return
|
||||
}
|
||||
|
||||
let inviteFlow =
|
||||
InviteFlow(presentingViewController: fromViewController, contactsManager: contactsManager)
|
||||
inviteFlow.sendSMSTo(phoneNumbers: phoneNumbers)
|
||||
}
|
||||
|
||||
func addToContacts() {
|
||||
Logger.info("\(logTag) \(#function)")
|
||||
|
||||
guard let fromViewController = fromViewController else {
|
||||
owsFail("\(logTag) missing fromViewController")
|
||||
return
|
||||
}
|
||||
|
||||
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
|
||||
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("CONVERSATION_SETTINGS_NEW_CONTACT",
|
||||
comment: "Label for 'new contact' button in conversation settings view."),
|
||||
style: .default) { _ in
|
||||
self.didPressCreateNewContact()
|
||||
})
|
||||
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("CONVERSATION_SETTINGS_ADD_TO_EXISTING_CONTACT",
|
||||
comment: "Label for 'new contact' button in conversation settings view."),
|
||||
style: .default) { _ in
|
||||
self.didPressAddToExistingContact()
|
||||
})
|
||||
actionSheet.addAction(OWSAlerts.cancelAction)
|
||||
|
||||
fromViewController.present(actionSheet, animated: true)
|
||||
}
|
||||
|
||||
private func showPhoneNumberPicker(phoneNumbers: [String], completion :@escaping ((String) -> Void)) {
|
||||
|
||||
guard let fromViewController = fromViewController else {
|
||||
owsFail("\(logTag) missing fromViewController")
|
||||
return
|
||||
}
|
||||
|
||||
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
|
||||
for phoneNumber in phoneNumbers {
|
||||
actionSheet.addAction(UIAlertAction(title: PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: phoneNumber),
|
||||
style: .default) { _ in
|
||||
completion(phoneNumber)
|
||||
})
|
||||
}
|
||||
actionSheet.addAction(OWSAlerts.cancelAction)
|
||||
|
||||
fromViewController.present(actionSheet, animated: true)
|
||||
}
|
||||
|
||||
func didPressCreateNewContact() {
|
||||
Logger.info("\(logTag) \(#function)")
|
||||
|
||||
presentNewContactView()
|
||||
}
|
||||
|
||||
func didPressAddToExistingContact() {
|
||||
Logger.info("\(logTag) \(#function)")
|
||||
|
||||
presentSelectAddToExistingContactView()
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
private func presentNewContactView() {
|
||||
|
||||
guard let fromViewController = fromViewController else {
|
||||
owsFail("\(logTag) missing fromViewController")
|
||||
return
|
||||
}
|
||||
|
||||
guard contactsManager.supportsContactEditing else {
|
||||
owsFail("\(logTag) Contact editing not supported")
|
||||
return
|
||||
}
|
||||
|
||||
guard let systemContact = OWSContacts.systemContact(for: contactShare.dbRecord) else {
|
||||
owsFail("\(logTag) Could not derive system contact.")
|
||||
return
|
||||
}
|
||||
|
||||
guard contactsManager.isSystemContactsAuthorized else {
|
||||
ContactsViewHelper.presentMissingContactAccessAlertController(from: fromViewController)
|
||||
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))
|
||||
|
||||
guard let navigationController = fromViewController.navigationController else {
|
||||
owsFail("\(logTag) missing navigationController")
|
||||
return
|
||||
}
|
||||
|
||||
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 let fromViewController = fromViewController else {
|
||||
owsFail("\(logTag) missing fromViewController")
|
||||
return
|
||||
}
|
||||
|
||||
guard contactsManager.supportsContactEditing else {
|
||||
owsFail("\(logTag) Contact editing not supported")
|
||||
return
|
||||
}
|
||||
|
||||
guard contactsManager.isSystemContactsAuthorized else {
|
||||
ContactsViewHelper.presentMissingContactAccessAlertController(from: fromViewController)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Revisit this.
|
||||
guard let firstPhoneNumber = contactShare.e164PhoneNumbers().first else {
|
||||
owsFail("\(logTag) Missing phone number.")
|
||||
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)
|
||||
|
||||
guard let navigationController = fromViewController.navigationController else {
|
||||
owsFail("\(logTag) missing navigationController")
|
||||
return
|
||||
}
|
||||
|
||||
navigationController.pushViewController(viewController, animated: true)
|
||||
}
|
||||
|
||||
// MARK: - CNContactViewControllerDelegate
|
||||
|
||||
@objc public func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
|
||||
Logger.info("\(logTag) \(#function)")
|
||||
|
||||
guard let fromViewController = fromViewController else {
|
||||
owsFail("\(logTag) missing fromViewController")
|
||||
return
|
||||
}
|
||||
|
||||
guard let navigationController = fromViewController.navigationController else {
|
||||
owsFail("\(logTag) missing navigationController")
|
||||
return
|
||||
}
|
||||
|
||||
guard let delegate = delegate else {
|
||||
owsFail("\(logTag) missing delegate")
|
||||
return
|
||||
}
|
||||
|
||||
navigationController.popToViewController(fromViewController, animated: true)
|
||||
|
||||
delegate.didCreateOrEditContact()
|
||||
}
|
||||
|
||||
@objc public func didFinishEditingContact() {
|
||||
Logger.info("\(logTag) \(#function)")
|
||||
|
||||
guard let fromViewController = fromViewController else {
|
||||
owsFail("\(logTag) missing fromViewController")
|
||||
return
|
||||
}
|
||||
|
||||
guard let navigationController = fromViewController.navigationController else {
|
||||
owsFail("\(logTag) missing navigationController")
|
||||
return
|
||||
}
|
||||
|
||||
guard let delegate = delegate else {
|
||||
owsFail("\(logTag) missing delegate")
|
||||
return
|
||||
}
|
||||
|
||||
navigationController.popToViewController(fromViewController, animated: true)
|
||||
|
||||
delegate.didCreateOrEditContact()
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ import Reachability
|
|||
import ContactsUI
|
||||
import MessageUI
|
||||
|
||||
class ContactViewController: OWSViewController, CNContactViewControllerDelegate {
|
||||
class ContactViewController: OWSViewController, ContactShareViewHelperDelegate {
|
||||
|
||||
enum ContactViewMode {
|
||||
case systemContactWithSignal,
|
||||
|
@ -37,6 +37,8 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate
|
|||
|
||||
private let contactShare: ContactShareViewModel
|
||||
|
||||
private var helper: ContactShareViewHelper!
|
||||
|
||||
// MARK: - Initializers
|
||||
|
||||
@available(*, unavailable, message: "use init(call:) constructor instead.")
|
||||
|
@ -50,6 +52,8 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate
|
|||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
self.helper = ContactShareViewHelper(contactShare: contactShare, contactsManager: contactsManager, fromViewController: self, delegate: self)
|
||||
|
||||
updateMode()
|
||||
|
||||
NotificationCenter.default.addObserver(forName: .OWSContactsManagerSignalAccountsDidChange, object: nil, queue: nil) { [weak self] _ in
|
||||
|
@ -110,7 +114,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate
|
|||
private func updateMode() {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
guard contactShare.phoneNumberStrings.count > 0 else {
|
||||
guard contactShare.e164PhoneNumbers().count > 0 else {
|
||||
viewMode = .noPhoneNumber
|
||||
return
|
||||
}
|
||||
|
@ -129,17 +133,19 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate
|
|||
private func systemContactsWithSignalAccountsForContact() -> [String] {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
return contactShare.phoneNumberStrings.filter({ (phoneNumber) -> Bool in
|
||||
return contactsManager.hasSignalAccount(forRecipientId: phoneNumber)
|
||||
})
|
||||
return contactShare.systemContactsWithSignalAccountPhoneNumbers(contactsManager)
|
||||
}
|
||||
|
||||
private func systemContactsForContact() -> [String] {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
return contactShare.phoneNumberStrings.filter({ (phoneNumber) -> Bool in
|
||||
return contactsManager.allContactsMap[phoneNumber] != nil
|
||||
})
|
||||
return contactShare.systemContactPhoneNumbers(contactsManager)
|
||||
}
|
||||
|
||||
private func phoneNumbersForContact() -> [String] {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
return contactShare.e164PhoneNumbers()
|
||||
}
|
||||
|
||||
private func updateContent() {
|
||||
|
@ -466,18 +472,6 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate
|
|||
return button
|
||||
}
|
||||
|
||||
func didPressCreateNewContact() {
|
||||
Logger.info("\(logTag) \(#function)")
|
||||
|
||||
presentNewContactView()
|
||||
}
|
||||
|
||||
func didPressAddToExistingContact() {
|
||||
Logger.info("\(logTag) \(#function)")
|
||||
|
||||
presentSelectAddToExistingContactView()
|
||||
}
|
||||
|
||||
func didPressShareContact(sender: UIGestureRecognizer) {
|
||||
Logger.info("\(logTag) \(#function)")
|
||||
|
||||
|
@ -490,92 +484,31 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate
|
|||
func didPressSendMessage() {
|
||||
Logger.info("\(logTag) \(#function)")
|
||||
|
||||
presentThreadAndPeform(action: .compose)
|
||||
self.helper.sendMessageToContact()
|
||||
}
|
||||
|
||||
func didPressAudioCall() {
|
||||
Logger.info("\(logTag) \(#function)")
|
||||
|
||||
presentThreadAndPeform(action: .audioCall)
|
||||
self.helper.audioCallToContact()
|
||||
}
|
||||
|
||||
func didPressVideoCall() {
|
||||
Logger.info("\(logTag) \(#function)")
|
||||
|
||||
presentThreadAndPeform(action: .videoCall)
|
||||
}
|
||||
|
||||
func presentThreadAndPeform(action: ConversationViewAction) {
|
||||
// TODO: We're taking the first Signal account id. We might
|
||||
// want to let the user select if there's more than one.
|
||||
let phoneNumbers = systemContactsWithSignalAccountsForContact()
|
||||
guard phoneNumbers.count > 0 else {
|
||||
owsFail("\(logTag) missing Signal recipient id.")
|
||||
return
|
||||
}
|
||||
guard phoneNumbers.count > 1 else {
|
||||
let recipientId = systemContactsWithSignalAccountsForContact().first!
|
||||
SignalApp.shared().presentConversation(forRecipientId: recipientId, action: action)
|
||||
return
|
||||
}
|
||||
|
||||
showPhoneNumberPicker(phoneNumbers: phoneNumbers, completion: { (recipientId) in
|
||||
SignalApp.shared().presentConversation(forRecipientId: recipientId, action: action)
|
||||
})
|
||||
self.helper.videoCallToContact()
|
||||
}
|
||||
|
||||
func didPressInvite() {
|
||||
Logger.info("\(logTag) \(#function)")
|
||||
|
||||
guard MFMessageComposeViewController.canSendText() else {
|
||||
Logger.info("\(logTag) Device cannot send text")
|
||||
OWSAlerts.showErrorAlert(message: NSLocalizedString("UNSUPPORTED_FEATURE_ERROR", comment: ""))
|
||||
return
|
||||
}
|
||||
let phoneNumbers = contactShare.phoneNumberStrings
|
||||
guard phoneNumbers.count > 0 else {
|
||||
owsFail("\(logTag) no phone numbers.")
|
||||
return
|
||||
}
|
||||
|
||||
let inviteFlow =
|
||||
InviteFlow(presentingViewController: self, contactsManager: contactsManager)
|
||||
inviteFlow.sendSMSTo(phoneNumbers: phoneNumbers)
|
||||
self.helper.inviteContact()
|
||||
}
|
||||
|
||||
func didPressAddToContacts() {
|
||||
Logger.info("\(logTag) \(#function)")
|
||||
|
||||
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
|
||||
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("CONVERSATION_SETTINGS_NEW_CONTACT",
|
||||
comment: "Label for 'new contact' button in conversation settings view."),
|
||||
style: .default) { _ in
|
||||
self.didPressCreateNewContact()
|
||||
})
|
||||
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("CONVERSATION_SETTINGS_ADD_TO_EXISTING_CONTACT",
|
||||
comment: "Label for 'new contact' button in conversation settings view."),
|
||||
style: .default) { _ in
|
||||
self.didPressAddToExistingContact()
|
||||
})
|
||||
actionSheet.addAction(OWSAlerts.cancelAction)
|
||||
|
||||
self.present(actionSheet, animated: true)
|
||||
}
|
||||
|
||||
private func showPhoneNumberPicker(phoneNumbers: [String], completion :@escaping ((String) -> Void)) {
|
||||
|
||||
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
|
||||
for phoneNumber in phoneNumbers {
|
||||
actionSheet.addAction(UIAlertAction(title: PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: phoneNumber),
|
||||
style: .default) { _ in
|
||||
completion(phoneNumber)
|
||||
})
|
||||
}
|
||||
actionSheet.addAction(OWSAlerts.cancelAction)
|
||||
|
||||
self.present(actionSheet, animated: true)
|
||||
self.helper.addToContacts()
|
||||
}
|
||||
|
||||
func didPressDismiss() {
|
||||
|
@ -619,80 +552,11 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate
|
|||
UIApplication.shared.openURL(url as URL)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
// MARK: - ContactShareViewHelperDelegate
|
||||
|
||||
private func presentNewContactView() {
|
||||
guard contactsManager.supportsContactEditing else {
|
||||
owsFail("\(logTag) Contact editing not supported")
|
||||
return
|
||||
}
|
||||
|
||||
guard let systemContact = OWSContacts.systemContact(for: contactShare.dbRecord) else {
|
||||
owsFail("\(logTag) Could not derive system contactShare.")
|
||||
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("\(logTag) Contact editing not supported")
|
||||
return
|
||||
}
|
||||
|
||||
guard contactsManager.isSystemContactsAuthorized else {
|
||||
ContactsViewHelper.presentMissingContactAccessAlertController(from: self)
|
||||
return
|
||||
}
|
||||
|
||||
guard let firstPhoneNumber = contactShare.phoneNumbers.first else {
|
||||
owsFail("\(logTag) Missing phone number.")
|
||||
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)
|
||||
}
|
||||
|
||||
// MARK: - CNContactViewControllerDelegate
|
||||
|
||||
@objc public func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
|
||||
public func didCreateOrEditContact() {
|
||||
Logger.info("\(logTag) \(#function)")
|
||||
|
||||
self.navigationController?.popToViewController(self, animated: true)
|
||||
|
||||
updateContent()
|
||||
}
|
||||
|
||||
@objc public func didFinishEditingContact() {
|
||||
Logger.info("\(logTag) \(#function)")
|
||||
|
||||
self.navigationController?.popToViewController(self, animated: true)
|
||||
|
||||
updateContent()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,14 +5,31 @@
|
|||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ContactShareViewModel;
|
||||
@class OWSContact;
|
||||
@class OWSContactsManager;
|
||||
|
||||
@protocol OWSContactShareViewDelegate <NSObject>
|
||||
|
||||
- (void)sendMessageToContactShare:(ContactShareViewModel *)contactShare;
|
||||
- (void)sendInviteToContactShare:(ContactShareViewModel *)contactShare;
|
||||
- (void)showAddToContactUIForContactShare:(ContactShareViewModel *)contactShare;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@interface OWSContactShareView : UIView
|
||||
|
||||
- (instancetype)initWithContactShare:(ContactShareViewModel *)contactShare isIncoming:(BOOL)isIncoming;
|
||||
- (instancetype)initWithContactShare:(ContactShareViewModel *)contactShare
|
||||
isIncoming:(BOOL)isIncoming
|
||||
delegate:(id<OWSContactShareViewDelegate>)delegate;
|
||||
|
||||
- (void)createContents;
|
||||
|
||||
+ (CGFloat)bubbleHeight;
|
||||
+ (CGFloat)bubbleHeightForContactShare:(ContactShareViewModel *)contactShare;
|
||||
|
||||
// Returns YES IFF the tap was handled.
|
||||
- (BOOL)handleTapGesture:(UITapGestureRecognizer *)sender;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -18,7 +18,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
@interface OWSContactShareView ()
|
||||
|
||||
@property (nonatomic) ContactShareViewModel *contactShare;
|
||||
@property (nonatomic, weak) id<OWSContactShareViewDelegate> delegate;
|
||||
|
||||
@property (nonatomic) BOOL isIncoming;
|
||||
@property (nonatomic) OWSContactsManager *contactsManager;
|
||||
|
||||
@property (nonatomic, nullable) UIView *buttonView;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -26,13 +31,17 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
@implementation OWSContactShareView
|
||||
|
||||
- (instancetype)initWithContactShare:(ContactShareViewModel *)contactShare isIncoming:(BOOL)isIncoming
|
||||
- (instancetype)initWithContactShare:(ContactShareViewModel *)contactShare
|
||||
isIncoming:(BOOL)isIncoming
|
||||
delegate:(id<OWSContactShareViewDelegate>)delegate
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
_contactShare = contactShare;
|
||||
_isIncoming = isIncoming;
|
||||
self.delegate = delegate;
|
||||
self.contactShare = contactShare;
|
||||
self.isIncoming = isIncoming;
|
||||
self.contactsManager = [Environment current].contactsManager;
|
||||
}
|
||||
|
||||
return self;
|
||||
|
@ -65,14 +74,60 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
return [OWSContactShareView iconVMargin];
|
||||
}
|
||||
|
||||
+ (CGFloat)bubbleHeight
|
||||
+ (BOOL)hasSendTextButton:(ContactShareViewModel *)contactShare contactsManager:(OWSContactsManager *)contactsManager
|
||||
{
|
||||
OWSAssert(contactShare);
|
||||
OWSAssert(contactsManager);
|
||||
|
||||
return [contactShare systemContactsWithSignalAccountPhoneNumbers:contactsManager].count > 0;
|
||||
}
|
||||
|
||||
+ (BOOL)hasInviteButton:(ContactShareViewModel *)contactShare contactsManager:(OWSContactsManager *)contactsManager
|
||||
{
|
||||
OWSAssert(contactShare);
|
||||
OWSAssert(contactsManager);
|
||||
|
||||
return [contactShare systemContactPhoneNumbers:contactsManager].count > 0;
|
||||
}
|
||||
|
||||
+ (BOOL)hasAddToContactsButton:(ContactShareViewModel *)contactShare
|
||||
{
|
||||
OWSAssert(contactShare);
|
||||
|
||||
return [contactShare e164PhoneNumbers].count > 0;
|
||||
}
|
||||
|
||||
|
||||
+ (BOOL)hasAnyButton:(ContactShareViewModel *)contactShare contactsManager:(OWSContactsManager *)contactsManager
|
||||
{
|
||||
OWSAssert(contactShare);
|
||||
|
||||
return ([self hasSendTextButton:contactShare contactsManager:contactsManager] ||
|
||||
[self hasInviteButton:contactShare contactsManager:contactsManager] ||
|
||||
[self hasAddToContactsButton:contactShare]);
|
||||
}
|
||||
|
||||
+ (CGFloat)bubbleHeightForContactShare:(ContactShareViewModel *)contactShare
|
||||
{
|
||||
OWSAssert(contactShare);
|
||||
|
||||
OWSContactsManager *contactsManager = [Environment current].contactsManager;
|
||||
|
||||
if ([self hasAnyButton:contactShare contactsManager:contactsManager]) {
|
||||
return self.contentHeight + self.buttonHeight;
|
||||
} else {
|
||||
return self.contentHeight;
|
||||
}
|
||||
}
|
||||
|
||||
+ (CGFloat)contentHeight
|
||||
{
|
||||
return self.iconSize + self.iconVMargin * 2;
|
||||
}
|
||||
|
||||
- (CGFloat)bubbleHeight
|
||||
+ (CGFloat)buttonHeight
|
||||
{
|
||||
return [OWSContactShareView bubbleHeight];
|
||||
return 44.f;
|
||||
}
|
||||
|
||||
+ (CGFloat)iconSize
|
||||
|
@ -108,7 +163,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
[contentView autoPinLeadingToSuperviewMarginWithInset:self.isIncoming ? kBubbleTailWidth : 0.f];
|
||||
[contentView autoPinTrailingToSuperviewMarginWithInset:self.isIncoming ? 0.f : kBubbleTailWidth];
|
||||
[contentView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:self.vMargin];
|
||||
[contentView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:self.vMargin];
|
||||
|
||||
AvatarImageView *avatarView = [AvatarImageView new];
|
||||
avatarView.image =
|
||||
|
@ -130,14 +184,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
labelsView.spacing = 2;
|
||||
[labelsView addArrangedSubview:topLabel];
|
||||
|
||||
// TODO: Should we just try to show the _first_ phone number?
|
||||
// What about email?
|
||||
// What if the second phone number is a signal account?
|
||||
NSString *_Nullable firstPhoneNumber = self.contactShare.phoneNumbers.firstObject.phoneNumber;
|
||||
NSString *_Nullable firstPhoneNumber =
|
||||
[self.contactShare systemContactsWithSignalAccountPhoneNumbers:self.contactsManager].firstObject;
|
||||
if (firstPhoneNumber.length > 0) {
|
||||
UILabel *bottomLabel = [UILabel new];
|
||||
bottomLabel.text = [PhoneNumber bestEffortLocalizedPhoneNumberWithE164:firstPhoneNumber];
|
||||
// TODO:
|
||||
bottomLabel.textColor = [UIColor ows_darkGrayColor];
|
||||
bottomLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
bottomLabel.font = [UIFont ows_dynamicTypeCaption1Font];
|
||||
|
@ -170,6 +221,64 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
[stackView addArrangedSubview:avatarView];
|
||||
[stackView addArrangedSubview:labelsView];
|
||||
[stackView addArrangedSubview:disclosureImageView];
|
||||
|
||||
if ([OWSContactShareView hasAnyButton:self.contactShare contactsManager:self.contactsManager]) {
|
||||
UIStackView *buttonView = [UIStackView new];
|
||||
self.buttonView = buttonView;
|
||||
buttonView.layoutMargins = UIEdgeInsetsZero;
|
||||
[buttonView addBackgroundViewWithBackgroundColor:[UIColor whiteColor]];
|
||||
buttonView.axis = UILayoutConstraintAxisHorizontal;
|
||||
buttonView.alignment = UIStackViewAlignmentCenter;
|
||||
[self addSubview:buttonView];
|
||||
[buttonView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:contentView withOffset:self.vMargin];
|
||||
[buttonView autoPinWidthToSuperview];
|
||||
[buttonView autoPinEdgeToSuperviewEdge:ALEdgeBottom];
|
||||
[buttonView autoSetDimension:ALDimensionHeight toSize:OWSContactShareView.buttonHeight];
|
||||
|
||||
UILabel *label = [UILabel new];
|
||||
if ([OWSContactShareView hasSendTextButton:self.contactShare contactsManager:self.contactsManager]) {
|
||||
label.text = NSLocalizedString(@"ACTION_SEND_MESSAGE", @"Label for 'sent message' button in contact view.");
|
||||
} else if ([OWSContactShareView hasInviteButton:self.contactShare contactsManager:self.contactsManager]) {
|
||||
label.text = NSLocalizedString(@"ACTION_INVITE", @"Label for 'invite' button in contact view.");
|
||||
} else if ([OWSContactShareView hasAddToContactsButton:self.contactShare]) {
|
||||
label.text = NSLocalizedString(@"CONVERSATION_VIEW_ADD_TO_CONTACTS_OFFER",
|
||||
@"Message shown in conversation view that offers to add an unknown user to your phone's contacts.");
|
||||
} else {
|
||||
OWSFail(@"%@ unexpected button state.", self.logTag);
|
||||
}
|
||||
label.font = [UIFont ows_dynamicTypeBodyFont];
|
||||
label.textColor = UIColor.ows_materialBlueColor;
|
||||
label.textAlignment = NSTextAlignmentCenter;
|
||||
[buttonView addArrangedSubview:label];
|
||||
|
||||
[buttonView logFrameLaterWithLabel:@"buttonView"];
|
||||
[label logFrameLaterWithLabel:@"label"];
|
||||
} else {
|
||||
[contentView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:self.vMargin];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)handleTapGesture:(UITapGestureRecognizer *)sender
|
||||
{
|
||||
if (!self.buttonView) {
|
||||
return NO;
|
||||
}
|
||||
CGPoint location = [sender locationInView:self.buttonView];
|
||||
if (!CGRectContainsPoint(self.buttonView.bounds, location)) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if ([OWSContactShareView hasSendTextButton:self.contactShare contactsManager:self.contactsManager]) {
|
||||
[self.delegate sendMessageToContactShare:self.contactShare];
|
||||
} else if ([OWSContactShareView hasInviteButton:self.contactShare contactsManager:self.contactsManager]) {
|
||||
[self.delegate sendInviteToContactShare:self.contactShare];
|
||||
} else if ([OWSContactShareView hasAddToContactsButton:self.contactShare]) {
|
||||
[self.delegate showAddToContactUIForContactShare:self.contactShare];
|
||||
} else {
|
||||
OWSFail(@"%@ unexpected button tap.", self.logTag);
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ContactShareViewModel;
|
||||
@class ConversationViewItem;
|
||||
@class OWSContact;
|
||||
@class OWSQuotedReplyModel;
|
||||
@class TSAttachmentPointer;
|
||||
@class TSAttachmentStream;
|
||||
|
@ -42,6 +44,11 @@ typedef NS_ENUM(NSUInteger, OWSMessageGestureLocation) {
|
|||
|
||||
- (void)didTapContactShareViewItem:(ConversationViewItem *)viewItem;
|
||||
|
||||
- (void)sendMessageToContactShare:(ContactShareViewModel *)contactShare NS_SWIFT_NAME(sendMessage(toContactShare:));
|
||||
- (void)sendInviteToContactShare:(ContactShareViewModel *)contactShare NS_SWIFT_NAME(sendInvite(toContactShare:));
|
||||
- (void)showAddToContactUIForContactShare:(ContactShareViewModel *)contactShare
|
||||
NS_SWIFT_NAME(showAddToContactUI(forContactShare:));
|
||||
|
||||
@end
|
||||
|
||||
@interface OWSMessageBubbleView : UIView
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OWSMessageBubbleView () <OWSQuotedMessageViewDelegate>
|
||||
@interface OWSMessageBubbleView () <OWSQuotedMessageViewDelegate, OWSContactShareViewDelegate>
|
||||
|
||||
@property (nonatomic) OWSBubbleView *bubbleView;
|
||||
|
||||
|
@ -378,7 +378,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
lastSubview = bodyMediaView;
|
||||
bottomMargin = 0;
|
||||
|
||||
BOOL shouldStrokeMediaView = [bodyMediaView isKindOfClass:[UIImageView class]];
|
||||
BOOL shouldStrokeMediaView = ([bodyMediaView isKindOfClass:[UIImageView class]] ||
|
||||
[bodyMediaView isKindOfClass:[OWSContactShareView class]]);
|
||||
if (shouldStrokeMediaView) {
|
||||
OWSBubbleStrokeView *bubbleStrokeView = [OWSBubbleStrokeView new];
|
||||
bubbleStrokeView.strokeThickness = 1.f;
|
||||
|
@ -796,9 +797,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
{
|
||||
OWSAssert(self.viewItem.contactShare);
|
||||
|
||||
OWSContactShareView *contactShareView =
|
||||
[[OWSContactShareView alloc] initWithContactShare:self.viewItem.contactShare
|
||||
isIncoming:self.isIncoming];
|
||||
OWSContactShareView *contactShareView = [[OWSContactShareView alloc] initWithContactShare:self.viewItem.contactShare
|
||||
isIncoming:self.isIncoming
|
||||
delegate:self];
|
||||
[contactShareView createContents];
|
||||
// TODO: Should we change appearance if contact avatar is uploading?
|
||||
|
||||
|
@ -931,7 +932,10 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
case OWSMessageCellType_DownloadingAttachment:
|
||||
return CGSizeMake(200, 90);
|
||||
case OWSMessageCellType_ContactShare:
|
||||
return CGSizeMake(maxMessageWidth, [OWSContactShareView bubbleHeight]);
|
||||
OWSAssert(self.viewItem.contactShare);
|
||||
|
||||
return CGSizeMake(
|
||||
maxMessageWidth, [OWSContactShareView bubbleHeightForContactShare:self.viewItem.contactShare]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1127,6 +1131,13 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
}
|
||||
}
|
||||
|
||||
if ([self.bodyMediaView isKindOfClass:[OWSContactShareView class]]) {
|
||||
OWSContactShareView *contactShareView = (OWSContactShareView *)self.bodyMediaView;
|
||||
if ([contactShareView handleTapGesture:sender]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CGPoint locationInMessageBubble = [sender locationInView:self];
|
||||
switch ([self gestureLocationForLocation:locationInMessageBubble]) {
|
||||
case OWSMessageGestureLocation_Default:
|
||||
|
@ -1247,6 +1258,32 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
failedThumbnailDownloadAttachmentPointer:attachmentPointer];
|
||||
}
|
||||
|
||||
#pragma mark - OWSContactShareViewDelegate
|
||||
|
||||
- (void)sendMessageToContactShare:(ContactShareViewModel *)contactShare
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssert(contactShare);
|
||||
|
||||
[self.delegate sendMessageToContactShare:contactShare];
|
||||
}
|
||||
|
||||
- (void)sendInviteToContactShare:(ContactShareViewModel *)contactShare
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssert(contactShare);
|
||||
|
||||
[self.delegate sendInviteToContactShare:contactShare];
|
||||
}
|
||||
|
||||
- (void)showAddToContactUIForContactShare:(ContactShareViewModel *)contactShare
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssert(contactShare);
|
||||
|
||||
[self.delegate showAddToContactUIForContactShare:contactShare];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -123,6 +123,7 @@ typedef enum : NSUInteger {
|
|||
CNContactViewControllerDelegate,
|
||||
ContactEditingDelegate,
|
||||
ContactsPickerDelegate,
|
||||
ContactShareViewHelperDelegate,
|
||||
ContactsViewHelperDelegate,
|
||||
DisappearingTimerConfigurationViewDelegate,
|
||||
OWSConversationSettingsViewDelegate,
|
||||
|
@ -231,6 +232,7 @@ typedef enum : NSUInteger {
|
|||
@property (nonatomic) BOOL isPickingMediaAsDocument;
|
||||
@property (nonatomic, nullable) NSNumber *previousLastTimestamp;
|
||||
@property (nonatomic, nullable) NSNumber *viewHorizonTimestamp;
|
||||
@property (nonatomic) ContactShareViewHelper *contactShareViewHelper;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -2106,6 +2108,42 @@ typedef enum : NSUInteger {
|
|||
[self.navigationController pushViewController:view animated:YES];
|
||||
}
|
||||
|
||||
- (void)sendMessageToContactShare:(ContactShareViewModel *)contactShare
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssert(contactShare);
|
||||
|
||||
self.contactShareViewHelper = [[ContactShareViewHelper alloc] initWithContactShare:contactShare
|
||||
contactsManager:self.contactsManager
|
||||
fromViewController:self
|
||||
delegate:self];
|
||||
[self.contactShareViewHelper sendMessageToContact];
|
||||
}
|
||||
|
||||
- (void)sendInviteToContactShare:(ContactShareViewModel *)contactShare
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssert(contactShare);
|
||||
|
||||
self.contactShareViewHelper = [[ContactShareViewHelper alloc] initWithContactShare:contactShare
|
||||
contactsManager:self.contactsManager
|
||||
fromViewController:self
|
||||
delegate:self];
|
||||
[self.contactShareViewHelper inviteContact];
|
||||
}
|
||||
|
||||
- (void)showAddToContactUIForContactShare:(ContactShareViewModel *)contactShare
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssert(contactShare);
|
||||
|
||||
self.contactShareViewHelper = [[ContactShareViewHelper alloc] initWithContactShare:contactShare
|
||||
contactsManager:self.contactsManager
|
||||
fromViewController:self
|
||||
delegate:self];
|
||||
[self.contactShareViewHelper addToContacts];
|
||||
}
|
||||
|
||||
- (void)didTapFailedIncomingAttachment:(ConversationViewItem *)viewItem
|
||||
attachmentPointer:(TSAttachmentPointer *)attachmentPointer
|
||||
{
|
||||
|
@ -5030,6 +5068,15 @@ interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransiti
|
|||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
#pragma mark - ContactShareViewHelperDelegate
|
||||
|
||||
- (void)didCreateOrEditContact
|
||||
{
|
||||
DDLogInfo(@"%@ in %s", self.logTag, __PRETTY_FUNCTION__);
|
||||
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -12,7 +12,7 @@ enum MessageMetadataViewMode: UInt {
|
|||
case focusOnMetadata
|
||||
}
|
||||
|
||||
class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDelegate, OWSMessageBubbleViewDelegate {
|
||||
class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDelegate, OWSMessageBubbleViewDelegate, ContactShareViewHelperDelegate {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
|
@ -40,6 +40,8 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
|
|||
var attachmentStream: TSAttachmentStream?
|
||||
var messageBody: String?
|
||||
|
||||
private var contactShareViewHelper: ContactShareViewHelper?
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
@available(*, unavailable, message:"use other constructor instead.")
|
||||
|
@ -621,6 +623,30 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
|
|||
self.navigationController?.pushViewController(contactViewController, animated: true)
|
||||
}
|
||||
|
||||
func sendMessage(toContactShare contactShare: ContactShareViewModel) {
|
||||
contactShareViewHelper = ContactShareViewHelper(contactShare: contactShare,
|
||||
contactsManager: contactsManager,
|
||||
fromViewController: self,
|
||||
delegate: self)
|
||||
contactShareViewHelper?.sendMessageToContact()
|
||||
}
|
||||
|
||||
func sendInvite(toContactShare contactShare: ContactShareViewModel) {
|
||||
contactShareViewHelper = ContactShareViewHelper(contactShare: contactShare,
|
||||
contactsManager: contactsManager,
|
||||
fromViewController: self,
|
||||
delegate: self)
|
||||
contactShareViewHelper?.inviteContact()
|
||||
}
|
||||
|
||||
func showAddToContactUI(forContactShare contactShare: ContactShareViewModel) {
|
||||
contactShareViewHelper = ContactShareViewHelper(contactShare: contactShare,
|
||||
contactsManager: contactsManager,
|
||||
fromViewController: self,
|
||||
delegate: self)
|
||||
contactShareViewHelper?.addToContacts()
|
||||
}
|
||||
|
||||
var audioAttachmentPlayer: OWSAudioPlayer?
|
||||
|
||||
func didTapAudioViewItem(_ viewItem: ConversationViewItem, attachmentStream: TSAttachmentStream) {
|
||||
|
@ -700,4 +726,10 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
|
|||
self.navigationController?.popViewController(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ContactShareViewHelperDelegate
|
||||
|
||||
public func didCreateOrEditContact() {
|
||||
updateContent()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,8 +65,16 @@ public class ContactShareViewModel: NSObject {
|
|||
}
|
||||
}
|
||||
|
||||
public var phoneNumberStrings: [String] {
|
||||
return phoneNumbers.map { $0.phoneNumber }
|
||||
public func systemContactsWithSignalAccountPhoneNumbers(_ contactsManager: ContactsManagerProtocol) -> [String] {
|
||||
return dbRecord.systemContactsWithSignalAccountPhoneNumbers(contactsManager)
|
||||
}
|
||||
|
||||
public func systemContactPhoneNumbers(_ contactsManager: ContactsManagerProtocol) -> [String] {
|
||||
return dbRecord.systemContactPhoneNumbers(contactsManager)
|
||||
}
|
||||
|
||||
public func e164PhoneNumbers() -> [String] {
|
||||
return dbRecord.e164PhoneNumbers()
|
||||
}
|
||||
|
||||
public var displayName: String {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
@objc
|
||||
public class TappableStackView: UIStackView {
|
||||
let actionBlock : (() -> Void)
|
||||
|
||||
|
|
|
@ -135,6 +135,14 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value);
|
|||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@interface UIStackView (OWS)
|
||||
|
||||
- (void)addBackgroundViewWithBackgroundColor:(UIColor *)backgroundColor;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Macros
|
||||
|
||||
CG_INLINE CGSize CGSizeCeil(CGSize size)
|
||||
|
|
|
@ -516,4 +516,18 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value)
|
|||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation UIStackView (OWS)
|
||||
|
||||
- (void)addBackgroundViewWithBackgroundColor:(UIColor *)backgroundColor
|
||||
{
|
||||
UIView *subview = [UIView new];
|
||||
subview.backgroundColor = backgroundColor;
|
||||
[self addSubview:subview];
|
||||
[subview autoPinEdgesToSuperviewEdges];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -60,6 +60,8 @@ extern NSString *const OWSContactsManagerSignalAccountsDidChangeNotification;
|
|||
|
||||
#pragma mark - Util
|
||||
|
||||
- (BOOL)isSystemContact:(NSString *)recipientId;
|
||||
- (BOOL)isSystemContactWithSignalAccount:(NSString *)recipientId;
|
||||
- (BOOL)hasNameInSystemContactsForRecipientId:(NSString *)recipientId;
|
||||
- (NSString *)displayNameForPhoneIdentifier:(nullable NSString *)identifier;
|
||||
- (NSString *)displayNameForSignalAccount:(SignalAccount *)signalAccount;
|
||||
|
|
|
@ -459,6 +459,20 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
|
|||
|
||||
#pragma mark - Whisper User Management
|
||||
|
||||
- (BOOL)isSystemContact:(NSString *)recipientId
|
||||
{
|
||||
OWSAssert(recipientId.length > 0);
|
||||
|
||||
return self.allContactsMap[recipientId] != nil;
|
||||
}
|
||||
|
||||
- (BOOL)isSystemContactWithSignalAccount:(NSString *)recipientId
|
||||
{
|
||||
OWSAssert(recipientId.length > 0);
|
||||
|
||||
return [self hasSignalAccountForRecipientId:recipientId];
|
||||
}
|
||||
|
||||
- (BOOL)hasNameInSystemContactsForRecipientId:(NSString *)recipientId
|
||||
{
|
||||
return [self cachedContactNameForRecipientId:recipientId].length > 0;
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
+ (PhoneNumber *)phoneNumberFromE164:(NSString *)text;
|
||||
|
||||
+ (PhoneNumber *)tryParsePhoneNumberFromText:(NSString *)text fromRegion:(NSString *)regionCode;
|
||||
+ (PhoneNumber *)tryParsePhoneNumberFromUserSpecifiedText:(NSString *)text;
|
||||
+ (PhoneNumber *)tryParsePhoneNumberFromE164:(NSString *)text;
|
||||
|
||||
|
|
|
@ -170,14 +170,6 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
|
|||
return regionCode;
|
||||
}
|
||||
|
||||
|
||||
+ (PhoneNumber *)tryParsePhoneNumberFromText:(NSString *)text fromRegion:(NSString *)regionCode {
|
||||
OWSAssert(text != nil);
|
||||
OWSAssert(regionCode != nil);
|
||||
|
||||
return [self phoneNumberFromText:text andRegion:regionCode];
|
||||
}
|
||||
|
||||
+ (PhoneNumber *)tryParsePhoneNumberFromUserSpecifiedText:(NSString *)text {
|
||||
OWSAssert(text != nil);
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
//
|
||||
|
||||
#import <Mantle/MTLModel.h>
|
||||
#import <SignalServiceKit/ContactsManagerProtocol.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
@ -140,10 +141,19 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value);
|
|||
familyName:(nullable NSString *)familyName
|
||||
nameSuffix:(nullable NSString *)nameSuffix;
|
||||
|
||||
#pragma mark - Phone Numbers and Recipient IDs
|
||||
|
||||
- (NSArray<NSString *> *)systemContactsWithSignalAccountPhoneNumbers:(id<ContactsManagerProtocol>)contactsManager
|
||||
NS_SWIFT_NAME(systemContactsWithSignalAccountPhoneNumbers(_:));
|
||||
- (NSArray<NSString *> *)systemContactPhoneNumbers:(id<ContactsManagerProtocol>)contactsManager
|
||||
NS_SWIFT_NAME(systemContactPhoneNumbers(_:));
|
||||
- (NSArray<NSString *> *)e164PhoneNumbers NS_SWIFT_NAME(e164PhoneNumbers());
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
// TODO: Move to separate source file, rename to OWSContactConversion.
|
||||
@interface OWSContacts : NSObject
|
||||
|
||||
#pragma mark - VCard Serialization
|
||||
|
|
|
@ -279,6 +279,8 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value)
|
|||
@property (nonatomic, nullable) NSString *avatarAttachmentId;
|
||||
@property (nonatomic) BOOL isProfileAvatar;
|
||||
|
||||
@property (nonatomic, nullable) NSArray<NSString *> *e164PhoneNumbersCached;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
@ -483,6 +485,50 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value)
|
|||
self.avatarAttachmentId = attachmentStream.uniqueId;
|
||||
}
|
||||
|
||||
#pragma mark - Phone Numbers and Recipient IDs
|
||||
|
||||
- (NSArray<NSString *> *)systemContactsWithSignalAccountPhoneNumbers:(id<ContactsManagerProtocol>)contactsManager
|
||||
{
|
||||
OWSAssert(contactsManager);
|
||||
|
||||
return [self.e164PhoneNumbers
|
||||
filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSString *_Nullable recipientId,
|
||||
NSDictionary<NSString *, id> *_Nullable bindings) {
|
||||
return [contactsManager isSystemContactWithSignalAccount:recipientId];
|
||||
}]];
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)systemContactPhoneNumbers:(id<ContactsManagerProtocol>)contactsManager
|
||||
{
|
||||
OWSAssert(contactsManager);
|
||||
|
||||
return [self.e164PhoneNumbers
|
||||
filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSString *_Nullable recipientId,
|
||||
NSDictionary<NSString *, id> *_Nullable bindings) {
|
||||
return [contactsManager isSystemContact:recipientId];
|
||||
}]];
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)e164PhoneNumbers
|
||||
{
|
||||
if (self.e164PhoneNumbersCached) {
|
||||
return self.e164PhoneNumbersCached;
|
||||
}
|
||||
NSMutableArray<NSString *> *e164PhoneNumbers = [NSMutableArray new];
|
||||
for (OWSContactPhoneNumber *phoneNumber in self.phoneNumbers) {
|
||||
PhoneNumber *_Nullable parsedPhoneNumber;
|
||||
parsedPhoneNumber = [PhoneNumber tryParsePhoneNumberFromE164:phoneNumber.phoneNumber];
|
||||
if (!parsedPhoneNumber) {
|
||||
parsedPhoneNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:phoneNumber.phoneNumber];
|
||||
}
|
||||
if (parsedPhoneNumber) {
|
||||
[e164PhoneNumbers addObject:parsedPhoneNumber.toE164];
|
||||
}
|
||||
}
|
||||
self.e164PhoneNumbersCached = e164PhoneNumbers;
|
||||
return e164PhoneNumbers;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
@ -553,7 +599,20 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value)
|
|||
NSMutableArray<OWSContactPhoneNumber *> *phoneNumbers = [NSMutableArray new];
|
||||
for (CNLabeledValue<CNPhoneNumber *> *phoneNumberField in systemContact.phoneNumbers) {
|
||||
OWSContactPhoneNumber *phoneNumber = [OWSContactPhoneNumber new];
|
||||
phoneNumber.phoneNumber = phoneNumberField.value.stringValue;
|
||||
|
||||
// Make a best effort to parse the phone number to e164.
|
||||
NSString *unparsedPhoneNumber = phoneNumberField.value.stringValue;
|
||||
PhoneNumber *_Nullable parsedPhoneNumber;
|
||||
parsedPhoneNumber = [PhoneNumber tryParsePhoneNumberFromE164:unparsedPhoneNumber];
|
||||
if (!parsedPhoneNumber) {
|
||||
parsedPhoneNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:unparsedPhoneNumber];
|
||||
}
|
||||
if (parsedPhoneNumber) {
|
||||
phoneNumber.phoneNumber = parsedPhoneNumber.toE164;
|
||||
} else {
|
||||
phoneNumber.phoneNumber = unparsedPhoneNumber;
|
||||
}
|
||||
|
||||
if ([phoneNumberField.label isEqualToString:CNLabelHome]) {
|
||||
phoneNumber.phoneType = OWSContactPhoneType_Home;
|
||||
} else if ([phoneNumberField.label isEqualToString:CNLabelWork]) {
|
||||
|
|
|
@ -12,4 +12,7 @@
|
|||
- (NSString * _Nonnull)displayNameForPhoneIdentifier:(NSString * _Nullable)phoneNumber;
|
||||
- (NSArray<SignalAccount *> * _Nonnull)signalAccounts;
|
||||
|
||||
- (BOOL)isSystemContact:(NSString *)recipientId;
|
||||
- (BOOL)isSystemContactWithSignalAccount:(NSString *)recipientId;
|
||||
|
||||
@end
|
||||
|
|
Loading…
Reference in a new issue