//
// C o p y r i g h t ( c ) 2 0 1 7 O p e n W h i s p e r S y s t e m s . A l l r i g h t s r e s e r v e d .
//
import Foundation
import Social
import ContactsUI
import MessageUI
@objc ( OWSInviteFlow )
class InviteFlow : NSObject , MFMessageComposeViewControllerDelegate , MFMailComposeViewControllerDelegate , ContactsPickerDelegate {
enum Channel {
case message , mail , twitter
}
let TAG = " [ShareActions] "
let installUrl = " https://signal.org/install/ "
let homepageUrl = " https://whispersystems.org "
let actionSheetController : UIAlertController
let presentingViewController : UIViewController
let contactsManager : OWSContactsManager
var channel : Channel ?
required init ( presentingViewController : UIViewController , contactsManager : OWSContactsManager ) {
self . presentingViewController = presentingViewController
self . contactsManager = contactsManager
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: T w i t t e r
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 : installUrl )
twitterViewController . add ( tweetUrl )
twitterViewController . add ( # imageLiteral ( resourceName : " twitter_sharing_image " ) )
let tweetTitle = NSLocalizedString ( " SHARE_ACTION_TWEET " , comment : " action sheet item " )
return UIAlertAction ( title : tweetTitle , style : . default ) { _ in
Logger . debug ( " \( self . TAG ) Chose tweet " )
self . presentingViewController . present ( twitterViewController , animated : true , completion : nil )
}
}
func dismissAction ( ) -> UIAlertAction {
return UIAlertAction ( title : CommonStrings . dismissButton , style : . cancel )
}
// MARK: C o n t a c t s P i c k e r D e l e g a t e
@ available ( iOS 9.0 , * )
func contactsPicker ( _ : ContactsPicker , didSelectMultipleContacts contacts : [ Contact ] ) {
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 :
let phoneNumbers : [ String ] = contacts . map { $0 . userTextPhoneNumbers . first } . filter { $0 != nil } . map { $0 ! }
sendSMSTo ( phoneNumbers : phoneNumbers )
case . mail :
let recipients : [ String ] = contacts . map { $0 . emails . first } . filter { $0 != nil } . map { $0 ! }
sendMailTo ( emails : recipients )
default :
Logger . error ( " \( TAG ) unexpected channel after returning from contact picker: \( inviteChannel ) " )
}
}
@ available ( iOS 9.0 , * )
func contactsPicker ( _ : ContactsPicker , shouldSelectContact contact : Contact ) -> Bool {
guard let inviteChannel = channel else {
Logger . error ( " \( TAG ) unexpected nil channel in contact picker. " )
return true
}
switch inviteChannel {
case . message :
return contact . userTextPhoneNumbers . count > 0
case . mail :
return contact . emails . count > 0
default :
Logger . error ( " \( TAG ) unexpected channel after returning from contact picker: \( inviteChannel ) " )
}
return true
}
// MARK: S M S
@ 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 ) { _ in
Logger . debug ( " \( self . TAG ) Chose message. " )
self . channel = . message
let picker = ContactsPicker ( delegate : self , multiSelection : true , subtitleCellType : . phoneNumber )
let navigationController = UINavigationController ( rootViewController : picker )
self . presentingViewController . present ( navigationController , animated : true )
}
}
func sendSMSTo ( phoneNumbers : [ String ] ) {
self . presentingViewController . dismiss ( animated : true ) {
if #available ( iOS 10.0 , * ) {
// i O S 1 0 m e s s a g e c o m p o s e v i e w d o e s n ' t r e s p e c t s o m e s y s t e m a p p e a r e n c e a t t r i b u t e s .
// S p e c i f i c a l l y , t h e t i t l e i s w h i t e , b u t t h e n a v b a r i s g r a y .
// S o , w e h a v e t o s e t s y s t e m a p p e a r e n c e b e f o r e i n i t ' i n g t h e m e s s a g e c o m p o s e v i e w c o n t r o l l e r i n o r d e r
// t o m a k e i t s c o l o r s l e g i b l e .
// T h e n w e h a v e t o b e s u r e t o s e t i t b a c k i n t h e C o m p o s e V i e w C o n t r o l l e r D e l e g a t e c a l l b a c k .
UIUtil . applyDefaultSystemAppearence ( )
}
let messageComposeViewController = MFMessageComposeViewController ( )
messageComposeViewController . messageComposeDelegate = self
messageComposeViewController . recipients = phoneNumbers
let inviteText = NSLocalizedString ( " SMS_INVITE_BODY " , comment : " body sent to contacts when inviting to Install Signal " )
messageComposeViewController . body = inviteText . appending ( " \( self . installUrl ) " )
self . presentingViewController . navigationController ? . present ( messageComposeViewController , animated : true )
}
}
// MARK: M e s s a g e C o m p o s e V i e w C o n t r o l l e r D e l e g a t e
func messageComposeViewController ( _ controller : MFMessageComposeViewController , didFinishWith result : MessageComposeResult ) {
// R e v e r t s y s t e m s t y l i n g a p p l i e d t o m a k e m e s s a g i n g a p p l e g i b l e o n i O S 1 0 .
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 : CommonStrings . dismissButton , 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: M a i l
@ 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 ) { _ in
Logger . debug ( " \( self . TAG ) Chose mail. " )
self . channel = . mail
let picker = ContactsPicker ( delegate : self , multiSelection : true , subtitleCellType : . email )
let navigationController = UINavigationController ( rootViewController : picker )
self . presentingViewController . present ( navigationController , animated : true )
}
}
@ available ( iOS 9.0 , * )
func sendMailTo ( emails recipientEmails : [ String ] ) {
let mailComposeViewController = MFMailComposeViewController ( )
mailComposeViewController . mailComposeDelegate = self
mailComposeViewController . setBccRecipients ( recipientEmails )
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}} and {{link to WhisperSystems home page}} " )
let body = String . init ( format : bodyFormat , installUrl , homepageUrl )
mailComposeViewController . setSubject ( subject )
mailComposeViewController . setMessageBody ( body , isHTML : false )
self . presentingViewController . dismiss ( animated : true ) {
self . presentingViewController . navigationController ? . present ( mailComposeViewController , animated : true ) {
UIUtil . applySignalAppearence ( )
}
}
}
// MARK: M a i l C o m p o s e V i e w C o n t r o l l e r D e l e g a t e
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 : CommonStrings . dismissButton , 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. " )
}
}
}