Use FullSreen media VC for message details

// FREEBIE
pull/1/head
Michael Kirk 8 years ago
parent c7c433c59c
commit 8454e512d8

@ -4,6 +4,7 @@
#import "FullImageViewController.h" #import "FullImageViewController.h"
#import "AttachmentSharing.h" #import "AttachmentSharing.h"
#import "ConversationViewController.h"
#import "ConversationViewItem.h" #import "ConversationViewItem.h"
#import "Signal-Swift.h" #import "Signal-Swift.h"
#import "TSAttachmentStream.h" #import "TSAttachmentStream.h"
@ -19,8 +20,6 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
#define kMaxZoomScale 8.0f
// In order to use UIMenuController, the view from which it is // In order to use UIMenuController, the view from which it is
// presented must have certain custom behaviors. // presented must have certain custom behaviors.
@interface AttachmentMenuView : UIView @interface AttachmentMenuView : UIView
@ -132,7 +131,11 @@ NS_ASSUME_NONNULL_BEGIN
if (self.attachmentStream) { if (self.attachmentStream) {
return self.attachmentStream.image; return self.attachmentStream.image;
} else if (self.attachment) { } else if (self.attachment) {
return self.attachment.image; if (self.isVideo) {
return self.attachment.videoPreview;
} else {
return self.attachment.image;
}
} else { } else {
return nil; return nil;
} }
@ -220,6 +223,7 @@ NS_ASSUME_NONNULL_BEGIN
if (minScale != self.scrollView.minimumZoomScale) { if (minScale != self.scrollView.minimumZoomScale) {
self.scrollView.minimumZoomScale = minScale; self.scrollView.minimumZoomScale = minScale;
self.scrollView.maximumZoomScale = minScale * 8;
self.scrollView.zoomScale = minScale; self.scrollView.zoomScale = minScale;
} }
} }
@ -235,8 +239,6 @@ NS_ASSUME_NONNULL_BEGIN
self.scrollView = scrollView; self.scrollView = scrollView;
scrollView.delegate = self; scrollView.delegate = self;
// TODO set max based on MIN.
scrollView.maximumZoomScale = kMaxZoomScale;
scrollView.showsVerticalScrollIndicator = NO; scrollView.showsVerticalScrollIndicator = NO;
scrollView.showsHorizontalScrollIndicator = NO; scrollView.showsHorizontalScrollIndicator = NO;
scrollView.decelerationRate = UIScrollViewDecelerationRateFast; scrollView.decelerationRate = UIScrollViewDecelerationRateFast;
@ -247,7 +249,7 @@ NS_ASSUME_NONNULL_BEGIN
if (self.isAnimated) { if (self.isAnimated) {
if ([self.fileData ows_isValidImage]) { if ([self.fileData ows_isValidImage]) {
YYImage *animatedGif = [YYImage imageWithData:self.fileData]; YYImage *animatedGif = [YYImage imageWithData:self.fileData];
YYAnimatedImageView *animatedView = [[YYAnimatedImageView alloc] init]; YYAnimatedImageView *animatedView = [YYAnimatedImageView new];
animatedView.image = animatedGif; animatedView.image = animatedGif;
self.imageView = animatedView; self.imageView = animatedView;
} else { } else {
@ -308,21 +310,27 @@ NS_ASSUME_NONNULL_BEGIN
[playVideoButton autoCenterInSuperview]; [playVideoButton autoCenterInSuperview];
} }
UIToolbar *footerBar = [UIToolbar new];
_footerBar = footerBar;
footerBar.barTintColor = [UIColor ows_signalBrandBlueColor];
self.videoPlayBarButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemPlay
target:self
action:@selector(didPressPlayBarButton:)];
self.videoPauseBarButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemPause
target:self
action:@selector(didPressPauseBarButton:)];
[self updateFooterBarButtonItemsWithIsPlayingVideo:YES];
[self.view addSubview:footerBar];
[footerBar autoPinWidthToSuperview]; // Don't show footer bar after tapping approval-view
[footerBar autoPinToBottomLayoutGuideOfViewController:self withInset:0]; if (self.viewItem) {
[footerBar autoSetDimension:ALDimensionHeight toSize:kFooterHeight]; UIToolbar *footerBar = [UIToolbar new];
_footerBar = footerBar;
footerBar.barTintColor = [UIColor ows_signalBrandBlueColor];
self.videoPlayBarButton =
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemPlay
target:self
action:@selector(didPressPlayBarButton:)];
self.videoPauseBarButton =
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemPause
target:self
action:@selector(didPressPauseBarButton:)];
[self updateFooterBarButtonItemsWithIsPlayingVideo:YES];
[self.view addSubview:footerBar];
[footerBar autoPinWidthToSuperview];
[footerBar autoPinToBottomLayoutGuideOfViewController:self withInset:0];
[footerBar autoSetDimension:ALDimensionHeight toSize:kFooterHeight];
}
} }
- (void)updateFooterBarButtonItemsWithIsPlayingVideo:(BOOL)isPlayingVideo - (void)updateFooterBarButtonItemsWithIsPlayingVideo:(BOOL)isPlayingVideo
@ -438,8 +446,8 @@ NS_ASSUME_NONNULL_BEGIN
_areToolbarsHidden = areToolbarsHidden; _areToolbarsHidden = areToolbarsHidden;
// Hiding the status bar affects the positioing of the navbar. We don't want to show that in the animation // Hiding the status bar affects the positioing of the navbar. We don't want to show that in an animation, it's
// so when *showing* the toolbars, we show the status bar first. When hiding, we hide it last. // better to just have everythign "flit" in/out.
[[UIApplication sharedApplication] setStatusBarHidden:areToolbarsHidden withAnimation:UIStatusBarAnimationNone]; [[UIApplication sharedApplication] setStatusBarHidden:areToolbarsHidden withAnimation:UIStatusBarAnimationNone];
[self.navigationController setNavigationBarHidden:areToolbarsHidden animated:NO]; [self.navigationController setNavigationBarHidden:areToolbarsHidden animated:NO];
self.videoProgressBar.hidden = areToolbarsHidden; self.videoProgressBar.hidden = areToolbarsHidden;
@ -490,7 +498,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)didTapDismissButton:(id)sender - (void)didTapDismissButton:(id)sender
{ {
[self dismiss]; [self dismissSelfAnimated:YES completion:nil];
} }
- (void)didTapImage:(id)sender - (void)didTapImage:(id)sender
@ -519,7 +527,7 @@ NS_ASSUME_NONNULL_BEGIN
return; return;
} }
[self dismiss]; [self dismissSelfAnimated:YES completion:nil];
} }
- (void)longPressGesture:(UIGestureRecognizer *)sender { - (void)longPressGesture:(UIGestureRecognizer *)sender {
@ -551,23 +559,56 @@ NS_ASSUME_NONNULL_BEGIN
- (void)didPressShare:(id)sender - (void)didPressShare:(id)sender
{ {
DDLogInfo(@"%@: sharing image.", self.logTag); DDLogInfo(@"%@: didPressShare", self.logTag);
if (!self.viewItem) {
OWSFail(@"share should only be available when a viewItem is present");
return;
}
[self.viewItem shareAction]; [self.viewItem shareAction];
} }
- (void)didPressDelete:(id)sender - (void)didPressDelete:(id)sender
{ {
DDLogInfo(@"%@: sharing image.", self.logTag); DDLogInfo(@"%@: didPressDelete", self.logTag);
if (!self.viewItem) {
OWSFail(@"delete should only be available when a viewItem is present");
return;
}
UIAlertController *actionSheet = UIAlertController *actionSheet =
[UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
[actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"TXT_DELETE_TITLE", nil)
style:UIAlertActionStyleDestructive [actionSheet
handler:^(UIAlertAction *action) { addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"TXT_DELETE_TITLE", nil)
[self.viewItem deleteAction]; style:UIAlertActionStyleDestructive
[self dismiss]; handler:^(UIAlertAction *action) {
}]]; OWSAssert([self.presentingViewController
isKindOfClass:[UINavigationController class]]);
UINavigationController *navController
= (UINavigationController *)self.presentingViewController;
if ([navController.topViewController
isKindOfClass:[ConversationViewController class]]) {
[self dismissSelfAnimated:YES
completion:^{
[self.viewItem deleteAction];
}];
} else if ([navController.topViewController
isKindOfClass:[MessageDetailViewController class]]) {
[self dismissSelfAnimated:NO
completion:^{
[self.viewItem deleteAction];
}];
[navController popViewControllerAnimated:YES];
} else {
OWSFail(@"Unexpected presentation context.");
[self dismissSelfAnimated:YES
completion:^{
[self.viewItem deleteAction];
}];
}
}]];
[actionSheet addAction:[OWSAlerts cancelAction]]; [actionSheet addAction:[OWSAlerts cancelAction]];
@ -576,6 +617,10 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)canPerformAction:(SEL)action withSender:(nullable id)sender - (BOOL)canPerformAction:(SEL)action withSender:(nullable id)sender
{ {
if (self.viewItem == nil) {
return NO;
}
if (action == self.viewItem.metadataActionSelector) { if (action == self.viewItem.metadataActionSelector) {
return NO; return NO;
} }
@ -584,21 +629,41 @@ NS_ASSUME_NONNULL_BEGIN
- (void)copyAction:(nullable id)sender - (void)copyAction:(nullable id)sender
{ {
if (!self.viewItem) {
OWSFail(@"copy should only be available when a viewItem is present");
return;
}
[self.viewItem copyAction]; [self.viewItem copyAction];
} }
- (void)shareAction:(nullable id)sender - (void)shareAction:(nullable id)sender
{ {
[self.viewItem shareAction]; if (!self.viewItem) {
OWSFail(@"share should only be available when a viewItem is present");
return;
}
[self didPressShare:sender];
} }
- (void)saveAction:(nullable id)sender - (void)saveAction:(nullable id)sender
{ {
if (!self.viewItem) {
OWSFail(@"save should only be available when a viewItem is present");
return;
}
[self.viewItem saveAction]; [self.viewItem saveAction];
} }
- (void)deleteAction:(nullable id)sender - (void)deleteAction:(nullable id)sender
{ {
if (!self.viewItem) {
OWSFail(@"delete should only be available when a viewItem is present");
return;
}
[self didPressDelete:sender]; [self didPressDelete:sender];
} }
@ -674,7 +739,7 @@ NS_ASSUME_NONNULL_BEGIN
}]; }];
} }
- (void)dismiss - (void)dismissSelfAnimated:(BOOL)isAnimated completion:(void (^_Nullable)(void))completion
{ {
self.view.userInteractionEnabled = NO; self.view.userInteractionEnabled = NO;
[UIApplication sharedApplication].statusBarHidden = NO; [UIApplication sharedApplication].statusBarHidden = NO;
@ -686,21 +751,26 @@ NS_ASSUME_NONNULL_BEGIN
// Move the image view pack to it's initial position, i.e. where // Move the image view pack to it's initial position, i.e. where
// it sits on the screen in the conversation view. // it sits on the screen in the conversation view.
[self applyInitialImageViewConstraints]; [self applyInitialImageViewConstraints];
[UIView animateWithDuration:0.2
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^(void) {
[self.imageView.superview layoutIfNeeded];
// In case user has hidden bars, which changes background to black.
self.view.backgroundColor = UIColor.whiteColor;
// fade out content and toolbars if (isAnimated) {
self.navigationController.view.alpha = 0.0; [UIView animateWithDuration:0.2
} delay:0.0
completion:^(BOOL finished) { options:UIViewAnimationOptionCurveEaseInOut
[self.presentingViewController dismissViewControllerAnimated:NO completion:nil]; animations:^(void) {
}]; [self.imageView.superview layoutIfNeeded];
// In case user has hidden bars, which changes background to black.
self.view.backgroundColor = UIColor.whiteColor;
// fade out content and toolbars
self.navigationController.view.alpha = 0.0;
}
completion:^(BOOL finished) {
[self.presentingViewController dismissViewControllerAnimated:NO completion:completion];
}];
} else {
[self.presentingViewController dismissViewControllerAnimated:NO completion:completion];
}
} }
#pragma mark - UIScrollViewDelegate #pragma mark - UIScrollViewDelegate
@ -773,7 +843,6 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssert(self.videoPlayer); OWSAssert(self.videoPlayer);
DDLogVerbose(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); DDLogVerbose(@"%@ %s", self.logTag, __PRETTY_FUNCTION__);
// [self dismissViewControllerAnimated:NO completion:nil];
self.areToolbarsHidden = NO; self.areToolbarsHidden = NO;
self.playVideoButton.hidden = NO; self.playVideoButton.hidden = NO;

@ -1,5 +1,5 @@
// //
// Copyright (c) 2017 Open Whisper Systems. All rights reserved. // Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// //
import Foundation import Foundation
@ -19,6 +19,11 @@ class MediaMessageView: UIView, OWSAudioAttachmentPlayerDelegate {
let mode: MediaMessageViewMode let mode: MediaMessageViewMode
// If this is for a persisted interaction, viewItem and attachmentStream will not be nil
// If this is for an attachmet draft, before sending, viewItem and attachmentStream will be nil
var viewItem: ConversationViewItem?
var attachmentStream: TSAttachmentStream?
let attachment: SignalAttachment let attachment: SignalAttachment
var videoPlayer: MPMoviePlayerController? var videoPlayer: MPMoviePlayerController?
@ -45,10 +50,16 @@ class MediaMessageView: UIView, OWSAudioAttachmentPlayerDelegate {
fatalError("\(#function) is unimplemented.") fatalError("\(#function) is unimplemented.")
} }
required init(attachment: SignalAttachment, mode: MediaMessageViewMode) { convenience init(attachment: SignalAttachment, mode: MediaMessageViewMode) {
self.init(attachment: attachment, mode: mode, viewItem: nil, attachmentStream: nil)
}
required init(attachment: SignalAttachment, mode: MediaMessageViewMode, viewItem: ConversationViewItem?, attachmentStream: TSAttachmentStream?) {
assert(!attachment.hasError) assert(!attachment.hasError)
self.mode = mode
self.attachment = attachment self.attachment = attachment
self.mode = mode
self.viewItem = viewItem
self.attachmentStream = attachmentStream
super.init(frame: CGRect.zero) super.init(frame: CGRect.zero)
createViews() createViews()
@ -401,13 +412,7 @@ class MediaMessageView: UIView, OWSAudioAttachmentPlayerDelegate {
guard let fromView = sender.view else { guard let fromView = sender.view else {
return return
} }
guard let fromViewController = fromViewController() else { showFullImageViewController(fromView: fromView)
return
}
let window = UIApplication.shared.keyWindow
let convertedRect = fromView.convert(fromView.bounds, to:window)
let viewController = FullImageViewController(attachment:attachment, from:convertedRect)
viewController.present(from:fromViewController)
} }
private func fromViewController() -> UIViewController? { private func fromViewController() -> UIViewController? {
@ -429,44 +434,28 @@ class MediaMessageView: UIView, OWSAudioAttachmentPlayerDelegate {
guard sender.state == .recognized else { guard sender.state == .recognized else {
return return
} }
guard let dataUrl = attachment.dataUrl else { guard let fromView = sender.view else {
return
}
guard let videoPlayer = MPMoviePlayerController(contentURL: dataUrl) else {
return return
} }
videoPlayer.prepareToPlay() showFullImageViewController(fromView: fromView)
NotificationCenter.default.addObserver(forName: .MPMoviePlayerWillExitFullscreen, object: nil, queue: nil) { [weak self] _ in
self?.moviePlayerWillExitFullscreen()
}
NotificationCenter.default.addObserver(forName: .MPMoviePlayerDidExitFullscreen, object: nil, queue: nil) { [weak self] _ in
self?.moviePlayerDidExitFullscreen()
}
videoPlayer.controlStyle = .default
videoPlayer.shouldAutoplay = true
self.addSubview(videoPlayer.view)
videoPlayer.view.frame = self.bounds
self.videoPlayer = videoPlayer
videoPlayer.view.autoPinToSuperviewEdges()
ViewControllerUtils.setAudioIgnoresHardwareMuteSwitch(true)
videoPlayer.setFullscreen(true, animated:false)
} }
private func moviePlayerWillExitFullscreen() { func showFullImageViewController(fromView: UIView) {
clearVideoPlayer() guard let fromViewController = fromViewController() else {
} return
}
let window = UIApplication.shared.keyWindow
let convertedRect = fromView.convert(fromView.bounds, to:window)
private func moviePlayerDidExitFullscreen() { let viewController: FullImageViewController = {
clearVideoPlayer() if let viewItem = self.viewItem, let attachmentStream = self.attachmentStream {
} return FullImageViewController(attachmentStream: attachmentStream, from: convertedRect, viewItem: viewItem)
} else {
// e.g. when MediaMessageView does not belong to a persisted interaction, e.g. approval view.
return FullImageViewController(attachment: attachment, from:convertedRect)
}
}()
private func clearVideoPlayer() { viewController.present(from:fromViewController)
videoPlayer?.stop()
videoPlayer?.view.removeFromSuperview()
videoPlayer = nil
ViewControllerUtils.setAudioIgnoresHardwareMuteSwitch(false)
} }
} }

@ -1,5 +1,5 @@
// //
// Copyright (c) 2017 Open Whisper Systems. All rights reserved. // Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// //
import Foundation import Foundation
@ -406,7 +406,7 @@ class MessageDetailViewController: OWSViewController, UIScrollViewDelegate {
} }
guard let attachment = TSAttachment.fetch(uniqueId: attachmentId) else { guard let attachment = TSAttachment.fetch(uniqueId: attachmentId) else {
owsFail("Missing attachment") Logger.warn("\(TAG) Missing attachment. Was it deleted?")
return rows return rows
} }
self.attachment = attachment self.attachment = attachment
@ -433,7 +433,7 @@ class MessageDetailViewController: OWSViewController, UIScrollViewDelegate {
let contentType = attachment.contentType let contentType = attachment.contentType
if let dataUTI = MIMETypeUtil.utiType(forMIMEType: contentType) { if let dataUTI = MIMETypeUtil.utiType(forMIMEType: contentType) {
let attachment = SignalAttachment(dataSource: dataSource, dataUTI: dataUTI) let attachment = SignalAttachment(dataSource: dataSource, dataUTI: dataUTI)
let mediaMessageView = MediaMessageView(attachment: attachment, mode: .small) let mediaMessageView = MediaMessageView(attachment: attachment, mode: .small, viewItem: viewItem, attachmentStream: attachmentStream)
mediaMessageView.backgroundColor = UIColor.white mediaMessageView.backgroundColor = UIColor.white
self.mediaMessageView = mediaMessageView self.mediaMessageView = mediaMessageView
rows.append(mediaMessageView) rows.append(mediaMessageView)

Loading…
Cancel
Save