@ -22,6 +22,7 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
var thread : TSContactThread !
var call : SignalCall !
var hasDismissed = false
// MARK: V i e w s
@ -74,6 +75,18 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
}
}
// MARK: S e t t i n g s N a g V i e w s
var isShowingSettingsNag = false {
didSet {
if oldValue != isShowingSettingsNag {
updateCallUI ( callState : call . state )
}
}
}
var settingsNagView : UIView !
var settingsNagDescriptionLabel : UILabel !
// MARK: I n i t i a l i z e r s
required init ? ( coder aDecoder : NSCoder ) {
@ -160,6 +173,7 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
createContactViews ( )
createOngoingCallControls ( )
createIncomingCallControls ( )
createSettingsNagViews ( )
}
func didTouchRootView ( sender : UIGestureRecognizer ) {
@ -200,6 +214,62 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
self . view . addSubview ( contactAvatarView )
}
func createSettingsNagViews ( ) {
settingsNagView = UIView ( )
settingsNagView . isHidden = true
self . view . addSubview ( settingsNagView )
let viewStack = UIView ( )
settingsNagView . addSubview ( viewStack )
viewStack . autoPinWidthToSuperview ( )
viewStack . autoVCenterInSuperview ( )
settingsNagDescriptionLabel = UILabel ( )
settingsNagDescriptionLabel . text = NSLocalizedString ( " CALL_VIEW_SETTINGS_NAG_DESCRIPTION_ALL " ,
comment : " Reminder to the user of the benefits of enabling CallKit and disabling CallKit privacy. " )
settingsNagDescriptionLabel . font = UIFont . ows_regularFont ( withSize : ScaleFromIPhone5To7Plus ( 16 , 18 ) )
settingsNagDescriptionLabel . textColor = UIColor . white
settingsNagDescriptionLabel . numberOfLines = 0
settingsNagDescriptionLabel . lineBreakMode = . byWordWrapping
viewStack . addSubview ( settingsNagDescriptionLabel )
settingsNagDescriptionLabel . autoPinWidthToSuperview ( )
settingsNagDescriptionLabel . autoPinEdge ( toSuperviewEdge : . top )
let buttonHeight = ScaleFromIPhone5To7Plus ( 35 , 45 )
let buttonFont = UIFont . ows_regularFont ( withSize : ScaleFromIPhone5To7Plus ( 14 , 18 ) )
let buttonCornerRadius = CGFloat ( 4 )
let descriptionVSpacingHeight = ScaleFromIPhone5To7Plus ( 30 , 60 )
let callSettingsButton = UIButton ( )
callSettingsButton . setTitle ( NSLocalizedString ( " CALL_VIEW_SETTINGS_NAG_SHOW_CALL_SETTINGS " ,
comment : " Label for button that shows the privacy settings " ) , for : . normal )
callSettingsButton . setTitleColor ( UIColor . white , for : . normal )
callSettingsButton . titleLabel ! . font = buttonFont
callSettingsButton . addTarget ( self , action : #selector ( didPressShowCallSettings ) , for : . touchUpInside )
callSettingsButton . backgroundColor = UIColor . ows_signalBrandBlue ( )
callSettingsButton . layer . cornerRadius = buttonCornerRadius
callSettingsButton . clipsToBounds = true
viewStack . addSubview ( callSettingsButton )
callSettingsButton . autoSetDimension ( . height , toSize : buttonHeight )
callSettingsButton . autoPinWidthToSuperview ( )
callSettingsButton . autoPinEdge ( . top , to : . bottom , of : settingsNagDescriptionLabel , withOffset : descriptionVSpacingHeight )
let notNowButton = UIButton ( )
notNowButton . setTitle ( NSLocalizedString ( " CALL_VIEW_SETTINGS_NAG_NOT_NOW_BUTTON " ,
comment : " Label for button that dismiss the call view's settings nag. " ) , for : . normal )
notNowButton . setTitleColor ( UIColor . white , for : . normal )
notNowButton . titleLabel ! . font = buttonFont
notNowButton . addTarget ( self , action : #selector ( didPressDismissNag ) , for : . touchUpInside )
notNowButton . backgroundColor = UIColor . ows_signalBrandBlue ( )
notNowButton . layer . cornerRadius = buttonCornerRadius
notNowButton . clipsToBounds = true
viewStack . addSubview ( notNowButton )
notNowButton . autoSetDimension ( . height , toSize : buttonHeight )
notNowButton . autoPinWidthToSuperview ( )
notNowButton . autoPinEdge ( toSuperviewEdge : . bottom )
notNowButton . autoPinEdge ( . top , to : . bottom , of : callSettingsButton , withOffset : 12 )
}
func buttonSize ( ) -> CGFloat {
return ScaleFromIPhone5To7Plus ( 84 , 108 )
}
@ -356,8 +426,10 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
let contactVSpacing = CGFloat ( 3 )
let ongoingHMargin = ScaleFromIPhone5To7Plus ( 46 , 72 )
let incomingHMargin = ScaleFromIPhone5To7Plus ( 46 , 72 )
let settingsNagHMargin = CGFloat ( 30 )
let ongoingBottomMargin = ScaleFromIPhone5To7Plus ( 23 , 41 )
let incomingBottomMargin = CGFloat ( 41 )
let settingsNagBottomMargin = CGFloat ( 41 )
let avatarTopSpacing = ScaleFromIPhone5To7Plus ( 25 , 50 )
// T h e b u t t o n s h a v e b u i l t - i n 1 0 % m a r g i n s , s o t o a p p e a r c e n t e r e d
// t h e a v a t a r ' s b o t t o m s p a c i n g s h o u l d b e a b i t l e s s .
@ -401,6 +473,11 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
incomingCallView . autoPinEdge ( toSuperviewEdge : . bottom , withInset : incomingBottomMargin )
incomingCallView . autoPinWidthToSuperview ( withMargin : incomingHMargin )
incomingCallView . setContentHuggingVerticalHigh ( )
// S e t t i n g s n a g v i e w s
settingsNagView . autoPinEdge ( toSuperviewEdge : . bottom , withInset : settingsNagBottomMargin )
settingsNagView . autoPinWidthToSuperview ( withMargin : settingsNagHMargin )
settingsNagView . autoPinEdge ( . top , to : . bottom , of : callStatusLabel )
}
updateRemoteVideoLayout ( )
@ -529,7 +606,7 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
return NSLocalizedString ( " END_CALL_RESPONDER_IS_BUSY " , comment : " Call setup status label " )
case . localFailure :
if let error = call . error {
switch ( error ) {
switch error {
case . timeout ( description : _ ) :
if self . call . direction = = . outgoing {
return NSLocalizedString ( " CALL_SCREEN_STATUS_NO_ANSWER " , comment : " Call setup status label after outgoing call times out " )
@ -555,6 +632,13 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
assert ( Thread . isMainThread )
updateCallStatusLabel ( callState : callState )
if isShowingSettingsNag {
settingsNagView . isHidden = false
contactAvatarView . isHidden = true
ongoingCallView . isHidden = true
return
}
audioModeMuteButton . isSelected = call . isMuted
videoModeMuteButton . isSelected = call . isMuted
audioModeVideoButton . isSelected = call . hasLocalVideo
@ -595,13 +679,10 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
switch callState {
case . remoteHangup , . remoteBusy , . localFailure :
Logger . debug ( " \( TAG ) dismissing after delay because new state is \( callState ) " )
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 1.5 ) {
self . dismiss ( animated : true )
}
dismissIfPossible ( shouldDelay : true )
case . localHangup :
Logger . debug ( " \( TAG ) dismissing immediately from local hangup " )
self . dismiss ( animated : true )
dismissIfPossible ( shouldDelay : false )
default : break
}
@ -637,7 +718,7 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
Logger . warn ( " \( TAG ) hung up, but call was unexpectedly nil " )
}
self . dismiss ( animated : tru e)
dismissIfPossible ( shouldDelay : fals e)
}
func didPressMute ( sender muteButton : UIButton ) {
@ -663,7 +744,7 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
func didPressTextMessage ( sender speakerphoneButton : UIButton ) {
Logger . info ( " \( TAG ) called \( #function ) " )
self . dismiss ( animated : tru e)
dismissIfPossible ( shouldDelay : fals e)
}
func didPressAnswerCall ( sender : UIButton ) {
@ -676,9 +757,7 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
NSLocalizedString ( " END_CALL_UNCATEGORIZED_FAILURE " , comment : " Call setup status label " ) )
self . callStatusLabel . text = text
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 1.5 ) {
self . dismiss ( animated : true )
}
dismissIfPossible ( shouldDelay : true )
return
}
@ -707,7 +786,57 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
Logger . warn ( " \( TAG ) denied call, but call was unexpectedly nil " )
}
self . dismiss ( animated : true )
dismissIfPossible ( shouldDelay : false )
}
func didPressShowCallSettings ( sender : UIButton ) {
Logger . info ( " \( TAG ) called \( #function ) " )
markSettingsNagAsComplete ( )
dismissIfPossible ( shouldDelay : false , ignoreNag : true , completion : {
// F i n d t h e f r o n t m o s t p r e s e n t e d U I V i e w C o n t r o l l e r f r o m w h i c h t o p r e s e n t t h e
// s e t t i n g s v i e w s .
let window = UIApplication . shared . keyWindow
var fromViewController = window ! . rootViewController
let storyboard = fromViewController ? . storyboard
while fromViewController ? . presentedViewController != nil {
fromViewController = fromViewController ? . presentedViewController
}
assert ( fromViewController != nil )
assert ( storyboard != nil )
// C o n s t r u c t t h e " s e t t i n g s " v i e w & p u s h t h e " p r i v a c y s e t t i n g s " v i e w .
let navigationController = storyboard ? . instantiateViewController ( withIdentifier : " SettingsNavigationController " ) as ! UINavigationController
assert ( navigationController . viewControllers . count = = 1 )
let privacySettingsViewController = PrivacySettingsTableViewController ( )
navigationController . pushViewController ( privacySettingsViewController , animated : false )
fromViewController ? . present ( navigationController , animated : true , completion : nil )
} )
}
func didPressDismissNag ( sender : UIButton ) {
Logger . info ( " \( TAG ) called \( #function ) " )
markSettingsNagAsComplete ( )
dismissIfPossible ( shouldDelay : false , ignoreNag : true )
}
// W e o n l y s h o w t h e " b l o c k i n g " s e t t i n g s n a g u n t i l t h e u s e r h a s c h o s e n
// t o v i e w t h e p r i v a c y s e t t i n g s _ o r _ d i s m i s s e d t h e n a g a t l e a s t o n c e .
//
// I n e i t h e r c a s e , w e s e t t h e " C a l l K i t e n a b l e d " a n d " C a l l K i t p r i v a c y e n a b l e d "
// s e t t i n g s t o t h e i r d e f a u l t v a l u e s t o i n d i c a t e t h a t t h e u s e r h a s r e v i e w e d
// t h e m .
private func markSettingsNagAsComplete ( ) {
Logger . info ( " \( TAG ) called \( #function ) " )
let preferences = Environment . getCurrent ( ) . preferences !
preferences . setIsCallKitEnabled ( preferences . isCallKitEnabled ( ) )
preferences . setIsCallKitPrivacyEnabled ( preferences . isCallKitPrivacyEnabled ( ) )
}
// MARK: - C a l l O b s e r v e r
@ -775,6 +904,57 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
updateRemoteVideoLayout ( )
}
internal func dismissIfPossible ( shouldDelay : Bool , ignoreNag : Bool = false , completion : ( ( ) -> Swift . Void ) ? = nil ) {
if hasDismissed {
// D o n ' t d i s m i s s t w i c e .
return
} else if ! ignoreNag &&
call . direction = = . incoming &&
supportsCallKit ( ) &&
( ! Environment . getCurrent ( ) . preferences . isCallKitEnabled ( ) ||
Environment . getCurrent ( ) . preferences . isCallKitPrivacyEnabled ( ) ) {
isShowingSettingsNag = true
// U p d a t e t h e n a g v i e w ' s c o p y t o r e f l e c t t h e s e t t i n g s s t a t e .
if Environment . getCurrent ( ) . preferences . isCallKitEnabled ( ) {
settingsNagDescriptionLabel . text = NSLocalizedString ( " CALL_VIEW_SETTINGS_NAG_DESCRIPTION_PRIVACY " ,
comment : " Reminder to the user of the benefits of disabling CallKit privacy. " )
} else {
settingsNagDescriptionLabel . text = NSLocalizedString ( " CALL_VIEW_SETTINGS_NAG_DESCRIPTION_ALL " ,
comment : " Reminder to the user of the benefits of enabling CallKit and disabling CallKit privacy. " )
}
settingsNagDescriptionLabel . superview ? . setNeedsLayout ( )
if Environment . getCurrent ( ) . preferences . isCallKitEnabledSet ( ) ||
Environment . getCurrent ( ) . preferences . isCallKitPrivacySet ( ) {
// U s e r h a s a l r e a d y t o u c h e d t h e s e p r e f e r e n c e s , o n l y s h o w
// t h e " f l e e t i n g " n a g , n o t t h e " b l o c k i n g " n a g .
// S h o w n a g f o r N s e c o n d s .
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 5 ) { [ weak self ] in
guard let strongSelf = self else { return }
strongSelf . dismissIfPossible ( shouldDelay : false , ignoreNag : true )
}
}
} else if shouldDelay {
hasDismissed = true
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 1.5 ) { [ weak self ] in
guard let strongSelf = self else { return }
strongSelf . dismiss ( animated : true , completion : completion )
}
} else {
hasDismissed = true
self . dismiss ( animated : false , completion : completion )
}
}
// MARK: - U t i l
private func supportsCallKit ( ) -> Bool {
return ProcessInfo ( ) . isOperatingSystemAtLeast ( OperatingSystemVersion ( majorVersion : 10 , minorVersion : 0 , patchVersion : 0 ) )
}
// MARK: - C a l l S e r v i c e O b s e r v e r
internal func didUpdateCall ( call : SignalCall ? ) {