/** * @copyright Copyright (c) 2020 Ivan Sein * * @author Ivan Sein * * @license GNU GPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #import "ShareConfirmationViewController.h" #import "ShareConfirmationCollectionViewCell.h" @import NextcloudKit; #import #import #import #import #import "CCCertificate.h" #import "NCAPIController.h" #import "NCAppBranding.h" #import "NCIntentController.h" #import "NCKeyChainController.h" #import "NCNavigationController.h" #import "NCUserDefaults.h" #import "NCUtils.h" #import "MBProgressHUD.h" @interface ShareConfirmationViewController () { UIBarButtonItem *_sendButton; UIActivityIndicatorView *_sharingIndicatorView; MBProgressHUD *_hud; dispatch_group_t _uploadGroup; BOOL _uploadFailed; NSMutableArray *_uploadErrors; NSDictionary *_objectShareRichObject; } @property (nonatomic, strong) UIImagePickerController *imagePicker; @end @implementation ShareConfirmationViewController - (id)initWithRoom:(NCRoom *)room account:(TalkAccount *)account serverCapabilities:(ServerCapabilities *)serverCapabilities { self = [super init]; if (self) { self.room = room; self.account = account; self.serverCapabilities = serverCapabilities; self.type = ShareConfirmationTypeItem; } return self; } - (void)viewDidLoad { [super viewDidLoad]; self.navigationController.navigationBar.translucent = NO; self.navigationController.navigationBar.tintColor = [NCAppBranding themeTextColor]; self.navigationController.navigationBar.barTintColor = [NCAppBranding themeColor]; self.tabBarController.tabBar.tintColor = [NCAppBranding themeColor]; UIColor *themeColor = [NCAppBranding themeColor]; UINavigationBarAppearance *appearance = [[UINavigationBarAppearance alloc] init]; [appearance configureWithOpaqueBackground]; appearance.backgroundColor = themeColor; appearance.titleTextAttributes = @{NSForegroundColorAttributeName:[NCAppBranding themeTextColor]}; self.navigationItem.standardAppearance = appearance; self.navigationItem.compactAppearance = appearance; self.navigationItem.scrollEdgeAppearance = appearance; self.pageControl.currentPageIndicatorTintColor = [NCAppBranding elementColor]; self.pageControl.pageIndicatorTintColor = [NCAppBranding placeholderColor]; self.pageControl.hidesForSinglePage = YES; self.pageControl.numberOfPages = 1; if (_isModal) { UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancelButtonPressed)]; cancelButton.accessibilityHint = NSLocalizedString(@"Double tap to dismiss sharing options", nil); self.navigationController.navigationBar.topItem.leftBarButtonItem = cancelButton; } _sendButton = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Send", nil) style:UIBarButtonItemStyleDone target:self action:@selector(sendButtonPressed)]; _sendButton.accessibilityHint = NSLocalizedString(@"Double tap to share with selected conversations", nil); self.navigationItem.rightBarButtonItem = _sendButton; _sharingIndicatorView = [[UIActivityIndicatorView alloc] init]; _sharingIndicatorView.color = [NCAppBranding themeTextColor]; // Configure communication lib NSString *userToken = [[NCKeyChainController sharedInstance] tokenForAccountId:_account.accountId]; NSString *userAgent = [NSString stringWithFormat:@"Mozilla/5.0 (iOS) Nextcloud-Talk v%@", [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]]; [[NKCommon shared] setupWithAccount:_account.accountId user:_account.user userId:_account.userId password:userToken urlBase:_account.server userAgent:userAgent nextcloudVersion:_serverCapabilities.versionMajor delegate:self]; // Set to section NSDictionary *attributes = @{NSFontAttributeName:[UIFont systemFontOfSize:15], NSForegroundColorAttributeName:[UIColor labelColor]}; NSDictionary *subAttribute = @{NSFontAttributeName:[UIFont boldSystemFontOfSize:15], NSForegroundColorAttributeName:[UIColor tertiaryLabelColor]}; NSString *localizedToString = NSLocalizedString(@"To:", @"TRANSLATORS this is for sending something 'to' a user. Eg. 'To: John Doe'"); NSMutableAttributedString *toString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@ %@", localizedToString, _room.displayName] attributes:attributes]; [toString addAttributes:subAttribute range:NSMakeRange(0, [localizedToString length])]; self.toLabel.attributedText = toString; // Toolbar section [self.removeItemButton setEnabled:([self.shareItemController.shareItems count] > 1)]; [self.removeItemButton setTintColor:([self.shareItemController.shareItems count] > 1) ? nil : [UIColor clearColor]]; self.shareCollectionView.delegate = self; self.shareItemController = [[ShareItemController alloc] init]; self.shareItemController.delegate = self; NSBundle *bundle = [NSBundle bundleForClass:[ShareConfirmationCollectionViewCell class]]; [self.shareCollectionView registerNib:[UINib nibWithNibName:kShareConfirmationTableCellNibName bundle:bundle] forCellWithReuseIdentifier:kShareConfirmationCellIdentifier]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidHide:) name:UIKeyboardDidHideNotification object:nil]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; if (_type == ShareConfirmationTypeText) { [self.itemToolbar setHidden:YES]; [self.shareTextView becomeFirstResponder]; } } - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { if (_type == ShareConfirmationTypeText) { return; } // Hide the collection view [self.shareCollectionView setHidden:YES]; // Invalidate layout to remove warning about item size must be less than UICollectionView [self.shareCollectionView.collectionViewLayout invalidateLayout]; ShareItem *currentItem = [self getCurrentShareItem]; [coordinator animateAlongsideTransition:^(id _Nonnull context) { // Invalidate the view now so cell size is correctly calculated // The size of the collection view is correct at this moment [self.shareCollectionView.collectionViewLayout invalidateLayout]; } completion:^(id _Nonnull context) { // Scroll to the element and make collection view appear [self scrollToItem:currentItem animated:NO]; [self.shareCollectionView setHidden:NO]; }]; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } #pragma mark - Button Actions - (void)cancelButtonPressed { [self.delegate shareConfirmationViewControllerDidFinish:self]; } - (void)sendButtonPressed { if (_type == ShareConfirmationTypeText) { [self sendSharedText]; } else if (_type == ShareConfirmationTypeObjectShare) { [self sendObjectShare]; } else { [self uploadAndShareFiles]; } [self startAnimatingSharingIndicator]; } - (IBAction)removeItemButtonPressed:(id)sender { ShareItem *item = [self getCurrentShareItem]; if (item) { [self.shareItemController removeItem:item]; } } - (IBAction)cropItemButtonPressed:(id)sender { ShareItem *item = [self getCurrentShareItem]; UIImage *image = [self.shareItemController getImageFromItem:item]; if (!image) { return; } TOCropViewController *cropViewController = [[TOCropViewController alloc] initWithImage:image]; cropViewController.delegate = self; [self presentViewController:cropViewController animated:YES completion:nil]; } - (IBAction)previewItemButtonPressed:(id)sender { [self previewCurrentItem]; } - (IBAction)addItemButtonPressed:(id)sender { [self presentAdditionalItemOptions]; } - (void)shareText:(NSString *)sharedText { _type = ShareConfirmationTypeText; dispatch_async(dispatch_get_main_queue(), ^{ self.shareCollectionView.hidden = YES; self.itemToolbar.hidden = YES; self.shareTextView.hidden = NO; self.shareTextView.text = sharedText; }); } - (void)shareObjectShareMessage:(NCChatMessage *)objectShareMessage { _type = ShareConfirmationTypeObjectShare; dispatch_async(dispatch_get_main_queue(), ^{ self.shareCollectionView.hidden = YES; self.itemToolbar.hidden = YES; self.shareTextView.hidden = NO; self.shareTextView.userInteractionEnabled = NO; self.shareTextView.text = objectShareMessage.parsedMessage.string; self->_objectShareRichObject = objectShareMessage.richObjectFromObjectShare; }); } - (void)setIsModal:(BOOL)isModal { _isModal = isModal; } #pragma mark - Add additional items - (void)presentAdditionalItemOptions { UIAlertController *optionsActionSheet = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; UIAlertAction *cameraAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Camera", nil) style:UIAlertActionStyleDefault handler:^void (UIAlertAction *action) { [self checkAndPresentCamera]; }]; [cameraAction setValue:[[UIImage imageNamed:@"camera"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forKey:@"image"]; UIAlertAction *photoLibraryAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Photo Library", nil) style:UIAlertActionStyleDefault handler:^void (UIAlertAction *action) { [self presentPhotoLibrary]; }]; [photoLibraryAction setValue:[[UIImage imageNamed:@"photos"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forKey:@"image"]; UIAlertAction *filesAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Files", nil) style:UIAlertActionStyleDefault handler:^void (UIAlertAction *action) { [self presentDocumentPicker]; }]; [filesAction setValue:[[UIImage imageNamed:@"files"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forKey:@"image"]; #ifndef APP_EXTENSION // Camera access is not available in app extensions // https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionOverview.html if ([UIImagePickerController isSourceTypeAvailable: UIImagePickerControllerSourceTypeCamera]) { [optionsActionSheet addAction:cameraAction]; } #endif [optionsActionSheet addAction:photoLibraryAction]; [optionsActionSheet addAction:filesAction]; [optionsActionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil) style:UIAlertActionStyleCancel handler:nil]]; // Presentation on iPads optionsActionSheet.popoverPresentationController.barButtonItem = self.addItemButton; [self presentViewController:optionsActionSheet animated:YES completion:nil]; } - (void)checkAndPresentCamera { // https://stackoverflow.com/a/20464727/2512312 NSString *mediaType = AVMediaTypeVideo; AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType]; if(authStatus == AVAuthorizationStatusAuthorized) { [self presentCamera]; return; } else if(authStatus == AVAuthorizationStatusNotDetermined){ [AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) { if(granted){ [self presentCamera]; } }]; return; } UIAlertController * alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Could not access camera", nil) message:NSLocalizedString(@"Camera access is not allowed. Check your settings.", nil) preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* okButton = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:nil]; [alert addAction:okButton]; [self presentViewController:alert animated:YES completion:nil]; } - (void)presentCamera { dispatch_async(dispatch_get_main_queue(), ^{ self->_imagePicker = [[UIImagePickerController alloc] init]; self->_imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; self->_imagePicker.cameraFlashMode = [NCUserDefaults preferredCameraFlashMode]; self->_imagePicker.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:self->_imagePicker.sourceType]; self->_imagePicker.delegate = self; [self presentViewController:self->_imagePicker animated:YES completion:nil]; }); } - (void)presentPhotoLibrary { dispatch_async(dispatch_get_main_queue(), ^{ self->_imagePicker = [[UIImagePickerController alloc] init]; self->_imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; self->_imagePicker.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:self->_imagePicker.sourceType]; self->_imagePicker.delegate = self; [self presentViewController:self->_imagePicker animated:YES completion:nil]; }); } - (void)presentDocumentPicker { dispatch_async(dispatch_get_main_queue(), ^{ UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeImport]; documentPicker.delegate = self; [self presentViewController:documentPicker animated:YES completion:nil]; }); } #pragma mark - Actions - (void)sendSharedText { [[NCAPIController sharedInstance] sendChatMessage:self.shareTextView.text toRoom:_room.token displayName:nil replyTo:-1 referenceId:nil silently:NO forAccount:_account withCompletionBlock:^(NSError *error) { if (error) { [self.delegate shareConfirmationViewControllerDidFailed:self]; NSLog(@"Failed to send shared item"); } else { [self.delegate shareConfirmationViewControllerDidFinish:self]; [[NCIntentController sharedInstance] donateSendMessageIntentForRoom:self->_room]; } [self stopAnimatingSharingIndicator]; }]; } - (void)sendObjectShare { [[NCAPIController sharedInstance] shareRichObject:_objectShareRichObject inRoom:_room.token forAccount:_account withCompletionBlock:^(NSError *error) { if (error) { [self.delegate shareConfirmationViewControllerDidFailed:self]; NSLog(@"Failed to send shared item"); } else { [self.delegate shareConfirmationViewControllerDidFinish:self]; } [self stopAnimatingSharingIndicator]; }]; } - (void)updateHudProgress { if (!_hud) { return; } dispatch_async(dispatch_get_main_queue(), ^{ CGFloat progress = 0; long items = 0; for (ShareItem *item in self->_shareItemController.shareItems) { progress += item.uploadProgress; items++; } self->_hud.progress = (progress / items); }); } - (void)uploadAndShareFiles { _hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES]; _hud.mode = MBProgressHUDModeAnnularDeterminate; _hud.label.text = [NSString stringWithFormat:NSLocalizedString(@"Uploading %ld elements", nil), [self.shareItemController.shareItems count]]; if ([self.shareItemController.shareItems count] == 1) { _hud.label.text = NSLocalizedString(@"Uploading 1 element", nil); } _uploadGroup = dispatch_group_create(); _uploadFailed = NO; _uploadErrors = [[NSMutableArray alloc] init]; for (ShareItem *item in self.shareItemController.shareItems) { NSLog(@"Uploading %@", item.fileURL); dispatch_group_enter(_uploadGroup); [[NCAPIController sharedInstance] uniqueNameForFileUploadWithName:item.fileName originalName:YES forAccount:_account withCompletionBlock:^(NSString *fileServerURL, NSString *fileServerPath, NSInteger errorCode, NSString *errorDescription) { if (fileServerURL && fileServerPath) { [self uploadFileToServerURL:fileServerURL withFilePath:fileServerPath withItem:item]; } else { self->_uploadFailed = YES; [self->_uploadErrors addObject:errorDescription]; dispatch_group_leave(self->_uploadGroup); } }]; } dispatch_group_notify(_uploadGroup, dispatch_get_main_queue(),^{ [self stopAnimatingSharingIndicator]; [self->_hud hideAnimated:YES]; [self->_shareItemController removeAllItems]; // TODO: Do error reporting per item if (self->_uploadFailed) { UIAlertController * alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Upload failed", nil) message:[self->_uploadErrors componentsJoinedByString:@"\n"] preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* okButton = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [self.delegate shareConfirmationViewControllerDidFailed:self]; }]; [alert addAction:okButton]; [self presentViewController:alert animated:YES completion:nil]; } else { [self.delegate shareConfirmationViewControllerDidFinish:self]; } }); } - (void)uploadFileToServerURL:(NSString *)fileServerURL withFilePath:(NSString *)filePath withItem:(ShareItem *)item { [[NextcloudKit shared] uploadWithServerUrlFileName:fileServerURL fileNameLocalPath:item.filePath dateCreationFile:nil dateModificationFile:nil customUserAgent:nil addCustomHeaders:nil queue:dispatch_get_main_queue() taskHandler:^(NSURLSessionTask *task) { NSLog(@"Upload task"); } progressHandler:^(NSProgress *progress) { item.uploadProgress = progress.fractionCompleted; [self updateHudProgress]; } completionHandler:^(NSString *accountId, NSString *ocId, NSString *etag, NSDate *date, int64_t size, NSDictionary *allHeaderFields, NKError *error) { if (error.errorCode == 0) { [[NCAPIController sharedInstance] shareFileOrFolderForAccount:self->_account atPath:filePath toRoom:self->_room.token talkMetaData:nil withCompletionBlock:^(NSError *error) { if (error) { NSLog(@"Failed to send shared file"); self->_uploadFailed = YES; [self->_uploadErrors addObject:error.description]; } dispatch_group_leave(self->_uploadGroup); }]; } else if (error.errorCode == 404 || error.errorCode == 409) { [[NCAPIController sharedInstance] checkOrCreateAttachmentFolderForAccount:self->_account withCompletionBlock:^(BOOL created, NSInteger errorCode) { if (created) { [self uploadFileToServerURL:fileServerURL withFilePath:filePath withItem:item]; } else { self->_uploadFailed = YES; [self->_uploadErrors addObject:error.errorDescription]; dispatch_group_leave(self->_uploadGroup); } }]; } else { self->_uploadFailed = YES; [self->_uploadErrors addObject:error.errorDescription]; dispatch_group_leave(self->_uploadGroup); } }]; } #pragma mark - User Interface - (void)startAnimatingSharingIndicator { dispatch_async(dispatch_get_main_queue(), ^{ [self->_sharingIndicatorView startAnimating]; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self->_sharingIndicatorView]; }); } - (void)stopAnimatingSharingIndicator { dispatch_async(dispatch_get_main_queue(), ^{ [self->_sharingIndicatorView stopAnimating]; self.navigationItem.rightBarButtonItem = self->_sendButton; }); } -(void)keyboardWillShow:(NSNotification *)notification { // see https://stackoverflow.com/a/22719225/2512312 NSDictionary *info = notification.userInfo; NSValue *value = info[UIKeyboardFrameEndUserInfoKey]; CGRect rawFrame = [value CGRectValue]; CGRect keyboardFrame = [self.view convertRect:rawFrame fromView:nil]; [UIView animateWithDuration:0.3 animations:^{ self.bottomSpacer.constant = keyboardFrame.size.height; [self.view layoutIfNeeded]; }]; } -(void)keyboardDidHide:(NSNotification *)notification { [UIView animateWithDuration:0.3 animations:^{ self.bottomSpacer.constant = 0; [self.view layoutIfNeeded]; }]; } - (void)updateToolbarForCurrentItem { ShareItem *item = [self getCurrentShareItem]; if (item) { [UIView transitionWithView:self.itemToolbar duration:0.3 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{ [self.cropItemButton setEnabled:item.isImage]; [self.previewItemButton setEnabled:[QLPreviewController canPreviewItem:item.fileURL]]; [self.addItemButton setEnabled:([self.shareItemController.shareItems count] < 5)]; } completion:nil]; } [self.removeItemButton setEnabled:([self.shareItemController.shareItems count] > 1)]; [self.removeItemButton setTintColor:([self.shareItemController.shareItems count] > 1) ? nil : [UIColor clearColor]]; } #pragma mark - UIImagePickerController Delegate - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { [self saveImagePickerSettings:picker]; NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType]; if ([mediaType isEqualToString:@"public.image"]) { UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage]; [self dismissViewControllerAnimated:YES completion:^{ [self.shareItemController addItemWithImage:image]; [self collectionViewScrollToEnd]; }]; } else if ([mediaType isEqualToString:@"public.movie"]) { NSURL *videoURL = [info objectForKey:UIImagePickerControllerMediaURL]; [self dismissViewControllerAnimated:YES completion:^{ [self.shareItemController addItemWithURL:videoURL]; [self collectionViewScrollToEnd]; }]; } } - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { [self saveImagePickerSettings:picker]; [self dismissViewControllerAnimated:YES completion:nil]; } - (void)saveImagePickerSettings:(UIImagePickerController *)picker { if (picker.sourceType == UIImagePickerControllerSourceTypeCamera && picker.cameraCaptureMode == UIImagePickerControllerCameraCaptureModePhoto) { [NCUserDefaults setPreferredCameraFlashMode:picker.cameraFlashMode]; } } #pragma mark - UIDocumentPickerViewController Delegate - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray *)urls { [self shareDocumentsWithURLs:urls fromController:controller]; } - (void)shareDocumentsWithURLs:(NSArray *)urls fromController:(UIDocumentPickerViewController *)controller { if (controller.documentPickerMode == UIDocumentPickerModeImport) { for (NSURL* url in urls) { [self.shareItemController addItemWithURL:url]; } [self collectionViewScrollToEnd]; } } - (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller { } #pragma mark - ScrollView/CollectionView - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { ShareConfirmationCollectionViewCell *cell = (ShareConfirmationCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:kShareConfirmationCellIdentifier forIndexPath:indexPath]; ShareItem *item = [self.shareItemController.shareItems objectAtIndex:indexPath.row]; // Setting placeholder here in case we can't generate any other preview [cell setPlaceHolderImage:item.placeholderImage]; [cell setPlaceHolderText:item.fileName]; // Check if we got an image UIImage *image = [self.shareItemController getImageFromItem:item]; if (image) { // We're able to get an image directly from the fileURL -> use it [cell setPreviewImage:image]; } else { // We need to generate our own preview/thumbnail here [self generatePreviewForCell:cell withCollectionView:collectionView withItem:item]; } return cell; } - (void)generatePreviewForCell:(ShareConfirmationCollectionViewCell *)cell withCollectionView:(UICollectionView *)collectionView withItem:(ShareItem *)item { CGSize size = CGSizeMake(collectionView.bounds.size.width, collectionView.bounds.size.height); CGFloat scale = [UIScreen mainScreen].scale; // updateHandler might be called multiple times, starting from low quality representation to high-quality QLThumbnailGenerationRequest *request = [[QLThumbnailGenerationRequest alloc] initWithFileAtURL:item.fileURL size:size scale:scale representationTypes:(QLThumbnailGenerationRequestRepresentationTypeLowQualityThumbnail | QLThumbnailGenerationRequestRepresentationTypeThumbnail)]; [QLThumbnailGenerator.sharedGenerator generateRepresentationsForRequest:request updateHandler:^(QLThumbnailRepresentation * _Nullable thumbnail, QLThumbnailRepresentationType type, NSError * _Nullable error) { if (error) { return; } dispatch_async(dispatch_get_main_queue(), ^{ [cell setPreviewImage:thumbnail.UIImage]; }); }]; } - (NSInteger)collectionView:(nonnull UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return [self.shareItemController.shareItems count]; } - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { return 1; } - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { return CGSizeMake(collectionView.bounds.size.width, collectionView.bounds.size.height); } - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { [self previewCurrentItem]; } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { [self updatePageControlPage]; } - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { [self updatePageControlPage]; } - (void)collectionViewScrollToEnd { [self scrollToItem:self.shareItemController.shareItems.lastObject animated:YES]; } - (void)scrollToItem:(ShareItem *)item animated:(BOOL)animated { if (!item) { return; } dispatch_async(dispatch_get_main_queue(), ^{ NSInteger indexForItem = [self.shareItemController.shareItems indexOfObject:item]; if (indexForItem != NSNotFound) { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:indexForItem inSection:0]; [self.shareCollectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionNone animated:animated]; } }); } - (ShareItem *)getCurrentShareItem { NSInteger currentIndex = self.shareCollectionView.contentOffset.x / self.shareCollectionView.frame.size.width; if (currentIndex >= [self.shareItemController.shareItems count]) { return nil; } return [self.shareItemController.shareItems objectAtIndex:currentIndex]; } #pragma mark - PageControl - (IBAction)pageControlValueChanged:(id)sender { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:self.pageControl.currentPage inSection:0]; [self.shareCollectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionNone animated:YES]; } - (void)updatePageControlPage { // see: https://stackoverflow.com/a/46181277/2512312 dispatch_async(dispatch_get_main_queue(), ^{ self.pageControl.currentPage = self.shareCollectionView.contentOffset.x / self.shareCollectionView.frame.size.width; [self updateToolbarForCurrentItem]; }); } #pragma mark - PreviewController - (void)previewCurrentItem { ShareItem *item = [self getCurrentShareItem]; // Only open preview if there's an actual item and it can be previewed if (!item || !item.fileURL || ![QLPreviewController canPreviewItem:item.fileURL]) { return; } QLPreviewController * preview = [[QLPreviewController alloc] init]; preview.dataSource = self; preview.delegate = self; preview.navigationController.navigationBar.tintColor = [NCAppBranding themeTextColor]; preview.navigationController.navigationBar.barTintColor = [NCAppBranding themeColor]; preview.tabBarController.tabBar.tintColor = [NCAppBranding themeColor]; UIColor *themeColor = [NCAppBranding themeColor]; UINavigationBarAppearance *appearance = [[UINavigationBarAppearance alloc] init]; [appearance configureWithOpaqueBackground]; appearance.backgroundColor = themeColor; appearance.titleTextAttributes = @{NSForegroundColorAttributeName:[NCAppBranding themeTextColor]}; preview.navigationItem.standardAppearance = appearance; preview.navigationItem.compactAppearance = appearance; preview.navigationItem.scrollEdgeAppearance = appearance; [self.navigationController pushViewController:preview animated:YES]; } - (nonnull id)previewController:(nonnull QLPreviewController *)controller previewItemAtIndex:(NSInteger)index { // Don't use index here, as this relates to numberOfPreviewItems // When we have numberOfPreviewItems > 1 this will show an additional list of items ShareItem *item = [self getCurrentShareItem]; if (item && item.fileURL) { return item.fileURL; } return nil; } - (NSInteger)numberOfPreviewItemsInPreviewController:(QLPreviewController *)controller { return 1; } - (QLPreviewItemEditingMode)previewController:(QLPreviewController *)controller editingModeForPreviewItem:(id)previewItem { return QLPreviewItemEditingModeCreateCopy; } - (void)previewController:(QLPreviewController *)controller didSaveEditedCopyOfPreviewItem:(id)previewItem atURL:(NSURL *)modifiedContentsURL { ShareItem *item = [self getCurrentShareItem]; if (item) { [self.shareItemController updateItem:item withURL:modifiedContentsURL]; } } #pragma mark - ShareItemController Delegate - (void)shareItemControllerItemsChanged:(nonnull ShareItemController *)shareItemController { dispatch_async(dispatch_get_main_queue(), ^{ NSUInteger shareItemCount = [shareItemController.shareItems count]; if (shareItemCount == 0) { if (self.extensionContext) { NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:nil]; [self.extensionContext cancelRequestWithError:error]; } else { [self dismissViewControllerAnimated:YES completion:nil]; } } else { [self.shareCollectionView reloadData]; // Make sure all changes are full populated before we update our ui elements [self.shareCollectionView layoutIfNeeded]; [self updateToolbarForCurrentItem]; self.pageControl.numberOfPages = [shareItemController.shareItems count]; } }); } #pragma mark - TOCropViewController Delegate - (void)cropViewController:(TOCropViewController *)cropViewController didCropToImage:(UIImage *)image withRect:(CGRect)cropRect angle:(NSInteger)angle { ShareItem *item = [self getCurrentShareItem]; if (item) { [self.shareItemController updateItem:item withImage:image]; // Fixes bug on iPad where collectionView is scrolled between two pages [self scrollToItem:item animated:YES]; } // Fixes weird iOS 13 bug: https://github.com/TimOliver/TOCropViewController/issues/365 cropViewController.transitioningDelegate = nil; [cropViewController dismissViewControllerAnimated:YES completion:nil]; } - (void)cropViewController:(TOCropViewController *)cropViewController didFinishCancelled:(BOOL)cancelled { ShareItem *item = [self getCurrentShareItem]; if (item) { // Fixes bug on iPad where collectionView is scrolled between two pages [self scrollToItem:item animated:YES]; } // Fixes weird iOS 13 bug: https://github.com/TimOliver/TOCropViewController/issues/365 cropViewController.transitioningDelegate = nil; [cropViewController dismissViewControllerAnimated:YES completion:nil]; } #pragma mark - NKCommon Delegate - (void)authenticationChallenge:(NSURLSession *)session didReceive:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler { // The pinnning check if ([[CCCertificate sharedManager] checkTrustedChallenge:challenge]) { completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); } else { completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); } } @end