mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Gallery pager style changes
This commit is contained in:
parent
c5bcba45be
commit
47a7114317
10 changed files with 379 additions and 251 deletions
|
@ -451,6 +451,7 @@
|
|||
4C948FF72146EB4800349F0D /* BlockListCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C948FF62146EB4800349F0D /* BlockListCache.swift */; };
|
||||
4C9CA25D217E676900607C63 /* ZXingObjC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C9CA25C217E676900607C63 /* ZXingObjC.framework */; };
|
||||
4CA46F4A219C78050038ABDE /* GalleryRailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA46F49219C78050038ABDE /* GalleryRailView.swift */; };
|
||||
4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA46F4B219CCC630038ABDE /* CaptionView.swift */; };
|
||||
4CA5F793211E1F06008C2708 /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA5F792211E1F06008C2708 /* Toast.swift */; };
|
||||
4CB5F26720F6E1E2004D1B42 /* MenuActionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF4C0920F55BBA005DA313 /* MenuActionsViewController.swift */; };
|
||||
4CB5F26920F7D060004D1B42 /* MessageActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB5F26820F7D060004D1B42 /* MessageActions.swift */; };
|
||||
|
@ -1160,6 +1161,7 @@
|
|||
4C948FF62146EB4800349F0D /* BlockListCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockListCache.swift; sourceTree = "<group>"; };
|
||||
4C9CA25C217E676900607C63 /* ZXingObjC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ZXingObjC.framework; path = ThirdParty/Carthage/Build/iOS/ZXingObjC.framework; sourceTree = "<group>"; };
|
||||
4CA46F49219C78050038ABDE /* GalleryRailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryRailView.swift; sourceTree = "<group>"; };
|
||||
4CA46F4B219CCC630038ABDE /* CaptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaptionView.swift; sourceTree = "<group>"; };
|
||||
4CA5F792211E1F06008C2708 /* Toast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = "<group>"; };
|
||||
4CB5F26820F7D060004D1B42 /* MessageActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageActions.swift; sourceTree = "<group>"; };
|
||||
4CB93DC12180FF07004B9764 /* ProximityMonitoringManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProximityMonitoringManager.swift; sourceTree = "<group>"; };
|
||||
|
@ -2271,6 +2273,7 @@
|
|||
34B6A902218B3F62007C4606 /* TypingIndicatorView.swift */,
|
||||
4C1885D1218F8E1C00B67051 /* PhotoGridViewCell.swift */,
|
||||
4CA46F49219C78050038ABDE /* GalleryRailView.swift */,
|
||||
4CA46F4B219CCC630038ABDE /* CaptionView.swift */,
|
||||
);
|
||||
name = Views;
|
||||
path = views;
|
||||
|
@ -3464,6 +3467,7 @@
|
|||
452C468F1E427E200087B011 /* OutboundCallInitiator.swift in Sources */,
|
||||
34D2CCDA2062E7D000CB1A14 /* OWSScreenLockUI.m in Sources */,
|
||||
45F170BB1E2FC5D3003FC1F2 /* CallAudioService.swift in Sources */,
|
||||
4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */,
|
||||
345BC30C2047030700257B7C /* OWS2FASettingsViewController.m in Sources */,
|
||||
340FC8CA20517B84007AEB0F /* OWSBackupImportJob.m in Sources */,
|
||||
340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */,
|
||||
|
|
|
@ -223,14 +223,6 @@ protocol MediaGalleryDataSourceDelegate: class {
|
|||
|
||||
class MediaGalleryNavigationController: OWSNavigationController {
|
||||
|
||||
// MARK: View LifeCycle
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
// If the user's device is already rotated, try to respect that by rotating to landscape now
|
||||
UIViewController.attemptRotationToDeviceOrientation()
|
||||
}
|
||||
|
||||
var presentationView: UIImageView!
|
||||
var retainUntilDismissed: MediaGallery?
|
||||
|
||||
|
@ -254,6 +246,10 @@ class MediaGalleryNavigationController: OWSNavigationController {
|
|||
|
||||
// MARK: View Lifecycle
|
||||
|
||||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||
return .lightContent
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
|
@ -273,6 +269,23 @@ class MediaGalleryNavigationController: OWSNavigationController {
|
|||
presentationView.layer.minificationFilter = kCAFilterTrilinear
|
||||
presentationView.layer.magnificationFilter = kCAFilterTrilinear
|
||||
presentationView.contentMode = .scaleAspectFit
|
||||
|
||||
guard let navigationBar = self.navigationBar as? OWSNavigationBar else {
|
||||
owsFailDebug("navigationBar had unexpected class: \(self.navigationBar)")
|
||||
return
|
||||
}
|
||||
|
||||
navigationBar.respectsTheme = false
|
||||
navigationBar.barStyle = .black
|
||||
navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: Theme.galleryIconColor]
|
||||
navigationBar.barTintColor = Theme.galleryBackgroundColor.withAlphaComponent(0.6)
|
||||
navigationBar.tintColor = Theme.galleryIconColor
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
// If the user's device is already rotated, try to respect that by rotating to landscape now
|
||||
UIViewController.attemptRotationToDeviceOrientation()
|
||||
}
|
||||
|
||||
// MARK: Orientation
|
||||
|
@ -437,7 +450,7 @@ class MediaGallery: NSObject, MediaGalleryDataSource, MediaTileViewControllerDel
|
|||
|
||||
// fade out content behind the pageViewController
|
||||
// and behind the presentation view
|
||||
self.navigationController.view.backgroundColor = Theme.backgroundColor
|
||||
self.navigationController.view.backgroundColor = Theme.galleryBackgroundColor
|
||||
},
|
||||
completion: { (_: Bool) in
|
||||
// At this point our presentation view should be overlayed perfectly
|
||||
|
@ -538,9 +551,20 @@ class MediaGallery: NSObject, MediaGalleryDataSource, MediaTileViewControllerDel
|
|||
}
|
||||
}
|
||||
|
||||
public func dismissMediaDetailViewController(_ mediaPageViewController: MediaPageViewController, animated isAnimated: Bool, completion: (() -> Void)?) {
|
||||
public func dismissMediaDetailViewController(_ mediaPageViewController: MediaPageViewController, animated isAnimated: Bool, completion completionParam: (() -> Void)?) {
|
||||
|
||||
guard let presentingViewController = self.navigationController.presentingViewController else {
|
||||
owsFailDebug("presentingController was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
let completion = {
|
||||
completionParam?()
|
||||
UIApplication.shared.isStatusBarHidden = false
|
||||
presentingViewController.setNeedsStatusBarAppearanceUpdate()
|
||||
}
|
||||
|
||||
navigationController.view.isUserInteractionEnabled = false
|
||||
UIApplication.shared.isStatusBarHidden = false
|
||||
|
||||
guard let detailView = mediaPageViewController.view else {
|
||||
owsFailDebug("detailView was unexpectedly nil")
|
||||
|
@ -583,12 +607,6 @@ class MediaGallery: NSObject, MediaGalleryDataSource, MediaTileViewControllerDel
|
|||
} else {
|
||||
self.navigationController.presentationView.layer.cornerRadius = kOWSMessageCellCornerRadius_Small
|
||||
}
|
||||
},
|
||||
completion: { (_: Bool) in
|
||||
// In case user has hidden bars, which changes background to black.
|
||||
// We don't want to change this while detailView is visible, lest
|
||||
// we obscure out the presentationView
|
||||
detailView.backgroundColor = Theme.backgroundColor
|
||||
})
|
||||
|
||||
// This intentionally overlaps the previous animation a bit
|
||||
|
@ -598,7 +616,7 @@ class MediaGallery: NSObject, MediaGalleryDataSource, MediaTileViewControllerDel
|
|||
animations: {
|
||||
guard let replacingView = self.replacingView else {
|
||||
owsFailDebug("replacingView was unexpectedly nil")
|
||||
self.navigationController.presentingViewController?.dismiss(animated: false, completion: completion)
|
||||
presentingViewController.dismiss(animated: false, completion: completion)
|
||||
return
|
||||
}
|
||||
// fade out content and toolbars
|
||||
|
@ -606,16 +624,16 @@ class MediaGallery: NSObject, MediaGalleryDataSource, MediaTileViewControllerDel
|
|||
replacingView.alpha = 1.0
|
||||
},
|
||||
completion: { (_: Bool) in
|
||||
self.navigationController.presentingViewController?.dismiss(animated: false, completion: completion)
|
||||
presentingViewController.dismiss(animated: false, completion: completion)
|
||||
})
|
||||
} else {
|
||||
guard let replacingView = self.replacingView else {
|
||||
owsFailDebug("replacingView was unexpectedly nil")
|
||||
navigationController.presentingViewController?.dismiss(animated: false, completion: completion)
|
||||
presentingViewController.dismiss(animated: false, completion: completion)
|
||||
return
|
||||
}
|
||||
replacingView.alpha = 1.0
|
||||
navigationController.presentingViewController?.dismiss(animated: false, completion: completion)
|
||||
presentingViewController.dismiss(animated: false, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -99,36 +99,17 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
|||
Logger.debug("deinit")
|
||||
}
|
||||
|
||||
// MARK: - Subview
|
||||
|
||||
// MARK: Bottom Bar
|
||||
var bottomContainer: UIView!
|
||||
var footerBar: UIToolbar!
|
||||
var videoPlayBarButton: UIBarButtonItem!
|
||||
var videoPauseBarButton: UIBarButtonItem!
|
||||
let captionContainerView: CaptionContainerView = CaptionContainerView()
|
||||
var galleryRailView: GalleryRailView = GalleryRailView()
|
||||
|
||||
var pagerScrollView: UIScrollView!
|
||||
|
||||
// MARK: Caption
|
||||
|
||||
var currentCaptionView: CaptionView!
|
||||
var pendingCaptionView: CaptionView!
|
||||
|
||||
// MARK: ImageRail
|
||||
|
||||
var galleryRailView: GalleryRailView!
|
||||
|
||||
private func makeClearToolbar() -> UIToolbar {
|
||||
let toolbar = UIToolbar()
|
||||
|
||||
toolbar.backgroundColor = UIColor.clear
|
||||
|
||||
// Making a toolbar transparent requires setting an empty uiimage
|
||||
toolbar.setBackgroundImage(UIImage(), forToolbarPosition: .any, barMetrics: .default)
|
||||
|
||||
// hide 1px top-border
|
||||
toolbar.clipsToBounds = true
|
||||
|
||||
return toolbar
|
||||
}
|
||||
|
||||
// MARK:
|
||||
// MARK: UIViewController overrides
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
@ -171,51 +152,27 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
|||
|
||||
// Views
|
||||
|
||||
view.backgroundColor = Theme.backgroundColor
|
||||
view.backgroundColor = Theme.galleryBackgroundColor
|
||||
|
||||
let captionViewsContainer = UIView()
|
||||
captionViewsContainer.setContentHuggingHigh()
|
||||
captionViewsContainer.setCompressionResistanceHigh()
|
||||
captionContainerView.delegate = self
|
||||
updateCaptionContainerVisibility()
|
||||
|
||||
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 galleryRailView = GalleryRailView()
|
||||
galleryRailView.delegate = self
|
||||
galleryRailView.autoSetDimension(.height, toSize: 60)
|
||||
self.galleryRailView = galleryRailView
|
||||
galleryRailView.autoSetDimension(.height, toSize: 72)
|
||||
|
||||
let footerBar = self.makeClearToolbar()
|
||||
self.footerBar = footerBar
|
||||
footerBar.tintColor = .white
|
||||
|
||||
let bottomContainer = UIView()
|
||||
self.bottomContainer = bottomContainer
|
||||
bottomContainer.backgroundColor = UIColor.ows_black.withAlphaComponent(0.4)
|
||||
|
||||
let toolbarStack = UIStackView(arrangedSubviews: [galleryRailView, footerBar])
|
||||
toolbarStack.axis = .vertical
|
||||
let toolbarBarBlurView = UIVisualEffectView(effect: Theme.barBlurEffect)
|
||||
toolbarStack.insertSubview(toolbarBarBlurView, at: 0)
|
||||
toolbarBarBlurView.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
let bottomStack = UIStackView(arrangedSubviews: [captionViewsContainer, toolbarStack])
|
||||
let bottomStack = UIStackView(arrangedSubviews: [captionContainerView, galleryRailView, footerBar])
|
||||
bottomStack.axis = .vertical
|
||||
bottomContainer.addSubview(bottomStack)
|
||||
bottomStack.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
self.videoPlayBarButton = UIBarButtonItem(barButtonSystemItem: .play, target: self, action: #selector(didPressPlayBarButton))
|
||||
self.videoPauseBarButton = UIBarButtonItem(barButtonSystemItem: .pause, target: self, action: #selector(didPressPauseBarButton))
|
||||
|
||||
self.view.addSubview(bottomContainer)
|
||||
bottomContainer.autoPinWidthToSuperview()
|
||||
bottomContainer.autoPinEdge(toSuperviewEdge: .bottom)
|
||||
|
@ -223,6 +180,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
|||
footerBar.autoSetDimension(.height, toSize: 44)
|
||||
|
||||
updateTitle()
|
||||
updateCaption(item: currentItem)
|
||||
updateMediaRail()
|
||||
updateFooterBarButtonItems(isPlayingVideo: true)
|
||||
|
||||
|
@ -233,6 +191,19 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
|||
view.addGestureRecognizer(verticalSwipe)
|
||||
}
|
||||
|
||||
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() {
|
||||
Logger.info("")
|
||||
super.didReceiveMemoryWarning()
|
||||
|
||||
self.cachedPages = [:]
|
||||
}
|
||||
|
||||
// MARK: KVO
|
||||
|
||||
var pagerScrollViewContentOffsetObservation: NSKeyValueObservation?
|
||||
|
@ -246,36 +217,8 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
|||
guard width > 0 else {
|
||||
return
|
||||
}
|
||||
|
||||
let ratioComplete = abs((newValue.x - width) / width)
|
||||
updatePagerTransition(ratioComplete: ratioComplete)
|
||||
}
|
||||
|
||||
func updatePagerTransition(ratioComplete: CGFloat) {
|
||||
if let currentCaptionText = currentCaptionView.text, currentCaptionText.count > 0 {
|
||||
currentCaptionView.alpha = 1 - ratioComplete
|
||||
} else {
|
||||
currentCaptionView.alpha = 0
|
||||
}
|
||||
|
||||
if let pendingCaptionText = pendingCaptionView.text, pendingCaptionText.count > 0 {
|
||||
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() {
|
||||
Logger.info("")
|
||||
super.didReceiveMemoryWarning()
|
||||
|
||||
self.cachedPages = [:]
|
||||
captionContainerView.updatePagerTransition(ratioComplete: ratioComplete)
|
||||
}
|
||||
|
||||
// MARK: View Helpers
|
||||
|
@ -292,6 +235,20 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
|||
}
|
||||
}
|
||||
|
||||
private func makeClearToolbar() -> UIToolbar {
|
||||
let toolbar = UIToolbar()
|
||||
|
||||
toolbar.backgroundColor = UIColor.clear
|
||||
|
||||
// Making a toolbar transparent requires setting an empty uiimage
|
||||
toolbar.setBackgroundImage(UIImage(), forToolbarPosition: .any, barMetrics: .default)
|
||||
|
||||
// hide 1px top-border
|
||||
toolbar.clipsToBounds = true
|
||||
|
||||
return toolbar
|
||||
}
|
||||
|
||||
private var shouldHideToolbars: Bool = false {
|
||||
didSet {
|
||||
if (oldValue == shouldHideToolbars) {
|
||||
|
@ -303,10 +260,6 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
|||
UIApplication.shared.setStatusBarHidden(shouldHideToolbars, with: .none)
|
||||
self.navigationController?.setNavigationBarHidden(shouldHideToolbars, animated: false)
|
||||
|
||||
// We don't animate the background color change because the old color shows through momentarily
|
||||
// behind where the status bar "used to be".
|
||||
self.view.backgroundColor = (shouldHideToolbars ? UIColor.black : Theme.backgroundColor)
|
||||
|
||||
UIView.animate(withDuration: 0.1) {
|
||||
self.currentViewController.setShouldHideToolbars(self.shouldHideToolbars)
|
||||
self.bottomContainer.isHidden = self.shouldHideToolbars
|
||||
|
@ -314,6 +267,39 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: Bar Buttons
|
||||
|
||||
let shareBarButton: UIBarButtonItem = {
|
||||
let shareBarButton = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(didPressShare))
|
||||
shareBarButton.tintColor = Theme.galleryIconColor
|
||||
return shareBarButton
|
||||
}()
|
||||
|
||||
let deleteBarButton: UIBarButtonItem = {
|
||||
let deleteBarButton = UIBarButtonItem(barButtonSystemItem: .trash,
|
||||
target: self,
|
||||
action: #selector(didPressDelete))
|
||||
deleteBarButton.tintColor = Theme.galleryIconColor
|
||||
return deleteBarButton
|
||||
}()
|
||||
|
||||
func buildFlexibleSpace() -> UIBarButtonItem {
|
||||
return UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
|
||||
}
|
||||
|
||||
var videoPlayBarButton: UIBarButtonItem = {
|
||||
let videoPlayBarButton = UIBarButtonItem(barButtonSystemItem: .play, target: self, action: #selector(didPressPlayBarButton))
|
||||
videoPlayBarButton.tintColor = Theme.galleryIconColor
|
||||
return videoPlayBarButton
|
||||
}()
|
||||
|
||||
let videoPauseBarButton: UIBarButtonItem = {
|
||||
let videoPauseBarButton = UIBarButtonItem(barButtonSystemItem: .pause, target: self, action:
|
||||
#selector(didPressPauseBarButton))
|
||||
videoPauseBarButton.tintColor = Theme.galleryIconColor
|
||||
return videoPauseBarButton
|
||||
}()
|
||||
|
||||
private func updateFooterBarButtonItems(isPlayingVideo: Bool) {
|
||||
// TODO do we still need this? seems like a vestige
|
||||
// from when media detail view was used for attachment approval
|
||||
|
@ -323,20 +309,18 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
|||
}
|
||||
|
||||
var toolbarItems: [UIBarButtonItem] = [
|
||||
UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(didPressShare)),
|
||||
UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
|
||||
shareBarButton,
|
||||
buildFlexibleSpace()
|
||||
]
|
||||
|
||||
if (self.currentItem.isVideo) {
|
||||
toolbarItems += [
|
||||
isPlayingVideo ? self.videoPauseBarButton : self.videoPlayBarButton,
|
||||
UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
|
||||
buildFlexibleSpace()
|
||||
]
|
||||
}
|
||||
|
||||
toolbarItems.append(UIBarButtonItem(barButtonSystemItem: .trash,
|
||||
target: self,
|
||||
action: #selector(didPressDelete)))
|
||||
toolbarItems.append(deleteBarButton)
|
||||
|
||||
self.footerBar.setItems(toolbarItems, animated: false)
|
||||
}
|
||||
|
@ -482,16 +466,11 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
|||
}
|
||||
self.pendingViewController = pendingViewController
|
||||
|
||||
CATransaction.begin()
|
||||
CATransaction.disableActions()
|
||||
if let pendingCaptionText = pendingViewController.galleryItem.captionForDisplay, pendingCaptionText.count > 0 {
|
||||
self.pendingCaptionView.text = pendingCaptionText
|
||||
self.captionContainerView.pendingText = pendingCaptionText
|
||||
} else {
|
||||
self.pendingCaptionView.text = nil
|
||||
self.captionContainerView.pendingText = nil
|
||||
}
|
||||
self.pendingCaptionView.sizeToFit()
|
||||
self.pendingCaptionView.superview?.layoutIfNeeded()
|
||||
CATransaction.commit()
|
||||
|
||||
// Ensure upcoming page respects current toolbar status
|
||||
pendingViewController.setShouldHideToolbars(self.shouldHideToolbars)
|
||||
|
@ -515,13 +494,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
|||
// 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
|
||||
captionContainerView.completePagerTransition()
|
||||
}
|
||||
|
||||
updateTitle()
|
||||
|
@ -529,6 +502,8 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
|||
previousPage.zoomOut(animated: false)
|
||||
previousPage.stopAnyVideo()
|
||||
updateFooterBarButtonItems(isPlayingVideo: false)
|
||||
} else {
|
||||
captionContainerView.pendingText = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -703,7 +678,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
|||
|
||||
lazy private var portraitHeaderNameLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.textColor = Theme.navbarTitleColor
|
||||
label.textColor = Theme.galleryIconColor
|
||||
label.font = UIFont.ows_regularFont(withSize: 17)
|
||||
label.textAlignment = .center
|
||||
label.adjustsFontSizeToFitWidth = true
|
||||
|
@ -714,7 +689,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
|||
|
||||
lazy private var portraitHeaderDateLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.textColor = Theme.navbarTitleColor
|
||||
label.textColor = Theme.galleryIconColor
|
||||
label.font = UIFont.ows_regularFont(withSize: 12)
|
||||
label.textAlignment = .center
|
||||
label.adjustsFontSizeToFitWidth = true
|
||||
|
@ -756,7 +731,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
|||
}
|
||||
|
||||
private func updateCaption(item: MediaGalleryItem) {
|
||||
self.currentCaptionView.text = item.captionForDisplay
|
||||
captionContainerView.currentText = item.captionForDisplay
|
||||
}
|
||||
|
||||
private func updateTitle(item: MediaGalleryItem) {
|
||||
|
@ -802,103 +777,25 @@ extension MediaPageViewController: GalleryRailViewDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
class CaptionView: UIView {
|
||||
extension MediaPageViewController: CaptionContainerViewDelegate {
|
||||
|
||||
var text: String? {
|
||||
get { return textView.text }
|
||||
set { textView.text = newValue }
|
||||
func captionContainerViewDidUpdateText(_ captionContainerView: CaptionContainerView) {
|
||||
updateCaptionContainerVisibility()
|
||||
}
|
||||
|
||||
// MARK: Subviews
|
||||
// MARK: Helpers
|
||||
|
||||
let backgroundGradientView = GradientView(from: .clear, to: .black)
|
||||
|
||||
let textView: CaptionTextView = {
|
||||
let textView = CaptionTextView()
|
||||
|
||||
textView.font = UIFont.ows_dynamicTypeBody
|
||||
textView.textColor = .white
|
||||
textView.backgroundColor = .clear
|
||||
textView.isEditable = false
|
||||
textView.isSelectable = false
|
||||
|
||||
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(200)
|
||||
|
||||
override var text: String! {
|
||||
didSet {
|
||||
invalidateIntrinsicContentSize()
|
||||
}
|
||||
func updateCaptionContainerVisibility() {
|
||||
if let currentText = captionContainerView.currentText, currentText.count > 0 {
|
||||
captionContainerView.isHidden = false
|
||||
return
|
||||
}
|
||||
|
||||
override var font: UIFont? {
|
||||
didSet {
|
||||
invalidateIntrinsicContentSize()
|
||||
}
|
||||
if let pendingText = captionContainerView.pendingText, pendingText.count > 0 {
|
||||
captionContainerView.isHidden = false
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
captionContainerView.isHidden = true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ public class MediaTileViewController: UICollectionViewController, MediaGalleryDa
|
|||
return
|
||||
}
|
||||
|
||||
collectionView.backgroundColor = Theme.backgroundColor
|
||||
collectionView.backgroundColor = Theme.galleryBackgroundColor
|
||||
|
||||
collectionView.register(PhotoGridViewCell.self, forCellWithReuseIdentifier: PhotoGridViewCell.reuseIdentifier)
|
||||
collectionView.register(MediaGallerySectionHeader.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: MediaGallerySectionHeader.reuseIdentifier)
|
||||
|
|
182
Signal/src/views/CaptionView.swift
Normal file
182
Signal/src/views/CaptionView.swift
Normal file
|
@ -0,0 +1,182 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
public protocol CaptionContainerViewDelegate: class {
|
||||
func captionContainerViewDidUpdateText(_ captionContainerView: CaptionContainerView)
|
||||
}
|
||||
|
||||
public class CaptionContainerView: UIView {
|
||||
|
||||
weak var delegate: CaptionContainerViewDelegate?
|
||||
|
||||
var currentText: String? {
|
||||
get { return currentCaptionView.text }
|
||||
set {
|
||||
currentCaptionView.text = newValue
|
||||
delegate?.captionContainerViewDidUpdateText(self)
|
||||
}
|
||||
}
|
||||
|
||||
var pendingText: String? {
|
||||
get { return pendingCaptionView.text }
|
||||
set {
|
||||
pendingCaptionView.text = newValue
|
||||
delegate?.captionContainerViewDidUpdateText(self)
|
||||
}
|
||||
}
|
||||
|
||||
func updatePagerTransition(ratioComplete: CGFloat) {
|
||||
if let currentText = self.currentText, currentText.count > 1 {
|
||||
currentCaptionView.alpha = 1 - ratioComplete
|
||||
} else {
|
||||
currentCaptionView.alpha = 0
|
||||
}
|
||||
|
||||
if let pendingText = self.pendingText, pendingText.count > 1 {
|
||||
pendingCaptionView.alpha = ratioComplete
|
||||
} else {
|
||||
pendingCaptionView.alpha = 0
|
||||
}
|
||||
}
|
||||
|
||||
func completePagerTransition() {
|
||||
updatePagerTransition(ratioComplete: 1)
|
||||
|
||||
// promote "pending" to "current" caption view.
|
||||
let oldCaptionView = self.currentCaptionView
|
||||
self.currentCaptionView = self.pendingCaptionView
|
||||
self.pendingCaptionView = oldCaptionView
|
||||
self.pendingText = nil
|
||||
}
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
setContentHuggingHigh()
|
||||
setCompressionResistanceHigh()
|
||||
|
||||
addSubview(currentCaptionView)
|
||||
currentCaptionView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .top)
|
||||
currentCaptionView.autoPinEdge(toSuperviewEdge: .top, withInset: 0, relation: .greaterThanOrEqual)
|
||||
|
||||
pendingCaptionView.alpha = 0
|
||||
addSubview(pendingCaptionView)
|
||||
pendingCaptionView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .top)
|
||||
pendingCaptionView.autoPinEdge(toSuperviewEdge: .top, withInset: 0, relation: .greaterThanOrEqual)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
// MARK: Subviews
|
||||
|
||||
private var pendingCaptionView: CaptionView = CaptionView()
|
||||
private var currentCaptionView: CaptionView = CaptionView()
|
||||
}
|
||||
|
||||
private class CaptionView: UIView {
|
||||
|
||||
var text: String? {
|
||||
get { return textView.text }
|
||||
|
||||
set {
|
||||
if let captionText = newValue, captionText.count > 0 {
|
||||
textView.text = captionText
|
||||
} else {
|
||||
textView.text = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Subviews
|
||||
|
||||
let textView: CaptionTextView = {
|
||||
let textView = CaptionTextView()
|
||||
|
||||
textView.font = UIFont.ows_dynamicTypeBody
|
||||
textView.textColor = .white
|
||||
textView.backgroundColor = .clear
|
||||
textView.isEditable = false
|
||||
textView.isSelectable = false
|
||||
|
||||
return textView
|
||||
}()
|
||||
|
||||
let scrollFadeView = GradientView(from: .clear, to: .black)
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
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(200)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -57,7 +57,6 @@ class GalleryRailCellView: UIView {
|
|||
|
||||
layoutMargins = .zero
|
||||
self.clipsToBounds = true
|
||||
adjustAspectRatio(isSelected: isSelected)
|
||||
addSubview(imageView)
|
||||
imageView.autoPinEdgesToSuperviewMargins()
|
||||
|
||||
|
@ -88,7 +87,7 @@ class GalleryRailCellView: UIView {
|
|||
guard self.item === item else { return }
|
||||
|
||||
self.imageView.image = image
|
||||
}.retainUntilComplete()
|
||||
}.retainUntilComplete()
|
||||
}
|
||||
|
||||
// MARK: Selected
|
||||
|
@ -97,34 +96,25 @@ class GalleryRailCellView: UIView {
|
|||
|
||||
func setIsSelected(_ isSelected: Bool) {
|
||||
self.isSelected = isSelected
|
||||
adjustAspectRatio(isSelected: isSelected)
|
||||
if isSelected {
|
||||
self.layoutMargins = UIEdgeInsets(top: 0, left: 3, bottom: 0, right: 3)
|
||||
layoutMargins = UIEdgeInsets(top: 0, left: 6, bottom: 0, right: 6)
|
||||
imageView.layer.borderColor = UIColor(rgbHex: 0x1f8fe8).cgColor
|
||||
imageView.layer.borderWidth = 2
|
||||
imageView.layer.cornerRadius = 2
|
||||
} else {
|
||||
self.layoutMargins = .zero
|
||||
layoutMargins = .zero
|
||||
imageView.layer.borderWidth = 0
|
||||
imageView.layer.cornerRadius = 0
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Subview Helpers
|
||||
|
||||
var aspectRatioConstraint: NSLayoutConstraint?
|
||||
func adjustAspectRatio(isSelected: Bool) {
|
||||
if let oldConstraint = aspectRatioConstraint {
|
||||
NSLayoutConstraint.deactivate([oldConstraint])
|
||||
}
|
||||
|
||||
if isSelected, let itemAspectRatio = item?.aspectRatio {
|
||||
aspectRatioConstraint = imageView.autoPin(toAspectRatio: itemAspectRatio)
|
||||
} else {
|
||||
// Portrait mode AR by default
|
||||
let kDefaultAspectRatio: CGFloat = 9.0 / 16.0
|
||||
aspectRatioConstraint = imageView.autoPin(toAspectRatio: kDefaultAspectRatio)
|
||||
}
|
||||
}
|
||||
|
||||
let imageView: UIImageView = {
|
||||
let imageView = UIImageView()
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
imageView.autoPinToSquareAspectRatio()
|
||||
imageView.clipsToBounds = true
|
||||
|
||||
return imageView
|
||||
}()
|
||||
|
@ -183,15 +173,21 @@ class GalleryRailView: UIView, GalleryRailCellViewDelegate {
|
|||
}
|
||||
|
||||
self.itemProvider = itemProvider
|
||||
scrollView.subviews.forEach { $0.removeFromSuperview() }
|
||||
|
||||
guard itemProvider.railItems.count > 1 else {
|
||||
UIView.animate(withDuration: animationDuration) {
|
||||
self.isHidden = true
|
||||
}
|
||||
let cellViews = scrollView.subviews
|
||||
|
||||
UIView.animate(withDuration: animationDuration,
|
||||
animations: {
|
||||
cellViews.forEach { $0.isHidden = true }
|
||||
self.isHidden = true
|
||||
},
|
||||
completion: { _ in cellViews.forEach { $0.removeFromSuperview() } })
|
||||
return
|
||||
}
|
||||
|
||||
scrollView.subviews.forEach { $0.removeFromSuperview() }
|
||||
|
||||
UIView.animate(withDuration: animationDuration) {
|
||||
self.isHidden = false
|
||||
}
|
||||
|
@ -200,7 +196,7 @@ class GalleryRailView: UIView, GalleryRailCellViewDelegate {
|
|||
self.cellViews = cellViews
|
||||
let stackView = UIStackView(arrangedSubviews: cellViews)
|
||||
stackView.axis = .horizontal
|
||||
stackView.spacing = 4
|
||||
stackView.spacing = 2
|
||||
|
||||
scrollView.addSubview(stackView)
|
||||
stackView.autoPinEdgesToSuperviewEdges()
|
||||
|
|
|
@ -154,7 +154,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
// Status bar is overlaying the green "call banner"
|
||||
return UIStatusBarStyleLightContent;
|
||||
} else {
|
||||
return (Theme.isDarkThemeEnabled ? UIStatusBarStyleLightContent : super.preferredStatusBarStyle);
|
||||
UIViewController *presentedViewController = self.presentedViewController;
|
||||
if (presentedViewController) {
|
||||
return presentedViewController.preferredStatusBarStyle;
|
||||
} else {
|
||||
return (Theme.isDarkThemeEnabled ? UIStatusBarStyleLightContent : super.preferredStatusBarStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -58,6 +58,12 @@ public class OWSNavigationBar: UINavigationBar {
|
|||
// MARK: Theme
|
||||
|
||||
private func applyTheme() {
|
||||
guard respectsTheme else {
|
||||
self.blurEffectView?.removeFromSuperview()
|
||||
self.setBackgroundImage(nil, for: .default)
|
||||
return
|
||||
}
|
||||
|
||||
if UIAccessibilityIsReduceTransparencyEnabled() {
|
||||
self.blurEffectView?.removeFromSuperview()
|
||||
let color = Theme.navbarBackgroundColor
|
||||
|
@ -107,6 +113,13 @@ public class OWSNavigationBar: UINavigationBar {
|
|||
applyTheme()
|
||||
}
|
||||
|
||||
@objc
|
||||
public var respectsTheme: Bool = true {
|
||||
didSet {
|
||||
themeDidChange()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Layout
|
||||
|
||||
@objc
|
||||
|
|
|
@ -39,6 +39,9 @@ extern NSString *const ThemeDidChangeNotification;
|
|||
@property (class, readonly, nonatomic) UIColor *cellSelectedColor;
|
||||
@property (class, readonly, nonatomic) UIColor *cellSeparatorColor;
|
||||
|
||||
@property (class, readonly, nonatomic) UIColor *galleryBackgroundColor;
|
||||
@property (class, readonly, nonatomic) UIColor *galleryIconColor;
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@property (class, readonly, nonatomic) UIBarStyle barStyle;
|
||||
|
|
|
@ -118,6 +118,16 @@ NSString *const ThemeKeyThemeEnabled = @"ThemeKeyThemeEnabled";
|
|||
return Theme.hairlineColor;
|
||||
}
|
||||
|
||||
+ (UIColor *)galleryBackgroundColor
|
||||
{
|
||||
return UIColor.ows_gray95Color;
|
||||
}
|
||||
|
||||
+ (UIColor *)galleryIconColor
|
||||
{
|
||||
return UIColor.ows_gray05Color;
|
||||
}
|
||||
|
||||
+ (UIColor *)conversationButtonBackgroundColor
|
||||
{
|
||||
return (Theme.isDarkThemeEnabled ? [UIColor colorWithWhite:0.35f alpha:1.f] : UIColor.ows_gray02Color);
|
||||
|
|
Loading…
Reference in a new issue