diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index e2a037011..d313c83a8 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -2051,12 +2051,12 @@ typedef enum : NSUInteger { } TSMessage *mediaMessage = (TSMessage *)viewItem.interaction; - MediaGalleryViewController *vc = [[MediaGalleryViewController alloc] - initWithThread:self.thread - uiDatabaseConnection:self.uiDatabaseConnection - options:MediaGalleryOptionSliderEnabled | MediaGalleryOptionShowAllMediaButton]; + MediaGallery *mediaGallery = + [[MediaGallery alloc] initWithThread:self.thread + uiDatabaseConnection:self.uiDatabaseConnection + options:MediaGalleryOptionSliderEnabled | MediaGalleryOptionShowAllMediaButton]; - [vc presentDetailViewFromViewController:self mediaMessage:mediaMessage replacingView:imageView]; + [mediaGallery presentDetailViewFromViewController:self mediaMessage:mediaMessage replacingView:imageView]; } - (void)didTapVideoViewItem:(id)viewItem @@ -2080,12 +2080,12 @@ typedef enum : NSUInteger { } TSMessage *mediaMessage = (TSMessage *)viewItem.interaction; - MediaGalleryViewController *vc = [[MediaGalleryViewController alloc] - initWithThread:self.thread - uiDatabaseConnection:self.uiDatabaseConnection - options:MediaGalleryOptionSliderEnabled | MediaGalleryOptionShowAllMediaButton]; + MediaGallery *mediaGallery = + [[MediaGallery alloc] initWithThread:self.thread + uiDatabaseConnection:self.uiDatabaseConnection + options:MediaGalleryOptionSliderEnabled | MediaGalleryOptionShowAllMediaButton]; - [vc presentDetailViewFromViewController:self mediaMessage:mediaMessage replacingView:imageView]; + [mediaGallery presentDetailViewFromViewController:self mediaMessage:mediaMessage replacingView:imageView]; } - (void)didTapAudioViewItem:(id)viewItem attachmentStream:(TSAttachmentStream *)attachmentStream diff --git a/Signal/src/ViewControllers/MediaGalleryViewController.swift b/Signal/src/ViewControllers/MediaGalleryViewController.swift index 694e49863..a222b7ec1 100644 --- a/Signal/src/ViewControllers/MediaGalleryViewController.swift +++ b/Signal/src/ViewControllers/MediaGalleryViewController.swift @@ -168,42 +168,7 @@ protocol MediaGalleryDataSourceDelegate: class { func mediaGalleryDataSource(_ mediaGalleryDataSource: MediaGalleryDataSource, deletedSections: IndexSet, deletedItems: [IndexPath]) } -class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSource, MediaTileViewControllerDelegate { - - private var pageViewController: MediaPageViewController? - - private let uiDatabaseConnection: YapDatabaseConnection - private let editingDatabaseConnection: YapDatabaseConnection - private let mediaGalleryFinder: OWSMediaGalleryFinder - - private var initialDetailItem: MediaGalleryItem? - private let thread: TSThread - private let options: MediaGalleryOption - - // we start with a small range size for quick loading. - private let fetchRangeSize: UInt = 10 - - deinit { - Logger.debug("deinit") - } - - @objc - init(thread: TSThread, uiDatabaseConnection: YapDatabaseConnection, options: MediaGalleryOption = []) { - self.thread = thread - assert(uiDatabaseConnection.isInLongLivedReadTransaction()) - self.uiDatabaseConnection = uiDatabaseConnection - - self.editingDatabaseConnection = OWSPrimaryStorage.shared().newDatabaseConnection() - - self.options = options - self.mediaGalleryFinder = OWSMediaGalleryFinder(thread: thread) - - super.init(nibName: nil, bundle: nil) - } - - required init?(coder aDecoder: NSCoder) { - notImplemented() - } +class MediaGalleryNavigationController: OWSNavigationController { // MARK: View LifeCycle @@ -213,6 +178,9 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc UIViewController.attemptRotationToDeviceOrientation() } + var presentationView: UIImageView! + var retainUntilDismissed: MediaGallery? + // HACK: Though we don't have an input accessory view, the VC we are presented above (ConversationVC) does. // If the app is backgrounded and then foregrounded, when OWSWindowManager calls mainWindow.makeKeyAndVisible // the ConversationVC's inputAccessoryView will appear *above* us unless we'd previously become first responder. @@ -254,6 +222,52 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc presentationView.contentMode = .scaleAspectFit } + // MARK: Orientation + + public override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + return .allButUpsideDown + } +} + +@objc +class MediaGallery: NSObject, MediaGalleryDataSource, MediaTileViewControllerDelegate { + + @objc + weak public var navigationController: MediaGalleryNavigationController! + + private var pageViewController: MediaPageViewController? + + private let uiDatabaseConnection: YapDatabaseConnection + private let editingDatabaseConnection: YapDatabaseConnection + private let mediaGalleryFinder: OWSMediaGalleryFinder + + private var initialDetailItem: MediaGalleryItem? + private let thread: TSThread + private let options: MediaGalleryOption + + // we start with a small range size for quick loading. + private let fetchRangeSize: UInt = 10 + + deinit { + Logger.debug("") + } + + @objc + init(thread: TSThread, uiDatabaseConnection: YapDatabaseConnection, options: MediaGalleryOption = []) { + self.thread = thread + assert(uiDatabaseConnection.isInLongLivedReadTransaction()) + self.uiDatabaseConnection = uiDatabaseConnection + + self.editingDatabaseConnection = OWSPrimaryStorage.shared().newDatabaseConnection() + + self.options = options + self.mediaGalleryFinder = OWSMediaGalleryFinder(thread: thread) + let navController = MediaGalleryNavigationController() + self.navigationController = navController + super.init() + navController.retainUntilDismissed = self + } + // MARK: Present/Dismiss private var currentItem: MediaGalleryItem { @@ -261,7 +275,6 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc } private var replacingView: UIView? - private var presentationView: UIImageView! private var presentationViewConstraints: [NSLayoutConstraint] = [] // TODO rename to replacingOriginRect @@ -292,7 +305,7 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc self.addDataSourceDelegate(pageViewController) self.pageViewController = pageViewController - self.setViewControllers([pageViewController], animated: false) + navigationController.setViewControllers([pageViewController], animated: false) self.replacingView = replacingView @@ -300,13 +313,13 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc self.originRect = convertedRect // loadView hasn't necessarily been called yet. - self.loadViewIfNeeded() + navigationController.loadViewIfNeeded() - self.presentationView.image = initialDetailItem.attachmentStream.thumbnailImageLarge(success: { [weak self] (image) in + navigationController.presentationView.image = initialDetailItem.attachmentStream.thumbnailImageLarge(success: { [weak self] (image) in guard let strongSelf = self else { return } - strongSelf.presentationView.image = image + strongSelf.navigationController.presentationView.image = image }, failure: { Logger.warn("Could not load presentation image.") }) @@ -314,8 +327,8 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc self.applyInitialMediaViewConstraints() // Restore presentationView.alpha in case a previous dismiss left us in a bad state. - self.setNavigationBarHidden(false, animated: false) - self.presentationView.alpha = 1 + navigationController.setNavigationBarHidden(false, animated: false) + navigationController.presentationView.alpha = 1 // We want to animate the tapped media from it's position in the previous VC // to it's resting place in the center of this view controller. @@ -328,7 +341,7 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc // 2. For Video views, the AVPlayerLayer content does not scale with the presentation animation. So you instead get a full scale // video, wherein only the cropping is animated. // Using a simple image view allows us to address both these problems relatively easily. - self.view.alpha = 0.0 + navigationController.view.alpha = 0.0 guard let detailView = pageViewController.view else { owsFailDebug("detailView was unexpectedly nil") @@ -337,23 +350,23 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc // At this point our media view should be overlayed perfectly // by our presentationView. Swapping them out should be imperceptible. - self.presentationView.isHidden = false + navigationController.presentationView.isHidden = false // We don't hide the pageViewController entirely - e.g. we want the toolbars to fade in. pageViewController.currentViewController.view.isHidden = true detailView.backgroundColor = .clear - self.view.backgroundColor = .clear + navigationController.view.backgroundColor = .clear - self.presentationView.layer.cornerRadius = kOWSMessageCellCornerRadius_Small + navigationController.presentationView.layer.cornerRadius = kOWSMessageCellCornerRadius_Small - fromViewController.present(self, animated: false) { + fromViewController.present(navigationController, animated: false) { // 1. Fade in the entire view. UIView.animate(withDuration: 0.1) { self.replacingView?.alpha = 0.0 - self.view.alpha = 1.0 + self.navigationController.view.alpha = 1.0 } - self.presentationView.superview?.layoutIfNeeded() + self.navigationController.presentationView.superview?.layoutIfNeeded() self.applyFinalMediaViewConstraints() // 2. Animate imageView from it's initial position, which should match where it was @@ -364,20 +377,20 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc options: .curveEaseOut, animations: { - self.presentationView.layer.cornerRadius = 0 - self.presentationView.superview?.layoutIfNeeded() + self.navigationController.presentationView.layer.cornerRadius = 0 + self.navigationController.presentationView.superview?.layoutIfNeeded() // fade out content behind the pageViewController // and behind the presentation view - self.view.backgroundColor = Theme.backgroundColor + self.navigationController.view.backgroundColor = Theme.backgroundColor }, completion: { (_: Bool) in // At this point our presentation view should be overlayed perfectly // with our media view. Swapping them out should be imperceptible. pageViewController.currentViewController.view.isHidden = false - self.presentationView.isHidden = true + self.navigationController.presentationView.isHidden = true - self.view.isUserInteractionEnabled = true + self.navigationController.view.isUserInteractionEnabled = true pageViewController.wasPresented() @@ -388,7 +401,7 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc // // We don't need to do this when pushing VCs onto the SignalsNavigationController - only when // presenting directly from ConversationVC. - _ = self.becomeFirstResponder() + _ = self.navigationController.becomeFirstResponder() }) } } @@ -424,7 +437,7 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc } else { // If from conversation view mediaTileViewController.focusedItem = focusedItem - self.pushViewController(mediaTileViewController, animated: true) + navigationController.pushViewController(mediaTileViewController, animated: true) } } @@ -457,7 +470,7 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc guard let pageViewController = self.pageViewController else { owsFailDebug("pageViewController was unexpectedly nil") - self.dismiss(animated: true) + self.navigationController.dismiss(animated: true) return } @@ -466,32 +479,32 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc pageViewController.willBePresentedAgain() // TODO fancy zoom animation - self.popViewController(animated: true) + self.navigationController.popViewController(animated: true) } } public func dismissMediaDetailViewController(_ mediaPageViewController: MediaPageViewController, animated isAnimated: Bool, completion: (() -> Void)?) { - self.view.isUserInteractionEnabled = false + navigationController.view.isUserInteractionEnabled = false UIApplication.shared.isStatusBarHidden = false guard let detailView = mediaPageViewController.view else { owsFailDebug("detailView was unexpectedly nil") - self.presentingViewController?.dismiss(animated: false, completion: completion) + self.navigationController.presentingViewController?.dismiss(animated: false, completion: completion) return } mediaPageViewController.currentViewController.view.isHidden = true - self.presentationView.isHidden = false + navigationController.presentationView.isHidden = false // Move the presentationView back to it's initial position, i.e. where // it sits on the screen in the conversation view. let changedItems = currentItem != self.initialDetailItem if changedItems { - self.presentationView.image = currentItem.attachmentStream.thumbnailImageLarge(success: { [weak self] (image) in + navigationController.presentationView.image = currentItem.attachmentStream.thumbnailImageLarge(success: { [weak self] (image) in guard let strongSelf = self else { return } - strongSelf.presentationView.image = image + strongSelf.navigationController.presentationView.image = image }, failure: { Logger.warn("Could not load presentation image.") }) @@ -506,14 +519,14 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc options: .curveEaseOut, animations: { // Move back over it's original location - self.presentationView.superview?.layoutIfNeeded() + self.navigationController.presentationView.superview?.layoutIfNeeded() detailView.alpha = 0 if changedItems { - self.presentationView.alpha = 0 + self.navigationController.presentationView.alpha = 0 } else { - self.presentationView.layer.cornerRadius = kOWSMessageCellCornerRadius_Small + self.navigationController.presentationView.layer.cornerRadius = kOWSMessageCellCornerRadius_Small } }, completion: { (_: Bool) in @@ -530,24 +543,24 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc animations: { guard let replacingView = self.replacingView else { owsFailDebug("replacingView was unexpectedly nil") - self.presentingViewController?.dismiss(animated: false, completion: completion) + self.navigationController.presentingViewController?.dismiss(animated: false, completion: completion) return } // fade out content and toolbars - self.view.alpha = 0.0 + self.navigationController.view.alpha = 0.0 replacingView.alpha = 1.0 }, completion: { (_: Bool) in - self.presentingViewController?.dismiss(animated: false, completion: completion) + self.navigationController.presentingViewController?.dismiss(animated: false, completion: completion) }) } else { guard let replacingView = self.replacingView else { owsFailDebug("replacingView was unexpectedly nil") - self.presentingViewController?.dismiss(animated: false, completion: completion) + navigationController.presentingViewController?.dismiss(animated: false, completion: completion) return } replacingView.alpha = 1.0 - self.presentingViewController?.dismiss(animated: false, completion: completion) + navigationController.presentingViewController?.dismiss(animated: false, completion: completion) } } @@ -562,17 +575,17 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc return } - guard let presentationSuperview = self.presentationView.superview else { + guard let presentationSuperview = navigationController.presentationView.superview else { owsFailDebug("presentationView.superview was unexpectedly nil") return } let convertedRect: CGRect = presentationSuperview.convert(originRect, from: UIApplication.shared.keyWindow) - self.presentationViewConstraints += self.presentationView.autoSetDimensions(to: convertedRect.size) + self.presentationViewConstraints += navigationController.presentationView.autoSetDimensions(to: convertedRect.size) self.presentationViewConstraints += [ - self.presentationView.autoPinEdge(toSuperviewEdge: .top, withInset: convertedRect.origin.y), - self.presentationView.autoPinEdge(toSuperviewEdge: .left, withInset: convertedRect.origin.x) + navigationController.presentationView.autoPinEdge(toSuperviewEdge: .top, withInset: convertedRect.origin.y), + navigationController.presentationView.autoPinEdge(toSuperviewEdge: .left, withInset: convertedRect.origin.x) ] } @@ -583,10 +596,10 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc } self.presentationViewConstraints = [ - self.presentationView.autoPinEdge(toSuperviewEdge: .leading), - self.presentationView.autoPinEdge(toSuperviewEdge: .top), - self.presentationView.autoPinEdge(toSuperviewEdge: .trailing), - self.presentationView.autoPinEdge(toSuperviewEdge: .bottom) + navigationController.presentationView.autoPinEdge(toSuperviewEdge: .leading), + navigationController.presentationView.autoPinEdge(toSuperviewEdge: .top), + navigationController.presentationView.autoPinEdge(toSuperviewEdge: .trailing), + navigationController.presentationView.autoPinEdge(toSuperviewEdge: .bottom) ] } @@ -597,9 +610,9 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc } self.presentationViewConstraints += [ - self.presentationView.autoPinEdge(toSuperviewEdge: .leading), - self.presentationView.autoPinEdge(toSuperviewEdge: .trailing), - self.presentationView.autoPinEdge(.top, to: .bottom, of: self.view) + navigationController.presentationView.autoPinEdge(toSuperviewEdge: .leading), + navigationController.presentationView.autoPinEdge(toSuperviewEdge: .trailing), + navigationController.presentationView.autoPinEdge(.top, to: .bottom, of: self.navigationController.view) ] } @@ -916,10 +929,4 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc } return Int(count) - deletedMessages.count } - - // MARK: Orientation - - public override var supportedInterfaceOrientations: UIInterfaceOrientationMask { - return .allButUpsideDown - } } diff --git a/Signal/src/ViewControllers/MessageDetailViewController.swift b/Signal/src/ViewControllers/MessageDetailViewController.swift index 88b233142..005f9a426 100644 --- a/Signal/src/ViewControllers/MessageDetailViewController.swift +++ b/Signal/src/ViewControllers/MessageDetailViewController.swift @@ -615,17 +615,17 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele // MARK: OWSMessageBubbleViewDelegate func didTapImageViewItem(_ viewItem: ConversationViewItem, attachmentStream: TSAttachmentStream, imageView: UIView) { - let mediaGalleryViewController = MediaGalleryViewController(thread: self.thread, uiDatabaseConnection: self.uiDatabaseConnection) + let mediaGallery = MediaGallery(thread: self.thread, uiDatabaseConnection: self.uiDatabaseConnection) - mediaGalleryViewController.addDataSourceDelegate(self) - mediaGalleryViewController.presentDetailView(fromViewController: self, mediaMessage: self.message, replacingView: imageView) + mediaGallery.addDataSourceDelegate(self) + mediaGallery.presentDetailView(fromViewController: self, mediaMessage: self.message, replacingView: imageView) } func didTapVideoViewItem(_ viewItem: ConversationViewItem, attachmentStream: TSAttachmentStream, imageView: UIView) { - let mediaGalleryViewController = MediaGalleryViewController(thread: self.thread, uiDatabaseConnection: self.uiDatabaseConnection) + let mediaGallery = MediaGallery(thread: self.thread, uiDatabaseConnection: self.uiDatabaseConnection) - mediaGalleryViewController.addDataSourceDelegate(self) - mediaGalleryViewController.presentDetailView(fromViewController: self, mediaMessage: self.message, replacingView: imageView) + mediaGallery.addDataSourceDelegate(self) + mediaGallery.presentDetailView(fromViewController: self, mediaMessage: self.message, replacingView: imageView) } func didTapContactShare(_ viewItem: ConversationViewItem) { diff --git a/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m b/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m index 4135b7d4d..aa16a1fa9 100644 --- a/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m +++ b/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m @@ -54,7 +54,7 @@ const CGFloat kIconViewLength = 24; @property (nonatomic) NSArray *disappearingMessagesDurations; @property (nonatomic) OWSDisappearingMessagesConfiguration *disappearingMessagesConfiguration; -@property (nullable, nonatomic) MediaGalleryViewController *mediaGalleryViewController; +@property (nullable, nonatomic) MediaGalleryNavigationController *mediaGalleryViewController; @property (nonatomic, readonly) TSAccountManager *accountManager; @property (nonatomic, readonly) OWSContactsManager *contactsManager; @property (nonatomic, readonly) OWSMessageSender *messageSender; @@ -1273,19 +1273,18 @@ const CGFloat kIconViewLength = 24; - (void)showMediaGallery { - OWSLogDebug(@"in showMediaGallery"); + OWSLogDebug(@""); - MediaGalleryViewController *vc = - [[MediaGalleryViewController alloc] initWithThread:self.thread - uiDatabaseConnection:self.uiDatabaseConnection - options:MediaGalleryOptionSliderEnabled]; + MediaGallery *mediaGallery = [[MediaGallery alloc] initWithThread:self.thread + uiDatabaseConnection:self.uiDatabaseConnection + options:MediaGalleryOptionSliderEnabled]; // although we don't present the mediaGalleryViewController directly, we need to maintain a strong // reference to it until we're dismissed. - self.mediaGalleryViewController = vc; + self.mediaGalleryViewController = mediaGallery.navigationController; OWSAssertDebug([self.navigationController isKindOfClass:[OWSNavigationController class]]); - [vc pushTileViewFromNavController:(OWSNavigationController *)self.navigationController]; + [mediaGallery pushTileViewFromNavController:(OWSNavigationController *)self.navigationController]; } #pragma mark - Notifications diff --git a/SignalMessaging/ViewControllers/OWSNavigationController.m b/SignalMessaging/ViewControllers/OWSNavigationController.m index 875b0deaa..e1ebb9ebb 100644 --- a/SignalMessaging/ViewControllers/OWSNavigationController.m +++ b/SignalMessaging/ViewControllers/OWSNavigationController.m @@ -24,28 +24,24 @@ NS_ASSUME_NONNULL_BEGIN @implementation OWSNavigationController -- (instancetype)initWithRootViewController:(UIViewController *)rootViewController +- (instancetype)init { - self = [self initWithNavigationBarClass:[OWSNavigationBar class] toolbarClass:nil]; + self = [super initWithNavigationBarClass:[OWSNavigationBar class] toolbarClass:nil]; if (!self) { return self; } + [self setupNavbar]; - [self pushViewController:rootViewController animated:NO]; + return self; +} - if (![self.navigationBar isKindOfClass:[OWSNavigationBar class]]) { - OWSFailDebug(@"navigationBar was unexpected class: %@", self.navigationBar); +- (instancetype)initWithRootViewController:(UIViewController *)rootViewController +{ + self = [self init]; + if (!self) { return self; } - - OWSNavigationBar *navbar = (OWSNavigationBar *)self.navigationBar; - navbar.navBarLayoutDelegate = self; - [self updateLayoutForNavbar:navbar]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(themeDidChange:) - name:ThemeDidChangeNotification - object:nil]; + [self pushViewController:rootViewController animated:NO]; return self; } @@ -55,6 +51,8 @@ NS_ASSUME_NONNULL_BEGIN [[NSNotificationCenter defaultCenter] removeObserver:self]; } +#pragma mark - + - (void)themeDidChange:(NSNotification *)notification { OWSAssertIsOnMainThread(); @@ -73,6 +71,22 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - UINavigationBarDelegate +- (void)setupNavbar +{ + if (![self.navigationBar isKindOfClass:[OWSNavigationBar class]]) { + OWSFailDebug(@"navigationBar was unexpected class: %@", self.navigationBar); + return; + } + OWSNavigationBar *navbar = (OWSNavigationBar *)self.navigationBar; + navbar.navBarLayoutDelegate = self; + [self updateLayoutForNavbar:navbar]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(themeDidChange:) + name:ThemeDidChangeNotification + object:nil]; +} + // All OWSNavigationController serve as the UINavigationBarDelegate for their navbar. // We override shouldPopItem: in order to cancel some back button presses - for example, // if a view has unsaved changes.