зеркало из https://github.com/nextcloud/talk-ios.git
886 строки
36 KiB
Objective-C
886 строки
36 KiB
Objective-C
/**
|
|
* @copyright Copyright (c) 2020 Ivan Sein <ivan@nextcloud.com>
|
|
*
|
|
* @author Ivan Sein <ivan@nextcloud.com>
|
|
*
|
|
* @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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#import "ShareConfirmationViewController.h"
|
|
#import "ShareConfirmationCollectionViewCell.h"
|
|
|
|
@import NextcloudKit;
|
|
|
|
#import <AVFoundation/AVFoundation.h>
|
|
#import <QuickLook/QuickLook.h>
|
|
#import <QuickLookThumbnailing/QuickLookThumbnailing.h>
|
|
#import <TOCropViewController/TOCropViewController.h>
|
|
|
|
#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 () <NKCommonDelegate, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, QLPreviewControllerDataSource, QLPreviewControllerDelegate, ShareItemControllerDelegate, TOCropViewControllerDelegate, UIImagePickerControllerDelegate, UIDocumentPickerDelegate, UINavigationControllerDelegate>
|
|
{
|
|
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<UIViewControllerTransitionCoordinator>)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<UIViewControllerTransitionCoordinatorContext> _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<UIViewControllerTransitionCoordinatorContext> _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<NSURL *> *)urls
|
|
{
|
|
[self shareDocumentsWithURLs:urls fromController:controller];
|
|
}
|
|
|
|
- (void)shareDocumentsWithURLs:(NSArray<NSURL *> *)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<QLPreviewItem>)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<QLPreviewItem>)previewItem {
|
|
return QLPreviewItemEditingModeCreateCopy;
|
|
}
|
|
|
|
- (void)previewController:(QLPreviewController *)controller didSaveEditedCopyOfPreviewItem:(id<QLPreviewItem>)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
|