//
// C o p y r i g h t ( c ) 2 0 1 9 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 AVFoundation
import MediaPlayer
import PromiseKit
@objc
public protocol AttachmentApprovalViewControllerDelegate : class {
func attachmentApproval ( _ attachmentApproval : AttachmentApprovalViewController , didApproveAttachments attachments : [ SignalAttachment ] , messageText : String ? )
func attachmentApproval ( _ attachmentApproval : AttachmentApprovalViewController , didCancelAttachments attachments : [ SignalAttachment ] )
@objc optional func attachmentApproval ( _ attachmentApproval : AttachmentApprovalViewController , addMoreToAttachments attachments : [ SignalAttachment ] )
@objc optional func attachmentApproval ( _ attachmentApproval : AttachmentApprovalViewController , changedCaptionOfAttachment attachment : SignalAttachment )
}
// MARK: -
@objc
public enum AttachmentApprovalViewControllerMode : UInt {
case modal
case sharedNavigation
}
// MARK: -
@objc
public class AttachmentApprovalViewController : UIPageViewController , UIPageViewControllerDataSource , UIPageViewControllerDelegate {
// MARK: - P r o p e r t i e s
private let mode : AttachmentApprovalViewControllerMode
public weak var approvalDelegate : AttachmentApprovalViewControllerDelegate ?
public var isEditingCaptions = false {
didSet {
updateContents ( )
}
}
// MARK: - I n i t i a l i z e r s
@ available ( * , unavailable , message : " use attachment: constructor instead. " )
required public init ? ( coder aDecoder : NSCoder ) {
notImplemented ( )
}
let kSpacingBetweenItems : CGFloat = 20
@objc
required public init ( mode : AttachmentApprovalViewControllerMode ,
attachments : [ SignalAttachment ] ) {
assert ( attachments . count > 0 )
self . mode = mode
let attachmentItems = attachments . map { SignalAttachmentItem ( attachment : $0 ) }
self . attachmentItemCollection = AttachmentItemCollection ( attachmentItems : attachmentItems )
super . init ( transitionStyle : . scroll ,
navigationOrientation : . horizontal ,
options : [ UIPageViewControllerOptionInterPageSpacingKey : kSpacingBetweenItems ] )
self . dataSource = self
self . delegate = self
NotificationCenter . default . addObserver ( self ,
selector : #selector ( didBecomeActive ) ,
name : NSNotification . Name . OWSApplicationDidBecomeActive ,
object : nil )
}
deinit {
NotificationCenter . default . removeObserver ( self )
}
@objc
public class func wrappedInNavController ( attachments : [ SignalAttachment ] , approvalDelegate : AttachmentApprovalViewControllerDelegate ) -> OWSNavigationController {
let vc = AttachmentApprovalViewController ( mode : . modal , attachments : attachments )
vc . approvalDelegate = approvalDelegate
let navController = OWSNavigationController ( rootViewController : vc )
navController . ows_prefersStatusBarHidden = true
guard let navigationBar = navController . navigationBar as ? OWSNavigationBar else {
owsFailDebug ( " navigationBar was nil or unexpected class " )
return navController
}
navigationBar . overrideTheme ( type : . clear )
return navController
}
// MARK: - N o t i f i c a t i o n s
@objc func didBecomeActive ( ) {
AssertIsOnMainThread ( )
updateContents ( )
}
// MARK: - S u b v i e w s
var galleryRailView : GalleryRailView {
return bottomToolView . galleryRailView
}
var attachmentTextToolbar : AttachmentTextToolbar {
return bottomToolView . attachmentTextToolbar
}
lazy var bottomToolView : AttachmentApprovalInputAccessoryView = {
let isAddMoreVisible = mode = = . sharedNavigation
let bottomToolView = AttachmentApprovalInputAccessoryView ( isAddMoreVisible : isAddMoreVisible )
bottomToolView . delegate = self
return bottomToolView
} ( )
lazy var touchInterceptorView = UIView ( )
// MARK: - V i e w L i f e c y c l e
public override var prefersStatusBarHidden : Bool {
return true
}
override public func viewDidLoad ( ) {
super . viewDidLoad ( )
self . view . backgroundColor = . black
// a v o i d a n u n p l e a s a n t " b o u n c e " w h i c h d o e s n ' t m a k e s e n s e i n t h e c o n t e x t o f a s i n g l e i t e m .
pagerScrollView ? . isScrollEnabled = attachmentItems . count > 1
// B o t t o m T o o l b a r
galleryRailView . delegate = self
attachmentTextToolbar . attachmentTextToolbarDelegate = self
// N a v i g a t i o n
self . navigationItem . title = nil
guard let firstItem = attachmentItems . first else {
owsFailDebug ( " firstItem was unexpectedly nil " )
return
}
self . setCurrentItem ( firstItem , direction : . forward , animated : false )
// l a y o u t i m m e d i a t e l y t o a v o i d a n i m a t i n g t h e l a y o u t p r o c e s s d u r i n g t h e t r a n s i t i o n
self . currentPageViewController . view . layoutIfNeeded ( )
view . addSubview ( touchInterceptorView )
touchInterceptorView . autoPinEdgesToSuperviewEdges ( )
touchInterceptorView . isHidden = true
let tapGesture = UITapGestureRecognizer ( target : self , action : #selector ( didTapTouchInterceptorView ( gesture : ) ) )
touchInterceptorView . addGestureRecognizer ( tapGesture )
}
override public func viewWillAppear ( _ animated : Bool ) {
Logger . debug ( " " )
super . viewWillAppear ( animated )
guard let navigationBar = navigationController ? . navigationBar as ? OWSNavigationBar else {
owsFailDebug ( " navigationBar was nil or unexpected class " )
return
}
navigationBar . overrideTheme ( type : . clear )
updateContents ( )
}
override public func viewDidAppear ( _ animated : Bool ) {
Logger . debug ( " " )
super . viewDidAppear ( animated )
updateContents ( )
}
override public func viewWillDisappear ( _ animated : Bool ) {
Logger . debug ( " " )
super . viewWillDisappear ( animated )
}
private func updateContents ( ) {
updateNavigationBar ( )
updateInputAccessory ( )
updateControlVisibility ( )
touchInterceptorView . isHidden = ! isEditingCaptions
}
// MARK: - I n p u t A c c e s s o r y
override public var inputAccessoryView : UIView ? {
bottomToolView . layoutIfNeeded ( )
return bottomToolView
}
override public var canBecomeFirstResponder : Bool {
return ! shouldHideControls
}
public func updateInputAccessory ( ) {
var currentPageViewController : AttachmentPrepViewController ?
if pageViewControllers . count = = 1 {
currentPageViewController = pageViewControllers . first
}
let currentAttachmentItem : SignalAttachmentItem ? = currentPageViewController ? . attachmentItem
bottomToolView . update ( isEditingCaptions : isEditingCaptions , currentAttachmentItem : currentAttachmentItem )
}
// MARK: - N a v i g a t i o n B a r
public func updateNavigationBar ( ) {
guard ! shouldHideControls else {
self . navigationItem . leftBarButtonItem = nil
self . navigationItem . rightBarButtonItem = nil
return
}
guard ! isEditingCaptions else {
// H i d e a l l n a v i g a t i o n b a r i t e m s w h i l e t h e c a p t i o n v i e w i s o p e n .
self . navigationItem . leftBarButtonItem = UIBarButtonItem ( title : NSLocalizedString ( " ATTACHMENT_APPROVAL_CAPTION_TITLE " , comment : " Title for 'caption' mode of the attachment approval view. " ) , style : . plain , target : nil , action : nil )
let doneButton = navigationBarButton ( imageName : " image_editor_checkmark_full " ,
selector : #selector ( didTapCaptionDone ( sender : ) ) )
let navigationBarItems = [ doneButton ]
updateNavigationBar ( navigationBarItems : navigationBarItems )
return
}
var navigationBarItems = [ UIView ] ( )
if let viewControllers = viewControllers ,
viewControllers . count = = 1 ,
let firstViewController = viewControllers . first as ? AttachmentPrepViewController {
navigationBarItems = firstViewController . navigationBarItems ( )
// S h o w t h e c a p t i o n U I i f t h e r e ' s m o r e t h a n o n e a t t a c h m e n t
// O R i f t h e a t t a c h m e n t a l r e a d y h a s a c a p t i o n .
let attachmentCount = attachmentItemCollection . count
var shouldShowCaptionUI = attachmentCount > 0
if let captionText = firstViewController . attachmentItem . captionText , captionText . count > 0 {
shouldShowCaptionUI = true
}
if shouldShowCaptionUI {
let captionButton = navigationBarButton ( imageName : " image_editor_caption " ,
selector : #selector ( didTapCaption ( sender : ) ) )
navigationBarItems . append ( captionButton )
}
}
updateNavigationBar ( navigationBarItems : navigationBarItems )
let hasCancel = ( mode != . sharedNavigation )
if hasCancel {
// M i m i c a U I B a r B u t t o n I t e m o f t y p e . c a n c e l , b u t w i t h a s h a d o w .
let cancelButton = OWSButton ( title : CommonStrings . cancelButton ) { [ weak self ] in
self ? . cancelPressed ( )
}
cancelButton . setTitleColor ( . white , for : . normal )
if let titleLabel = cancelButton . titleLabel {
titleLabel . font = UIFont . systemFont ( ofSize : 18.0 )
titleLabel . layer . shadowColor = UIColor . black . cgColor
titleLabel . layer . shadowRadius = 2.0
titleLabel . layer . shadowOpacity = 0.66
titleLabel . layer . shadowOffset = . zero
} else {
owsFailDebug ( " Missing titleLabel. " )
}
cancelButton . sizeToFit ( )
navigationItem . leftBarButtonItem = UIBarButtonItem ( customView : cancelButton )
} else {
// M i m i c a c o n v e n t i o n a l b a c k b u t t o n , b u t w i t h a s h a d o w .
let isRTL = CurrentAppContext ( ) . isRTL
let imageName = isRTL ? " NavBarBackRTL " : " NavBarBack "
let backButton = OWSButton ( imageName : imageName , tintColor : . white ) { [ weak self ] in
self ? . navigationController ? . popViewController ( animated : true )
}
// N u d g e c l o s e r t o t h e l e f t e d g e t o m a t c h d e f a u l t b a c k b u t t o n i t e m .
let kExtraLeftPadding : CGFloat = isRTL ? + 0 : - 8
// G i v e s o m e e x t r a h i t a r e a t o t h e b a c k b u t t o n . T h i s i s a l i t t l e s m a l l e r
// t h a n t h e d e f a u l t b a c k b u t t o n , b u t m a k e s s e n s e f o r o u r l e f t a l i g n e d t i t l e
// v i e w i n t h e M e s s a g e s V i e w C o n t r o l l e r
let kExtraRightPadding : CGFloat = isRTL ? - 0 : + 10
// E x t r a h i t a r e a a b o v e / b e l o w
let kExtraHeightPadding : CGFloat = 4
// M a t c h i n g t h e d e f a u l t b a c k b u t t o n p l a c e m e n t i s t r i c k y .
// W e c a n ' t j u s t a d j u s t t h e i m a g e E d g e I n s e t s o n a U I B a r B u t t o n I t e m d i r e c t l y ,
// s o w e a d j u s t t h e i m a g e E d g e I n s e t s o n a U I B u t t o n , t h e n w r a p t h a t
// i n a U I B a r B u t t o n I t e m .
backButton . contentHorizontalAlignment = . left
// D e f a u l t b a c k b u t t o n i s 1 . 5 p i x e l l o w e r t h a n o u r e x t r a c t e d i m a g e .
let kTopInsetPadding : CGFloat = 1.5
backButton . imageEdgeInsets = UIEdgeInsets ( top : kTopInsetPadding , left : kExtraLeftPadding , bottom : 0 , right : 0 )
var backImageSize = CGSize . zero
if let backImage = UIImage ( named : imageName ) {
backImageSize = backImage . size
} else {
owsFailDebug ( " Missing backImage. " )
}
backButton . frame = CGRect ( origin : . zero , size : CGSize ( width : backImageSize . width + kExtraRightPadding ,
height : backImageSize . height + kExtraHeightPadding ) )
backButton . layer . shadowColor = UIColor . black . cgColor
backButton . layer . shadowRadius = 2.0
backButton . layer . shadowOpacity = 0.66
backButton . layer . shadowOffset = . zero
// N o t e : u s i n g a c u s t o m l e f t B a r B u t t o n I t e m b r e a k s t h e i n t e r a c t i v e p o p g e s t u r e .
navigationItem . leftBarButtonItem = UIBarButtonItem ( customView : backButton )
}
}
// MARK: - C o n t r o l V i s i b i l i t y
public var shouldHideControls : Bool {
guard let pageViewController = pageViewControllers . first else {
return false
}
return pageViewController . shouldHideControls
}
private func updateControlVisibility ( ) {
let hasPresentedView = self . presentedViewController != nil
if ! shouldHideControls , ! isFirstResponder , ! hasPresentedView {
becomeFirstResponder ( )
}
bottomToolView . shouldHideControls = shouldHideControls
}
// MARK: - V i e w H e l p e r s
func remove ( attachmentItem : SignalAttachmentItem ) {
if attachmentItem = = currentItem {
if let nextItem = attachmentItemCollection . itemAfter ( item : attachmentItem ) {
setCurrentItem ( nextItem , direction : . forward , animated : true )
} else if let prevItem = attachmentItemCollection . itemBefore ( item : attachmentItem ) {
setCurrentItem ( prevItem , direction : . reverse , animated : true )
} else {
owsFailDebug ( " removing last item shouldn't be possible because rail should not be visible " )
return
}
}
guard let cell = galleryRailView . cellViews . first ( where : { $0 . item = = = attachmentItem } ) else {
owsFailDebug ( " cell was unexpectedly nil " )
return
}
UIView . animate ( withDuration : 0.2 ,
animations : {
// s h r i n k s t a c k v i e w i t e m u n t i l i t d i s a p p e a r s
cell . isHidden = true
// s i m u l t a n e o u s l y f a d e o u t
cell . alpha = 0
} ,
completion : { _ in
self . attachmentItemCollection . remove ( item : attachmentItem )
self . updateMediaRail ( )
} )
}
lazy var pagerScrollView : UIScrollView ? = {
// T h i s i s k i n d o f a h a c k . S i n c e w e d o n ' t h a v e f i r s t c l a s s a c c e s s t o t h e s u p e r v i e w ' s ` s c r o l l V i e w `
// w e t r a v e r s e t h e v i e w h i e r a r c h y u n t i l w e f i n d i t .
let pagerScrollView = view . subviews . first { $0 is UIScrollView } as ? UIScrollView
assert ( pagerScrollView != nil )
return pagerScrollView
} ( )
// MARK: - U I P a g e V i e w C o n t r o l l e r D e l e g a t e
public func pageViewController ( _ pageViewController : UIPageViewController , willTransitionTo pendingViewControllers : [ UIViewController ] ) {
Logger . debug ( " " )
assert ( pendingViewControllers . count = = 1 )
pendingViewControllers . forEach { viewController in
guard let pendingPage = viewController as ? AttachmentPrepViewController else {
owsFailDebug ( " unexpected viewController: \( viewController ) " )
return
}
// u s e c o m p a c t s c a l e w h e n k e y b o a r d i s p o p p e d .
let scale : AttachmentPrepViewController . AttachmentViewScale = self . isFirstResponder ? . fullsize : . compact
pendingPage . setAttachmentViewScale ( scale , animated : false )
}
}
public func pageViewController ( _ pageViewController : UIPageViewController , didFinishAnimating finished : Bool , previousViewControllers : [ UIViewController ] , transitionCompleted : Bool ) {
Logger . debug ( " " )
assert ( previousViewControllers . count = = 1 )
previousViewControllers . forEach { viewController in
guard let previousPage = viewController as ? AttachmentPrepViewController else {
owsFailDebug ( " unexpected viewController: \( viewController ) " )
return
}
if transitionCompleted {
previousPage . zoomOut ( animated : false )
updateMediaRail ( )
}
}
updateContents ( )
}
// MARK: - U I P a g e V i e w C o n t r o l l e r D a t a S o u r c e
public func pageViewController ( _ pageViewController : UIPageViewController , viewControllerBefore viewController : UIViewController ) -> UIViewController ? {
guard let currentViewController = viewController as ? AttachmentPrepViewController else {
owsFailDebug ( " unexpected viewController: \( viewController ) " )
return nil
}
let currentItem = currentViewController . attachmentItem
guard let previousItem = attachmentItem ( before : currentItem ) else {
return nil
}
guard let previousPage : AttachmentPrepViewController = buildPage ( item : previousItem ) else {
return nil
}
return previousPage
}
public func pageViewController ( _ pageViewController : UIPageViewController , viewControllerAfter viewController : UIViewController ) -> UIViewController ? {
Logger . debug ( " " )
guard let currentViewController = viewController as ? AttachmentPrepViewController else {
owsFailDebug ( " unexpected viewController: \( viewController ) " )
return nil
}
let currentItem = currentViewController . attachmentItem
guard let nextItem = attachmentItem ( after : currentItem ) else {
return nil
}
guard let nextPage : AttachmentPrepViewController = buildPage ( item : nextItem ) else {
return nil
}
return nextPage
}
public var currentPageViewController : AttachmentPrepViewController {
return pageViewControllers . first !
}
public var pageViewControllers : [ AttachmentPrepViewController ] {
return super . viewControllers ! . map { $0 as ! AttachmentPrepViewController }
}
var currentItem : SignalAttachmentItem ! {
get {
return currentPageViewController . attachmentItem
}
set {
setCurrentItem ( newValue , direction : . forward , animated : false )
}
}
private var cachedPages : [ SignalAttachmentItem : AttachmentPrepViewController ] = [ : ]
private func buildPage ( item : SignalAttachmentItem ) -> AttachmentPrepViewController ? {
if let cachedPage = cachedPages [ item ] {
Logger . debug ( " cache hit. " )
return cachedPage
}
Logger . debug ( " cache miss. " )
let viewController = AttachmentPrepViewController ( attachmentItem : item )
viewController . prepDelegate = self
cachedPages [ item ] = viewController
return viewController
}
private func setCurrentItem ( _ item : SignalAttachmentItem , direction : UIPageViewControllerNavigationDirection , animated isAnimated : Bool ) {
guard let page = self . buildPage ( item : item ) else {
owsFailDebug ( " unexpectedly unable to build new page " )
return
}
page . loadViewIfNeeded ( )
self . setViewControllers ( [ page ] , direction : direction , animated : isAnimated , completion : nil )
updateMediaRail ( )
}
func updateMediaRail ( ) {
guard let currentItem = self . currentItem else {
owsFailDebug ( " currentItem was unexpectedly nil " )
return
}
let cellViewBuilder : ( ) -> ApprovalRailCellView = { [ weak self ] in
let cell = ApprovalRailCellView ( )
cell . approvalRailCellDelegate = self
return cell
}
galleryRailView . configureCellViews ( itemProvider : attachmentItemCollection ,
focusedItem : currentItem ,
cellViewBuilder : cellViewBuilder )
galleryRailView . isHidden = attachmentItemCollection . attachmentItems . count < 2
}
let attachmentItemCollection : AttachmentItemCollection
var attachmentItems : [ SignalAttachmentItem ] {
return attachmentItemCollection . attachmentItems
}
var attachments : [ SignalAttachment ] {
return attachmentItems . map { ( attachmentItem ) in
autoreleasepool {
return self . processedAttachment ( forAttachmentItem : attachmentItem )
}
}
}
// F o r a n y a t t a c h m e n t s e d i t e d w i t h t h e i m a g e e d i t o r , r e t u r n s a
// n e w S i g n a l A t t a c h m e n t t h a t r e f l e c t s t h o s e c h a n g e s . O t h e r w i s e ,
// r e t u r n s t h e o r i g i n a l a t t a c h m e n t .
//
// I f a n y e r r o r s o c c u r s i n t h e e x p o r t p r o c e s s , w e f a i l o v e r t o
// s e n d i n g t h e o r i g i n a l a t t a c h m e n t . T h i s s e e m s b e t t e r t h a n t r y i n g
// t o i n v o l v e t h e u s e r i n r e s o l v i n g t h e i s s u e .
func processedAttachment ( forAttachmentItem attachmentItem : SignalAttachmentItem ) -> SignalAttachment {
guard let imageEditorModel = attachmentItem . imageEditorModel else {
// I m a g e w a s n o t e d i t e d .
return attachmentItem . attachment
}
guard imageEditorModel . isDirty ( ) else {
// I m a g e e d i t o r h a s n o c h a n g e s .
return attachmentItem . attachment
}
guard let dstImage = ImageEditorCanvasView . renderForOutput ( model : imageEditorModel , transform : imageEditorModel . currentTransform ( ) ) else {
owsFailDebug ( " Could not render for output. " )
return attachmentItem . attachment
}
var dataUTI = kUTTypeImage as String
guard let dstData : Data = {
let isLossy : Bool = attachmentItem . attachment . mimeType . caseInsensitiveCompare ( OWSMimeTypeImageJpeg ) = = . orderedSame
if isLossy {
dataUTI = kUTTypeJPEG as String
return UIImageJPEGRepresentation ( dstImage , 0.9 )
} else {
dataUTI = kUTTypePNG as String
return UIImagePNGRepresentation ( dstImage )
}
} ( ) else {
owsFailDebug ( " Could not export for output. " )
return attachmentItem . attachment
}
guard let dataSource = DataSourceValue . dataSource ( with : dstData , utiType : dataUTI ) else {
owsFailDebug ( " Could not prepare data source for output. " )
return attachmentItem . attachment
}
// R e w r i t e t h e f i l e n a m e ' s e x t e n s i o n t o r e f l e c t t h e o u t p u t f i l e f o r m a t .
var filename : String ? = attachmentItem . attachment . sourceFilename
if let sourceFilename = attachmentItem . attachment . sourceFilename {
if let fileExtension : String = MIMETypeUtil . fileExtension ( forUTIType : dataUTI ) {
filename = ( sourceFilename as NSString ) . deletingPathExtension . appendingFileExtension ( fileExtension )
}
}
dataSource . sourceFilename = filename
let dstAttachment = SignalAttachment . attachment ( dataSource : dataSource , dataUTI : dataUTI , imageQuality : . original )
if let attachmentError = dstAttachment . error {
owsFailDebug ( " Could not prepare attachment for output: \( attachmentError ) . " )
return attachmentItem . attachment
}
// P r e s e r v e c a p t i o n t e x t .
dstAttachment . captionText = attachmentItem . captionText
return dstAttachment
}
func attachmentItem ( before currentItem : SignalAttachmentItem ) -> SignalAttachmentItem ? {
guard let currentIndex = attachmentItems . index ( of : currentItem ) else {
owsFailDebug ( " currentIndex was unexpectedly nil " )
return nil
}
let index : Int = attachmentItems . index ( before : currentIndex )
guard let previousItem = attachmentItems [ safe : index ] else {
// a l r e a d y a t f i r s t i t e m
return nil
}
return previousItem
}
func attachmentItem ( after currentItem : SignalAttachmentItem ) -> SignalAttachmentItem ? {
guard let currentIndex = attachmentItems . index ( of : currentItem ) else {
owsFailDebug ( " currentIndex was unexpectedly nil " )
return nil
}
let index : Int = attachmentItems . index ( after : currentIndex )
guard let nextItem = attachmentItems [ safe : index ] else {
// a l r e a d y a t l a s t i t e m
return nil
}
return nextItem
}
// MARK: - E v e n t H a n d l e r s
@objc
func didTapTouchInterceptorView ( gesture : UITapGestureRecognizer ) {
Logger . info ( " " )
isEditingCaptions = false
}
private func cancelPressed ( ) {
self . approvalDelegate ? . attachmentApproval ( self , didCancelAttachments : attachments )
}
@objc func didTapCaption ( sender : UIButton ) {
Logger . verbose ( " " )
isEditingCaptions = true
}
@objc func didTapCaptionDone ( sender : UIButton ) {
Logger . verbose ( " " )
isEditingCaptions = false
}
}
// MARK: -
extension AttachmentApprovalViewController : AttachmentTextToolbarDelegate {
func attachmentTextToolbarDidBeginEditing ( _ attachmentTextToolbar : AttachmentTextToolbar ) {
currentPageViewController . setAttachmentViewScale ( . compact , animated : true )
}
func attachmentTextToolbarDidEndEditing ( _ attachmentTextToolbar : AttachmentTextToolbar ) {
currentPageViewController . setAttachmentViewScale ( . fullsize , animated : true )
}
func attachmentTextToolbarDidTapSend ( _ attachmentTextToolbar : AttachmentTextToolbar ) {
// T o o l b a r f l i c k e r s i n a n d o u t i f t h e r e a r e e r r o r s
// a n d r e m a i n s v i s i b l e m o m e n t a r i l y a f t e r s h a r e e x t e n s i o n i s d i s m i s s e d .
// I t ' s e a s i e s t t o j u s t h i d e i t a t t h i s p o i n t s i n c e w e ' r e d o n e w i t h i t .
currentPageViewController . shouldAllowAttachmentViewResizing = false
attachmentTextToolbar . isUserInteractionEnabled = false
attachmentTextToolbar . isHidden = true
approvalDelegate ? . attachmentApproval ( self , didApproveAttachments : attachments , messageText : attachmentTextToolbar . messageText )
}
func attachmentTextToolbarDidAddMore ( _ attachmentTextToolbar : AttachmentTextToolbar ) {
self . approvalDelegate ? . attachmentApproval ? ( self , addMoreToAttachments : attachments )
}
}
// MARK: -
extension AttachmentApprovalViewController : AttachmentPrepViewControllerDelegate {
func prepViewControllerUpdateNavigationBar ( ) {
updateNavigationBar ( )
}
func prepViewControllerUpdateControls ( ) {
updateControlVisibility ( )
}
}
// MARK: G a l l e r y R a i l
extension SignalAttachmentItem : GalleryRailItem {
var aspectRatio : CGFloat {
return self . imageSize . aspectRatio
}
func getRailImage ( ) -> Promise < UIImage > {
return self . getThumbnailImage ( )
}
}
// MARK: -
extension AttachmentItemCollection : GalleryRailItemProvider {
var railItems : [ GalleryRailItem ] {
return self . attachmentItems
}
}
// MARK: -
extension AttachmentApprovalViewController : GalleryRailViewDelegate {
public func galleryRailView ( _ galleryRailView : GalleryRailView , didTapItem imageRailItem : GalleryRailItem ) {
guard let targetItem = imageRailItem as ? SignalAttachmentItem else {
owsFailDebug ( " unexpected imageRailItem: \( imageRailItem ) " )
return
}
guard let currentIndex = attachmentItems . index ( of : currentItem ) else {
owsFailDebug ( " currentIndex was unexpectedly nil " )
return
}
guard let targetIndex = attachmentItems . index ( of : targetItem ) else {
owsFailDebug ( " targetIndex was unexpectedly nil " )
return
}
let direction : UIPageViewControllerNavigationDirection = currentIndex < targetIndex ? . forward : . reverse
self . setCurrentItem ( targetItem , direction : direction , animated : true )
}
}
// MARK: -
enum KeyboardScenario {
case hidden , editingMessage , editingCaption
}
// MARK: -
extension AttachmentApprovalViewController : ApprovalRailCellViewDelegate {
func approvalRailCellView ( _ approvalRailCellView : ApprovalRailCellView , didRemoveItem attachmentItem : SignalAttachmentItem ) {
remove ( attachmentItem : attachmentItem )
}
}
// MARK: -
extension AttachmentApprovalViewController : AttachmentApprovalInputAccessoryViewDelegate {
public func attachmentApprovalInputUpdateMediaRail ( ) {
updateMediaRail ( )
}
public func attachmentApprovalInputStartEditingCaptions ( ) {
isEditingCaptions = true
}
public func attachmentApprovalInputStopEditingCaptions ( ) {
isEditingCaptions = false
}
}