mirror of https://github.com/oxen-io/session-ios
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
620 lines
22 KiB
Swift
620 lines
22 KiB
Swift
6 years ago
|
//
|
||
|
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||
|
//
|
||
|
|
||
|
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: - Properties
|
||
|
|
||
|
private let mode: AttachmentApprovalViewControllerMode
|
||
|
|
||
|
public weak var approvalDelegate: AttachmentApprovalViewControllerDelegate?
|
||
|
|
||
|
// MARK: - Initializers
|
||
|
|
||
|
@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
|
||
|
}
|
||
|
|
||
|
@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: - Subviews
|
||
|
|
||
|
var galleryRailView: GalleryRailView {
|
||
|
return bottomToolView.galleryRailView
|
||
|
}
|
||
|
|
||
|
var mediaMessageTextToolbar: MediaMessageTextToolbar {
|
||
|
return bottomToolView.mediaMessageTextToolbar
|
||
|
}
|
||
|
|
||
|
lazy var bottomToolView: AttachmentApprovalInputAccessoryView = {
|
||
|
let isAddMoreVisible = mode == .sharedNavigation
|
||
|
let bottomToolView = AttachmentApprovalInputAccessoryView(isAddMoreVisible: isAddMoreVisible)
|
||
|
|
||
|
return bottomToolView
|
||
|
}()
|
||
|
|
||
|
// MARK: - View Lifecycle
|
||
|
|
||
|
public override var prefersStatusBarHidden: Bool {
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
override public func viewDidLoad() {
|
||
|
super.viewDidLoad()
|
||
|
|
||
|
self.view.backgroundColor = .black
|
||
|
|
||
|
// avoid an unpleasant "bounce" which doesn't make sense in the context of a single item.
|
||
|
pagerScrollView?.isScrollEnabled = attachmentItems.count > 1
|
||
|
|
||
|
// Bottom Toolbar
|
||
|
galleryRailView.delegate = self
|
||
|
mediaMessageTextToolbar.mediaMessageTextToolbarDelegate = self
|
||
|
|
||
|
// Navigation
|
||
|
|
||
|
self.navigationItem.title = nil
|
||
|
|
||
|
guard let firstItem = attachmentItems.first else {
|
||
|
owsFailDebug("firstItem was unexpectedly nil")
|
||
|
return
|
||
|
}
|
||
|
|
||
|
self.setCurrentItem(firstItem, direction: .forward, animated: false)
|
||
|
|
||
|
// layout immediately to avoid animating the layout process during the transition
|
||
|
self.currentPageViewController.view.layoutIfNeeded()
|
||
|
}
|
||
|
|
||
|
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)
|
||
|
|
||
|
updateNavigationBar()
|
||
|
updateControlVisibility()
|
||
|
}
|
||
|
|
||
|
override public func viewDidAppear(_ animated: Bool) {
|
||
|
Logger.debug("")
|
||
|
|
||
|
super.viewDidAppear(animated)
|
||
|
|
||
|
updateNavigationBar()
|
||
|
updateControlVisibility()
|
||
|
}
|
||
|
|
||
|
override public func viewWillDisappear(_ animated: Bool) {
|
||
|
Logger.debug("")
|
||
|
super.viewWillDisappear(animated)
|
||
|
}
|
||
|
|
||
|
override public var inputAccessoryView: UIView? {
|
||
|
bottomToolView.layoutIfNeeded()
|
||
|
return bottomToolView
|
||
|
}
|
||
|
|
||
|
override public var canBecomeFirstResponder: Bool {
|
||
|
return !shouldHideControls
|
||
|
}
|
||
|
|
||
|
// MARK: - Navigation Bar
|
||
|
|
||
|
public func updateNavigationBar() {
|
||
|
guard !shouldHideControls else {
|
||
|
self.navigationItem.leftBarButtonItem = nil
|
||
|
self.navigationItem.rightBarButtonItem = nil
|
||
|
return
|
||
|
}
|
||
|
|
||
|
var navigationBarItems = [UIView]()
|
||
|
var isShowingCaptionView = false
|
||
|
|
||
|
if let viewControllers = viewControllers,
|
||
|
viewControllers.count == 1,
|
||
|
let firstViewController = viewControllers.first as? AttachmentPrepViewController {
|
||
|
navigationBarItems = firstViewController.navigationBarItems()
|
||
|
isShowingCaptionView = firstViewController.isShowingCaptionView
|
||
|
}
|
||
|
|
||
|
guard !isShowingCaptionView else {
|
||
|
// Hide all navigation bar items while the caption view is open.
|
||
|
self.navigationItem.leftBarButtonItem = nil
|
||
|
self.navigationItem.rightBarButtonItem = nil
|
||
|
return
|
||
|
}
|
||
|
|
||
|
updateNavigationBar(navigationBarItems: navigationBarItems)
|
||
|
|
||
|
let hasCancel = (mode != .sharedNavigation)
|
||
|
if hasCancel {
|
||
|
let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel,
|
||
|
target: self, action: #selector(cancelPressed))
|
||
|
cancelButton.tintColor = .white
|
||
|
self.navigationItem.leftBarButtonItem = cancelButton
|
||
|
} else {
|
||
|
// Note: using a custom leftBarButtonItem breaks the interactive pop gesture.
|
||
|
self.navigationItem.leftBarButtonItem = self.createOWSBackButton()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// MARK: - Control Visibility
|
||
|
|
||
|
public var shouldHideControls: Bool {
|
||
|
guard let pageViewController = pageViewControllers.first else {
|
||
|
return false
|
||
|
}
|
||
|
return pageViewController.shouldHideControls
|
||
|
}
|
||
|
|
||
|
private func updateControlVisibility() {
|
||
|
if shouldHideControls {
|
||
|
if isFirstResponder {
|
||
|
resignFirstResponder()
|
||
|
}
|
||
|
} else {
|
||
|
if !isFirstResponder {
|
||
|
becomeFirstResponder()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// MARK: - View Helpers
|
||
|
|
||
|
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: {
|
||
|
// shrink stack view item until it disappears
|
||
|
cell.isHidden = true
|
||
|
|
||
|
// simultaneously fade out
|
||
|
cell.alpha = 0
|
||
|
},
|
||
|
completion: { _ in
|
||
|
self.attachmentItemCollection.remove(item: attachmentItem)
|
||
|
self.updateMediaRail()
|
||
|
})
|
||
|
}
|
||
|
|
||
|
lazy var pagerScrollView: UIScrollView? = {
|
||
|
// This is kind of a hack. Since we don't have first class access to the superview's `scrollView`
|
||
|
// we traverse the view hierarchy until we find it.
|
||
|
let pagerScrollView = view.subviews.first { $0 is UIScrollView } as? UIScrollView
|
||
|
assert(pagerScrollView != nil)
|
||
|
|
||
|
return pagerScrollView
|
||
|
}()
|
||
|
|
||
|
// MARK: - UIPageViewControllerDelegate
|
||
|
|
||
|
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
|
||
|
}
|
||
|
|
||
|
// use compact scale when keyboard is popped.
|
||
|
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()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
updateNavigationBar()
|
||
|
updateControlVisibility()
|
||
|
}
|
||
|
|
||
|
// MARK: - UIPageViewControllerDataSource
|
||
|
|
||
|
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)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// For any attachments edited with the image editor, returns a
|
||
|
// new SignalAttachment that reflects those changes. Otherwise,
|
||
|
// returns the original attachment.
|
||
|
//
|
||
|
// If any errors occurs in the export process, we fail over to
|
||
|
// sending the original attachment. This seems better than trying
|
||
|
// to involve the user in resolving the issue.
|
||
|
func processedAttachment(forAttachmentItem attachmentItem: SignalAttachmentItem) -> SignalAttachment {
|
||
|
guard let imageEditorModel = attachmentItem.imageEditorModel else {
|
||
|
// Image was not edited.
|
||
|
return attachmentItem.attachment
|
||
|
}
|
||
|
guard imageEditorModel.isDirty() else {
|
||
|
// Image editor has no changes.
|
||
|
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
|
||
|
}
|
||
|
|
||
|
// Rewrite the filename's extension to reflect the output file format.
|
||
|
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
|
||
|
}
|
||
|
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 {
|
||
|
// already at first item
|
||
|
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 {
|
||
|
// already at last item
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return nextItem
|
||
|
}
|
||
|
|
||
|
// MARK: - Event Handlers
|
||
|
|
||
|
@objc func cancelPressed(sender: UIButton) {
|
||
|
self.approvalDelegate?.attachmentApproval(self, didCancelAttachments: attachments)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
extension AttachmentApprovalViewController: MediaMessageTextToolbarDelegate {
|
||
|
func mediaMessageTextToolbarDidBeginEditing(_ mediaMessageTextToolbar: MediaMessageTextToolbar) {
|
||
|
currentPageViewController.setAttachmentViewScale(.compact, animated: true)
|
||
|
}
|
||
|
|
||
|
func mediaMessageTextToolbarDidEndEditing(_ mediaMessageTextToolbar: MediaMessageTextToolbar) {
|
||
|
currentPageViewController.setAttachmentViewScale(.fullsize, animated: true)
|
||
|
}
|
||
|
|
||
|
func mediaMessageTextToolbarDidTapSend(_ mediaMessageTextToolbar: MediaMessageTextToolbar) {
|
||
|
// Toolbar flickers in and out if there are errors
|
||
|
// and remains visible momentarily after share extension is dismissed.
|
||
|
// It's easiest to just hide it at this point since we're done with it.
|
||
|
currentPageViewController.shouldAllowAttachmentViewResizing = false
|
||
|
mediaMessageTextToolbar.isUserInteractionEnabled = false
|
||
|
mediaMessageTextToolbar.isHidden = true
|
||
|
|
||
|
approvalDelegate?.attachmentApproval(self, didApproveAttachments: attachments, messageText: mediaMessageTextToolbar.messageText)
|
||
|
}
|
||
|
|
||
|
func mediaMessageTextToolbarDidAddMore(_ mediaMessageTextToolbar: MediaMessageTextToolbar) {
|
||
|
self.approvalDelegate?.attachmentApproval?(self, addMoreToAttachments: attachments)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// MARK: -
|
||
|
|
||
|
extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate {
|
||
|
func prepViewController(_ prepViewController: AttachmentPrepViewController, didUpdateCaptionForAttachmentItem attachmentItem: SignalAttachmentItem) {
|
||
|
self.approvalDelegate?.attachmentApproval?(self, changedCaptionOfAttachment: attachmentItem.attachment)
|
||
|
|
||
|
updateMediaRail()
|
||
|
}
|
||
|
|
||
|
func prepViewControllerUpdateNavigationBar() {
|
||
|
updateNavigationBar()
|
||
|
}
|
||
|
|
||
|
func prepViewControllerUpdateControls() {
|
||
|
updateControlVisibility()
|
||
|
}
|
||
|
|
||
|
func prepViewControllerAttachmentCount() -> Int {
|
||
|
return attachmentItemCollection.count
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// MARK: GalleryRail
|
||
|
|
||
|
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)
|
||
|
}
|
||
|
}
|