mirror of https://github.com/oxen-io/session-ios
Mail and Message invite flow
* Spruce up compose contact-picker - Fix random sorting for contacts missing first or last name - Add Avatar to contact picker - de-dupe contacts Better copy for INVALID_MESSAGE error. // FREEBIEpull/1/head
parent
bed5250397
commit
06ca3c9290
@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "logo_with_background.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "logo_with_background@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "logo_with_background@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
@ -0,0 +1,257 @@
|
||||
// Created by Michael Kirk on 11/18/16.
|
||||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Social
|
||||
import ContactsUI
|
||||
import MessageUI
|
||||
|
||||
@objc(OWSInviteFlow)
|
||||
class InviteFlow: NSObject, CNContactPickerDelegate, MFMessageComposeViewControllerDelegate, MFMailComposeViewControllerDelegate {
|
||||
|
||||
enum Channel {
|
||||
case message, mail, twitter
|
||||
}
|
||||
|
||||
let TAG = "[ShareActions]"
|
||||
|
||||
// redirects to either the ios appstore or google play store depending on the user agent.
|
||||
// Appropriate to send to mobile browsers, but for email we want to expose both directs link
|
||||
// since an e.g. mac user, might have an Android, or Windows user might have an iPhone.
|
||||
let mobileInstallUrl = "https://signal.org/install/"
|
||||
let iOSInstallUrl = "https://itunes.apple.com/us/app/signal-private-messenger/id874139669"
|
||||
let androidInstallUrl = "https://play.google.com/store/apps/details?id=org.thoughtcrime.securesms"
|
||||
let homepageUrl = "https://whispersystems.org"
|
||||
|
||||
let actionSheetController: UIAlertController
|
||||
let presentingViewController: UIViewController
|
||||
var channel: Channel?
|
||||
|
||||
required init(presentingViewController: UIViewController) {
|
||||
self.presentingViewController = presentingViewController
|
||||
|
||||
actionSheetController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
|
||||
super.init()
|
||||
|
||||
actionSheetController.addAction(dismissAction())
|
||||
|
||||
if #available(iOS 9.0, *) {
|
||||
if let messageAction = messageAction() {
|
||||
actionSheetController.addAction(messageAction)
|
||||
}
|
||||
|
||||
if let mailAction = mailAction() {
|
||||
actionSheetController.addAction(mailAction)
|
||||
}
|
||||
}
|
||||
|
||||
if let tweetAction = tweetAction() {
|
||||
actionSheetController.addAction(tweetAction)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Twitter
|
||||
|
||||
func canTweet() -> Bool {
|
||||
return SLComposeViewController.isAvailable(forServiceType: SLServiceTypeTwitter)
|
||||
}
|
||||
|
||||
func tweetAction() -> UIAlertAction? {
|
||||
guard canTweet() else {
|
||||
Logger.info("\(TAG) Twitter not supported.")
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let twitterViewController = SLComposeViewController(forServiceType: SLServiceTypeTwitter) else {
|
||||
Logger.error("\(TAG) unable to build twitter controller.")
|
||||
return nil
|
||||
}
|
||||
|
||||
let tweetString = NSLocalizedString("SETTINGS_INVITE_TWITTER_TEXT", comment:"content of tweet when inviting via twitter")
|
||||
twitterViewController.setInitialText(tweetString)
|
||||
|
||||
let tweetUrl = URL(string: mobileInstallUrl)
|
||||
twitterViewController.add(tweetUrl)
|
||||
twitterViewController.add(#imageLiteral(resourceName: "logo_with_background"))
|
||||
|
||||
let tweetTitle = NSLocalizedString("SHARE_ACTION_TWEET", comment:"action sheet item")
|
||||
return UIAlertAction(title: tweetTitle, style: .default) { action in
|
||||
Logger.debug("\(self.TAG) Chose tweet")
|
||||
|
||||
self.presentingViewController.present(twitterViewController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func dismissAction() -> UIAlertAction {
|
||||
return UIAlertAction(title: NSLocalizedString("DISMISS_BUTTON_TEXT", comment:""), style: .cancel)
|
||||
}
|
||||
|
||||
// MARK: ContactPickerDelegate
|
||||
|
||||
/*!
|
||||
* @abstract Invoked when the picker is closed.
|
||||
* @discussion The picker will be dismissed automatically after a contact or property is picked.
|
||||
*/
|
||||
@available(iOS 9.0, *)
|
||||
func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
|
||||
Logger.debug("\(TAG) pickerDidCancel")
|
||||
}
|
||||
|
||||
/*!
|
||||
* @abstract Plural delegate methods.
|
||||
* @discussion These delegate methods will be invoked when the user is done selecting multiple contacts or properties.
|
||||
* Implementing one of these methods will configure the picker for multi-selection.
|
||||
*/
|
||||
@available(iOS 9.0, *)
|
||||
func contactPicker(_ picker: CNContactPickerViewController, didSelect contacts: [CNContact]) {
|
||||
Logger.debug("\(TAG) didSelectContacts:\(contacts)")
|
||||
|
||||
guard let inviteChannel = channel else {
|
||||
Logger.error("\(TAG) unexpected nil channel after returning from contact picker.")
|
||||
return
|
||||
}
|
||||
|
||||
switch inviteChannel {
|
||||
case .message:
|
||||
sendSMSTo(contacts: contacts)
|
||||
case .mail:
|
||||
sendMailTo(contacts:contacts)
|
||||
default:
|
||||
Logger.error("\(TAG) unexpected channel after returning from contact picker: \(inviteChannel)")
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 9.0, *)
|
||||
func contactPicker(_ picker: CNContactPickerViewController, didSelectContactProperties contactProperties: [CNContactProperty]) {
|
||||
Logger.debug("\(TAG) didSelectContactProperties:\(contactProperties)")
|
||||
}
|
||||
|
||||
// MARK: SMS
|
||||
|
||||
@available(iOS 9.0, *)
|
||||
func messageAction() -> UIAlertAction? {
|
||||
|
||||
guard MFMessageComposeViewController.canSendText() else {
|
||||
Logger.info("\(TAG) Device cannot send text")
|
||||
return nil
|
||||
}
|
||||
|
||||
let messageTitle = NSLocalizedString("SHARE_ACTION_MESSAGE", comment: "action sheet item to open native messages app")
|
||||
return UIAlertAction(title: messageTitle, style: .default) { action in
|
||||
Logger.debug("\(self.TAG) Chose message.")
|
||||
self.channel = .message
|
||||
|
||||
let picker = CNContactPickerViewController()
|
||||
picker.predicateForSelectionOfContact = NSPredicate(value: false)
|
||||
picker.predicateForEnablingContact = NSPredicate(format: "phoneNumbers.@count > 0")
|
||||
picker.delegate = self
|
||||
|
||||
self.presentingViewController.present(picker, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 9.0, *)
|
||||
func sendSMSTo(contacts: [CNContact]) {
|
||||
self.presentingViewController.dismiss(animated: true) {
|
||||
if #available(iOS 10.0, *) {
|
||||
// iOS10 message compose view doesn't respect some system appearence attributes.
|
||||
// Specifically, the title is white, but the navbar is gray.
|
||||
// So, we have to set system appearence before init'ing the message compose view controller in order
|
||||
// to make its colors legible.
|
||||
// Then we have to be sure to set it back in the ComposeViewControllerDelegate callback.
|
||||
UIUtil.applyDefaultSystemAppearence()
|
||||
}
|
||||
let messageComposeViewController = MFMessageComposeViewController()
|
||||
messageComposeViewController.messageComposeDelegate = self
|
||||
messageComposeViewController.recipients = contacts.map { $0.phoneNumbers.first }.filter { $0 != nil }.map { $0!.value.stringValue }
|
||||
|
||||
let inviteText = NSLocalizedString("SMS_INVITE_BODY", comment:"body sent to contacts when inviting to Install Signal")
|
||||
messageComposeViewController.body = inviteText.appending(" \(self.mobileInstallUrl)")
|
||||
self.presentingViewController.navigationController?.present(messageComposeViewController, animated:true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: MessageComposeViewControllerDelegate
|
||||
|
||||
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
|
||||
// Revert system styling applied to make messaging app legible on iOS10.
|
||||
UIUtil.applySignalAppearence()
|
||||
self.presentingViewController.dismiss(animated: true, completion: nil)
|
||||
|
||||
switch result {
|
||||
case .failed:
|
||||
let warning = UIAlertController(title: nil, message: NSLocalizedString("SEND_INVITE_FAILURE", comment:"Alert body after invite failed"), preferredStyle: .alert)
|
||||
warning.addAction(UIAlertAction(title: NSLocalizedString("DISMISS_BUTTON_TEXT", comment:""), style: .default, handler: nil))
|
||||
self.presentingViewController.present(warning, animated: true, completion: nil)
|
||||
case .sent:
|
||||
Logger.debug("\(self.TAG) user successfully invited their friends via SMS.")
|
||||
case .cancelled:
|
||||
Logger.debug("\(self.TAG) user cancelled message invite")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Mail
|
||||
|
||||
@available(iOS 9.0, *)
|
||||
func mailAction() -> UIAlertAction? {
|
||||
guard MFMailComposeViewController.canSendMail() else {
|
||||
Logger.info("\(TAG) Device cannot send mail")
|
||||
return nil
|
||||
}
|
||||
|
||||
let mailActionTitle = NSLocalizedString("SHARE_ACTION_MAIL", comment: "action sheet item to open native mail app")
|
||||
return UIAlertAction(title: mailActionTitle, style: .default) { action in
|
||||
Logger.debug("\(self.TAG) Chose mail.")
|
||||
self.channel = .mail
|
||||
|
||||
let picker = CNContactPickerViewController()
|
||||
picker.predicateForSelectionOfContact = NSPredicate(value: false)
|
||||
picker.predicateForEnablingContact = NSPredicate(format: "emailAddresses.@count > 0")
|
||||
picker.delegate = self
|
||||
|
||||
self.presentingViewController.present(picker, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 9.0, *)
|
||||
func sendMailTo(contacts: [CNContact]) {
|
||||
let mailComposeViewController = MFMailComposeViewController()
|
||||
mailComposeViewController.mailComposeDelegate = self
|
||||
|
||||
let recipients: [String] = contacts.map { $0.emailAddresses.first }.filter { $0 != nil }.map { $0!.value as String }
|
||||
mailComposeViewController.setBccRecipients(recipients)
|
||||
|
||||
let subject = NSLocalizedString("EMAIL_INVITE_SUBJECT", comment:"subject of email sent to contacts when inviting to install Signal")
|
||||
let bodyFormat = NSLocalizedString("EMAIL_INVITE_BODY", comment:"body of email sent to contacts when inviting to install Signal. Embeds {{link to install Signal-iOS}}, {{link to install Signal-Android}}, and {{link to WhisperSystems home page}}")
|
||||
let body = String.init(format: bodyFormat, iOSInstallUrl, androidInstallUrl, homepageUrl)
|
||||
mailComposeViewController.setSubject(subject)
|
||||
mailComposeViewController.setMessageBody(body, isHTML: false)
|
||||
|
||||
self.presentingViewController.dismiss(animated: true) {
|
||||
self.presentingViewController.navigationController?.present(mailComposeViewController, animated:true) {
|
||||
UIUtil.applySignalAppearence();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: MailComposeViewControllerDelegate
|
||||
|
||||
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
||||
self.presentingViewController.dismiss(animated: true, completion: nil)
|
||||
|
||||
switch result {
|
||||
case .failed:
|
||||
let warning = UIAlertController(title: nil, message: NSLocalizedString("SEND_INVITE_FAILURE", comment:"Alert body after invite failed"), preferredStyle: .alert)
|
||||
warning.addAction(UIAlertAction(title: NSLocalizedString("DISMISS_BUTTON_TEXT", comment:""), style: .default, handler: nil))
|
||||
self.presentingViewController.present(warning, animated: true, completion: nil)
|
||||
case .sent:
|
||||
Logger.debug("\(self.TAG) user successfully invited their friends via mail.")
|
||||
case .saved:
|
||||
Logger.debug("\(self.TAG) user saved mail invite.")
|
||||
case .cancelled:
|
||||
Logger.debug("\(self.TAG) user cancelled mail invite.")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue