Merge branch 'mkirk/media-gallery-shows-captions'

pull/1/head
Michael Kirk 7 years ago
commit cc068b0b76

@ -12,10 +12,12 @@ public class MediaGalleryItem: Equatable, Hashable {
let message: TSMessage let message: TSMessage
let attachmentStream: TSAttachmentStream let attachmentStream: TSAttachmentStream
let galleryDate: GalleryDate let galleryDate: GalleryDate
let captionForDisplay: String?
init(message: TSMessage, attachmentStream: TSAttachmentStream) { init(message: TSMessage, attachmentStream: TSAttachmentStream) {
self.message = message self.message = message
self.attachmentStream = attachmentStream self.attachmentStream = attachmentStream
self.captionForDisplay = attachmentStream.caption?.filterForDisplay
self.galleryDate = GalleryDate(message: message) self.galleryDate = GalleryDate(message: message)
} }

@ -101,11 +101,17 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
Logger.debug("deinit") Logger.debug("deinit")
} }
var bottomContainer: UIView!
var footerBar: UIToolbar! var footerBar: UIToolbar!
var videoPlayBarButton: UIBarButtonItem! var videoPlayBarButton: UIBarButtonItem!
var videoPauseBarButton: UIBarButtonItem! var videoPauseBarButton: UIBarButtonItem!
var pagerScrollView: UIScrollView! var pagerScrollView: UIScrollView!
// MARK: Caption
var currentCaptionView: CaptionView!
var pendingCaptionView: CaptionView!
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
@ -142,6 +148,10 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
// e.g. when getting to media details via message details screen, there's only // e.g. when getting to media details via message details screen, there's only
// one "Page" so the bounce doesn't make sense. // one "Page" so the bounce doesn't make sense.
pagerScrollView.isScrollEnabled = sliderEnabled pagerScrollView.isScrollEnabled = sliderEnabled
pagerScrollViewContentOffsetObservation = pagerScrollView.observe(\.contentOffset, options: [.new]) { [weak self] object, change in
guard let strongSelf = self else { return }
strongSelf.pagerScrollView(strongSelf.pagerScrollView, contentOffsetDidChange: change)
}
// Views // Views
@ -152,12 +162,39 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
let footerBar = UIToolbar() let footerBar = UIToolbar()
self.footerBar = footerBar self.footerBar = footerBar
let captionViewsContainer = UIView()
captionViewsContainer.setContentHuggingHigh()
captionViewsContainer.setCompressionResistanceHigh()
let currentCaptionView = CaptionView()
self.currentCaptionView = currentCaptionView
captionViewsContainer.addSubview(currentCaptionView)
currentCaptionView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .top)
currentCaptionView.autoPinEdge(toSuperviewEdge: .top, withInset: 0, relation: .greaterThanOrEqual)
currentCaptionView.text = currentItem.captionForDisplay
let pendingCaptionView = CaptionView()
self.pendingCaptionView = pendingCaptionView
pendingCaptionView.alpha = 0
captionViewsContainer.addSubview(pendingCaptionView)
pendingCaptionView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .top)
pendingCaptionView.autoPinEdge(toSuperviewEdge: .top, withInset: 0, relation: .greaterThanOrEqual)
let bottomContainer = UIView()
self.bottomContainer = bottomContainer
let bottomStack = UIStackView(arrangedSubviews: [captionViewsContainer, footerBar])
bottomStack.axis = .vertical
bottomContainer.addSubview(bottomStack)
bottomStack.autoPinEdgesToSuperviewEdges()
self.videoPlayBarButton = UIBarButtonItem(barButtonSystemItem: .play, target: self, action: #selector(didPressPlayBarButton)) self.videoPlayBarButton = UIBarButtonItem(barButtonSystemItem: .play, target: self, action: #selector(didPressPlayBarButton))
self.videoPauseBarButton = UIBarButtonItem(barButtonSystemItem: .pause, target: self, action: #selector(didPressPauseBarButton)) self.videoPauseBarButton = UIBarButtonItem(barButtonSystemItem: .pause, target: self, action: #selector(didPressPauseBarButton))
self.updateFooterBarButtonItems(isPlayingVideo: true) self.updateFooterBarButtonItems(isPlayingVideo: true)
self.view.addSubview(footerBar) self.view.addSubview(bottomContainer)
footerBar.autoPinWidthToSuperview() bottomContainer.autoPinWidthToSuperview()
bottomContainer.autoPinEdge(toSuperviewEdge: .bottom)
footerBar.autoPin(toBottomLayoutGuideOf: self, withInset: 0) footerBar.autoPin(toBottomLayoutGuideOf: self, withInset: 0)
footerBar.autoSetDimension(.height, toSize: kFooterHeight) footerBar.autoSetDimension(.height, toSize: kFooterHeight)
@ -168,6 +205,44 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
view.addGestureRecognizer(verticalSwipe) view.addGestureRecognizer(verticalSwipe)
} }
// MARK: KVO
var pagerScrollViewContentOffsetObservation: NSKeyValueObservation?
func pagerScrollView(_ pagerScrollView: UIScrollView, contentOffsetDidChange change: NSKeyValueObservedChange<CGPoint>) {
guard let newValue = change.newValue else {
owsFailDebug("newValue was unexpectedly nil")
return
}
let width = pagerScrollView.frame.size.width
guard width > 0 else {
return
}
let ratioComplete = abs((newValue.x - width) / width)
updatePagerTransition(ratioComplete: ratioComplete)
}
func updatePagerTransition(ratioComplete: CGFloat) {
if currentCaptionView.text != nil {
currentCaptionView.alpha = 1 - ratioComplete
} else {
currentCaptionView.alpha = 0
}
if pendingCaptionView.text != nil {
pendingCaptionView.alpha = ratioComplete
} else {
pendingCaptionView.alpha = 0
}
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
let isLandscape = size.width > size.height
self.navigationItem.titleView = isLandscape ? nil : self.portraitHeaderView
}
override func didReceiveMemoryWarning() { override func didReceiveMemoryWarning() {
Logger.info("") Logger.info("")
super.didReceiveMemoryWarning() super.didReceiveMemoryWarning()
@ -189,32 +264,6 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
} }
} }
@objc
public func didPressAllMediaButton(sender: Any) {
Logger.debug("")
currentViewController.stopAnyVideo()
guard let mediaGalleryDataSource = self.mediaGalleryDataSource else {
owsFailDebug("mediaGalleryDataSource was unexpectedly nil")
return
}
mediaGalleryDataSource.showAllMedia(focusedItem: currentItem)
}
@objc
public func didSwipeView(sender: Any) {
Logger.debug("")
self.dismissSelf(animated: true)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
let isLandscape = size.width > size.height
self.navigationItem.titleView = isLandscape ? nil : self.portraitHeaderView
}
private var shouldHideToolbars: Bool = false { private var shouldHideToolbars: Bool = false {
didSet { didSet {
if (oldValue == shouldHideToolbars) { if (oldValue == shouldHideToolbars) {
@ -232,7 +281,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
UIView.animate(withDuration: 0.1) { UIView.animate(withDuration: 0.1) {
self.currentViewController.setShouldHideToolbars(self.shouldHideToolbars) self.currentViewController.setShouldHideToolbars(self.shouldHideToolbars)
self.footerBar.isHidden = self.shouldHideToolbars self.bottomContainer.isHidden = self.shouldHideToolbars
} }
} }
} }
@ -266,6 +315,26 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
// MARK: Actions // MARK: Actions
@objc
public func didPressAllMediaButton(sender: Any) {
Logger.debug("")
currentViewController.stopAnyVideo()
guard let mediaGalleryDataSource = self.mediaGalleryDataSource else {
owsFailDebug("mediaGalleryDataSource was unexpectedly nil")
return
}
mediaGalleryDataSource.showAllMedia(focusedItem: currentItem)
}
@objc
public func didSwipeView(sender: Any) {
Logger.debug("")
self.dismissSelf(animated: true)
}
@objc @objc
public func didPressDismissButton(_ sender: Any) { public func didPressDismissButton(_ sender: Any) {
dismissSelf(animated: true) dismissSelf(animated: true)
@ -364,18 +433,31 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
// MARK: UIPageViewControllerDelegate // MARK: UIPageViewControllerDelegate
var pendingViewController: MediaDetailViewController?
public func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) { public func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
Logger.debug("") Logger.debug("")
assert(pendingViewControllers.count == 1) assert(pendingViewControllers.count == 1)
pendingViewControllers.forEach { viewController in pendingViewControllers.forEach { viewController in
guard let pendingPage = viewController as? MediaDetailViewController else { guard let pendingViewController = viewController as? MediaDetailViewController else {
owsFailDebug("unexpected mediaDetailViewController: \(viewController)") owsFailDebug("unexpected mediaDetailViewController: \(viewController)")
return return
} }
self.pendingViewController = pendingViewController
CATransaction.begin()
CATransaction.disableActions()
if let pendingCaptionText = pendingViewController.galleryItem.captionForDisplay, pendingCaptionText.count > 0 {
self.pendingCaptionView.text = pendingCaptionText
} else {
self.pendingCaptionView.text = nil
}
self.pendingCaptionView.sizeToFit()
self.pendingCaptionView.superview?.layoutIfNeeded()
CATransaction.commit()
// Ensure upcoming page respects current toolbar status // Ensure upcoming page respects current toolbar status
pendingPage.setShouldHideToolbars(self.shouldHideToolbars) pendingViewController.setShouldHideToolbars(self.shouldHideToolbars)
} }
} }
@ -391,6 +473,20 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
// Do any cleanup for the no-longer visible view controller // Do any cleanup for the no-longer visible view controller
if transitionCompleted { if transitionCompleted {
pendingViewController = nil
// This can happen when trying to page past the last (or first) view controller
// In that case, we don't want to change the captionView.
if (previousPage != currentViewController) {
updatePagerTransition(ratioComplete: 1)
// promote "pending" to "current" caption view.
let oldCaptionView = self.currentCaptionView
self.currentCaptionView = self.pendingCaptionView
self.pendingCaptionView = oldCaptionView
self.pendingCaptionView.text = nil
}
updateTitle() updateTitle()
previousPage.zoomOut(animated: false) previousPage.zoomOut(animated: false)
previousPage.stopAnyVideo() previousPage.stopAnyVideo()
@ -650,3 +746,102 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
} }
} }
} }
class CaptionView: UIView {
var text: String? {
get { return textView.text }
set { textView.text = newValue }
}
// MARK: Subviews
let backgroundGradientView = GradientView(from: .clear, to: .black)
let textView: CaptionTextView = {
let textView = CaptionTextView()
textView.font = UIFont.ows_dynamicTypeBody
textView.textColor = .white
textView.backgroundColor = .clear
return textView
}()
let scrollFadeView = GradientView(from: .clear, to: .black)
// MARK: Initializers
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(backgroundGradientView)
backgroundGradientView.autoPinEdgesToSuperviewEdges()
addSubview(textView)
textView.autoPinEdgesToSuperviewMargins()
addSubview(scrollFadeView)
scrollFadeView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .top)
scrollFadeView.autoSetDimension(.height, toSize: 20)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: UIView overrides
override func layoutSubviews() {
super.layoutSubviews()
scrollFadeView.isHidden = !textView.doesContentNeedScroll
}
// MARK: -
class CaptionTextView: UITextView {
var kMaxHeight: CGFloat = ScaleFromIPhone5(100)
override var text: String! {
didSet {
invalidateIntrinsicContentSize()
}
}
override var font: UIFont? {
didSet {
invalidateIntrinsicContentSize()
}
}
var doesContentNeedScroll: Bool {
return self.bounds.height == kMaxHeight
}
override func layoutSubviews() {
super.layoutSubviews()
// Enable/disable scrolling depending on wether we've clipped
// content in `intrinsicContentSize`
if doesContentNeedScroll {
if !isScrollEnabled {
isScrollEnabled = true
}
} else if isScrollEnabled {
isScrollEnabled = false
}
}
override var intrinsicContentSize: CGSize {
var size = super.intrinsicContentSize
if size.height == UIViewNoIntrinsicMetric {
size.height = layoutManager.usedRect(for: textContainer).height + textContainerInset.top + textContainerInset.bottom
}
size.height = min(kMaxHeight, size.height)
return size
}
}
}

@ -14,6 +14,10 @@ public extension String {
return (self as NSString).rtlSafeAppend(string) return (self as NSString).rtlSafeAppend(string)
} }
var filterForDisplay: String? {
return (self as NSString).filterStringForDisplay()
}
// Truncates string to be less than or equal to byteCount, while ensuring we never truncate partial characters for multibyte characters. // Truncates string to be less than or equal to byteCount, while ensuring we never truncate partial characters for multibyte characters.
func truncated(toByteCount byteCount: UInt) -> String? { func truncated(toByteCount byteCount: UInt) -> String? {
var lowerBoundCharCount = 0 var lowerBoundCharCount = 0

Loading…
Cancel
Save