1484 lines
60 KiB
Objective-C
1484 lines
60 KiB
Objective-C
/*
|
|
Copyright 2018-2024 New Vector Ltd.
|
|
Copyright 2017 Vector Creations Ltd
|
|
Copyright 2015 OpenMarket Ltd
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
Please see LICENSE in the repository root for full details.
|
|
*/
|
|
|
|
#import "MXKAttachmentsViewController.h"
|
|
|
|
#import <WebKit/WebKit.h>
|
|
|
|
@import MatrixSDK.MXMediaManager;
|
|
|
|
#import "MXKMediaCollectionViewCell.h"
|
|
|
|
#import "MXKPieChartView.h"
|
|
|
|
#import "MXKConstants.h"
|
|
|
|
#import "MXKTools.h"
|
|
|
|
#import "NSBundle+MatrixKit.h"
|
|
|
|
#import "MXKEventFormatter.h"
|
|
|
|
#import "MXKAttachmentInteractionController.h"
|
|
|
|
#import "MXKSwiftHeader.h"
|
|
|
|
#import "LegacyAppDelegate.h"
|
|
|
|
@interface MXKAttachmentsViewController () <UINavigationControllerDelegate, UIViewControllerTransitioningDelegate>
|
|
{
|
|
/**
|
|
Current alert (if any).
|
|
*/
|
|
UIAlertController *currentAlert;
|
|
|
|
/**
|
|
Navigation bar handling
|
|
*/
|
|
NSTimer *navigationBarDisplayTimer;
|
|
|
|
/**
|
|
SplitViewController handling
|
|
*/
|
|
BOOL shouldRestoreBottomBar;
|
|
UISplitViewControllerDisplayMode savedSplitViewControllerDisplayMode;
|
|
|
|
/**
|
|
Audio session handling
|
|
*/
|
|
NSString *savedAVAudioSessionCategory;
|
|
|
|
/**
|
|
The attachments array (MXAttachment instances).
|
|
*/
|
|
NSMutableArray *attachments;
|
|
|
|
/**
|
|
The index of the current visible collection item
|
|
*/
|
|
NSInteger currentVisibleItemIndex;
|
|
|
|
/**
|
|
The document interaction Controller used to share attachment
|
|
*/
|
|
UIDocumentInteractionController *documentInteractionController;
|
|
MXKAttachment *currentSharedAttachment;
|
|
|
|
/**
|
|
Tells whether back pagination is in progress.
|
|
*/
|
|
BOOL isBackPaginationInProgress;
|
|
|
|
/**
|
|
A temporary file used to store decrypted attachments
|
|
*/
|
|
NSString *tempFile;
|
|
|
|
/**
|
|
Path to a file containing video data for the currently selected
|
|
attachment, if it's a video attachment and the data is
|
|
available.
|
|
*/
|
|
NSString *videoFile;
|
|
}
|
|
|
|
//animations
|
|
@property (nonatomic) MXKAttachmentInteractionController *interactionController;
|
|
|
|
@property (nonatomic, weak) UIViewController <MXKSourceAttachmentAnimatorDelegate> *sourceViewController;
|
|
|
|
@property (nonatomic) UIImageView *originalImageView;
|
|
@property (nonatomic) CGRect convertedFrame;
|
|
|
|
@property (nonatomic) BOOL customAnimationsEnabled;
|
|
|
|
@property (nonatomic) BOOL isLoadingVideo;
|
|
|
|
@end
|
|
|
|
@implementation MXKAttachmentsViewController
|
|
@synthesize attachments;
|
|
|
|
#pragma mark - Class methods
|
|
|
|
+ (UINib *)nib
|
|
{
|
|
return [UINib nibWithNibName:NSStringFromClass([MXKAttachmentsViewController class])
|
|
bundle:[NSBundle bundleForClass:[MXKAttachmentsViewController class]]];
|
|
}
|
|
|
|
+ (instancetype)attachmentsViewController
|
|
{
|
|
return [[[self class] alloc] initWithNibName:NSStringFromClass([MXKAttachmentsViewController class])
|
|
bundle:[NSBundle bundleForClass:[MXKAttachmentsViewController class]]];
|
|
}
|
|
|
|
+ (instancetype)animatedAttachmentsViewControllerWithSourceViewController:(UIViewController <MXKSourceAttachmentAnimatorDelegate> *)sourceViewController
|
|
{
|
|
MXKAttachmentsViewController *attachmentsController = [[[self class] alloc] initWithNibName:NSStringFromClass([MXKAttachmentsViewController class])
|
|
bundle:[NSBundle bundleForClass:[MXKAttachmentsViewController class]]];
|
|
|
|
//create an interactionController for it to handle the gestue recognizer and control the interactions
|
|
attachmentsController.interactionController = [[MXKAttachmentInteractionController alloc] initWithDestinationViewController:attachmentsController sourceViewController:sourceViewController];
|
|
|
|
//we use the animationsEnabled property to enable/disable animations. Instances created not using this method should use the default animations
|
|
attachmentsController.customAnimationsEnabled = YES;
|
|
|
|
//this properties will be needed by animationControllers in order to perform the animations
|
|
attachmentsController.sourceViewController = sourceViewController;
|
|
|
|
//setting transitioningDelegate and navigationController.delegate so that the animations will work for present/dismiss as well as push/pop
|
|
attachmentsController.transitioningDelegate = attachmentsController;
|
|
sourceViewController.navigationController.delegate = attachmentsController;
|
|
|
|
|
|
return attachmentsController;
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (void)finalizeInit
|
|
{
|
|
[super finalizeInit];
|
|
|
|
tempFile = nil;
|
|
}
|
|
|
|
- (void)viewDidLoad
|
|
{
|
|
[super viewDidLoad];
|
|
|
|
// Check whether the view controller has been pushed via storyboard
|
|
if (!_attachmentsCollection)
|
|
{
|
|
// Instantiate view controller objects
|
|
[[[self class] nib] instantiateWithOwner:self options:nil];
|
|
}
|
|
|
|
self.backButton.image = [NSBundle mxk_imageFromMXKAssetsBundleWithName:@"back_icon"];
|
|
|
|
// Register collection view cell class
|
|
[self.attachmentsCollection registerClass:MXKMediaCollectionViewCell.class forCellWithReuseIdentifier:[MXKMediaCollectionViewCell defaultReuseIdentifier]];
|
|
|
|
// Hide collection to hide first scrolling into the attachments.
|
|
_attachmentsCollection.hidden = YES;
|
|
|
|
// Display collection cell in full screen
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated"
|
|
self.automaticallyAdjustsScrollViewInsets = NO;
|
|
#pragma clang diagnostic pop
|
|
}
|
|
|
|
- (BOOL)prefersStatusBarHidden
|
|
{
|
|
// Hide status bar.
|
|
// Caution: Enable [UIViewController prefersStatusBarHidden] use at application level
|
|
// by turning on UIViewControllerBasedStatusBarAppearance in Info.plist.
|
|
return YES;
|
|
}
|
|
|
|
- (void)viewWillAppear:(BOOL)animated
|
|
{
|
|
[super viewWillAppear:animated];
|
|
|
|
videoFile = nil;
|
|
|
|
savedAVAudioSessionCategory = [[AVAudioSession sharedInstance] category];
|
|
|
|
// Hide navigation bar by default.
|
|
[self hideNavigationBar];
|
|
|
|
// Hide status bar
|
|
// TODO: remove this [UIApplication statusBarHidden] use (deprecated since iOS 9).
|
|
// Note: setting statusBarHidden does nothing if your application is using the default UIViewController-based status bar system.
|
|
UIApplication *sharedApplication = [UIApplication performSelector:@selector(sharedApplication)];
|
|
if (sharedApplication)
|
|
{
|
|
sharedApplication.statusBarHidden = YES;
|
|
}
|
|
|
|
// Handle here the case of splitviewcontroller use on iOS 8 and later.
|
|
if (self.splitViewController && [self.splitViewController respondsToSelector:@selector(displayMode)])
|
|
{
|
|
if (self.hidesBottomBarWhenPushed)
|
|
{
|
|
// This screen should be displayed without tabbar, but hidesBottomBarWhenPushed flag has no effect in case of splitviewcontroller use.
|
|
// Trick: on iOS 8 and later the tabbar is hidden manually
|
|
shouldRestoreBottomBar = YES;
|
|
self.tabBarController.tabBar.hidden = YES;
|
|
}
|
|
|
|
// Hide the primary view controller to allow full screen display
|
|
savedSplitViewControllerDisplayMode = [self.splitViewController displayMode];
|
|
self.splitViewController.preferredDisplayMode = UISplitViewControllerDisplayModePrimaryHidden;
|
|
[self.splitViewController.view layoutIfNeeded];
|
|
}
|
|
|
|
[_attachmentsCollection reloadData];
|
|
}
|
|
|
|
- (void)viewDidAppear:(BOOL)animated
|
|
{
|
|
[super viewDidAppear:animated];
|
|
|
|
// Adjust content offset and make visible the attachmnet collections
|
|
[self refreshAttachmentCollectionContentOffset];
|
|
_attachmentsCollection.hidden = NO;
|
|
}
|
|
|
|
- (void)viewWillDisappear:(BOOL)animated
|
|
{
|
|
if (tempFile)
|
|
{
|
|
NSError *err;
|
|
[[NSFileManager defaultManager] removeItemAtPath:tempFile error:&err];
|
|
tempFile = nil;
|
|
}
|
|
|
|
if (currentAlert)
|
|
{
|
|
[currentAlert dismissViewControllerAnimated:NO completion:nil];
|
|
currentAlert = nil;
|
|
}
|
|
|
|
// Stop playing any video
|
|
for (MXKMediaCollectionViewCell *cell in self.attachmentsCollection.visibleCells)
|
|
{
|
|
[cell.moviePlayer.player pause];
|
|
cell.moviePlayer.player = nil;
|
|
}
|
|
|
|
// Restore audio category
|
|
if (savedAVAudioSessionCategory)
|
|
{
|
|
[[AVAudioSession sharedInstance] setCategory:savedAVAudioSessionCategory error:nil];
|
|
savedAVAudioSessionCategory = nil;
|
|
}
|
|
|
|
[navigationBarDisplayTimer invalidate];
|
|
navigationBarDisplayTimer = nil;
|
|
|
|
// Restore status bar
|
|
// TODO: remove this [UIApplication statusBarHidden] use (deprecated since iOS 9).
|
|
// Note: setting statusBarHidden does nothing if your application is using the default UIViewController-based status bar system.
|
|
UIApplication *sharedApplication = [UIApplication performSelector:@selector(sharedApplication)];
|
|
if (sharedApplication)
|
|
{
|
|
sharedApplication.statusBarHidden = NO;
|
|
}
|
|
|
|
if (shouldRestoreBottomBar)
|
|
{
|
|
self.tabBarController.tabBar.hidden = NO;
|
|
}
|
|
|
|
if (self.splitViewController && [self.splitViewController respondsToSelector:@selector(displayMode)])
|
|
{
|
|
self.splitViewController.preferredDisplayMode = savedSplitViewControllerDisplayMode;
|
|
[self.splitViewController.view layoutIfNeeded];
|
|
}
|
|
|
|
[super viewWillDisappear:animated];
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[self destroy];
|
|
}
|
|
|
|
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator
|
|
{
|
|
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
|
|
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(coordinator.transitionDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
|
|
// Cell width will be updated, force collection layout refresh to take into account the changes
|
|
[self->_attachmentsCollection.collectionViewLayout invalidateLayout];
|
|
|
|
// Refresh the current attachment display
|
|
[self refreshAttachmentCollectionContentOffset];
|
|
|
|
});
|
|
}
|
|
|
|
#pragma mark - Override MXKViewController
|
|
|
|
- (void)destroy
|
|
{
|
|
if (documentInteractionController)
|
|
{
|
|
[documentInteractionController dismissPreviewAnimated:NO];
|
|
[documentInteractionController dismissMenuAnimated:NO];
|
|
documentInteractionController = nil;
|
|
}
|
|
|
|
if (currentSharedAttachment)
|
|
{
|
|
[currentSharedAttachment onShareEnded];
|
|
currentSharedAttachment = nil;
|
|
}
|
|
|
|
if (self.sourceViewController)
|
|
{
|
|
self.sourceViewController.navigationController.delegate = nil;
|
|
self.sourceViewController = nil;
|
|
}
|
|
|
|
[super destroy];
|
|
}
|
|
|
|
#pragma mark - Public API
|
|
|
|
- (void)displayAttachments:(NSArray*)attachmentArray focusOn:(NSString*)eventId
|
|
{
|
|
if ([attachmentArray isEqualToArray:attachments] && eventId.length == 0)
|
|
{
|
|
// neither the attachments nor the focus changed, can be ignored
|
|
return;
|
|
}
|
|
NSString *currentAttachmentEventId = eventId;
|
|
NSString *currentAttachmentOriginalFileName = nil;
|
|
|
|
if (currentAttachmentEventId.length == 0 && attachments)
|
|
{
|
|
if (isBackPaginationInProgress && currentVisibleItemIndex == 0)
|
|
{
|
|
// Here the spinner were displayed, we update the viewer by displaying the first added attachment
|
|
// (the one just added before the first item of the current attachments array).
|
|
if (attachments.count)
|
|
{
|
|
// Retrieve the event id of the first item in the current attachments array
|
|
MXKAttachment *attachment = attachments[0];
|
|
NSString *firstAttachmentEventId = attachment.eventId;
|
|
NSString *firstAttachmentOriginalFileName = nil;
|
|
|
|
// The original file name is used when the attachment is a local echo.
|
|
// Indeed its event id may be replaced by the actual one in the new attachments array.
|
|
if ([firstAttachmentEventId hasPrefix:kMXEventLocalEventIdPrefix])
|
|
{
|
|
firstAttachmentOriginalFileName = attachment.originalFileName;
|
|
}
|
|
|
|
// Look for the attachment added before this attachment in new array.
|
|
for (attachment in attachmentArray)
|
|
{
|
|
if (firstAttachmentOriginalFileName && [attachment.originalFileName isEqualToString:firstAttachmentOriginalFileName])
|
|
{
|
|
break;
|
|
}
|
|
else if ([attachment.eventId isEqualToString:firstAttachmentEventId])
|
|
{
|
|
break;
|
|
}
|
|
currentAttachmentEventId = attachment.eventId;
|
|
}
|
|
}
|
|
}
|
|
else if (currentVisibleItemIndex != NSNotFound)
|
|
{
|
|
// Compute the attachment index
|
|
NSUInteger currentAttachmentIndex = (isBackPaginationInProgress ? currentVisibleItemIndex - 1 : currentVisibleItemIndex);
|
|
|
|
if (currentAttachmentIndex < attachments.count)
|
|
{
|
|
MXKAttachment *attachment = attachments[currentAttachmentIndex];
|
|
currentAttachmentEventId = attachment.eventId;
|
|
|
|
// The original file name is used when the attachment is a local echo.
|
|
// Indeed its event id may be replaced by the actual one in the new attachments array.
|
|
if ([currentAttachmentEventId hasPrefix:kMXEventLocalEventIdPrefix])
|
|
{
|
|
currentAttachmentOriginalFileName = attachment.originalFileName;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Stop back pagination (Do not call here 'stopBackPaginationActivity' because a full collection reload is planned at the end).
|
|
isBackPaginationInProgress = NO;
|
|
|
|
// Set/reset the attachments array
|
|
attachments = [NSMutableArray arrayWithArray:attachmentArray];
|
|
|
|
// Update the index of the current displayed attachment by looking for the
|
|
// current event id (or the current original file name, if any) in the new attachments array.
|
|
currentVisibleItemIndex = 0;
|
|
if (currentAttachmentEventId)
|
|
{
|
|
for (NSUInteger index = 0; index < attachments.count; index++)
|
|
{
|
|
MXKAttachment *attachment = attachments[index];
|
|
|
|
// Check first the original filename if any.
|
|
if (currentAttachmentOriginalFileName && [attachment.originalFileName isEqualToString:currentAttachmentOriginalFileName])
|
|
{
|
|
currentVisibleItemIndex = index;
|
|
break;
|
|
}
|
|
// Check the event id then
|
|
else if ([attachment.eventId isEqualToString:currentAttachmentEventId])
|
|
{
|
|
currentVisibleItemIndex = index;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Refresh
|
|
[_attachmentsCollection reloadData];
|
|
|
|
// Adjust content offset
|
|
[self refreshAttachmentCollectionContentOffset];
|
|
}
|
|
|
|
- (void)setComplete:(BOOL)complete
|
|
{
|
|
_complete = complete;
|
|
|
|
if (complete)
|
|
{
|
|
[self stopBackPaginationActivity];
|
|
}
|
|
}
|
|
|
|
- (IBAction)onButtonPressed:(id)sender
|
|
{
|
|
if (sender == self.backButton)
|
|
{
|
|
[self withdrawViewControllerAnimated:YES completion:nil];
|
|
}
|
|
}
|
|
|
|
#pragma mark - Privates
|
|
|
|
- (IBAction)hideNavigationBar
|
|
{
|
|
self.navigationBarContainer.hidden = YES;
|
|
|
|
[navigationBarDisplayTimer invalidate];
|
|
navigationBarDisplayTimer = nil;
|
|
}
|
|
|
|
- (void)refreshCurrentVisibleItemIndex
|
|
{
|
|
// Check whether the collection is actually rendered
|
|
if (_attachmentsCollection.contentSize.width)
|
|
{
|
|
// Get the window from the app delegate as this can be called before the view is presented.
|
|
UIWindow *window = LegacyAppDelegate.theDelegate.window;
|
|
currentVisibleItemIndex = _attachmentsCollection.contentOffset.x / window.bounds.size.width;
|
|
}
|
|
else
|
|
{
|
|
currentVisibleItemIndex = NSNotFound;
|
|
}
|
|
}
|
|
|
|
- (void)refreshAttachmentCollectionContentOffset
|
|
{
|
|
if (currentVisibleItemIndex != NSNotFound && _attachmentsCollection)
|
|
{
|
|
// Get the window from the app delegate as this can be called before the view is presented.
|
|
UIWindow *window = LegacyAppDelegate.theDelegate.window;
|
|
|
|
// Set the content offset to display the current attachment
|
|
CGPoint contentOffset = _attachmentsCollection.contentOffset;
|
|
contentOffset.x = currentVisibleItemIndex * window.bounds.size.width;
|
|
_attachmentsCollection.contentOffset = contentOffset;
|
|
}
|
|
}
|
|
|
|
- (void)refreshCurrentVisibleCell
|
|
{
|
|
// In case of attached image, load here the high res image.
|
|
|
|
[self refreshCurrentVisibleItemIndex];
|
|
|
|
if (currentVisibleItemIndex == NSNotFound) {
|
|
// Tell the delegate that no attachment is displayed for the moment
|
|
if ([self.delegate respondsToSelector:@selector(displayedNewAttachmentWithEventId:)])
|
|
{
|
|
[self.delegate displayedNewAttachmentWithEventId:nil];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NSInteger item = currentVisibleItemIndex;
|
|
if (isBackPaginationInProgress)
|
|
{
|
|
if (item == 0)
|
|
{
|
|
// Tell the delegate that no attachment is displayed for the moment
|
|
if ([self.delegate respondsToSelector:@selector(displayedNewAttachmentWithEventId:)])
|
|
{
|
|
[self.delegate displayedNewAttachmentWithEventId:nil];
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
item --;
|
|
}
|
|
|
|
if (item < attachments.count)
|
|
{
|
|
MXKAttachment *attachment = attachments[item];
|
|
NSString *mimeType = attachment.contentInfo[@"mimetype"];
|
|
|
|
// Tell the delegate which attachment has been shown using its eventId
|
|
if ([self.delegate respondsToSelector:@selector(displayedNewAttachmentWithEventId:)])
|
|
{
|
|
[self.delegate displayedNewAttachmentWithEventId:attachment.eventId];
|
|
}
|
|
|
|
// Check attachment type
|
|
if (attachment.type == MXKAttachmentTypeImage && attachment.contentURL && ![mimeType isEqualToString:@"image/gif"])
|
|
{
|
|
// Retrieve the related cell
|
|
UICollectionViewCell *cell = [_attachmentsCollection cellForItemAtIndexPath:[NSIndexPath indexPathForItem:currentVisibleItemIndex inSection:0]];
|
|
if ([cell isKindOfClass:[MXKMediaCollectionViewCell class]])
|
|
{
|
|
MXKMediaCollectionViewCell *mediaCollectionViewCell = (MXKMediaCollectionViewCell*)cell;
|
|
|
|
// Load high res image
|
|
mediaCollectionViewCell.mxkImageView.stretchable = YES;
|
|
mediaCollectionViewCell.mxkImageView.enableInMemoryCache = NO;
|
|
|
|
[mediaCollectionViewCell.mxkImageView setAttachment:attachment];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)stopBackPaginationActivity
|
|
{
|
|
if (isBackPaginationInProgress)
|
|
{
|
|
isBackPaginationInProgress = NO;
|
|
|
|
[self.attachmentsCollection deleteItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:0 inSection:0]]];
|
|
}
|
|
}
|
|
|
|
- (void)prepareVideoForItem:(NSInteger)item success:(void(^)(void))success failure:(void(^)(NSError *))failure
|
|
{
|
|
MXKAttachment *attachment = attachments[item];
|
|
if (attachment.isEncrypted)
|
|
{
|
|
[attachment decryptToTempFile:^(NSString *file) {
|
|
if (self->tempFile)
|
|
{
|
|
[[NSFileManager defaultManager] removeItemAtPath:self->tempFile error:nil];
|
|
}
|
|
self->tempFile = file;
|
|
self->videoFile = file;
|
|
success();
|
|
} failure:^(NSError *error) {
|
|
if (failure) failure(error);
|
|
}];
|
|
}
|
|
else
|
|
{
|
|
if ([[NSFileManager defaultManager] fileExistsAtPath:attachment.cacheFilePath])
|
|
{
|
|
videoFile = attachment.cacheFilePath;
|
|
success();
|
|
}
|
|
else
|
|
{
|
|
[attachment prepare:^{
|
|
self->videoFile = attachment.cacheFilePath;
|
|
success();
|
|
} failure:^(NSError *error) {
|
|
if (failure) failure(error);
|
|
}];
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma mark - UICollectionViewDataSource
|
|
|
|
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
|
|
{
|
|
if (isBackPaginationInProgress)
|
|
{
|
|
return (attachments.count + 1);
|
|
}
|
|
|
|
return attachments.count;
|
|
}
|
|
|
|
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
MXKMediaCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:[MXKMediaCollectionViewCell defaultReuseIdentifier]
|
|
forIndexPath:indexPath];
|
|
|
|
NSInteger item = indexPath.item;
|
|
|
|
if (isBackPaginationInProgress)
|
|
{
|
|
if (item == 0)
|
|
{
|
|
cell.mxkImageView.hidden = YES;
|
|
cell.customView.hidden = NO;
|
|
|
|
// Add back pagination spinner
|
|
UIActivityIndicatorView* spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
|
|
spinner.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin);
|
|
spinner.hidesWhenStopped = NO;
|
|
spinner.backgroundColor = [UIColor clearColor];
|
|
[spinner startAnimating];
|
|
|
|
spinner.center = cell.customView.center;
|
|
[cell.customView addSubview:spinner];
|
|
|
|
return cell;
|
|
}
|
|
|
|
item --;
|
|
}
|
|
|
|
if (item < attachments.count)
|
|
{
|
|
MXKAttachment *attachment = attachments[item];
|
|
NSString *mimeType = attachment.contentInfo[@"mimetype"];
|
|
|
|
// Use the cached thumbnail (if any) as preview
|
|
UIImage* preview = [attachment getCachedThumbnail];
|
|
|
|
// Check attachment type
|
|
if ((attachment.type == MXKAttachmentTypeImage || attachment.type == MXKAttachmentTypeSticker) && attachment.contentURL)
|
|
{
|
|
if ([mimeType isEqualToString:@"image/gif"])
|
|
{
|
|
cell.mxkImageView.hidden = YES;
|
|
// Set the preview as the default image even if the image view is hidden. It will be used during zoom out animation.
|
|
cell.mxkImageView.image = preview;
|
|
|
|
cell.customView.hidden = NO;
|
|
|
|
// Animated gif is displayed in webview
|
|
CGFloat minSize = (cell.frame.size.width < cell.frame.size.height) ? cell.frame.size.width : cell.frame.size.height;
|
|
CGFloat width, height;
|
|
if (attachment.contentInfo[@"w"] && attachment.contentInfo[@"h"])
|
|
{
|
|
width = [attachment.contentInfo[@"w"] integerValue];
|
|
height = [attachment.contentInfo[@"h"] integerValue];
|
|
if (width > minSize || height > minSize)
|
|
{
|
|
if (width > height)
|
|
{
|
|
height = (height * minSize) / width;
|
|
height = floorf(height / 2) * 2;
|
|
width = minSize;
|
|
}
|
|
else
|
|
{
|
|
width = (width * minSize) / height;
|
|
width = floorf(width / 2) * 2;
|
|
height = minSize;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
width = minSize;
|
|
height = minSize;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
width = minSize;
|
|
height = minSize;
|
|
}
|
|
|
|
WKWebView *animatedGifViewer = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, width, height)];
|
|
animatedGifViewer.center = cell.customView.center;
|
|
animatedGifViewer.opaque = NO;
|
|
animatedGifViewer.backgroundColor = cell.customView.backgroundColor;
|
|
animatedGifViewer.contentMode = UIViewContentModeScaleAspectFit;
|
|
animatedGifViewer.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin);
|
|
animatedGifViewer.userInteractionEnabled = NO;
|
|
[cell.customView addSubview:animatedGifViewer];
|
|
|
|
UIImageView *previewImage = [[UIImageView alloc] initWithFrame:animatedGifViewer.frame];
|
|
previewImage.contentMode = animatedGifViewer.contentMode;
|
|
previewImage.autoresizingMask = animatedGifViewer.autoresizingMask;
|
|
previewImage.image = preview;
|
|
previewImage.center = cell.customView.center;
|
|
[cell.customView addSubview:previewImage];
|
|
|
|
MXKPieChartView *pieChartView = [[MXKPieChartView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
|
|
pieChartView.progress = 0;
|
|
pieChartView.progressColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.25];
|
|
pieChartView.unprogressColor = [UIColor clearColor];
|
|
pieChartView.autoresizingMask = animatedGifViewer.autoresizingMask;
|
|
pieChartView.center = cell.customView.center;
|
|
[cell.customView addSubview:pieChartView];
|
|
|
|
// Add download progress observer
|
|
NSString *downloadId = attachment.downloadId;
|
|
cell.notificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXMediaLoaderStateDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
|
|
|
|
MXMediaLoader *loader = (MXMediaLoader*)notif.object;
|
|
if ([loader.downloadId isEqualToString:downloadId])
|
|
{
|
|
// update the image
|
|
switch (loader.state) {
|
|
case MXMediaLoaderStateDownloadInProgress:
|
|
{
|
|
NSNumber* progressNumber = [loader.statisticsDict valueForKey:kMXMediaLoaderProgressValueKey];
|
|
if (progressNumber)
|
|
{
|
|
pieChartView.progress = progressNumber.floatValue;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
}];
|
|
|
|
void (^onDownloaded)(NSData *) = ^(NSData *data){
|
|
if (cell.notificationObserver)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:cell.notificationObserver];
|
|
cell.notificationObserver = nil;
|
|
}
|
|
|
|
if (animatedGifViewer.superview)
|
|
{
|
|
[animatedGifViewer loadData:data MIMEType:@"image/gif" characterEncodingName:@"UTF-8" baseURL:[NSURL URLWithString:@"http://"]];
|
|
|
|
[pieChartView removeFromSuperview];
|
|
[previewImage removeFromSuperview];
|
|
}
|
|
};
|
|
|
|
void (^onFailure)(NSError *) = ^(NSError *error){
|
|
if (cell.notificationObserver)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:cell.notificationObserver];
|
|
cell.notificationObserver = nil;
|
|
}
|
|
|
|
MXLogDebug(@"[MXKAttachmentsVC] gif download failed");
|
|
// Notify MatrixKit user
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error];
|
|
};
|
|
|
|
|
|
[attachment getAttachmentData:^(NSData *data) {
|
|
onDownloaded(data);
|
|
} failure:^(NSError *error) {
|
|
onFailure(error);
|
|
}];
|
|
}
|
|
else if (indexPath.item == currentVisibleItemIndex)
|
|
{
|
|
// Load high res image
|
|
cell.mxkImageView.stretchable = YES;
|
|
[cell.mxkImageView setAttachment:attachment];
|
|
}
|
|
else
|
|
{
|
|
// Use the thumbnail here - Full res images should only be downloaded explicitly when requested (see [self refreshCurrentVisibleItemIndex])
|
|
cell.mxkImageView.stretchable = YES;
|
|
[cell.mxkImageView setAttachmentThumb:attachment];
|
|
}
|
|
}
|
|
else if (attachment.type == MXKAttachmentTypeVideo && attachment.contentURL)
|
|
{
|
|
cell.mxkImageView.mediaFolder = attachment.eventRoomId;
|
|
cell.mxkImageView.stretchable = NO;
|
|
cell.mxkImageView.enableInMemoryCache = YES;
|
|
// Display video thumbnail, the video is played only when user selects this cell
|
|
[cell.mxkImageView setAttachmentThumb:attachment];
|
|
|
|
cell.centerIcon.image = [NSBundle mxk_imageFromMXKAssetsBundleWithName:@"play"];
|
|
cell.centerIcon.hidden = NO;
|
|
}
|
|
|
|
// Add gesture recognizers on collection cell to handle tap and long press on collection cell.
|
|
// Note: tap gesture recognizer is required here because mxkImageView enables user interaction to allow image stretching.
|
|
// [collectionView:didSelectItemAtIndexPath] is not triggered when mxkImageView is displayed.
|
|
UITapGestureRecognizer *cellTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onCollectionViewCellTap:)];
|
|
[cellTapGesture setNumberOfTouchesRequired:1];
|
|
[cellTapGesture setNumberOfTapsRequired:1];
|
|
cell.tag = item;
|
|
[cell addGestureRecognizer:cellTapGesture];
|
|
|
|
UILongPressGestureRecognizer *cellLongPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onCollectionViewCellLongPress:)];
|
|
[cell addGestureRecognizer:cellLongPressGesture];
|
|
}
|
|
|
|
return cell;
|
|
}
|
|
|
|
#pragma mark - UICollectionViewDelegate
|
|
|
|
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
NSInteger item = indexPath.item;
|
|
|
|
BOOL navigationBarDisplayHandled = NO;
|
|
|
|
if (isBackPaginationInProgress)
|
|
{
|
|
if (item == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
item --;
|
|
}
|
|
|
|
// Check whether the selected attachment is a video
|
|
if (item < attachments.count)
|
|
{
|
|
MXKAttachment *attachment = attachments[item];
|
|
|
|
if (attachment.type == MXKAttachmentTypeVideo && attachment.contentURL)
|
|
{
|
|
MXKMediaCollectionViewCell *selectedCell = (MXKMediaCollectionViewCell*)[collectionView cellForItemAtIndexPath:indexPath];
|
|
|
|
// Add movie player if none
|
|
if (selectedCell.moviePlayer == nil)
|
|
{
|
|
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
|
|
|
|
selectedCell.moviePlayer = [[AVPlayerViewController alloc] init];
|
|
if (selectedCell.moviePlayer != nil)
|
|
{
|
|
// Switch in custom view
|
|
selectedCell.mxkImageView.hidden = YES;
|
|
selectedCell.customView.hidden = NO;
|
|
|
|
// Report the video preview
|
|
UIImageView *previewImage = [[UIImageView alloc] initWithFrame:selectedCell.customView.frame];
|
|
previewImage.contentMode = UIViewContentModeScaleAspectFit;
|
|
previewImage.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin);
|
|
previewImage.image = selectedCell.mxkImageView.image;
|
|
previewImage.center = selectedCell.customView.center;
|
|
[selectedCell.customView addSubview:previewImage];
|
|
|
|
selectedCell.moviePlayer.videoGravity = AVLayerVideoGravityResizeAspect;
|
|
selectedCell.moviePlayer.view.frame = selectedCell.customView.frame;
|
|
selectedCell.moviePlayer.view.center = selectedCell.customView.center;
|
|
selectedCell.moviePlayer.view.hidden = YES;
|
|
[selectedCell.customView addSubview:selectedCell.moviePlayer.view];
|
|
|
|
// Force the video to stay in fullscreen
|
|
NSLayoutConstraint* topConstraint = [NSLayoutConstraint constraintWithItem:selectedCell.moviePlayer.view
|
|
attribute:NSLayoutAttributeTop
|
|
relatedBy:NSLayoutRelationEqual
|
|
toItem:selectedCell.customView
|
|
attribute:NSLayoutAttributeTop
|
|
multiplier:1.0f
|
|
constant:0.0f];
|
|
|
|
NSLayoutConstraint *leadingConstraint = [NSLayoutConstraint constraintWithItem:selectedCell.moviePlayer.view
|
|
attribute:NSLayoutAttributeLeading
|
|
relatedBy:0
|
|
toItem:selectedCell.customView
|
|
attribute:NSLayoutAttributeLeading
|
|
multiplier:1.0
|
|
constant:0];
|
|
|
|
NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:selectedCell.moviePlayer.view
|
|
attribute:NSLayoutAttributeBottom
|
|
relatedBy:0
|
|
toItem:selectedCell.customView
|
|
attribute:NSLayoutAttributeBottom
|
|
multiplier:1
|
|
constant:0];
|
|
|
|
NSLayoutConstraint *trailingConstraint = [NSLayoutConstraint constraintWithItem:selectedCell.moviePlayer.view
|
|
attribute:NSLayoutAttributeTrailing
|
|
relatedBy:0
|
|
toItem:selectedCell.customView
|
|
attribute:NSLayoutAttributeTrailing
|
|
multiplier:1.0
|
|
constant:0];
|
|
|
|
selectedCell.moviePlayer.view.translatesAutoresizingMaskIntoConstraints = NO;
|
|
|
|
[NSLayoutConstraint activateConstraints:@[topConstraint, leadingConstraint, bottomConstraint, trailingConstraint]];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(moviePlayerPlaybackDidFinishWithErrorNotification:)
|
|
name:AVPlayerItemFailedToPlayToEndTimeNotification
|
|
object:nil];
|
|
}
|
|
}
|
|
|
|
if (selectedCell.moviePlayer)
|
|
{
|
|
if (selectedCell.moviePlayer.player.status == AVPlayerStatusReadyToPlay)
|
|
{
|
|
// Show or hide the navigation bar
|
|
|
|
// The video controls bar display is automatically managed by MPMoviePlayerController.
|
|
// We have no control on it and no notifications about its displays changes.
|
|
// The following code synchronizes the display of the navigation bar with the
|
|
// MPMoviePlayerController controls bar.
|
|
|
|
// Check the MPMoviePlayerController controls bar display status by an hacky way
|
|
BOOL controlsVisible = NO;
|
|
for(id views in [[selectedCell.moviePlayer view] subviews])
|
|
{
|
|
for(id subViews in [views subviews])
|
|
{
|
|
for (id controlView in [subViews subviews])
|
|
{
|
|
if ([controlView isKindOfClass:[UIView class]] && ((UIView*)controlView).tag == 1004)
|
|
{
|
|
UIView *subView = (UIView*)controlView;
|
|
|
|
controlsVisible = (subView.alpha <= 0.0) ? NO : YES;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply the same display to the navigation bar
|
|
self.navigationBarContainer.hidden = !controlsVisible;
|
|
|
|
navigationBarDisplayHandled = YES;
|
|
if (!self.navigationBarContainer.hidden)
|
|
{
|
|
// Automaticaly hide the nav bar after 5s. This is the same timer value that
|
|
// MPMoviePlayerController uses for its controls bar
|
|
[navigationBarDisplayTimer invalidate];
|
|
navigationBarDisplayTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(hideNavigationBar) userInfo:self repeats:NO];
|
|
}
|
|
}
|
|
else if (!self.isLoadingVideo)
|
|
{
|
|
self.isLoadingVideo = YES;
|
|
|
|
MXKPieChartView *pieChartView = [[MXKPieChartView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
|
|
pieChartView.progress = 0;
|
|
pieChartView.progressColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.25];
|
|
pieChartView.unprogressColor = [UIColor clearColor];
|
|
pieChartView.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin);
|
|
pieChartView.center = selectedCell.customView.center;
|
|
[selectedCell.customView addSubview:pieChartView];
|
|
|
|
// Add download progress observer
|
|
NSString *downloadId = attachment.downloadId;
|
|
selectedCell.notificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXMediaLoaderStateDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
|
|
|
|
MXMediaLoader *loader = (MXMediaLoader*)notif.object;
|
|
if ([loader.downloadId isEqualToString:downloadId])
|
|
{
|
|
// update progress
|
|
switch (loader.state) {
|
|
case MXMediaLoaderStateDownloadInProgress:
|
|
{
|
|
NSNumber* progressNumber = [loader.statisticsDict valueForKey:kMXMediaLoaderProgressValueKey];
|
|
if (progressNumber)
|
|
{
|
|
pieChartView.progress = progressNumber.floatValue;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
}];
|
|
|
|
[self prepareVideoForItem:item success:^{
|
|
|
|
if (selectedCell.notificationObserver)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:selectedCell.notificationObserver];
|
|
selectedCell.notificationObserver = nil;
|
|
}
|
|
|
|
if (selectedCell.moviePlayer.view.superview)
|
|
{
|
|
selectedCell.moviePlayer.view.hidden = NO;
|
|
selectedCell.centerIcon.hidden = YES;
|
|
selectedCell.moviePlayer.player = [AVPlayer playerWithURL:[NSURL fileURLWithPath:self->videoFile]];
|
|
[selectedCell.moviePlayer.player play];
|
|
|
|
[pieChartView removeFromSuperview];
|
|
self.isLoadingVideo = NO;
|
|
|
|
[self hideNavigationBar];
|
|
}
|
|
|
|
} failure:^(NSError *error) {
|
|
|
|
if (selectedCell.notificationObserver)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:selectedCell.notificationObserver];
|
|
selectedCell.notificationObserver = nil;
|
|
}
|
|
|
|
MXLogDebug(@"[MXKAttachmentsVC] video download failed");
|
|
|
|
[pieChartView removeFromSuperview];
|
|
self.isLoadingVideo = NO;
|
|
|
|
// Display the navigation bar so that the user can leave this screen
|
|
self.navigationBarContainer.hidden = NO;
|
|
|
|
// Notify MatrixKit user
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error];
|
|
|
|
}];
|
|
|
|
// Do not animate the navigation bar on video playback preparing
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Animate navigation bar if it is has not been handled
|
|
if (!navigationBarDisplayHandled)
|
|
{
|
|
if (self.navigationBarContainer.hidden)
|
|
{
|
|
self.navigationBarContainer.hidden = NO;
|
|
[navigationBarDisplayTimer invalidate];
|
|
navigationBarDisplayTimer = [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(hideNavigationBar) userInfo:self repeats:NO];
|
|
}
|
|
else
|
|
{
|
|
[self hideNavigationBar];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
// Here the cell is not displayed anymore, but it may be displayed again if the user swipes on it.
|
|
if ([cell isKindOfClass:[MXKMediaCollectionViewCell class]])
|
|
{
|
|
MXKMediaCollectionViewCell *mediaCollectionViewCell = (MXKMediaCollectionViewCell*)cell;
|
|
|
|
// Check whether a video was playing in this cell.
|
|
if (mediaCollectionViewCell.moviePlayer)
|
|
{
|
|
// This cell concerns an attached video.
|
|
// We stop the player, and restore the default display based on the video thumbnail
|
|
[mediaCollectionViewCell.moviePlayer.player pause];
|
|
mediaCollectionViewCell.moviePlayer.player = nil;
|
|
mediaCollectionViewCell.moviePlayer = nil;
|
|
|
|
mediaCollectionViewCell.mxkImageView.hidden = NO;
|
|
mediaCollectionViewCell.centerIcon.hidden = NO;
|
|
mediaCollectionViewCell.customView.hidden = YES;
|
|
|
|
// Remove potential media download observer
|
|
if (mediaCollectionViewCell.notificationObserver)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:mediaCollectionViewCell.notificationObserver];
|
|
mediaCollectionViewCell.notificationObserver = nil;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
|
|
{
|
|
// Detect horizontal bounce at the beginning of the collection to trigger pagination
|
|
if (scrollView == self.attachmentsCollection && !isBackPaginationInProgress && !self.complete && self.delegate)
|
|
{
|
|
if (scrollView.contentOffset.x < -30)
|
|
{
|
|
isBackPaginationInProgress = YES;
|
|
[self.attachmentsCollection insertItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:0 inSection:0]]];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
|
|
{
|
|
if (scrollView == self.attachmentsCollection)
|
|
{
|
|
if (isBackPaginationInProgress)
|
|
{
|
|
MXKAttachment *attachment = self.attachments.firstObject;
|
|
self.complete = ![self.delegate attachmentsViewController:self paginateAttachmentBefore:attachment.eventId];
|
|
}
|
|
else
|
|
{
|
|
[self refreshCurrentVisibleCell];
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma mark - UICollectionViewDelegateFlowLayout
|
|
|
|
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
// Use the window from the app delegate as this can be called before the view is presented.
|
|
return LegacyAppDelegate.theDelegate.window.bounds.size;
|
|
}
|
|
|
|
#pragma mark - Movie Player
|
|
|
|
- (void)moviePlayerPlaybackDidFinishWithErrorNotification:(NSNotification *)notification
|
|
{
|
|
NSDictionary *notificationUserInfo = [notification userInfo];
|
|
|
|
NSError *mediaPlayerError = [notificationUserInfo objectForKey:AVPlayerItemFailedToPlayToEndTimeErrorKey];
|
|
if (mediaPlayerError)
|
|
{
|
|
MXLogDebug(@"[MXKAttachmentsVC] Playback failed with error description: %@", [mediaPlayerError localizedDescription]);
|
|
|
|
// Display the navigation bar so that the user can leave this screen
|
|
self.navigationBarContainer.hidden = NO;
|
|
|
|
// Notify MatrixKit user
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:mediaPlayerError];
|
|
}
|
|
}
|
|
|
|
#pragma mark - Gesture recognizer
|
|
|
|
- (void)onCollectionViewCellTap:(UIGestureRecognizer*)gestureRecognizer
|
|
{
|
|
MXKMediaCollectionViewCell *selectedCell;
|
|
|
|
UIView *view = gestureRecognizer.view;
|
|
if ([view isKindOfClass:[MXKMediaCollectionViewCell class]])
|
|
{
|
|
selectedCell = (MXKMediaCollectionViewCell*)view;
|
|
}
|
|
|
|
// Notify the collection view delegate a cell has been selected.
|
|
if (selectedCell && selectedCell.tag < attachments.count)
|
|
{
|
|
[self collectionView:self.attachmentsCollection didSelectItemAtIndexPath:[NSIndexPath indexPathForItem:(isBackPaginationInProgress ? selectedCell.tag + 1: selectedCell.tag) inSection:0]];
|
|
}
|
|
}
|
|
|
|
- (void)onCollectionViewCellLongPress:(UIGestureRecognizer*)gestureRecognizer
|
|
{
|
|
MXKMediaCollectionViewCell *selectedCell;
|
|
|
|
if (gestureRecognizer.state == UIGestureRecognizerStateBegan)
|
|
{
|
|
UIView *view = gestureRecognizer.view;
|
|
if ([view isKindOfClass:[MXKMediaCollectionViewCell class]])
|
|
{
|
|
selectedCell = (MXKMediaCollectionViewCell*)view;
|
|
}
|
|
}
|
|
|
|
// Notify the collection view delegate a cell has been selected.
|
|
if (selectedCell && selectedCell.tag < attachments.count)
|
|
{
|
|
MXKAttachment *attachment = attachments[selectedCell.tag];
|
|
|
|
if (currentAlert)
|
|
{
|
|
[currentAlert dismissViewControllerAnimated:NO completion:nil];
|
|
}
|
|
|
|
__weak __typeof(self) weakSelf = self;
|
|
|
|
currentAlert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
|
|
|
|
if ([MXKAppSettings standardAppSettings].messageDetailsAllowSaving)
|
|
{
|
|
[currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n save]
|
|
style:UIAlertActionStyleDefault
|
|
handler:^(UIAlertAction * action) {
|
|
|
|
typeof(self) self = weakSelf;
|
|
self->currentAlert = nil;
|
|
|
|
[self startActivityIndicator];
|
|
|
|
[attachment save:^{
|
|
|
|
typeof(self) self = weakSelf;
|
|
[self stopActivityIndicator];
|
|
|
|
} failure:^(NSError *error) {
|
|
|
|
typeof(self) self = weakSelf;
|
|
[self stopActivityIndicator];
|
|
|
|
// Notify MatrixKit user
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error];
|
|
|
|
}];
|
|
|
|
}]];
|
|
}
|
|
|
|
if ([MXKAppSettings standardAppSettings].messageDetailsAllowCopyingMedia)
|
|
{
|
|
[currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n copyButtonName]
|
|
style:UIAlertActionStyleDefault
|
|
handler:^(UIAlertAction * action) {
|
|
|
|
typeof(self) self = weakSelf;
|
|
self->currentAlert = nil;
|
|
|
|
[self startActivityIndicator];
|
|
|
|
[attachment copy:^{
|
|
|
|
typeof(self) self = weakSelf;
|
|
[self stopActivityIndicator];
|
|
|
|
} failure:^(NSError *error) {
|
|
|
|
typeof(self) self = weakSelf;
|
|
[self stopActivityIndicator];
|
|
|
|
// Notify MatrixKit user
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error];
|
|
|
|
}];
|
|
|
|
}]];
|
|
}
|
|
|
|
if ([MXKAppSettings standardAppSettings].messageDetailsAllowSharing)
|
|
{
|
|
[currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n share]
|
|
style:UIAlertActionStyleDefault
|
|
handler:^(UIAlertAction * action) {
|
|
|
|
MXWeakify(self);
|
|
|
|
self->currentAlert = nil;
|
|
|
|
[self startActivityIndicator];
|
|
|
|
[attachment prepareShare:^(NSURL *fileURL) {
|
|
|
|
MXStrongifyAndReturnIfNil(self);
|
|
|
|
[self stopActivityIndicator];
|
|
|
|
self->documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:fileURL];
|
|
[self->documentInteractionController setDelegate:self];
|
|
self->currentSharedAttachment = attachment;
|
|
|
|
if (![self->documentInteractionController presentOptionsMenuFromRect:self.view.frame inView:self.view animated:YES])
|
|
{
|
|
self->documentInteractionController = nil;
|
|
[attachment onShareEnded];
|
|
self->currentSharedAttachment = nil;
|
|
}
|
|
|
|
} failure:^(NSError *error) {
|
|
|
|
MXStrongifyAndReturnIfNil(self);
|
|
|
|
[self stopActivityIndicator];
|
|
|
|
// Notify MatrixKit user
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error];
|
|
|
|
}];
|
|
|
|
}]];
|
|
}
|
|
|
|
if ([MXMediaManager existingDownloaderWithIdentifier:attachment.downloadId])
|
|
{
|
|
[currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancelDownload]
|
|
style:UIAlertActionStyleDefault
|
|
handler:^(UIAlertAction * action) {
|
|
|
|
typeof(self) self = weakSelf;
|
|
self->currentAlert = nil;
|
|
|
|
// Get again the loader
|
|
MXMediaLoader *loader = [MXMediaManager existingDownloaderWithIdentifier:attachment.downloadId];
|
|
if (loader)
|
|
{
|
|
[loader cancel];
|
|
}
|
|
|
|
}]];
|
|
}
|
|
|
|
if (currentAlert.actions.count)
|
|
{
|
|
[currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel]
|
|
style:UIAlertActionStyleCancel
|
|
handler:^(UIAlertAction * action) {
|
|
|
|
typeof(self) self = weakSelf;
|
|
self->currentAlert = nil;
|
|
|
|
}]];
|
|
|
|
[currentAlert popoverPresentationController].sourceView = _attachmentsCollection;
|
|
[currentAlert popoverPresentationController].sourceRect = _attachmentsCollection.bounds;
|
|
[self presentViewController:currentAlert animated:YES completion:nil];
|
|
}
|
|
else
|
|
{
|
|
currentAlert = nil;
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma mark - UIDocumentInteractionControllerDelegate
|
|
|
|
- (UIViewController *)documentInteractionControllerViewControllerForPreview: (UIDocumentInteractionController *) controller
|
|
{
|
|
return self;
|
|
}
|
|
|
|
// Preview presented/dismissed on document. Use to set up any HI underneath.
|
|
- (void)documentInteractionControllerWillBeginPreview:(UIDocumentInteractionController *)controller
|
|
{
|
|
documentInteractionController = controller;
|
|
}
|
|
|
|
- (void)documentInteractionControllerDidEndPreview:(UIDocumentInteractionController *)controller
|
|
{
|
|
documentInteractionController = nil;
|
|
if (currentSharedAttachment)
|
|
{
|
|
[currentSharedAttachment onShareEnded];
|
|
currentSharedAttachment = nil;
|
|
}
|
|
}
|
|
|
|
- (void)documentInteractionControllerDidDismissOptionsMenu:(UIDocumentInteractionController *)controller
|
|
{
|
|
documentInteractionController = nil;
|
|
if (currentSharedAttachment)
|
|
{
|
|
[currentSharedAttachment onShareEnded];
|
|
currentSharedAttachment = nil;
|
|
}
|
|
}
|
|
|
|
- (void)documentInteractionControllerDidDismissOpenInMenu:(UIDocumentInteractionController *)controller
|
|
{
|
|
documentInteractionController = nil;
|
|
if (currentSharedAttachment)
|
|
{
|
|
[currentSharedAttachment onShareEnded];
|
|
currentSharedAttachment = nil;
|
|
}
|
|
}
|
|
|
|
#pragma mark - MXKDestinationAttachmentAnimatorDelegate
|
|
|
|
- (BOOL)prepareSubviewsForTransition:(BOOL)isStartInteraction
|
|
{
|
|
// Sanity check
|
|
if (currentVisibleItemIndex >= attachments.count)
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
MXKAttachment *attachment = attachments[currentVisibleItemIndex];
|
|
NSString *mimeType = attachment.contentInfo[@"mimetype"];
|
|
|
|
// Check attachment type for GIFs - this is required because of the extra WKWebView
|
|
if (attachment.type == MXKAttachmentTypeImage && attachment.contentURL && [mimeType isEqualToString:@"image/gif"])
|
|
{
|
|
MXKMediaCollectionViewCell *cell = (MXKMediaCollectionViewCell *)[self.attachmentsCollection.visibleCells firstObject];
|
|
UIView *customView = cell.customView;
|
|
for (UIView *v in customView.subviews)
|
|
{
|
|
if ([v isKindOfClass:[WKWebView class]])
|
|
{
|
|
v.hidden = isStartInteraction;
|
|
return YES;
|
|
}
|
|
}
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (UIImageView *)finalImageView
|
|
{
|
|
MXKMediaCollectionViewCell *cell = (MXKMediaCollectionViewCell *)[self.attachmentsCollection.visibleCells firstObject];
|
|
return cell.mxkImageView.imageView;
|
|
}
|
|
|
|
#pragma mark - UIViewControllerTransitioningDelegate
|
|
|
|
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
|
|
{
|
|
if (self.customAnimationsEnabled)
|
|
{
|
|
return [[MXKAttachmentAnimator alloc] initWithAnimationType:PhotoBrowserZoomInAnimation sourceViewController:self.sourceViewController];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
|
|
{
|
|
[self hideNavigationBar];
|
|
|
|
if (self.customAnimationsEnabled)
|
|
{
|
|
return [[MXKAttachmentAnimator alloc] initWithAnimationType:PhotoBrowserZoomOutAnimation sourceViewController:self.sourceViewController];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator
|
|
{
|
|
//if there is an interaction, use the custom interaction controller to handle it
|
|
if (self.interactionController.interactionInProgress)
|
|
{
|
|
return self.interactionController;
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
#pragma mark - UINavigationControllerDelegate
|
|
|
|
- (id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController {
|
|
if (self.customAnimationsEnabled && self.interactionController.interactionInProgress)
|
|
{
|
|
return self.interactionController;
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation
|
|
fromViewController:(UIViewController *)fromVC
|
|
toViewController:(UIViewController *)toVC
|
|
{
|
|
|
|
if (self.customAnimationsEnabled)
|
|
{
|
|
if (operation == UINavigationControllerOperationPush)
|
|
{
|
|
return [[MXKAttachmentAnimator alloc] initWithAnimationType:PhotoBrowserZoomInAnimation sourceViewController:self.sourceViewController];
|
|
}
|
|
if (operation == UINavigationControllerOperationPop)
|
|
{
|
|
return [[MXKAttachmentAnimator alloc] initWithAnimationType:PhotoBrowserZoomOutAnimation sourceViewController:self.sourceViewController];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
@end
|