diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index b57b434a8..fb8c7f684 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -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 = ""; }; 4C9CA25C217E676900607C63 /* ZXingObjC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ZXingObjC.framework; path = ThirdParty/Carthage/Build/iOS/ZXingObjC.framework; sourceTree = ""; }; 4CA46F49219C78050038ABDE /* GalleryRailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryRailView.swift; sourceTree = ""; }; + 4CA46F4B219CCC630038ABDE /* CaptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaptionView.swift; sourceTree = ""; }; 4CA5F792211E1F06008C2708 /* Toast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = ""; }; 4CB5F26820F7D060004D1B42 /* MessageActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageActions.swift; sourceTree = ""; }; 4CB93DC12180FF07004B9764 /* ProximityMonitoringManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProximityMonitoringManager.swift; sourceTree = ""; }; @@ -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 */, diff --git a/Signal/src/ViewControllers/MediaGalleryViewController.swift b/Signal/src/ViewControllers/MediaGalleryViewController.swift index afd4ff1bc..5097252b6 100644 --- a/Signal/src/ViewControllers/MediaGalleryViewController.swift +++ b/Signal/src/ViewControllers/MediaGalleryViewController.swift @@ -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) } } diff --git a/Signal/src/ViewControllers/MediaPageViewController.swift b/Signal/src/ViewControllers/MediaPageViewController.swift index f8dbfc1df..16efbc121 100644 --- a/Signal/src/ViewControllers/MediaPageViewController.swift +++ b/Signal/src/ViewControllers/MediaPageViewController.swift @@ -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 } } diff --git a/Signal/src/ViewControllers/MediaTileViewController.swift b/Signal/src/ViewControllers/MediaTileViewController.swift index 5a2b1e353..b2b58297c 100644 --- a/Signal/src/ViewControllers/MediaTileViewController.swift +++ b/Signal/src/ViewControllers/MediaTileViewController.swift @@ -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) diff --git a/Signal/src/views/CaptionView.swift b/Signal/src/views/CaptionView.swift new file mode 100644 index 000000000..3f20985b5 --- /dev/null +++ b/Signal/src/views/CaptionView.swift @@ -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 + } + } +} diff --git a/Signal/src/views/GalleryRailView.swift b/Signal/src/views/GalleryRailView.swift index 6e7782a38..e43358dd0 100644 --- a/Signal/src/views/GalleryRailView.swift +++ b/Signal/src/views/GalleryRailView.swift @@ -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() diff --git a/SignalMessaging/ViewControllers/OWSNavigationController.m b/SignalMessaging/ViewControllers/OWSNavigationController.m index e1ebb9ebb..c8d36fc79 100644 --- a/SignalMessaging/ViewControllers/OWSNavigationController.m +++ b/SignalMessaging/ViewControllers/OWSNavigationController.m @@ -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); + } } } diff --git a/SignalMessaging/Views/OWSNavigationBar.swift b/SignalMessaging/Views/OWSNavigationBar.swift index deae451a0..a65ebd1de 100644 --- a/SignalMessaging/Views/OWSNavigationBar.swift +++ b/SignalMessaging/Views/OWSNavigationBar.swift @@ -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 diff --git a/SignalMessaging/appearance/Theme.h b/SignalMessaging/appearance/Theme.h index 0d9caf4a6..42e0a94cb 100644 --- a/SignalMessaging/appearance/Theme.h +++ b/SignalMessaging/appearance/Theme.h @@ -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; diff --git a/SignalMessaging/appearance/Theme.m b/SignalMessaging/appearance/Theme.m index 9096a9e9c..7afe212a2 100644 --- a/SignalMessaging/appearance/Theme.m +++ b/SignalMessaging/appearance/Theme.m @@ -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);