@ -7,8 +7,8 @@ import MediaPlayer
@objc
public protocol AttachmentApprovalViewControllerDelegate : class {
func didApproveAttachment ( )
func didCancelAttachment ( )
func didApproveAttachment ( attachment : SignalAttachment )
func didCancelAttachment ( attachment : SignalAttachment )
}
@objc
@ -21,7 +21,9 @@ public class AttachmentApprovalViewController: OWSViewController {
let attachment : SignalAttachment
let mediaMessageView : MediaMessageView
private ( set ) var bottomToolbar : UIToolbar !
private ( set ) var mediaMessageView : MediaMessageView !
private ( set ) var scrollView : UIScrollView !
// MARK: I n i t i a l i z e r s
@ -30,11 +32,12 @@ public class AttachmentApprovalViewController: OWSViewController {
fatalError ( " unimplemented " )
}
@objc
required public init ( attachment : SignalAttachment , delegate : AttachmentApprovalViewControllerDelegate ) {
assert ( ! attachment . hasError )
self . attachment = attachment
self . delegate = delegate
self . mediaMessageView = MediaMessageView ( attachment : attachment , mode : . large )
super . init ( nibName : nil , bundle : nil )
}
@ -42,12 +45,15 @@ public class AttachmentApprovalViewController: OWSViewController {
override public func viewDidLoad ( ) {
super . viewDidLoad ( )
self . navigationItem . title = dialogTitle ( )
}
view . backgroundColor = UIColor . white
createV iews( )
override public func viewWillLayoutSubviews ( ) {
Logger . debug ( " \( logTag ) in \( #function ) " )
super . viewWillLayoutSubv iews( )
self . navigationItem . title = dialogTitle ( )
// e . g . i f f l i p p i n g t o / f r o m l a n d s c a p e
updateMinZoomScaleForSize ( view . bounds . size )
}
private func dialogTitle ( ) -> String {
@ -59,12 +65,19 @@ public class AttachmentApprovalViewController: OWSViewController {
}
override public func viewWillAppear ( _ animated : Bool ) {
Logger . debug ( " \( logTag ) in \( #function ) " )
super . viewWillAppear ( animated )
mediaMessageView . viewWillAppear ( animated )
}
override public func viewDidAppear ( _ animated : Bool ) {
Logger . debug ( " \( logTag ) in \( #function ) " )
super . viewDidAppear ( animated )
}
override public func viewWillDisappear ( _ animated : Bool ) {
Logger . debug ( " \( logTag ) in \( #function ) " )
super . viewWillDisappear ( animated )
mediaMessageView . viewWillDisappear ( animated )
@ -72,100 +85,140 @@ public class AttachmentApprovalViewController: OWSViewController {
// MARK: - C r e a t e V i e w s
private func createViews ( ) {
let previewTopMargin : CGFloat = 30
let previewHMargin : CGFloat = 20
self . view . addSubview ( mediaMessageView )
mediaMessageView . autoPinWidthToSuperview ( withMargin : previewHMargin )
mediaMessageView . autoPin ( toTopLayoutGuideOf : self , withInset : previewTopMargin )
public override func loadView ( ) {
self . view = UIView ( )
self . mediaMessageView = MediaMessageView ( attachment : attachment , mode : . attachmentApproval )
// S c r o l l V i e w - u s e d t o z o o m / p a n o n i m a g e s a n d v i d e o
scrollView = UIScrollView ( )
view . addSubview ( scrollView )
scrollView . delegate = self
scrollView . showsHorizontalScrollIndicator = false
scrollView . showsVerticalScrollIndicator = false
// P a n n i n g s h o u l d s t o p p r e t t y s o o n a f t e r t h e u s e r s t o p s s c r o l l i n g
scrollView . decelerationRate = UIScrollViewDecelerationRateFast
// W e w a n t s c r o l l v i e w c o n t e n t u p a n d b e h i n d t h e s y s t e m s t a t u s b a r c o n t e n t
// b u t w e w a n t o t h e r c o n t e n t ( e . g . b a r b u t t o n s ) t o r e s p e c t t h e t o p l a y o u t g u i d e .
self . automaticallyAdjustsScrollViewInsets = false
scrollView . autoPinEdgesToSuperviewEdges ( )
let backgroundColor = UIColor . black
self . view . backgroundColor = backgroundColor
// C r e a t e f u l l s c r e e n c o n t a i n e r v i e w s o t h e s c r o l l V i e w
// c a n c o m p u t e a n a p p r o p r i a t e c o n t e n t s i z e i n w h i c h t o c e n t e r
// o u r m e d i a v i e w .
let containerView = UIView . container ( )
scrollView . addSubview ( containerView )
containerView . autoPinEdgesToSuperviewEdges ( )
containerView . autoMatch ( . height , to : . height , of : self . view )
containerView . autoMatch ( . width , to : . width , of : self . view )
containerView . addSubview ( mediaMessageView )
mediaMessageView . autoCenterInSuperview ( )
mediaMessageView . setCompressionResistanceHigh ( )
// A d d t o p a n d b o t t o m g r a d i e n t s t o e n s u r e t o o l b a r c o n t r o l s a r e l e g i b l e
// w h e n p l a c e d o v e r m e d i a w i t h a c l a s h i n g c o l o r
let topGradient = GradientView ( from : backgroundColor , to : UIColor . clear )
self . view . addSubview ( topGradient )
topGradient . autoPinWidthToSuperview ( )
topGradient . autoPinEdge ( toSuperviewEdge : . top )
topGradient . autoSetDimension ( . height , toSize : ScaleFromIPhone5 ( 60 ) )
let bottomGradient = GradientView ( from : UIColor . clear , to : backgroundColor )
self . view . addSubview ( bottomGradient )
bottomGradient . autoPinWidthToSuperview ( )
bottomGradient . autoPinEdge ( toSuperviewEdge : . bottom )
bottomGradient . autoSetDimension ( . height , toSize : ScaleFromIPhone5 ( 100 ) )
// H i d e t h e p l a y b u t t o n e m b e d d e d i n t h e M e d i a V i e w a n d r e p l a c e i t w i t h o u r o w n .
// T h i s a l l o w s u s t o z o o m i n o n t h e m e d i a v i e w w i t h o u t z o o m i n g i n o n t h e b u t t o n
if attachment . isVideo {
self . mediaMessageView . videoPlayButton ? . isHidden = true
let playButton = UIButton ( )
playButton . accessibilityLabel = NSLocalizedString ( " PLAY_BUTTON_ACCESSABILITY_LABEL " , comment : " accessability label for button to start media playback " )
playButton . setBackgroundImage ( # imageLiteral ( resourceName : " play_button " ) , for : . normal )
playButton . contentMode = . scaleAspectFit
let playButtonWidth = ScaleFromIPhone5 ( 70 )
playButton . autoSetDimensions ( to : CGSize ( width : playButtonWidth , height : playButtonWidth ) )
self . view . addSubview ( playButton )
playButton . addTarget ( self , action : #selector ( playButtonTapped ) , for : . touchUpInside )
playButton . autoCenterInSuperview ( )
}
createButtonRow ( mediaMessageView : mediaMessageView )
// T o p T o o l b a r
let topToolbar = makeClearToolbar ( )
self . view . addSubview ( topToolbar )
topToolbar . autoPinWidthToSuperview ( )
topToolbar . autoPin ( toTopLayoutGuideOf : self , withInset : 0 )
topToolbar . setContentHuggingVerticalHigh ( )
topToolbar . setCompressionResistanceVerticalHigh ( )
let cancelButton = UIBarButtonItem ( barButtonSystemItem : . stop , target : self , action : #selector ( cancelPressed ) )
cancelButton . tintColor = UIColor . white
topToolbar . items = [ cancelButton ]
// B o t t o m T o o l b a r
self . bottomToolbar = makeClearToolbar ( )
// M a k i n g a t o o l b a r t r a n s p a r e n t r e q u i r e s s e t t i n g a n e m p t y u i i m a g e
bottomToolbar . setBackgroundImage ( UIImage ( ) , forToolbarPosition : . any , barMetrics : . default )
bottomToolbar . backgroundColor = UIColor . clear
let sendTitle = NSLocalizedString ( " ATTACHMENT_APPROVAL_SEND_BUTTON " , comment : " Label for 'send' button in the 'attachment approval' dialog. " )
let sendButton = UIBarButtonItem ( title : sendTitle ,
style : . plain ,
target : self ,
action : #selector ( sendPressed ) )
sendButton . tintColor = UIColor . white
let flexibleSpace = UIBarButtonItem ( barButtonSystemItem : . flexibleSpace , target : nil , action : nil )
bottomToolbar . items = [ flexibleSpace , sendButton ]
self . view . addSubview ( bottomToolbar )
bottomToolbar . autoPin ( toBottomLayoutGuideOf : self , withInset : 0 )
bottomToolbar . autoPinWidthToSuperview ( )
bottomToolbar . setCompressionResistanceVerticalHigh ( )
bottomToolbar . setContentHuggingVerticalHigh ( )
}
private func wrapViewsInVerticalStack ( subviews : [ UIView ] ) -> UIView {
assert ( subviews . count > 0 )
private func makeClearToolbar( ) -> UIToolbar {
let toolbar = UIToolbar ( )
let stackView = UIView ( )
toolbar . backgroundColor = UIColor . clear
var lastView : UIView ?
for subview in subviews {
// M a k i n g a t o o l b a r t r a n s p a r e n t r e q u i r e s s e t t i n g a n e m p t y u i i m a g e
toolbar . setBackgroundImage ( UIImage ( ) , forToolbarPosition : . any , barMetrics : . default )
stackView . addSubview ( subview )
subview . autoHCenterInSuperview ( )
if lastView = = nil {
subview . autoPinEdge ( toSuperviewEdge : . top )
} else {
subview . autoPinEdge ( . top , to : . bottom , of : lastView ! , withOffset : 10 )
}
lastView = subview
}
// h i d e 1 p x t o p - b o r d e r
toolbar . clipsToBounds = true
lastView ? . autoPinEdge ( toSuperviewEdge : . bottom )
return stackView
}
private func createButtonRow ( mediaMessageView : UIView ) {
let buttonTopMargin = ScaleFromIPhone5To7Plus ( 30 , 40 )
let buttonBottomMargin = ScaleFromIPhone5To7Plus ( 25 , 40 )
let buttonHSpacing = ScaleFromIPhone5To7Plus ( 20 , 30 )
let buttonRow = UIView ( )
self . view . addSubview ( buttonRow )
buttonRow . autoPinWidthToSuperview ( )
buttonRow . autoPinEdge ( toSuperviewEdge : . bottom , withInset : buttonBottomMargin )
buttonRow . autoPinEdge ( . top , to : . bottom , of : mediaMessageView , withOffset : buttonTopMargin )
// W e u s e t h i s i n v i s i b l e s u b v i e w t o e n s u r e t h a t t h e b u t t o n s a r e c e n t e r e d
// h o r i z o n t a l l y .
let buttonSpacer = UIView ( )
buttonRow . addSubview ( buttonSpacer )
// V e r t i c a l p o s i t i o n i n g o f t h i s v i e w d o e s n ' t m a t t e r .
buttonSpacer . autoPinEdge ( toSuperviewEdge : . top )
buttonSpacer . autoSetDimension ( . width , toSize : buttonHSpacing )
buttonSpacer . autoHCenterInSuperview ( )
let cancelButton = createButton ( title : CommonStrings . cancelButton ,
color : UIColor . ows_destructiveRed ( ) ,
action : #selector ( cancelPressed ) )
buttonRow . addSubview ( cancelButton )
cancelButton . autoPinEdge ( toSuperviewEdge : . top )
cancelButton . autoPinEdge ( toSuperviewEdge : . bottom )
cancelButton . autoPinEdge ( . right , to : . left , of : buttonSpacer )
let sendButton = createButton ( title : NSLocalizedString ( " ATTACHMENT_APPROVAL_SEND_BUTTON " ,
comment : " Label for 'send' button in the 'attachment approval' dialog. " ) ,
color : UIColor ( rgbHex : 0x2ecc71 ) ,
action : #selector ( sendPressed ) )
buttonRow . addSubview ( sendButton )
sendButton . autoPinEdge ( toSuperviewEdge : . top )
sendButton . autoPinEdge ( toSuperviewEdge : . bottom )
sendButton . autoPinEdge ( . left , to : . right , of : buttonSpacer )
}
private func createButton ( title : String , color : UIColor , action : Selector ) -> UIView {
let buttonWidth = ScaleFromIPhone5To7Plus ( 110 , 140 )
let buttonHeight = ScaleFromIPhone5To7Plus ( 35 , 45 )
return OWSFlatButton . button ( title : title ,
titleColor : UIColor . white ,
backgroundColor : color ,
width : buttonWidth ,
height : buttonHeight ,
target : target ,
selector : action )
return toolbar
}
// MARK: - E v e n t H a n d l e r s
@objc
public func playButtonTapped ( ) {
mediaMessageView . playVideo ( )
}
func cancelPressed ( sender : UIButton ) {
self . delegate ? . didCancelAttachment ( )
self . delegate ? . didCancelAttachment ( attachment : attachment )
}
func sendPressed ( sender : UIButton ) {
// d i s a b l e c o n t r o l s a f t e r s e n d w a s t a p p e d .
self . bottomToolbar . isUserInteractionEnabled = false
// F I X M E
// t h i s i s j u s t a t e m p o r a r y h a c k t o p r o v i d e s o m e U I
@ -175,6 +228,90 @@ public class AttachmentApprovalViewController: OWSViewController {
activityIndicatorView . autoCenterInSuperview ( )
activityIndicatorView . startAnimating ( )
self . delegate ? . didApproveAttachment ( )
self . delegate ? . didApproveAttachment ( attachment : attachment )
}
}
extension AttachmentApprovalViewController : UIScrollViewDelegate {
public func viewForZooming ( in scrollView : UIScrollView ) -> UIView ? {
return mediaMessageView
}
fileprivate func updateMinZoomScaleForSize ( _ size : CGSize ) {
Logger . debug ( " \( logTag ) in \( #function ) " )
// E n s u r e b o u n d s h a v e b e e n c o m p u t e d
mediaMessageView . layoutIfNeeded ( )
guard mediaMessageView . bounds . width > 0 , mediaMessageView . bounds . height > 0 else {
Logger . warn ( " \( logTag ) bad bounds in \( #function ) " )
return
}
let widthScale = size . width / mediaMessageView . bounds . width
let heightScale = size . height / mediaMessageView . bounds . height
let minScale = min ( widthScale , heightScale )
scrollView . maximumZoomScale = minScale * 5.0
scrollView . minimumZoomScale = minScale
scrollView . zoomScale = minScale
}
// K e e p t h e m e d i a v i e w c e n t e r e d w i t h i n t h e s c r o l l v i e w a s y o u z o o m
public func scrollViewDidZoom ( _ scrollView : UIScrollView ) {
// T h e s c r o l l v i e w h a s z o o m e d , s o y o u n e e d t o r e - c e n t e r t h e c o n t e n t s
let scrollViewSize = self . scrollViewVisibleSize
// F i r s t a s s u m e t h a t m e d i a M e s s a g e V i e w c e n t e r c o i n c i d e s w i t h t h e c o n t e n t s c e n t e r
// T h i s i s c o r r e c t w h e n t h e m e d i a M e s s a g e V i e w i s b i g g e r t h a n s c r o l l V i e w d u e t o z o o m
var contentCenter = CGPoint ( x : ( scrollView . contentSize . width / 2 ) , y : ( scrollView . contentSize . height / 2 ) )
let scrollViewCenter = self . scrollViewCenter
// i f m e d i a M e s s a g e V i e w i s s m a l l e r t h a n t h e s c r o l l V i e w v i s i b l e s i z e - f i x t h e c o n t e n t c e n t e r a c c o r d i n g l y
if self . scrollView . contentSize . width < scrollViewSize . width {
contentCenter . x = scrollViewCenter . x
}
if self . scrollView . contentSize . height < scrollViewSize . height {
contentCenter . y = scrollViewCenter . y
}
self . mediaMessageView . center = contentCenter
}
// r e t u r n t h e s c r o l l v i e w c e n t e r
private var scrollViewCenter : CGPoint {
let size = scrollViewVisibleSize
return CGPoint ( x : ( size . width / 2 ) , y : ( size . height / 2 ) )
}
// R e t u r n s c r o l l v i e w s i z e w i t h o u t t h e a r e a o v e r l a p p i n g w i t h t a b a n d n a v b a r .
private var scrollViewVisibleSize : CGSize {
let contentInset = scrollView . contentInset
let scrollViewSize = scrollView . bounds . standardized . size
let width = scrollViewSize . width - ( contentInset . left + contentInset . right )
let height = scrollViewSize . height - ( contentInset . top + contentInset . bottom )
return CGSize ( width : width , height : height )
}
}
private class GradientView : UIView {
let gradientLayer = CAGradientLayer ( )
required init ( from fromColor : UIColor , to toColor : UIColor ) {
gradientLayer . colors = [ fromColor . cgColor , toColor . cgColor ]
super . init ( frame : CGRect . zero )
self . layer . addSublayer ( gradientLayer )
}
required init ? ( coder aDecoder : NSCoder ) {
fatalError ( " init(coder:) has not been implemented " )
}
override func layoutSubviews ( ) {
super . layoutSubviews ( )
gradientLayer . frame = self . bounds
}
}