From b6514349fbaa72b189e769c814c2730575b054b4 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Thu, 14 Oct 2021 12:05:28 +0300 Subject: [PATCH 1/5] vector-im/element-ios/issues/5009 - Refactored share extension and started using the shared code directly in the main application. --- Riot/Assets/en.lproj/Vector.strings | 1 + Riot/Generated/Strings.swift | 4 + Riot/Managers/AppInfo/BuildInfo.m | 4 + Riot/Modules/Room/RoomViewController.m | 69 +++- Riot/target.yml | 2 + .../Managers/ShareExtensionManager.h | 105 +---- .../Managers/ShareExtensionManager.m | 391 ++++++++++-------- .../Modules/Fallback/FallbackViewController.h | 4 +- .../Modules/Fallback/FallbackViewController.m | 6 - ...r.h => ShareExtensionRootViewController.h} | 4 +- .../Main/ShareExtensionRootViewController.m | 92 +++++ .../Main/SharePresentingViewController.m | 79 ---- .../Share/DataSources/ShareDataSource.h | 4 +- .../Share/DataSources/ShareDataSource.m | 17 +- .../Share/Listing/RoomsListViewController.h | 4 +- .../Share/Listing/RoomsListViewController.m | 111 +---- .../Listing/Views/RecentRoomTableViewCell.m | 5 + .../Modules/Share/ShareViewController.h | 44 +- .../Modules/Share/ShareViewController.m | 195 +++++---- .../Modules/Share/ShareViewController.xib | 65 +-- RiotShareExtension/SupportingFiles/Info.plist | 2 +- .../RiotShareExtension-Bridging-Header.h | 1 + RiotShareExtension/target.yml | 2 + 23 files changed, 607 insertions(+), 604 deletions(-) rename RiotShareExtension/Modules/Main/{SharePresentingViewController.h => ShareExtensionRootViewController.h} (88%) create mode 100644 RiotShareExtension/Modules/Main/ShareExtensionRootViewController.m delete mode 100644 RiotShareExtension/Modules/Main/SharePresentingViewController.m diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 63b974866..4dd0b28ed 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -360,6 +360,7 @@ Tap the + to start adding people."; "room_event_action_redact" = "Remove"; "room_event_action_more" = "More"; "room_event_action_share" = "Share"; +"room_event_action_forward" = "Forward"; "room_event_action_permalink" = "Permalink"; "room_event_action_view_source" = "View Source"; "room_event_action_view_decrypted_source" = "View Decrypted Source"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index f23c03d2a..2b8768a86 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -2887,6 +2887,10 @@ public class VectorL10n: NSObject { public static var roomEventActionEdit: String { return VectorL10n.tr("Vector", "room_event_action_edit") } + /// Forward + public static var roomEventActionForward: String { + return VectorL10n.tr("Vector", "room_event_action_forward") + } /// Reason for kicking this user public static var roomEventActionKickPromptReason: String { return VectorL10n.tr("Vector", "room_event_action_kick_prompt_reason") diff --git a/Riot/Managers/AppInfo/BuildInfo.m b/Riot/Managers/AppInfo/BuildInfo.m index 0853efe94..a1dbffe14 100644 --- a/Riot/Managers/AppInfo/BuildInfo.m +++ b/Riot/Managers/AppInfo/BuildInfo.m @@ -16,7 +16,11 @@ #import "BuildInfo.h" +#ifdef IS_SHARE_EXTENSION +#import "RiotShareExtension-Swift.h" +#else #import "Riot-Swift.h" +#endif #define MAKE_STRING(x) #x #define MAKE_NS_STRING(x) @MAKE_STRING(x) diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index d32da8258..7158ae820 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -16,6 +16,8 @@ limitations under the License. */ +@import MobileCoreServices; + #import "RoomViewController.h" #import "RoomDataSource.h" @@ -106,6 +108,7 @@ #import "AvatarGenerator.h" #import "Tools.h" #import "WidgetManager.h" +#import "ShareExtensionManager.h" #import "GBDeviceInfo_iOS.h" @@ -249,6 +252,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; @property (nonatomic, strong) VoiceMessageController *voiceMessageController; @property (nonatomic, strong) SpaceDetailPresenter *spaceDetailPresenter; +@property (nonatomic, strong) ShareExtensionManager *shareExtensionManager; + @property (nonatomic, strong) UserSuggestionCoordinatorBridge *userSuggestionCoordinator; @property (nonatomic, weak) IBOutlet UIView *userSuggestionContainerView; @@ -3190,6 +3195,25 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]]; } + [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionForward] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + NSExtensionItem *item = [[NSExtensionItem alloc] init]; + item.attachments = @[[[NSItemProvider alloc] initWithItem:selectedComponent.textMessage typeIdentifier:(__bridge NSString *)kUTTypeText]]; + + self.shareExtensionManager = [[ShareExtensionManager alloc] initWithShareExtensionContext:nil + extensionItems:@[item]]; + + MXWeakify(self); + [self.shareExtensionManager setCompletionCallback:^(ShareExtensionManagerResult result) { + MXStrongifyAndReturnIfNil(self); + [attachment onShareEnded]; + [self dismissViewControllerAnimated:YES completion:nil]; + }]; + + [self presentViewController:self.shareExtensionManager.mainViewController animated:YES completion:nil]; + }]]; + if (!isJitsiCallEvent) { [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionQuote] @@ -3340,7 +3364,10 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [self cancelEventSelection]; + [self startActivityIndicator]; + [attachment prepareShare:^(NSURL *fileURL) { + [self stopActivityIndicator]; __strong __typeof(weakSelf)self = weakSelf; self->documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:fileURL]; @@ -3355,10 +3382,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; } } failure:^(NSError *error) { - - //Alert user [self showError:error]; - + [self stopActivityIndicator]; }]; // Start animation in case of download during attachment preparing @@ -3368,6 +3393,44 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]]; } } + + if (attachment.type == MXKAttachmentTypeFile || + attachment.type == MXKAttachmentTypeImage || + attachment.type == MXKAttachmentTypeVideo) { + + [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionForward] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + NSDictionary *attachmentTypeToIdentifier = @{@(MXKAttachmentTypeFile): (__bridge NSString *)kUTTypeFileURL, + @(MXKAttachmentTypeImage): (__bridge NSString *)kUTTypeImage, + @(MXKAttachmentTypeVideo): (__bridge NSString *)kUTTypeVideo}; + + [self startActivityIndicator]; + + [attachment prepareShare:^(NSURL *fileURL) { + [self stopActivityIndicator]; + + NSExtensionItem *item = [[NSExtensionItem alloc] init]; + item.attachments = @[[[NSItemProvider alloc] initWithItem:fileURL typeIdentifier:attachmentTypeToIdentifier[@(attachment.type)]]]; + + self.shareExtensionManager = [[ShareExtensionManager alloc] initWithShareExtensionContext:nil + extensionItems:@[item]]; + + MXWeakify(self); + [self.shareExtensionManager setCompletionCallback:^(ShareExtensionManagerResult result) { + MXStrongifyAndReturnIfNil(self); + [attachment onShareEnded]; + [self dismissViewControllerAnimated:YES completion:nil]; + }]; + + [self presentViewController:self.shareExtensionManager.mainViewController animated:YES completion:nil]; + } failure:^(NSError *error) { + [self showError:error]; + [self stopActivityIndicator]; + }]; + }]]; + } } // Check status of the selected event diff --git a/Riot/target.yml b/Riot/target.yml index 9428387f6..a4a61ac4c 100644 --- a/Riot/target.yml +++ b/Riot/target.yml @@ -66,6 +66,8 @@ targets: excludes: - "Modules/Room/EmojiPicker/Data/EmojiMart/EmojiJSONStore.swift" - "**/*.strings" # Exclude all strings files + - path: ../RiotShareExtension/Managers + - path: ../RiotShareExtension/Modules # Add separately localizable files # Once a language has enough translations (>80%), it must be declared here diff --git a/RiotShareExtension/Managers/ShareExtensionManager.h b/RiotShareExtension/Managers/ShareExtensionManager.h index 042d55fb0..e8dc215e6 100644 --- a/RiotShareExtension/Managers/ShareExtensionManager.h +++ b/RiotShareExtension/Managers/ShareExtensionManager.h @@ -14,111 +14,24 @@ limitations under the License. */ -#import #import @class ShareExtensionManager; -@class SharePresentingViewController; -@protocol Configurable; -/** - Posted when the matrix user account and his data has been checked and updated. - The notification object is the MXKAccount instance. - */ -extern NSString *const kShareExtensionManagerDidUpdateAccountDataNotification; - - -/** - The protocol for the manager's delegate - */ -@protocol ShareExtensionManagerDelegate - -@required - -/** - Called when an image is going to be shared to show a compression prompt - @param extensionManager the ShareExtensionManager object that called the method - @param compressionPrompt the prompt that was prepared for the image which is going to be shared - */ -- (void)shareExtensionManager:(ShareExtensionManager *)extensionManager showImageCompressionPrompt:(UIAlertController *)compressionPrompt; - -@optional - -/** - Called when the manager starts sending the content to a room - @param extensionManager the ShareExtensionManager object that called the method - @param room the room where content will be sent - */ -- (void)shareExtensionManager:(ShareExtensionManager *)extensionManager didStartSendingContentToRoom:(MXRoom *)room; - -/** - Called when the progress of the uploading media changes - @param extensionManager the ShareExtensionManager object that called the method - @param progress the current progress - */ -- (void)shareExtensionManager:(ShareExtensionManager *)extensionManager mediaUploadProgress:(CGFloat)progress; - -@end - - -/** - A class used to share content from the extension - */ +typedef NS_ENUM(NSUInteger, ShareExtensionManagerResult) { + ShareExtensionManagerResultFinished, + ShareExtensionManagerResultCancelled, + ShareExtensionManagerResultFailed +}; @interface ShareExtensionManager : NSObject -/** - The share extension context that represents a user's sharing request, also stores the content to be shared - */ -@property (nonatomic) NSExtensionContext *shareExtensionContext; +@property (nonatomic, copy) void (^completionCallback)(ShareExtensionManagerResult); -/** - The share app extension’s primary view controller. - */ -@property (nonatomic) SharePresentingViewController *primaryViewController; +- (instancetype)initWithShareExtensionContext:(NSExtensionContext *)shareExtensionContext + extensionItems:(NSArray *)extensionItems; -/** - The current user account - */ -@property (nonatomic, readonly) MXKAccount *userAccount; - -/** - The shared file store - */ -@property (nonatomic, readonly) MXFileStore *fileStore; - -/** - A delegate used to notify about needed UI changes when sharing - */ -@property (nonatomic, weak) id delegate; - -// Build Settings -@property (nonatomic, readonly) id configuration; - -/** - The singleton instance - */ -+ (instancetype)sharedManager; - -/** - Send the content that the user has chosen to a room - @param room the room to send the content to - @param failureBlock the code to be executed when sharing has failed for whatever reason - note: there is no "successBlock" parameter because when the sharing succeeds, the extension needs to close itself - */ -- (void)sendContentToRoom:(MXRoom *)room failureBlock:(void(^)(NSError *error))failureBlock; - -/** - Checks if there is an image in the user chosen content - @return YES if there is, NO otherwise - */ -- (BOOL)hasImageTypeContent; - -/** - Terminate the extension and return to the app that started it - @param canceled YES if the user chose to cancel the sharing, NO otherwise - */ -- (void)terminateExtensionCanceled:(BOOL)canceled; +- (UIViewController *)mainViewController; @end diff --git a/RiotShareExtension/Managers/ShareExtensionManager.m b/RiotShareExtension/Managers/ShareExtensionManager.m index ea58a7d34..6d2ab3cea 100644 --- a/RiotShareExtension/Managers/ShareExtensionManager.m +++ b/RiotShareExtension/Managers/ShareExtensionManager.m @@ -15,15 +15,21 @@ */ #import "ShareExtensionManager.h" -#import "SharePresentingViewController.h" +#import "ShareViewController.h" +#import "ShareDataSource.h" + #import + @import MobileCoreServices; #import "objc/runtime.h" #include #import -#import "RiotShareExtension-Swift.h" -NSString *const kShareExtensionManagerDidUpdateAccountDataNotification = @"kShareExtensionManagerDidUpdateAccountDataNotification"; +#ifdef IS_SHARE_EXTENSION +#import "RiotShareExtension-Swift.h" +#else +#import "Riot-Swift.h" +#endif static const CGFloat kLargeImageSizeMaxDimension = 2048.0; @@ -35,48 +41,45 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) ImageCompressionModeLarge }; +@interface ShareExtensionManager () -@interface ShareExtensionManager () +@property (nonatomic, strong, readonly) NSExtensionContext *shareExtensionContext; +@property (nonatomic, strong, readonly) NSArray *extensionItems; -@property (nonatomic, readwrite) MXKAccount *userAccount; +@property (nonatomic, strong, readonly) NSMutableArray *pendingImages; +@property (nonatomic, strong, readonly) NSMutableDictionary *imageUploadProgresses; +@property (nonatomic, strong, readonly) id configuration; +@property (nonatomic, strong, readonly) ShareViewController *shareViewController; -@property (nonatomic) NSMutableArray *pendingImages; -@property (nonatomic) NSMutableDictionary *imageUploadProgresses; -@property (nonatomic) ImageCompressionMode imageCompressionMode; -@property (nonatomic) CGFloat actualLargeSize; +@property (nonatomic, strong) MXKAccount *userAccount; +@property (nonatomic, strong) MXFileStore *fileStore; + +@property (nonatomic, assign) ImageCompressionMode imageCompressionMode; +@property (nonatomic, assign) CGFloat actualLargeSize; @end @implementation ShareExtensionManager -#pragma mark - Lifecycle - -+ (instancetype)sharedManager +- (instancetype)initWithShareExtensionContext:(NSExtensionContext *)shareExtensionContext + extensionItems:(NSArray *)extensionItems { - static ShareExtensionManager *sharedInstance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ + if (self = [super init]) { - sharedInstance = [[self alloc] init]; + _shareExtensionContext = shareExtensionContext; + _extensionItems = extensionItems; - sharedInstance.pendingImages = [NSMutableArray array]; - sharedInstance.imageUploadProgresses = [NSMutableDictionary dictionary]; + _pendingImages = [NSMutableArray array]; + _imageUploadProgresses = [NSMutableDictionary dictionary]; - [[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(onMediaLoaderStateDidChange:) name:kMXMediaLoaderStateDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMediaLoaderStateDidChange:) name:kMXMediaLoaderStateDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(checkUserAccount) name:kMXKAccountManagerDidRemoveAccountNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(checkUserAccount) name:NSExtensionHostWillEnterForegroundNotification object:nil]; + [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; - // Add observer to handle logout - [[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(checkUserAccount) name:kMXKAccountManagerDidRemoveAccountNotification object:nil]; - - // Add observer on the Extension host - [[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(checkUserAccount) name:NSExtensionHostWillEnterForegroundNotification object:nil]; - - // Add observer to handle memory warning - [NSNotificationCenter.defaultCenter addObserver:sharedInstance selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; - - // Set static application settings - sharedInstance->_configuration = [CommonConfiguration new]; - [sharedInstance.configuration setupSettings]; + _configuration = [CommonConfiguration new]; + [_configuration setupSettings]; // NSLog -> console.log file when not debugging the app MXLogConfiguration *configuration = [[MXLogConfiguration alloc] init]; @@ -91,68 +94,74 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) } [MXLog configure:configuration]; - }); - return sharedInstance; -} - -- (void)checkUserAccount -{ - // Force account manager to reload account from the local storage. - [[MXKAccountManager sharedManager] forceReloadAccounts]; - - if (self.userAccount) - { - // Check whether the used account is still the first active one - MXKAccount *firstAccount = [MXKAccountManager sharedManager].activeAccounts.firstObject; - // Compare the access token - if (!firstAccount || ![self.userAccount.mxCredentials.accessToken isEqualToString:firstAccount.mxCredentials.accessToken]) - { - // Remove this account - self.userAccount = nil; - } + _shareViewController = [[ShareViewController alloc] initWithType:ShareViewControllerTypeSend + currentState:ShareViewControllerAccountStateNotConfigured]; + [_shareViewController setDelegate:self]; + + // Set up runtime language on each context update. + NSUserDefaults *sharedUserDefaults = [MXKAppSettings standardAppSettings].sharedUserDefaults; + NSString *language = [sharedUserDefaults objectForKey:@"appLanguage"]; + [NSBundle mxk_setLanguage:language]; + [NSBundle mxk_setFallbackLanguage:@"en"]; + + // Check the current matrix user. + [self checkUserAccount]; } - if (!self.userAccount) - { - // We consider the first enabled account. - // TODO: Handle multiple accounts - self.userAccount = [MXKAccountManager sharedManager].activeAccounts.firstObject; - } - - // Reset the file store to reload the room data. - if (_fileStore) - { - [_fileStore close]; - _fileStore = nil; - } - - if (self.userAccount) - { - _fileStore = [[MXFileStore alloc] initWithCredentials:self.userAccount.mxCredentials]; - } - - // Post notification - [[NSNotificationCenter defaultCenter] postNotificationName:kShareExtensionManagerDidUpdateAccountDataNotification object:self.userAccount userInfo:nil]; + return self; } #pragma mark - Public -- (void)setShareExtensionContext:(NSExtensionContext *)shareExtensionContext +- (UIViewController *)mainViewController { - _shareExtensionContext = shareExtensionContext; - - // Set up runtime language on each context update. - NSUserDefaults *sharedUserDefaults = [MXKAppSettings standardAppSettings].sharedUserDefaults; - NSString *language = [sharedUserDefaults objectForKey:@"appLanguage"]; - [NSBundle mxk_setLanguage:language]; - [NSBundle mxk_setFallbackLanguage:@"en"]; - - // Check the current matrix user. - [self checkUserAccount]; + return self.shareViewController; } -- (void)sendContentToRoom:(MXRoom *)room failureBlock:(void(^)(NSError *error))failureBlock +#pragma mark - ShareViewControllerDelegate + +- (void)shareViewControllerDidRequestShare:(ShareViewController *)shareViewController + forRoomIdentifier:(NSString *)roomIdentifier +{ + MXSession *session = [[MXSession alloc] initWithMatrixRestClient:[[MXRestClient alloc] initWithCredentials:self.userAccount.mxCredentials andOnUnrecognizedCertificateBlock:nil]]; + [MXFileStore setPreloadOptions:0]; + + MXWeakify(session); + [session setStore:self.fileStore success:^{ + MXStrongifyAndReturnIfNil(session); + + MXRoom *selectedRoom = [MXRoom loadRoomFromStore:self.fileStore withRoomId:roomIdentifier matrixSession:session]; + + // Do not warn for unknown devices. We have cross-signing now + session.crypto.warnOnUnknowDevices = NO; + + [self _sendContentToRoom:selectedRoom failureBlock:^(NSError* error) { + NSString *title = [VectorL10n roomEventFailedToSend]; + if ([error.domain isEqualToString:MXEncryptingErrorDomain]) + { + title = [VectorL10n shareExtensionFailedToEncrypt]; + } + + [self _showFailureAlert:title]; + }]; + + } failure:^(NSError *error) { + MXLogError(@"[ShareExtensionManager] Failed preparign matrix session"); + }]; +} + +- (void)shareViewControllerDidRequestDismissal:(ShareViewController *)shareViewController +{ + if (self.completionCallback) + { + self.completionCallback(ShareExtensionManagerResultCancelled); + } +} + +#pragma mark - Private + +- (void)_sendContentToRoom:(MXRoom *)room failureBlock:(void(^)(NSError *error))failureBlock { [self resetPendingData]; @@ -190,7 +199,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) __weak typeof(self) weakSelf = self; - for (NSExtensionItem *item in self.shareExtensionContext.inputItems) + for (NSExtensionItem *item in self.extensionItems) { for (NSItemProvider *itemProvider in item.attachments) { @@ -198,7 +207,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) { dispatch_group_enter(requestsGroup); - [itemProvider loadItemForTypeIdentifier:UTTypeFileUrl options:nil completionHandler:^(NSURL *fileUrl, NSError * _Null_unspecified error) { + [itemProvider loadItemForTypeIdentifier:UTTypeFileUrl options:nil completionHandler:^(NSURL *fileUrl, NSError *error) { // Switch back on the main thread to handle correctly the UI change dispatch_async(dispatch_get_main_queue(), ^{ @@ -269,7 +278,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) itemProvider.isLoaded = NO; - [itemProvider loadItemForTypeIdentifier:UTTypeImage options:nil completionHandler:^(id _Nullable itemProviderItem, NSError * _Null_unspecified error) + [itemProvider loadItemForTypeIdentifier:UTTypeImage options:nil completionHandler:^(id itemProviderItem, NSError *error) { if (weakSelf) { @@ -340,7 +349,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) if (compressionPrompt) { - [self.delegate shareExtensionManager:self showImageCompressionPrompt:compressionPrompt]; + [self presentCompressionPrompt:compressionPrompt]; } } else @@ -356,44 +365,44 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) dispatch_group_enter(requestsGroup); [itemProvider loadItemForTypeIdentifier:UTTypeVideo options:nil completionHandler:^(NSURL *videoLocalUrl, NSError * _Null_unspecified error) { - - // Switch back on the main thread to handle correctly the UI change - dispatch_async(dispatch_get_main_queue(), ^{ - - if (weakSelf) - { - typeof(self) self = weakSelf; - [self sendVideo:videoLocalUrl - toRoom:room - successBlock:^{ - requestSuccess(item); - } failureBlock:requestFailure]; - } - - }); - }]; + // Switch back on the main thread to handle correctly the UI change + dispatch_async(dispatch_get_main_queue(), ^{ + + if (weakSelf) + { + typeof(self) self = weakSelf; + [self sendVideo:videoLocalUrl + toRoom:room + successBlock:^{ + requestSuccess(item); + } failureBlock:requestFailure]; + } + + }); + + }]; } else if ([itemProvider hasItemConformingToTypeIdentifier:UTTypeMovie]) { dispatch_group_enter(requestsGroup); [itemProvider loadItemForTypeIdentifier:UTTypeMovie options:nil completionHandler:^(NSURL *videoLocalUrl, NSError * _Null_unspecified error) { - - // Switch back on the main thread to handle correctly the UI change - dispatch_async(dispatch_get_main_queue(), ^{ - - if (weakSelf) - { - typeof(self) self = weakSelf; - [self sendVideo:videoLocalUrl - toRoom:room - successBlock:^{ - requestSuccess(item); - } failureBlock:requestFailure]; - } - - }); + + // Switch back on the main thread to handle correctly the UI change + dispatch_async(dispatch_get_main_queue(), ^{ + + if (weakSelf) + { + typeof(self) self = weakSelf; + [self sendVideo:videoLocalUrl + toRoom:room + successBlock:^{ + requestSuccess(item); + } failureBlock:requestFailure]; + } + + }); }]; } @@ -412,65 +421,93 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) } else { - [self completeRequestReturningItems:returningExtensionItems completionHandler:nil]; + if (self.completionCallback) + { + self.completionCallback(ShareExtensionManagerResultFinished); + } } }); } -- (BOOL)hasImageTypeContent +- (void)_showFailureAlert:(NSString *)title { - for (NSExtensionItem *item in self.shareExtensionContext.inputItems) - { - for (NSItemProvider *itemProvider in item.attachments) + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleAlert]; + + MXWeakify(self); + UIAlertAction *okAction = [UIAlertAction actionWithTitle:[MatrixKitL10n ok] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + MXStrongifyAndReturnIfNil(self); + + if (self.completionCallback) { - if ([itemProvider hasItemConformingToTypeIdentifier:(__bridge NSString *)kUTTypeImage]) - { - return YES; - } + self.completionCallback(ShareExtensionManagerResultFailed); + } + }]; + + [alertController addAction:okAction]; + + [self.mainViewController presentViewController:alertController animated:YES completion:nil]; +} + +- (void)checkUserAccount +{ + // Force account manager to reload account from the local storage. + [[MXKAccountManager sharedManager] forceReloadAccounts]; + + if (self.userAccount) + { + // Check whether the used account is still the first active one + MXKAccount *firstAccount = [MXKAccountManager sharedManager].activeAccounts.firstObject; + + // Compare the access token + if (!firstAccount || ![self.userAccount.mxCredentials.accessToken isEqualToString:firstAccount.mxCredentials.accessToken]) + { + // Remove this account + self.userAccount = nil; } } - return NO; -} - -- (void)terminateExtensionCanceled:(BOOL)canceled -{ - if (canceled) + + if (!self.userAccount) { - [self.shareExtensionContext cancelRequestWithError:[NSError errorWithDomain:@"MXUserCancelErrorDomain" code:4201 userInfo:nil]]; - } - else - { - [self.shareExtensionContext cancelRequestWithError:[NSError errorWithDomain:@"MXFailureErrorDomain" code:500 userInfo:nil]]; + // We consider the first enabled account. + // TODO: Handle multiple accounts + self.userAccount = [MXKAccountManager sharedManager].activeAccounts.firstObject; } - [self.primaryViewController destroy]; - self.primaryViewController = nil; + // Reset the file store to reload the room data. + if (_fileStore) + { + [_fileStore close]; + _fileStore = nil; + } - // FIXME: Share extension memory usage increase when launched several times and then crash due to some memory leaks. - // For now, we force the share extension to exit and free memory. - [NSException raise:@"Kill the app extension" format:@"Free memory used by share extension"]; + if (self.userAccount) + { + _fileStore = [[MXFileStore alloc] initWithCredentials:self.userAccount.mxCredentials]; + + ShareDataSource *roomDataSource = [[ShareDataSource alloc] initWithMode:DataSourceModeRooms + fileStore:_fileStore + credentials:self.userAccount.mxCredentials]; + + ShareDataSource *peopleDataSource = [[ShareDataSource alloc] initWithMode:DataSourceModePeople + fileStore:_fileStore + credentials:self.userAccount.mxCredentials]; + + [self.shareViewController configureWithState:ShareViewControllerAccountStateConfigured + roomDataSource:roomDataSource + peopleDataSource:peopleDataSource]; + } else { + [self.shareViewController configureWithState:ShareViewControllerAccountStateNotConfigured + roomDataSource:nil + peopleDataSource:nil]; + } } -#pragma mark - Private - - (void)resetPendingData { [self.pendingImages removeAllObjects]; [self.imageUploadProgresses removeAllObjects]; } -- (void)completeRequestReturningItems:(nullable NSArray *)items completionHandler:(void(^ __nullable)(BOOL expired))completionHandler; -{ - [self.shareExtensionContext completeRequestReturningItems:items completionHandler:completionHandler]; - - [self.primaryViewController destroy]; - self.primaryViewController = nil; - - // FIXME: Share extension memory usage increase when launched several times and then crash due to some memory leaks. - // For now, we force the share extension to exit and free memory. - [NSException raise:@"Kill the app extension" format:@"Free memory used by share extension"]; -} - - (BOOL)isAPendingImageNotOrientedUp { BOOL isAPendingImageNotOrientedUp = NO; @@ -659,15 +696,12 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) - (void)didStartSendingToRoom:(MXRoom *)room { - if ([self.delegate respondsToSelector:@selector(shareExtensionManager:didStartSendingContentToRoom:)]) - { - [self.delegate shareExtensionManager:self didStartSendingContentToRoom:room]; - } + [self.shareViewController showProgressIndicator]; } - (BOOL)areAttachmentsFullyLoaded { - for (NSExtensionItem *item in self.shareExtensionContext.inputItems) + for (NSExtensionItem *item in self.extensionItems) { for (NSItemProvider *itemProvider in item.attachments) { @@ -682,7 +716,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) - (BOOL)areAllAttachmentsImages { - for (NSExtensionItem *item in self.shareExtensionContext.inputItems) + for (NSExtensionItem *item in self.extensionItems) { for (NSItemProvider *itemProvider in item.attachments) { @@ -868,6 +902,14 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) } } +- (void)presentCompressionPrompt:(UIAlertController *)compressionPrompt +{ + dispatch_async(dispatch_get_main_queue(), ^{ + [compressionPrompt popoverPresentationController].sourceView = self.mainViewController.view; + [compressionPrompt popoverPresentationController].sourceRect = self.mainViewController.view.frame; + [self.mainViewController presentViewController:compressionPrompt animated:YES completion:nil]; + }); +} #pragma mark - Notifications @@ -879,18 +921,16 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) case MXMediaLoaderStateUploadInProgress: { self.imageUploadProgresses[loader.uploadId] = (NSNumber *)loader.statisticsDict[kMXMediaLoaderProgressValueKey]; - if ([self.delegate respondsToSelector:@selector(shareExtensionManager:mediaUploadProgress:)]) + + const NSInteger totalImagesCount = self.pendingImages.count; + CGFloat totalProgress = 0.0; + + for (NSNumber *progress in self.imageUploadProgresses.allValues) { - const NSInteger totalImagesCount = self.pendingImages.count; - CGFloat totalProgress = 0.0; - - for (NSNumber *progress in self.imageUploadProgresses.allValues) - { - totalProgress += progress.floatValue/totalImagesCount; - } - - [self.delegate shareExtensionManager:self mediaUploadProgress:totalProgress]; + totalProgress += progress.floatValue/totalImagesCount; } + + [self.shareViewController setProgress:totalProgress]; break; } default: @@ -1161,7 +1201,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) MXWeakify(self); // Ignore showMediaCompressionPrompt setting due to memory constraints when encrypting large videos. - UIAlertController *compressionPrompt = [MXKTools videoConversionPromptForVideoAsset:videoAsset withCompletion:^(NSString * _Nullable presetName) { + UIAlertController *compressionPrompt = [MXKTools videoConversionPromptForVideoAsset:videoAsset withCompletion:^(NSString *presetName) { MXStrongifyAndReturnIfNil(self); // If the preset name is nil, the user cancelled. @@ -1207,10 +1247,9 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) }]; }]; - [self.delegate shareExtensionManager:self showImageCompressionPrompt:compressionPrompt]; + [self presentCompressionPrompt:compressionPrompt]; } - @end diff --git a/RiotShareExtension/Modules/Fallback/FallbackViewController.h b/RiotShareExtension/Modules/Fallback/FallbackViewController.h index ddee2a064..c4f26c2ef 100644 --- a/RiotShareExtension/Modules/Fallback/FallbackViewController.h +++ b/RiotShareExtension/Modules/Fallback/FallbackViewController.h @@ -14,8 +14,8 @@ limitations under the License. */ -#import +@import UIKit; -@interface FallbackViewController : MXKViewController +@interface FallbackViewController : UIViewController @end diff --git a/RiotShareExtension/Modules/Fallback/FallbackViewController.m b/RiotShareExtension/Modules/Fallback/FallbackViewController.m index 74f81b4a0..57412f705 100644 --- a/RiotShareExtension/Modules/Fallback/FallbackViewController.m +++ b/RiotShareExtension/Modules/Fallback/FallbackViewController.m @@ -42,10 +42,4 @@ self.logoImageView.tintColor = ThemeService.shared.theme.tintColor; } -- (void)didReceiveMemoryWarning -{ - [super didReceiveMemoryWarning]; - // Dispose of any resources that can be recreated. -} - @end diff --git a/RiotShareExtension/Modules/Main/SharePresentingViewController.h b/RiotShareExtension/Modules/Main/ShareExtensionRootViewController.h similarity index 88% rename from RiotShareExtension/Modules/Main/SharePresentingViewController.h rename to RiotShareExtension/Modules/Main/ShareExtensionRootViewController.h index c9eff12d0..e0868b6ad 100644 --- a/RiotShareExtension/Modules/Main/SharePresentingViewController.h +++ b/RiotShareExtension/Modules/Main/ShareExtensionRootViewController.h @@ -16,8 +16,6 @@ #import -@interface SharePresentingViewController : UIViewController - -- (void)destroy; +@interface ShareExtensionRootViewController : UIViewController @end diff --git a/RiotShareExtension/Modules/Main/ShareExtensionRootViewController.m b/RiotShareExtension/Modules/Main/ShareExtensionRootViewController.m new file mode 100644 index 000000000..ad5178c41 --- /dev/null +++ b/RiotShareExtension/Modules/Main/ShareExtensionRootViewController.m @@ -0,0 +1,92 @@ +/* + Copyright 2017 Aram Sargsyan + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "ShareExtensionRootViewController.h" +#import "ShareViewController.h" +#import "ShareExtensionManager.h" +#import "ThemeService.h" + +#ifdef IS_SHARE_EXTENSION +#import "RiotShareExtension-Swift.h" +#else +#import "Riot-Swift.h" +#endif + +@interface ShareExtensionRootViewController () + +@property (nonatomic, strong, readonly) ShareExtensionManager *shareExtensionManager; + +@end + +@implementation ShareExtensionRootViewController + +- (instancetype)init +{ + if(self = [super init]) { + + [ThemeService.shared setThemeId:RiotSettings.shared.userInterfaceTheme]; + + _shareExtensionManager = [[ShareExtensionManager alloc] initWithShareExtensionContext:self.extensionContext + extensionItems:self.extensionContext.inputItems]; + + MXWeakify(self); + [_shareExtensionManager setCompletionCallback:^(ShareExtensionManagerResult result) { + MXStrongifyAndReturnIfNil(self); + + switch (result) + { + case ShareExtensionManagerResultFinished: + [self.extensionContext completeRequestReturningItems:nil completionHandler:nil]; + [self _dismiss]; + break; + case ShareExtensionManagerResultCancelled: + [self.extensionContext cancelRequestWithError:[NSError errorWithDomain:@"MXUserCancelErrorDomain" code:4201 userInfo:nil]]; + [self _dismiss]; + break; + case ShareExtensionManagerResultFailed: + [self.extensionContext cancelRequestWithError:[NSError errorWithDomain:@"MXFailureErrorDomain" code:500 userInfo:nil]]; + [self _dismiss]; + break; + default: + break; + } + }]; + } + + return self; +} + +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + + [self presentViewController:self.shareExtensionManager.mainViewController animated:YES completion:nil]; +} + +#pragma mark - Private + +- (void)_dismiss +{ + [self dismissViewControllerAnimated:true completion:^{ + [self.presentingViewController dismissViewControllerAnimated:false completion:nil]; + +// // FIXME: Share extension memory usage increase when launched several times and then crash due to some memory leaks. +// // For now, we force the share extension to exit and free memory. +// [NSException raise:@"Kill the app extension" format:@"Free memory used by share extension"]; + }]; +} + +@end diff --git a/RiotShareExtension/Modules/Main/SharePresentingViewController.m b/RiotShareExtension/Modules/Main/SharePresentingViewController.m deleted file mode 100644 index ef57eba3c..000000000 --- a/RiotShareExtension/Modules/Main/SharePresentingViewController.m +++ /dev/null @@ -1,79 +0,0 @@ -/* - Copyright 2017 Aram Sargsyan - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#import "SharePresentingViewController.h" -#import "ShareViewController.h" -#import "ShareExtensionManager.h" -#import "ThemeService.h" - -#ifdef IS_SHARE_EXTENSION -#import "RiotShareExtension-Swift.h" -#else -#import "Riot-Swift.h" -#endif - -@interface SharePresentingViewController () - -@property (nonatomic) ShareViewController *shareViewController; - -@end - -@implementation SharePresentingViewController - -- (void)viewDidLoad -{ - [super viewDidLoad]; - - ShareExtensionManager *sharedManager = [ShareExtensionManager sharedManager]; - - sharedManager.primaryViewController = self; - sharedManager.shareExtensionContext = self.extensionContext; - - // Set up current theme - ThemeService.shared.themeId = RiotSettings.shared.userInterfaceTheme; - - [self presentShareViewController]; -} - -- (void)destroy -{ - if (self.shareViewController) - { - [self.shareViewController destroy]; - self.shareViewController = nil; - } -} - -- (void)presentShareViewController -{ - self.shareViewController = [[ShareViewController alloc] init]; - - self.shareViewController.providesPresentationContextTransitionStyle = YES; - self.shareViewController.definesPresentationContext = YES; - self.shareViewController.modalPresentationStyle = UIModalPresentationOverFullScreen; - self.shareViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; - - [self presentViewController:self.shareViewController animated:YES completion:nil]; -} - -- (void)didReceiveMemoryWarning -{ - [super didReceiveMemoryWarning]; - // Dispose of any resources that can be recreated. -} - - -@end diff --git a/RiotShareExtension/Modules/Share/DataSources/ShareDataSource.h b/RiotShareExtension/Modules/Share/DataSources/ShareDataSource.h index 2a50bfdee..6d62631da 100644 --- a/RiotShareExtension/Modules/Share/DataSources/ShareDataSource.h +++ b/RiotShareExtension/Modules/Share/DataSources/ShareDataSource.h @@ -25,7 +25,9 @@ typedef NS_ENUM(NSInteger, ShareDataSourceMode) @interface ShareDataSource : MXKRecentsDataSource -- (instancetype)initWithMode:(ShareDataSourceMode)dataSourceMode; +- (instancetype)initWithMode:(ShareDataSourceMode)dataSourceMode + fileStore:(MXFileStore *)fileStore + credentials:(MXCredentials *)credentials; /** Returns the cell data at the index path diff --git a/RiotShareExtension/Modules/Share/DataSources/ShareDataSource.m b/RiotShareExtension/Modules/Share/DataSources/ShareDataSource.m index e841769ea..802425ebc 100644 --- a/RiotShareExtension/Modules/Share/DataSources/ShareDataSource.m +++ b/RiotShareExtension/Modules/Share/DataSources/ShareDataSource.m @@ -20,7 +20,9 @@ @interface ShareDataSource () -@property (nonatomic, readwrite) ShareDataSourceMode dataSourceMode; +@property (nonatomic, assign, readonly) ShareDataSourceMode dataSourceMode; +@property (nonatomic, strong, readonly) MXFileStore *fileStore; +@property (nonatomic, strong, readonly) MXCredentials *credentials; @property NSArray *recentCellDatas; @property NSMutableArray *visibleRoomCellDatas; @@ -30,11 +32,14 @@ @implementation ShareDataSource - (instancetype)initWithMode:(ShareDataSourceMode)dataSourceMode + fileStore:(MXFileStore *)fileStore + credentials:(MXCredentials *)credentials { - self = [super init]; - if (self) + if (self = [super init]) { - self.dataSourceMode = dataSourceMode; + _dataSourceMode = dataSourceMode; + _fileStore = fileStore; + _credentials = credentials; [self loadCellData]; } @@ -53,12 +58,12 @@ - (void)loadCellData { - [[ShareExtensionManager sharedManager].fileStore asyncRoomsSummaries:^(NSArray * _Nonnull roomsSummaries) { + [self.fileStore asyncRoomsSummaries:^(NSArray *roomsSummaries) { NSMutableArray *cellData = [NSMutableArray array]; // Add a fake matrix session to each room summary to provide it a REST client (used to handle correctly the room avatar). - MXSession *session = [[MXSession alloc] initWithMatrixRestClient:[[MXRestClient alloc] initWithCredentials:[ShareExtensionManager sharedManager].userAccount.mxCredentials andOnUnrecognizedCertificateBlock:nil]]; + MXSession *session = [[MXSession alloc] initWithMatrixRestClient:[[MXRestClient alloc] initWithCredentials:self.credentials andOnUnrecognizedCertificateBlock:nil]]; for (MXRoomSummary *roomSummary in roomsSummaries) { diff --git a/RiotShareExtension/Modules/Share/Listing/RoomsListViewController.h b/RiotShareExtension/Modules/Share/Listing/RoomsListViewController.h index b8a50f7e4..bee26845b 100644 --- a/RiotShareExtension/Modules/Share/Listing/RoomsListViewController.h +++ b/RiotShareExtension/Modules/Share/Listing/RoomsListViewController.h @@ -18,8 +18,8 @@ #import "MXRoom+Riot.h" #import "ShareDataSource.h" +@class RoomsListViewController; + @interface RoomsListViewController : MXKRecentListViewController -@property (copy) void (^failureBlock)(void); - @end diff --git a/RiotShareExtension/Modules/Share/Listing/RoomsListViewController.m b/RiotShareExtension/Modules/Share/Listing/RoomsListViewController.m index 3f6e67b2e..7a9e198f0 100644 --- a/RiotShareExtension/Modules/Share/Listing/RoomsListViewController.m +++ b/RiotShareExtension/Modules/Share/Listing/RoomsListViewController.m @@ -28,9 +28,7 @@ #import "Riot-Swift.h" #endif -@interface RoomsListViewController () - -@property (nonatomic) MXKPieChartHUD *hudView; +@interface RoomsListViewController () // The fake search bar displayed at the top of the recents table. We switch on the actual search bar (self.recentsSearchBar) // when the user selects it. @@ -136,76 +134,6 @@ return; } -#pragma mark - Private - -- (void)showShareAlertForRoomPath:(NSIndexPath *)indexPath -{ - MXKRecentCellData *recentCellData = [self.dataSource cellDataAtIndexPath:indexPath]; - NSString *roomName = recentCellData.roomSummary.displayname; - if (!roomName.length) - { - roomName = [MatrixKitL10n roomDisplaynameEmptyRoom]; - } - - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[VectorL10n sendTo:roomName] message:nil preferredStyle:UIAlertControllerStyleAlert]; - - UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:[MatrixKitL10n cancel] style:UIAlertActionStyleCancel handler:nil]; - [alertController addAction:cancelAction]; - - UIAlertAction *sendAction = [UIAlertAction actionWithTitle:[MatrixKitL10n send] style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { - - // The selected room is instanciated here - MXSession *session = [[MXSession alloc] initWithMatrixRestClient:[[MXRestClient alloc] initWithCredentials:[ShareExtensionManager sharedManager].userAccount.mxCredentials andOnUnrecognizedCertificateBlock:nil]]; - - [MXFileStore setPreloadOptions:0]; - - MXWeakify(session); - [session setStore:[ShareExtensionManager sharedManager].fileStore success:^{ - MXStrongifyAndReturnIfNil(session); - - MXRoom *selectedRoom = [MXRoom loadRoomFromStore:[ShareExtensionManager sharedManager].fileStore withRoomId:recentCellData.roomSummary.roomId matrixSession:session]; - - // Do not warn for unknown devices. We have cross-signing now - session.crypto.warnOnUnknowDevices = NO; - - [ShareExtensionManager sharedManager].delegate = self; - - [[ShareExtensionManager sharedManager] sendContentToRoom:selectedRoom failureBlock:^(NSError* error) { - - NSString *title; - if ([error.domain isEqualToString:MXEncryptingErrorDomain]) - { - title = [VectorL10n shareExtensionFailedToEncrypt]; - } - - [self showFailureAlert:title]; - }]; - - } failure:^(NSError *error) { - - MXLogDebug(@"[RoomsListViewController] failed to prepare matrix session]"); - - }]; - }]; - - [alertController addAction:sendAction]; - - [self presentViewController:alertController animated:YES completion:nil]; -} - -- (void)showFailureAlert:(NSString *)title -{ - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title.length ? title : [VectorL10n roomEventFailedToSend] message:nil preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *okAction = [UIAlertAction actionWithTitle:[MatrixKitL10n ok] style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { - if (self.failureBlock) - { - self.failureBlock(); - } - }]; - [alertController addAction:okAction]; - [self presentViewController:alertController animated:YES completion:nil]; -} - #pragma mark - UITableViewDelegate - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath @@ -213,13 +141,6 @@ return [RecentRoomTableViewCell cellHeight]; } -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath -{ - [tableView deselectRowAtIndexPath:indexPath animated:YES]; - - [self showShareAlertForRoomPath:indexPath]; -} - #pragma mark - MXKDataSourceDelegate - (Class)cellViewClassForCellData:(MXKCellData*)cellData @@ -304,34 +225,4 @@ } } -#pragma mark - ShareExtensionManagerDelegate - -- (void)shareExtensionManager:(ShareExtensionManager *)extensionManager showImageCompressionPrompt:(UIAlertController *)compressionPrompt -{ - dispatch_async(dispatch_get_main_queue(), ^{ - [compressionPrompt popoverPresentationController].sourceView = self.view; - [compressionPrompt popoverPresentationController].sourceRect = self.view.frame; - [self presentViewController:compressionPrompt animated:YES completion:nil]; - }); -} - -- (void)shareExtensionManager:(ShareExtensionManager *)extensionManager didStartSendingContentToRoom:(MXRoom *)room -{ - dispatch_async(dispatch_get_main_queue(), ^{ - if (!self.hudView) - { - self.parentViewController.view.userInteractionEnabled = NO; - self.hudView = [MXKPieChartHUD showLoadingHudOnView:self.view WithMessage:[VectorL10n sending]]; - [self.hudView setProgress:0.0]; - } - }); -} - -- (void)shareExtensionManager:(ShareExtensionManager *)extensionManager mediaUploadProgress:(CGFloat)progress -{ - dispatch_async(dispatch_get_main_queue(), ^{ - [self.hudView setProgress:progress]; - }); -} - @end diff --git a/RiotShareExtension/Modules/Share/Listing/Views/RecentRoomTableViewCell.m b/RiotShareExtension/Modules/Share/Listing/Views/RecentRoomTableViewCell.m index 90550a99e..5abb551d9 100644 --- a/RiotShareExtension/Modules/Share/Listing/Views/RecentRoomTableViewCell.m +++ b/RiotShareExtension/Modules/Share/Listing/Views/RecentRoomTableViewCell.m @@ -18,7 +18,12 @@ #import "MXRoomSummary+Riot.h" #import "ThemeService.h" + +#ifdef IS_SHARE_EXTENSION #import "RiotShareExtension-Swift.h" +#else +#import "Riot-Swift.h" +#endif @interface RecentRoomTableViewCell () diff --git a/RiotShareExtension/Modules/Share/ShareViewController.h b/RiotShareExtension/Modules/Share/ShareViewController.h index 74e7af78a..edf5ce348 100644 --- a/RiotShareExtension/Modules/Share/ShareViewController.h +++ b/RiotShareExtension/Modules/Share/ShareViewController.h @@ -14,9 +14,47 @@ limitations under the License. */ -#import -#import +@import UIKit; -@interface ShareViewController : MXKViewController +@class ShareViewController; +@class ShareDataSource; + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, ShareViewControllerType) { + ShareViewControllerTypeSend, + ShareViewControllerTypeForward +}; + +typedef NS_ENUM(NSUInteger, ShareViewControllerAccountState) { + ShareViewControllerAccountStateConfigured, + ShareViewControllerAccountStateNotConfigured +}; + +@protocol ShareViewControllerDelegate + +- (void)shareViewControllerDidRequestShare:(ShareViewController *)shareViewController + forRoomIdentifier:(NSString *)roomIdentifier; + +- (void)shareViewControllerDidRequestDismissal:(ShareViewController *)shareViewController; @end + +@interface ShareViewController : UIViewController + +@property (nonatomic, weak, nullable) id delegate; + +- (instancetype)initWithType:(ShareViewControllerType)type + currentState:(ShareViewControllerAccountState)state; + +- (void)configureWithState:(ShareViewControllerAccountState)state + roomDataSource:(nullable ShareDataSource *)roomDataSource + peopleDataSource:(nullable ShareDataSource *)peopleDataSource; + +- (void)showProgressIndicator; + +- (void)setProgress:(CGFloat)progress; + +@end + +NS_ASSUME_NONNULL_END diff --git a/RiotShareExtension/Modules/Share/ShareViewController.m b/RiotShareExtension/Modules/Share/ShareViewController.m index af4796e72..21cce2d00 100644 --- a/RiotShareExtension/Modules/Share/ShareViewController.m +++ b/RiotShareExtension/Modules/Share/ShareViewController.m @@ -22,161 +22,188 @@ #import "ShareExtensionManager.h" #import "ThemeService.h" + +#ifdef IS_SHARE_EXTENSION #import "RiotShareExtension-Swift.h" +#else +#import "Riot-Swift.h" +#endif +@interface ShareViewController () -@interface ShareViewController () +@property (nonatomic, assign, readonly) ShareViewControllerType type; -@property (weak, nonatomic) IBOutlet UIView *masterContainerView; -@property (weak, nonatomic) IBOutlet UILabel *titleLabel; -@property (weak, nonatomic) IBOutlet UIView *contentView; +@property (nonatomic, assign) ShareViewControllerAccountState state; +@property (nonatomic, strong) ShareDataSource *roomDataSource; +@property (nonatomic, strong) ShareDataSource *peopleDataSource; -@property (nonatomic) SegmentedViewController *segmentedViewController; +@property (nonatomic, weak) IBOutlet UIView *masterContainerView; +@property (nonatomic, weak) IBOutlet UIButton *cancelButton; +@property (nonatomic, weak) IBOutlet UILabel *titleLabel; +@property (nonatomic, weak) IBOutlet UIButton *shareButton; +@property (nonatomic, weak) IBOutlet UIView *contentView; -@property (nonatomic) id shareExtensionManagerDidUpdateAccountDataObserver; +@property (nonatomic, strong) SegmentedViewController *segmentedViewController; +@property (nonatomic, strong) MXKPieChartHUD *hudView; @end @implementation ShareViewController -#pragma mark - Lifecycle +- (instancetype)initWithType:(ShareViewControllerType)type + currentState:(ShareViewControllerAccountState)state +{ + if (self = [super init]) + { + _type = type; + _state = state; + } + + return self; +} - (void)viewDidLoad { [super viewDidLoad]; - self.view.tintColor = ThemeService.shared.theme.tintColor; - self.titleLabel.textColor = ThemeService.shared.theme.textPrimaryColor; - self.masterContainerView.backgroundColor = ThemeService.shared.theme.baseColor; + [self.masterContainerView setBackgroundColor:ThemeService.shared.theme.baseColor]; + [self.masterContainerView.layer setCornerRadius:7.0]; - self.shareExtensionManagerDidUpdateAccountDataObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kShareExtensionManagerDidUpdateAccountDataNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - - [self configureViews]; + [self.titleLabel setTextColor:ThemeService.shared.theme.textPrimaryColor]; - }]; + [self.cancelButton setTintColor:ThemeService.shared.theme.tintColor]; + [self.cancelButton setTitle:[VectorL10n cancel] forState:UIControlStateNormal]; + + [self.shareButton setTintColor:ThemeService.shared.theme.tintColor]; + + [self configureWithState:self.state roomDataSource:self.roomDataSource peopleDataSource:self.peopleDataSource]; +} + +- (void)configureWithState:(ShareViewControllerAccountState)state + roomDataSource:(ShareDataSource *)roomDataSource + peopleDataSource:(ShareDataSource *)peopleDataSource +{ + self.state = state; + self.roomDataSource = roomDataSource; + self.peopleDataSource = peopleDataSource; + + if (!self.isViewLoaded) { + return; + } [self configureViews]; } -- (void)destroy +#pragma mark - MXKRecentListViewControllerDelegate + +- (void)recentListViewController:(MXKRecentListViewController *)recentListViewController + didSelectRoom:(NSString *)roomId + inMatrixSession:(MXSession *)mxSession { - [super destroy]; - - if (self.shareExtensionManagerDidUpdateAccountDataObserver) - { - [[NSNotificationCenter defaultCenter] removeObserver:self.shareExtensionManagerDidUpdateAccountDataObserver]; - self.shareExtensionManagerDidUpdateAccountDataObserver = nil; - } - - [self resetContentView]; + [self.delegate shareViewControllerDidRequestShare:self forRoomIdentifier:roomId]; } -- (void)resetContentView +- (void)recentListViewController:(MXKRecentListViewController *)recentListViewController + didSelectSuggestedRoom:(MXSpaceChildInfo *)childInfo { - // Empty the content view - NSArray *subviews = self.contentView.subviews; - for (UIView *subview in subviews) - { - [subview removeFromSuperview]; - } - - // Release the current segmented view controller if any - if (self.segmentedViewController) - { - [self.segmentedViewController removeFromParentViewController]; - - // Release correctly all the existing data source and view controllers. - [self.segmentedViewController destroy]; - self.segmentedViewController = nil; - } + [self.delegate shareViewControllerDidRequestShare:self forRoomIdentifier:childInfo.childRoomId]; +} + +#pragma mark - ShareExtensionManagerDelegate + +- (void)showProgressIndicator +{ + dispatch_async(dispatch_get_main_queue(), ^{ + if (!self.hudView) + { + self.parentViewController.view.userInteractionEnabled = NO; + self.hudView = [MXKPieChartHUD showLoadingHudOnView:self.view WithMessage:[VectorL10n sending]]; + [self.hudView setProgress:0.0]; + } + }); +} + +- (void)setProgress:(CGFloat)progress +{ + [self.hudView setProgress:progress]; } #pragma mark - Private - (void)configureViews { - self.masterContainerView.layer.cornerRadius = 7; - [self resetContentView]; - if ([ShareExtensionManager sharedManager].userAccount) + if (self.state == ShareViewControllerAccountStateConfigured) { self.titleLabel.text = [VectorL10n sendTo:@""]; + [self.shareButton setTitle:[VectorL10n roomEventActionForward] forState:UIControlStateNormal]; + [self configureSegmentedViewController]; } else { - NSDictionary *infoDictionary = [NSBundle mainBundle].infoDictionary; - NSString *bundleDisplayName = infoDictionary[@"CFBundleDisplayName"]; - self.titleLabel.text = bundleDisplayName; + self.titleLabel.text = [AppInfo.current displayName]; [self configureFallbackViewController]; } } - (void)configureSegmentedViewController { - self.segmentedViewController = [SegmentedViewController segmentedViewController]; - - NSArray *titles = @[[VectorL10n titleRooms], [VectorL10n titlePeople]]; - - void (^failureBlock)(void) = ^void() { - [self dismissViewControllerAnimated:YES completion:^{ - [[ShareExtensionManager sharedManager] terminateExtensionCanceled:NO]; - }]; - }; - - ShareDataSource *roomsDataSource = [[ShareDataSource alloc] initWithMode:DataSourceModeRooms]; RoomsListViewController *roomsViewController = [RoomsListViewController recentListViewController]; - roomsViewController.failureBlock = failureBlock; - [roomsViewController displayList:roomsDataSource]; + [roomsViewController displayList:self.roomDataSource]; + [roomsViewController setDelegate:self]; - ShareDataSource *peopleDataSource = [[ShareDataSource alloc] initWithMode:DataSourceModePeople]; RoomsListViewController *peopleViewController = [RoomsListViewController recentListViewController]; - peopleViewController.failureBlock = failureBlock; - [peopleViewController displayList:peopleDataSource]; + [peopleViewController setDelegate:self]; + [peopleViewController displayList:self.peopleDataSource]; - [self.segmentedViewController initWithTitles:titles viewControllers:@[roomsViewController, peopleViewController] defaultSelected:0]; + self.segmentedViewController = [SegmentedViewController segmentedViewController]; + [self.segmentedViewController initWithTitles:@[[VectorL10n titleRooms], [VectorL10n titlePeople]] + viewControllers:@[roomsViewController, peopleViewController] defaultSelected:0]; [self addChildViewController:self.segmentedViewController]; - [self.contentView addSubview:self.segmentedViewController.view]; + [self.contentView vc_addSubViewMatchingParent:self.segmentedViewController.view]; [self.segmentedViewController didMoveToParentViewController:self]; - - [self autoPinSubviewEdges:self.segmentedViewController.view toSuperviewEdges:self.contentView]; } - (void)configureFallbackViewController { FallbackViewController *fallbackVC = [FallbackViewController new]; [self addChildViewController:fallbackVC]; - [self.contentView addSubview:fallbackVC.view]; + [self.contentView vc_addSubViewMatchingParent:fallbackVC.view]; [fallbackVC didMoveToParentViewController:self]; - - [self autoPinSubviewEdges:fallbackVC.view toSuperviewEdges:self.contentView]; } -- (void)autoPinSubviewEdges:(UIView *)subview toSuperviewEdges:(UIView *)superview +- (void)resetContentView { - subview.translatesAutoresizingMaskIntoConstraints = NO; - NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeWidth multiplier:1 constant:0]; - widthConstraint.active = YES; - NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeHeight multiplier:1 constant:0]; - heightConstraint.active = YES; - NSLayoutConstraint *centerXConstraint = [NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]; - centerXConstraint.active = YES; - NSLayoutConstraint *centerYConstraint = [NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]; - centerYConstraint.active = YES; + NSArray *subviews = self.contentView.subviews; + for (UIView *subview in subviews) + { + [subview removeFromSuperview]; + } + + if (self.segmentedViewController) + { + [self.segmentedViewController removeFromParentViewController]; + + [self.segmentedViewController destroy]; + self.segmentedViewController = nil; + } } #pragma mark - Actions -- (IBAction)close:(UIButton *)sender +- (IBAction)onCancelButtonTap:(UIButton *)sender { - [self dismissViewControllerAnimated:YES completion:^{ - [[ShareExtensionManager sharedManager] terminateExtensionCanceled:YES]; - }]; + [self.delegate shareViewControllerDidRequestDismissal:self]; } +- (IBAction)onShareButtonTap:(UIButton *)sender +{ + +} @end diff --git a/RiotShareExtension/Modules/Share/ShareViewController.xib b/RiotShareExtension/Modules/Share/ShareViewController.xib index 04a1316c8..1718d4501 100644 --- a/RiotShareExtension/Modules/Share/ShareViewController.xib +++ b/RiotShareExtension/Modules/Share/ShareViewController.xib @@ -9,8 +9,10 @@ + + @@ -21,40 +23,50 @@ - + - + - + - + + + + - - + + + + - + @@ -64,9 +76,7 @@ - - @@ -74,21 +84,12 @@ - - - - - - - - - - + + + + - - - diff --git a/RiotShareExtension/SupportingFiles/Info.plist b/RiotShareExtension/SupportingFiles/Info.plist index f0a876166..fce6e4c3f 100644 --- a/RiotShareExtension/SupportingFiles/Info.plist +++ b/RiotShareExtension/SupportingFiles/Info.plist @@ -43,7 +43,7 @@ NSExtensionPointIdentifier com.apple.share-services NSExtensionPrincipalClass - SharePresentingViewController + ShareExtensionRootViewController diff --git a/RiotShareExtension/SupportingFiles/RiotShareExtension-Bridging-Header.h b/RiotShareExtension/SupportingFiles/RiotShareExtension-Bridging-Header.h index d3bf536b2..9d1bfc7d9 100644 --- a/RiotShareExtension/SupportingFiles/RiotShareExtension-Bridging-Header.h +++ b/RiotShareExtension/SupportingFiles/RiotShareExtension-Bridging-Header.h @@ -4,3 +4,4 @@ #import "ThemeService.h" #import "AvatarGenerator.h" +#import "BuildInfo.h" diff --git a/RiotShareExtension/target.yml b/RiotShareExtension/target.yml index 9e61389f1..81e952ff5 100644 --- a/RiotShareExtension/target.yml +++ b/RiotShareExtension/target.yml @@ -52,9 +52,11 @@ targets: - path: ../Riot/Managers/EncryptionKeyManager/EncryptionKeyManager.swift - path: ../Riot/Managers/KeyValueStorage - path: ../Riot/Managers/Settings/RiotSettings.swift + - path: ../Riot/Managers/AppInfo/ - path: ../Riot/Categories/UIColor.swift - path: ../Riot/Categories/UISearchBar.swift - path: ../Riot/Categories/String.swift + - path: ../Riot/Categories/UIView.swift - path: ../Riot/Modules/Common/Recents/CellData/RecentCellData.m - path: ../Riot/PropertyWrappers/UserDefaultsBackedPropertyWrapper.swift - path: ../Riot/Generated/Strings.swift From d7ab73524f8cdd2fa6223c4adaa1350cd4da4267 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Thu, 14 Oct 2021 12:57:32 +0300 Subject: [PATCH 2/5] vector-im/element-ios/issues/5009 - Moved files around, cleaned up imports and logs. --- Riot/Modules/Room/RoomViewController.m | 20 +++--- Riot/target.yml | 3 +- .../ShareExtensionRootViewController.h | 0 .../ShareExtensionRootViewController.m | 23 ++++--- .../FallbackViewController.h | 0 .../FallbackViewController.m | 0 .../FallbackViewController.xib | 0 .../RecentRoomTableViewCell.h | 0 .../RecentRoomTableViewCell.m | 0 .../RecentRoomTableViewCell.xib | 0 .../RoomsListViewController.h | 0 .../RoomsListViewController.m | 5 +- .../RoomsListViewController.xib | 0 .../DataSources => Shared}/ShareDataSource.h | 0 .../DataSources => Shared}/ShareDataSource.m | 1 - .../ShareManager.h} | 16 +++-- .../ShareManager.m} | 61 +++++++++---------- .../Share => Shared}/ShareViewController.h | 0 .../Share => Shared}/ShareViewController.m | 35 +++++------ .../Share => Shared}/ShareViewController.xib | 0 20 files changed, 76 insertions(+), 88 deletions(-) rename RiotShareExtension/{Modules/Main => }/ShareExtensionRootViewController.h (100%) rename RiotShareExtension/{Modules/Main => }/ShareExtensionRootViewController.m (70%) rename RiotShareExtension/{Modules/Fallback => Shared}/FallbackViewController.h (100%) rename RiotShareExtension/{Modules/Fallback => Shared}/FallbackViewController.m (100%) rename RiotShareExtension/{Modules/Fallback => Shared}/FallbackViewController.xib (100%) rename RiotShareExtension/{Modules/Share/Listing/Views => Shared}/RecentRoomTableViewCell.h (100%) rename RiotShareExtension/{Modules/Share/Listing/Views => Shared}/RecentRoomTableViewCell.m (100%) rename RiotShareExtension/{Modules/Share/Listing/Views => Shared}/RecentRoomTableViewCell.xib (100%) rename RiotShareExtension/{Modules/Share/Listing => Shared}/RoomsListViewController.h (100%) rename RiotShareExtension/{Modules/Share/Listing => Shared}/RoomsListViewController.m (98%) rename RiotShareExtension/{Modules/Share/Listing => Shared}/RoomsListViewController.xib (100%) rename RiotShareExtension/{Modules/Share/DataSources => Shared}/ShareDataSource.h (100%) rename RiotShareExtension/{Modules/Share/DataSources => Shared}/ShareDataSource.m (99%) rename RiotShareExtension/{Managers/ShareExtensionManager.h => Shared/ShareManager.h} (69%) rename RiotShareExtension/{Managers/ShareExtensionManager.m => Shared/ShareManager.m} (95%) rename RiotShareExtension/{Modules/Share => Shared}/ShareViewController.h (100%) rename RiotShareExtension/{Modules/Share => Shared}/ShareViewController.m (93%) rename RiotShareExtension/{Modules/Share => Shared}/ShareViewController.xib (100%) diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 7158ae820..bd7266eb9 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -108,7 +108,7 @@ #import "AvatarGenerator.h" #import "Tools.h" #import "WidgetManager.h" -#import "ShareExtensionManager.h" +#import "ShareManager.h" #import "GBDeviceInfo_iOS.h" @@ -252,7 +252,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; @property (nonatomic, strong) VoiceMessageController *voiceMessageController; @property (nonatomic, strong) SpaceDetailPresenter *spaceDetailPresenter; -@property (nonatomic, strong) ShareExtensionManager *shareExtensionManager; +@property (nonatomic, strong) ShareManager *shareManager; @property (nonatomic, strong) UserSuggestionCoordinatorBridge *userSuggestionCoordinator; @property (nonatomic, weak) IBOutlet UIView *userSuggestionContainerView; @@ -3201,17 +3201,17 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; NSExtensionItem *item = [[NSExtensionItem alloc] init]; item.attachments = @[[[NSItemProvider alloc] initWithItem:selectedComponent.textMessage typeIdentifier:(__bridge NSString *)kUTTypeText]]; - self.shareExtensionManager = [[ShareExtensionManager alloc] initWithShareExtensionContext:nil - extensionItems:@[item]]; + self.shareManager = [[ShareManager alloc] initWithShareExtensionContext:nil + extensionItems:@[item]]; MXWeakify(self); - [self.shareExtensionManager setCompletionCallback:^(ShareExtensionManagerResult result) { + [self.shareManager setCompletionCallback:^(ShareManagerResult result) { MXStrongifyAndReturnIfNil(self); [attachment onShareEnded]; [self dismissViewControllerAnimated:YES completion:nil]; }]; - [self presentViewController:self.shareExtensionManager.mainViewController animated:YES completion:nil]; + [self presentViewController:self.shareManager.mainViewController animated:YES completion:nil]; }]]; if (!isJitsiCallEvent) @@ -3414,17 +3414,17 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; NSExtensionItem *item = [[NSExtensionItem alloc] init]; item.attachments = @[[[NSItemProvider alloc] initWithItem:fileURL typeIdentifier:attachmentTypeToIdentifier[@(attachment.type)]]]; - self.shareExtensionManager = [[ShareExtensionManager alloc] initWithShareExtensionContext:nil - extensionItems:@[item]]; + self.shareManager = [[ShareManager alloc] initWithShareExtensionContext:nil + extensionItems:@[item]]; MXWeakify(self); - [self.shareExtensionManager setCompletionCallback:^(ShareExtensionManagerResult result) { + [self.shareManager setCompletionCallback:^(ShareManagerResult result) { MXStrongifyAndReturnIfNil(self); [attachment onShareEnded]; [self dismissViewControllerAnimated:YES completion:nil]; }]; - [self presentViewController:self.shareExtensionManager.mainViewController animated:YES completion:nil]; + [self presentViewController:self.shareManager.mainViewController animated:YES completion:nil]; } failure:^(NSError *error) { [self showError:error]; [self stopActivityIndicator]; diff --git a/Riot/target.yml b/Riot/target.yml index a4a61ac4c..0e9f722da 100644 --- a/Riot/target.yml +++ b/Riot/target.yml @@ -66,8 +66,7 @@ targets: excludes: - "Modules/Room/EmojiPicker/Data/EmojiMart/EmojiJSONStore.swift" - "**/*.strings" # Exclude all strings files - - path: ../RiotShareExtension/Managers - - path: ../RiotShareExtension/Modules + - path: ../RiotShareExtension/Shared # Add separately localizable files # Once a language has enough translations (>80%), it must be declared here diff --git a/RiotShareExtension/Modules/Main/ShareExtensionRootViewController.h b/RiotShareExtension/ShareExtensionRootViewController.h similarity index 100% rename from RiotShareExtension/Modules/Main/ShareExtensionRootViewController.h rename to RiotShareExtension/ShareExtensionRootViewController.h diff --git a/RiotShareExtension/Modules/Main/ShareExtensionRootViewController.m b/RiotShareExtension/ShareExtensionRootViewController.m similarity index 70% rename from RiotShareExtension/Modules/Main/ShareExtensionRootViewController.m rename to RiotShareExtension/ShareExtensionRootViewController.m index ad5178c41..bd5c5ad6b 100644 --- a/RiotShareExtension/Modules/Main/ShareExtensionRootViewController.m +++ b/RiotShareExtension/ShareExtensionRootViewController.m @@ -15,8 +15,7 @@ */ #import "ShareExtensionRootViewController.h" -#import "ShareViewController.h" -#import "ShareExtensionManager.h" +#import "ShareManager.h" #import "ThemeService.h" #ifdef IS_SHARE_EXTENSION @@ -27,7 +26,7 @@ @interface ShareExtensionRootViewController () -@property (nonatomic, strong, readonly) ShareExtensionManager *shareExtensionManager; +@property (nonatomic, strong, readonly) ShareManager *shareManager; @end @@ -39,24 +38,24 @@ [ThemeService.shared setThemeId:RiotSettings.shared.userInterfaceTheme]; - _shareExtensionManager = [[ShareExtensionManager alloc] initWithShareExtensionContext:self.extensionContext + _shareManager = [[ShareManager alloc] initWithShareExtensionContext:self.extensionContext extensionItems:self.extensionContext.inputItems]; MXWeakify(self); - [_shareExtensionManager setCompletionCallback:^(ShareExtensionManagerResult result) { + [_shareManager setCompletionCallback:^(ShareManagerResult result) { MXStrongifyAndReturnIfNil(self); switch (result) { - case ShareExtensionManagerResultFinished: + case ShareManagerResultFinished: [self.extensionContext completeRequestReturningItems:nil completionHandler:nil]; [self _dismiss]; break; - case ShareExtensionManagerResultCancelled: + case ShareManagerResultCancelled: [self.extensionContext cancelRequestWithError:[NSError errorWithDomain:@"MXUserCancelErrorDomain" code:4201 userInfo:nil]]; [self _dismiss]; break; - case ShareExtensionManagerResultFailed: + case ShareManagerResultFailed: [self.extensionContext cancelRequestWithError:[NSError errorWithDomain:@"MXFailureErrorDomain" code:500 userInfo:nil]]; [self _dismiss]; break; @@ -73,7 +72,7 @@ { [super viewWillAppear:animated]; - [self presentViewController:self.shareExtensionManager.mainViewController animated:YES completion:nil]; + [self presentViewController:self.shareManager.mainViewController animated:YES completion:nil]; } #pragma mark - Private @@ -83,9 +82,9 @@ [self dismissViewControllerAnimated:true completion:^{ [self.presentingViewController dismissViewControllerAnimated:false completion:nil]; -// // FIXME: Share extension memory usage increase when launched several times and then crash due to some memory leaks. -// // For now, we force the share extension to exit and free memory. -// [NSException raise:@"Kill the app extension" format:@"Free memory used by share extension"]; + // FIXME: Share extension memory usage increase when launched several times and then crash due to some memory leaks. + // For now, we force the share extension to exit and free memory. + [NSException raise:@"Kill the app extension" format:@"Free memory used by share extension"]; }]; } diff --git a/RiotShareExtension/Modules/Fallback/FallbackViewController.h b/RiotShareExtension/Shared/FallbackViewController.h similarity index 100% rename from RiotShareExtension/Modules/Fallback/FallbackViewController.h rename to RiotShareExtension/Shared/FallbackViewController.h diff --git a/RiotShareExtension/Modules/Fallback/FallbackViewController.m b/RiotShareExtension/Shared/FallbackViewController.m similarity index 100% rename from RiotShareExtension/Modules/Fallback/FallbackViewController.m rename to RiotShareExtension/Shared/FallbackViewController.m diff --git a/RiotShareExtension/Modules/Fallback/FallbackViewController.xib b/RiotShareExtension/Shared/FallbackViewController.xib similarity index 100% rename from RiotShareExtension/Modules/Fallback/FallbackViewController.xib rename to RiotShareExtension/Shared/FallbackViewController.xib diff --git a/RiotShareExtension/Modules/Share/Listing/Views/RecentRoomTableViewCell.h b/RiotShareExtension/Shared/RecentRoomTableViewCell.h similarity index 100% rename from RiotShareExtension/Modules/Share/Listing/Views/RecentRoomTableViewCell.h rename to RiotShareExtension/Shared/RecentRoomTableViewCell.h diff --git a/RiotShareExtension/Modules/Share/Listing/Views/RecentRoomTableViewCell.m b/RiotShareExtension/Shared/RecentRoomTableViewCell.m similarity index 100% rename from RiotShareExtension/Modules/Share/Listing/Views/RecentRoomTableViewCell.m rename to RiotShareExtension/Shared/RecentRoomTableViewCell.m diff --git a/RiotShareExtension/Modules/Share/Listing/Views/RecentRoomTableViewCell.xib b/RiotShareExtension/Shared/RecentRoomTableViewCell.xib similarity index 100% rename from RiotShareExtension/Modules/Share/Listing/Views/RecentRoomTableViewCell.xib rename to RiotShareExtension/Shared/RecentRoomTableViewCell.xib diff --git a/RiotShareExtension/Modules/Share/Listing/RoomsListViewController.h b/RiotShareExtension/Shared/RoomsListViewController.h similarity index 100% rename from RiotShareExtension/Modules/Share/Listing/RoomsListViewController.h rename to RiotShareExtension/Shared/RoomsListViewController.h diff --git a/RiotShareExtension/Modules/Share/Listing/RoomsListViewController.m b/RiotShareExtension/Shared/RoomsListViewController.m similarity index 98% rename from RiotShareExtension/Modules/Share/Listing/RoomsListViewController.m rename to RiotShareExtension/Shared/RoomsListViewController.m index 7a9e198f0..8f387a4d4 100644 --- a/RiotShareExtension/Modules/Share/Listing/RoomsListViewController.m +++ b/RiotShareExtension/Shared/RoomsListViewController.m @@ -14,13 +14,12 @@ limitations under the License. */ +#import + #import "RoomsListViewController.h" #import "RecentRoomTableViewCell.h" -#import "NSBundle+MatrixKit.h" -#import "ShareExtensionManager.h" #import "RecentCellData.h" #import "ThemeService.h" -#import #ifdef IS_SHARE_EXTENSION #import "RiotShareExtension-Swift.h" diff --git a/RiotShareExtension/Modules/Share/Listing/RoomsListViewController.xib b/RiotShareExtension/Shared/RoomsListViewController.xib similarity index 100% rename from RiotShareExtension/Modules/Share/Listing/RoomsListViewController.xib rename to RiotShareExtension/Shared/RoomsListViewController.xib diff --git a/RiotShareExtension/Modules/Share/DataSources/ShareDataSource.h b/RiotShareExtension/Shared/ShareDataSource.h similarity index 100% rename from RiotShareExtension/Modules/Share/DataSources/ShareDataSource.h rename to RiotShareExtension/Shared/ShareDataSource.h diff --git a/RiotShareExtension/Modules/Share/DataSources/ShareDataSource.m b/RiotShareExtension/Shared/ShareDataSource.m similarity index 99% rename from RiotShareExtension/Modules/Share/DataSources/ShareDataSource.m rename to RiotShareExtension/Shared/ShareDataSource.m index 802425ebc..b88a846a4 100644 --- a/RiotShareExtension/Modules/Share/DataSources/ShareDataSource.m +++ b/RiotShareExtension/Shared/ShareDataSource.m @@ -15,7 +15,6 @@ */ #import "ShareDataSource.h" -#import "ShareExtensionManager.h" #import "RecentRoomTableViewCell.h" @interface ShareDataSource () diff --git a/RiotShareExtension/Managers/ShareExtensionManager.h b/RiotShareExtension/Shared/ShareManager.h similarity index 69% rename from RiotShareExtension/Managers/ShareExtensionManager.h rename to RiotShareExtension/Shared/ShareManager.h index e8dc215e6..04b49bf48 100644 --- a/RiotShareExtension/Managers/ShareExtensionManager.h +++ b/RiotShareExtension/Shared/ShareManager.h @@ -16,17 +16,15 @@ #import -@class ShareExtensionManager; - -typedef NS_ENUM(NSUInteger, ShareExtensionManagerResult) { - ShareExtensionManagerResultFinished, - ShareExtensionManagerResultCancelled, - ShareExtensionManagerResultFailed +typedef NS_ENUM(NSUInteger, ShareManagerResult) { + ShareManagerResultFinished, + ShareManagerResultCancelled, + ShareManagerResultFailed }; -@interface ShareExtensionManager : NSObject +@interface ShareManager : NSObject -@property (nonatomic, copy) void (^completionCallback)(ShareExtensionManagerResult); +@property (nonatomic, copy) void (^completionCallback)(ShareManagerResult); - (instancetype)initWithShareExtensionContext:(NSExtensionContext *)shareExtensionContext extensionItems:(NSArray *)extensionItems; @@ -36,7 +34,7 @@ typedef NS_ENUM(NSUInteger, ShareExtensionManagerResult) { @end -@interface NSItemProvider (ShareExtensionManager) +@interface NSItemProvider (ShareManager) @property BOOL isLoaded; diff --git a/RiotShareExtension/Managers/ShareExtensionManager.m b/RiotShareExtension/Shared/ShareManager.m similarity index 95% rename from RiotShareExtension/Managers/ShareExtensionManager.m rename to RiotShareExtension/Shared/ShareManager.m index 6d2ab3cea..2aec891f4 100644 --- a/RiotShareExtension/Managers/ShareExtensionManager.m +++ b/RiotShareExtension/Shared/ShareManager.m @@ -14,16 +14,16 @@ limitations under the License. */ -#import "ShareExtensionManager.h" -#import "ShareViewController.h" -#import "ShareDataSource.h" +@import MobileCoreServices; + +#import "objc/runtime.h" +#import #import -@import MobileCoreServices; -#import "objc/runtime.h" -#include -#import +#import "ShareManager.h" +#import "ShareViewController.h" +#import "ShareDataSource.h" #ifdef IS_SHARE_EXTENSION #import "RiotShareExtension-Swift.h" @@ -41,7 +41,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) ImageCompressionModeLarge }; -@interface ShareExtensionManager () +@interface ShareManager () @property (nonatomic, strong, readonly) NSExtensionContext *shareExtensionContext; @property (nonatomic, strong, readonly) NSArray *extensionItems; @@ -60,7 +60,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) @end -@implementation ShareExtensionManager +@implementation ShareManager - (instancetype)initWithShareExtensionContext:(NSExtensionContext *)shareExtensionContext extensionItems:(NSArray *)extensionItems @@ -147,7 +147,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) }]; } failure:^(NSError *error) { - MXLogError(@"[ShareExtensionManager] Failed preparign matrix session"); + MXLogError(@"[ShareManager] Failed preparign matrix session"); }]; } @@ -155,7 +155,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) { if (self.completionCallback) { - self.completionCallback(ShareExtensionManagerResultCancelled); + self.completionCallback(ShareManagerResultCancelled); } } @@ -328,7 +328,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) } else { - MXLogDebug(@"[ShareExtensionManager] sendContentToRoom: failed to loadItemForTypeIdentifier. Error: %@", error); + MXLogError(@"[ShareManager] sendContentToRoom: failed to loadItemForTypeIdentifier. Error: %@", error); dispatch_group_leave(requestsGroup); } @@ -423,7 +423,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) { if (self.completionCallback) { - self.completionCallback(ShareExtensionManagerResultFinished); + self.completionCallback(ShareManagerResultFinished); } } }); @@ -439,7 +439,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) if (self.completionCallback) { - self.completionCallback(ShareExtensionManagerResultFailed); + self.completionCallback(ShareManagerResultFailed); } }]; @@ -683,7 +683,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) self.imageCompressionMode = ImageCompressionModeNone; } - MXLogDebug(@"[ShareExtensionManager] Send %lu image(s) without compression prompt using compression mode: %ld", (unsigned long)self.pendingImages.count, (long)self.imageCompressionMode); + MXLogDebug(@"[ShareManager] Send %lu image(s) without compression prompt using compression mode: %ld", (unsigned long)self.pendingImages.count, (long)self.imageCompressionMode); if (shareBlock) { @@ -874,8 +874,8 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) NSUInteger imageWidth = compressionSize.imageSize.width; NSUInteger imageHeight = compressionSize.imageSize.height; - MXLogDebug(@"[ShareExtensionManager] User choose image compression with output size %lu x %lu (output file size: %@)", (unsigned long)imageWidth, (unsigned long)imageHeight, fileSize); - MXLogDebug(@"[ShareExtensionManager] Number of images to send: %lu", (unsigned long)self.pendingImages.count); + MXLogDebug(@"[ShareManager] User choose image compression with output size %lu x %lu (output file size: %@)", (unsigned long)imageWidth, (unsigned long)imageHeight, fileSize); + MXLogDebug(@"[ShareManager] Number of images to send: %lu", (unsigned long)self.pendingImages.count); } // Log memory usage. @@ -894,11 +894,11 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) if (kerr == KERN_SUCCESS) { - MXLogDebug(@"[ShareExtensionManager] Memory in use (in MB): %f", memoryUsedInMegabytes); + MXLogDebug(@"[ShareManager] Memory in use (in MB): %f", memoryUsedInMegabytes); } else { - MXLogDebug(@"[ShareExtensionManager] Error with task_info(): %s", mach_error_string(kerr)); + MXLogDebug(@"[ShareManager] Error with task_info(): %s", mach_error_string(kerr)); } } @@ -940,7 +940,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) - (void)didReceiveMemoryWarning:(NSNotification*)notification { - MXLogDebug(@"[ShareExtensionManager] Did receive memory warning"); + MXLogDebug(@"[ShareManager] Did receive memory warning"); [self logMemoryUsage]; } @@ -951,7 +951,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) [self didStartSendingToRoom:room]; if (!text) { - MXLogDebug(@"[ShareExtensionManager] loadItemForTypeIdentifier: failed."); + MXLogError(@"[ShareManager] loadItemForTypeIdentifier: failed."); if (failureBlock) { failureBlock(nil); @@ -965,7 +965,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) successBlock(); } } failure:^(NSError *error) { - MXLogDebug(@"[ShareExtensionManager] sendTextMessage failed."); + MXLogError(@"[ShareManager] sendTextMessage failed with error %@", error); if (failureBlock) { failureBlock(error); @@ -978,7 +978,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) [self didStartSendingToRoom:room]; if (!fileUrl) { - MXLogDebug(@"[ShareExtensionManager] loadItemForTypeIdentifier: failed."); + MXLogError(@"[ShareManager] loadItemForTypeIdentifier: failed."); if (failureBlock) { failureBlock(nil); @@ -997,7 +997,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) successBlock(); } } failure:^(NSError *error) { - MXLogDebug(@"[ShareExtensionManager] sendFile failed."); + MXLogError(@"[ShareManager] sendFile failed with error %@", error); if (failureBlock) { failureBlock(error); @@ -1035,7 +1035,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) // Sanity check if (!mimeType) { - MXLogDebug(@"[ShareExtensionManager] sendImage failed. Cannot determine MIME type of %@", itemProvider); + MXLogError(@"[ShareManager] sendImage failed. Cannot determine MIME type of %@", itemProvider); if (failureBlock) { failureBlock(nil); @@ -1121,8 +1121,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) successBlock(); } } failure:^(NSError *error) { - - MXLogDebug(@"[ShareExtensionManager] sendImage failed."); + MXLogError(@"[ShareManager] sendImage failed with error %@", error); if (failureBlock) { failureBlock(error); @@ -1135,7 +1134,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) { if (imageDatas.count == 0 || imageDatas.count != itemProviders.count) { - MXLogDebug(@"[ShareExtensionManager] sendImages: no images to send."); + MXLogError(@"[ShareManager] sendImages: no images to send."); if (failureBlock) { @@ -1216,7 +1215,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) [self didStartSendingToRoom:room]; if (!videoLocalUrl) { - MXLogDebug(@"[ShareExtensionManager] loadItemForTypeIdentifier: failed."); + MXLogError(@"[ShareManager] loadItemForTypeIdentifier: failed."); if (failureBlock) { failureBlock(nil); @@ -1239,7 +1238,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) successBlock(); } } failure:^(NSError *error) { - MXLogDebug(@"[ShareExtensionManager] sendVideo failed."); + MXLogError(@"[ShareManager] Failed sending video with error %@", error); if (failureBlock) { failureBlock(error); @@ -1253,7 +1252,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) @end -@implementation NSItemProvider (ShareExtensionManager) +@implementation NSItemProvider (ShareManager) - (void)setIsLoaded:(BOOL)isLoaded { diff --git a/RiotShareExtension/Modules/Share/ShareViewController.h b/RiotShareExtension/Shared/ShareViewController.h similarity index 100% rename from RiotShareExtension/Modules/Share/ShareViewController.h rename to RiotShareExtension/Shared/ShareViewController.h diff --git a/RiotShareExtension/Modules/Share/ShareViewController.m b/RiotShareExtension/Shared/ShareViewController.m similarity index 93% rename from RiotShareExtension/Modules/Share/ShareViewController.m rename to RiotShareExtension/Shared/ShareViewController.m index 21cce2d00..148069665 100644 --- a/RiotShareExtension/Modules/Share/ShareViewController.m +++ b/RiotShareExtension/Shared/ShareViewController.m @@ -19,7 +19,6 @@ #import "RoomsListViewController.h" #import "FallbackViewController.h" #import "ShareDataSource.h" -#import "ShareExtensionManager.h" #import "ThemeService.h" @@ -96,6 +95,21 @@ [self configureViews]; } +- (void)showProgressIndicator +{ + if (!self.hudView) + { + self.parentViewController.view.userInteractionEnabled = NO; + self.hudView = [MXKPieChartHUD showLoadingHudOnView:self.view WithMessage:[VectorL10n sending]]; + [self.hudView setProgress:0.0]; + } +} + +- (void)setProgress:(CGFloat)progress +{ + [self.hudView setProgress:progress]; +} + #pragma mark - MXKRecentListViewControllerDelegate - (void)recentListViewController:(MXKRecentListViewController *)recentListViewController @@ -111,25 +125,6 @@ [self.delegate shareViewControllerDidRequestShare:self forRoomIdentifier:childInfo.childRoomId]; } -#pragma mark - ShareExtensionManagerDelegate - -- (void)showProgressIndicator -{ - dispatch_async(dispatch_get_main_queue(), ^{ - if (!self.hudView) - { - self.parentViewController.view.userInteractionEnabled = NO; - self.hudView = [MXKPieChartHUD showLoadingHudOnView:self.view WithMessage:[VectorL10n sending]]; - [self.hudView setProgress:0.0]; - } - }); -} - -- (void)setProgress:(CGFloat)progress -{ - [self.hudView setProgress:progress]; -} - #pragma mark - Private - (void)configureViews diff --git a/RiotShareExtension/Modules/Share/ShareViewController.xib b/RiotShareExtension/Shared/ShareViewController.xib similarity index 100% rename from RiotShareExtension/Modules/Share/ShareViewController.xib rename to RiotShareExtension/Shared/ShareViewController.xib From aa790b24a8d5d0d68bbe69ce4c1200f573c6d574 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Thu, 14 Oct 2021 14:35:36 +0300 Subject: [PATCH 3/5] vector-im/element-ios/issues/5009 - Fixed share extension setup and cleaned up code. --- Riot/Modules/Room/RoomViewController.m | 6 +- .../ShareExtensionRootViewController.m | 65 +- RiotShareExtension/Shared/ShareManager.h | 7 +- RiotShareExtension/Shared/ShareManager.m | 621 ++++++------------ 4 files changed, 247 insertions(+), 452 deletions(-) diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index bd7266eb9..928b44a88 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -3201,8 +3201,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; NSExtensionItem *item = [[NSExtensionItem alloc] init]; item.attachments = @[[[NSItemProvider alloc] initWithItem:selectedComponent.textMessage typeIdentifier:(__bridge NSString *)kUTTypeText]]; - self.shareManager = [[ShareManager alloc] initWithShareExtensionContext:nil - extensionItems:@[item]]; + self.shareManager = [[ShareManager alloc] initWithItems:@[item]]; MXWeakify(self); [self.shareManager setCompletionCallback:^(ShareManagerResult result) { @@ -3414,8 +3413,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; NSExtensionItem *item = [[NSExtensionItem alloc] init]; item.attachments = @[[[NSItemProvider alloc] initWithItem:fileURL typeIdentifier:attachmentTypeToIdentifier[@(attachment.type)]]]; - self.shareManager = [[ShareManager alloc] initWithShareExtensionContext:nil - extensionItems:@[item]]; + self.shareManager = [[ShareManager alloc] initWithItems:@[item]]; MXWeakify(self); [self.shareManager setCompletionCallback:^(ShareManagerResult result) { diff --git a/RiotShareExtension/ShareExtensionRootViewController.m b/RiotShareExtension/ShareExtensionRootViewController.m index bd5c5ad6b..b523d0fb6 100644 --- a/RiotShareExtension/ShareExtensionRootViewController.m +++ b/RiotShareExtension/ShareExtensionRootViewController.m @@ -32,45 +32,36 @@ @implementation ShareExtensionRootViewController -- (instancetype)init +- (void)viewDidLoad { - if(self = [super init]) { - - [ThemeService.shared setThemeId:RiotSettings.shared.userInterfaceTheme]; - - _shareManager = [[ShareManager alloc] initWithShareExtensionContext:self.extensionContext - extensionItems:self.extensionContext.inputItems]; - - MXWeakify(self); - [_shareManager setCompletionCallback:^(ShareManagerResult result) { - MXStrongifyAndReturnIfNil(self); - - switch (result) - { - case ShareManagerResultFinished: - [self.extensionContext completeRequestReturningItems:nil completionHandler:nil]; - [self _dismiss]; - break; - case ShareManagerResultCancelled: - [self.extensionContext cancelRequestWithError:[NSError errorWithDomain:@"MXUserCancelErrorDomain" code:4201 userInfo:nil]]; - [self _dismiss]; - break; - case ShareManagerResultFailed: - [self.extensionContext cancelRequestWithError:[NSError errorWithDomain:@"MXFailureErrorDomain" code:500 userInfo:nil]]; - [self _dismiss]; - break; - default: - break; - } - }]; - } + [super viewDidLoad]; - return self; -} - -- (void)viewWillAppear:(BOOL)animated -{ - [super viewWillAppear:animated]; + [ThemeService.shared setThemeId:RiotSettings.shared.userInterfaceTheme]; + + _shareManager = [[ShareManager alloc] initWithItems:self.extensionContext.inputItems]; + + MXWeakify(self); + [_shareManager setCompletionCallback:^(ShareManagerResult result) { + MXStrongifyAndReturnIfNil(self); + + switch (result) + { + case ShareManagerResultFinished: + [self.extensionContext completeRequestReturningItems:nil completionHandler:nil]; + [self _dismiss]; + break; + case ShareManagerResultCancelled: + [self.extensionContext cancelRequestWithError:[NSError errorWithDomain:@"MXUserCancelErrorDomain" code:4201 userInfo:nil]]; + [self _dismiss]; + break; + case ShareManagerResultFailed: + [self.extensionContext cancelRequestWithError:[NSError errorWithDomain:@"MXFailureErrorDomain" code:500 userInfo:nil]]; + [self _dismiss]; + break; + default: + break; + } + }]; [self presentViewController:self.shareManager.mainViewController animated:YES completion:nil]; } diff --git a/RiotShareExtension/Shared/ShareManager.h b/RiotShareExtension/Shared/ShareManager.h index 04b49bf48..b81233883 100644 --- a/RiotShareExtension/Shared/ShareManager.h +++ b/RiotShareExtension/Shared/ShareManager.h @@ -16,6 +16,8 @@ #import +NS_ASSUME_NONNULL_BEGIN + typedef NS_ENUM(NSUInteger, ShareManagerResult) { ShareManagerResultFinished, ShareManagerResultCancelled, @@ -26,8 +28,7 @@ typedef NS_ENUM(NSUInteger, ShareManagerResult) { @property (nonatomic, copy) void (^completionCallback)(ShareManagerResult); -- (instancetype)initWithShareExtensionContext:(NSExtensionContext *)shareExtensionContext - extensionItems:(NSArray *)extensionItems; +- (instancetype)initWithItems:(NSArray *)items; - (UIViewController *)mainViewController; @@ -39,3 +40,5 @@ typedef NS_ENUM(NSUInteger, ShareManagerResult) { @property BOOL isLoaded; @end + +NS_ASSUME_NONNULL_END diff --git a/RiotShareExtension/Shared/ShareManager.m b/RiotShareExtension/Shared/ShareManager.m index 2aec891f4..c34b841d1 100644 --- a/RiotShareExtension/Shared/ShareManager.m +++ b/RiotShareExtension/Shared/ShareManager.m @@ -43,13 +43,12 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) @interface ShareManager () -@property (nonatomic, strong, readonly) NSExtensionContext *shareExtensionContext; @property (nonatomic, strong, readonly) NSArray *extensionItems; +@property (nonatomic, strong, readonly) ShareViewController *shareViewController; @property (nonatomic, strong, readonly) NSMutableArray *pendingImages; @property (nonatomic, strong, readonly) NSMutableDictionary *imageUploadProgresses; @property (nonatomic, strong, readonly) id configuration; -@property (nonatomic, strong, readonly) ShareViewController *shareViewController; @property (nonatomic, strong) MXKAccount *userAccount; @property (nonatomic, strong) MXFileStore *fileStore; @@ -62,13 +61,11 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) @implementation ShareManager -- (instancetype)initWithShareExtensionContext:(NSExtensionContext *)shareExtensionContext - extensionItems:(NSArray *)extensionItems +- (instancetype)initWithItems:(NSArray *)items { if (self = [super init]) { - _shareExtensionContext = shareExtensionContext; - _extensionItems = extensionItems; + _extensionItems = items; _pendingImages = [NSMutableArray array]; _imageUploadProgresses = [NSMutableDictionary dictionary]; @@ -78,7 +75,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(checkUserAccount) name:NSExtensionHostWillEnterForegroundNotification object:nil]; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; - _configuration = [CommonConfiguration new]; + _configuration = [[CommonConfiguration alloc] init]; [_configuration setupSettings]; // NSLog -> console.log file when not debugging the app @@ -131,21 +128,12 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) [session setStore:self.fileStore success:^{ MXStrongifyAndReturnIfNil(session); + session.crypto.warnOnUnknowDevices = NO; // Do not warn for unknown devices. We have cross-signing now + MXRoom *selectedRoom = [MXRoom loadRoomFromStore:self.fileStore withRoomId:roomIdentifier matrixSession:session]; - - // Do not warn for unknown devices. We have cross-signing now - session.crypto.warnOnUnknowDevices = NO; - - [self _sendContentToRoom:selectedRoom failureBlock:^(NSError* error) { - NSString *title = [VectorL10n roomEventFailedToSend]; - if ([error.domain isEqualToString:MXEncryptingErrorDomain]) - { - title = [VectorL10n shareExtensionFailedToEncrypt]; - } - - [self _showFailureAlert:title]; + [self sendContentToRoom:selectedRoom success:nil failure:^(NSError *error){ + [self showFailureAlert:[VectorL10n roomEventFailedToSend]]; }]; - } failure:^(NSError *error) { MXLogError(@"[ShareManager] Failed preparign matrix session"); }]; @@ -153,15 +141,12 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) - (void)shareViewControllerDidRequestDismissal:(ShareViewController *)shareViewController { - if (self.completionCallback) - { - self.completionCallback(ShareManagerResultCancelled); - } + self.completionCallback(ShareManagerResultCancelled); } #pragma mark - Private -- (void)_sendContentToRoom:(MXRoom *)room failureBlock:(void(^)(NSError *error))failureBlock +- (void)sendContentToRoom:(MXRoom *)room success:(void(^)(void))success failure:(void(^)(NSError *))failure { [self resetPendingData]; @@ -176,17 +161,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) NSMutableArray *pendingImagesItemProviders = [NSMutableArray new]; // Used to keep NSItemProvider associated to pending images (used only when all items are images). __block NSError *firstRequestError = nil; - __block NSMutableArray *returningExtensionItems = [NSMutableArray new]; - dispatch_group_t requestsGroup = dispatch_group_create(); - - void (^requestSuccess)(NSExtensionItem*) = ^(NSExtensionItem *extensionItem) { - if (extensionItem && ![returningExtensionItems containsObject:extensionItem]) - { - [returningExtensionItems addObject:extensionItem]; - } - - dispatch_group_leave(requestsGroup); - }; + dispatch_group_t dispatchGroup = dispatch_group_create(); void (^requestFailure)(NSError*) = ^(NSError *requestError) { if (requestError && !firstRequestError) @@ -194,242 +169,171 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) firstRequestError = requestError; } - dispatch_group_leave(requestsGroup); + dispatch_group_leave(dispatchGroup); }; - __weak typeof(self) weakSelf = self; - + MXWeakify(self); for (NSExtensionItem *item in self.extensionItems) { for (NSItemProvider *itemProvider in item.attachments) { if ([itemProvider hasItemConformingToTypeIdentifier:UTTypeFileUrl]) { - dispatch_group_enter(requestsGroup); - + dispatch_group_enter(dispatchGroup); [itemProvider loadItemForTypeIdentifier:UTTypeFileUrl options:nil completionHandler:^(NSURL *fileUrl, NSError *error) { - - // Switch back on the main thread to handle correctly the UI change dispatch_async(dispatch_get_main_queue(), ^{ - - if (weakSelf) - { - typeof(self) self = weakSelf; - [self sendFileWithUrl:fileUrl - toRoom:room - successBlock:^{ - requestSuccess(item); - } failureBlock:requestFailure]; - } - + MXStrongifyAndReturnIfNil(self); + [self sendFileWithUrl:fileUrl toRoom:room successBlock:^{ + dispatch_group_leave(dispatchGroup); + } failureBlock:requestFailure]; }); }]; } else if ([itemProvider hasItemConformingToTypeIdentifier:UTTypeText]) { - dispatch_group_enter(requestsGroup); - - [itemProvider loadItemForTypeIdentifier:UTTypeText options:nil completionHandler:^(NSString *text, NSError * _Null_unspecified error) { - - // Switch back on the main thread to handle correctly the UI change + dispatch_group_enter(dispatchGroup); + [itemProvider loadItemForTypeIdentifier:UTTypeText options:nil completionHandler:^(NSString *text, NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ - - if (weakSelf) - { - typeof(self) self = weakSelf; - [self sendText:text - toRoom:room - successBlock:^{ - requestSuccess(item); - } failureBlock:requestFailure]; - } - + MXStrongifyAndReturnIfNil(self); + [self sendText:text toRoom:room successBlock:^{ + dispatch_group_leave(dispatchGroup); + } failureBlock:requestFailure]; }); }]; } else if ([itemProvider hasItemConformingToTypeIdentifier:UTTypeURL]) { - dispatch_group_enter(requestsGroup); - - [itemProvider loadItemForTypeIdentifier:UTTypeURL options:nil completionHandler:^(NSURL *url, NSError * _Null_unspecified error) { - - // Switch back on the main thread to handle correctly the UI change + dispatch_group_enter(dispatchGroup); + [itemProvider loadItemForTypeIdentifier:UTTypeURL options:nil completionHandler:^(NSURL *url, NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ - - if (weakSelf) - { - typeof(self) self = weakSelf; - [self sendText:url.absoluteString - toRoom:room - successBlock:^{ - requestSuccess(item); - } failureBlock:requestFailure]; - } - + MXStrongifyAndReturnIfNil(self); + [self sendText:url.absoluteString toRoom:room successBlock:^{ + dispatch_group_leave(dispatchGroup); + } failureBlock:requestFailure]; }); - }]; } else if ([itemProvider hasItemConformingToTypeIdentifier:UTTypeImage]) { - dispatch_group_enter(requestsGroup); - itemProvider.isLoaded = NO; - [itemProvider loadItemForTypeIdentifier:UTTypeImage options:nil completionHandler:^(id itemProviderItem, NSError *error) - { - if (weakSelf) - { - typeof(self) self = weakSelf; - itemProvider.isLoaded = YES; - - NSData *imageData; - - if ([(NSObject *)itemProviderItem isKindOfClass:[NSData class]]) - { - imageData = (NSData*)itemProviderItem; - } - else if ([(NSObject *)itemProviderItem isKindOfClass:[NSURL class]]) - { - NSURL *imageURL = (NSURL*)itemProviderItem; - imageData = [NSData dataWithContentsOfURL:imageURL]; - } - else if ([(NSObject *)itemProviderItem isKindOfClass:[UIImage class]]) - { - // An application can share directly an UIImage. - // The most common case is screenshot sharing without saving to file. - // As screenshot using PNG format when they are saved to file we also use PNG format when saving UIImage to NSData. - UIImage *image = (UIImage*)itemProviderItem; - imageData = UIImagePNGRepresentation(image); - } - - if (imageData) - { - if (areAllAttachmentsImages) - { - [self.pendingImages addObject:imageData]; - [pendingImagesItemProviders addObject:itemProvider]; - } - else - { - CGSize imageSize = [self imageSizeFromImageData:imageData]; - self.imageCompressionMode = ImageCompressionModeNone; - self.actualLargeSize = MAX(imageSize.width, imageSize.height); - - [self sendImageData:imageData - withProvider:itemProvider - toRoom:room - successBlock:^{ - requestSuccess(item); - } failureBlock:requestFailure]; - } - } - else - { - MXLogError(@"[ShareManager] sendContentToRoom: failed to loadItemForTypeIdentifier. Error: %@", error); - dispatch_group_leave(requestsGroup); - } - - // Only prompt for image resize if all items are images - // Ignore showMediaCompressionPrompt setting due to memory constraints with full size images. - if (areAllAttachmentsImages) - { - if ([self areAttachmentsFullyLoaded]) - { - UIAlertController *compressionPrompt = [self compressionPromptForPendingImagesWithShareBlock:^{ - [self sendImageDatas:self.pendingImages - withProviders:pendingImagesItemProviders - toRoom:room - successBlock:^{ - requestSuccess(item); - } failureBlock:requestFailure]; - }]; - - if (compressionPrompt) - { - [self presentCompressionPrompt:compressionPrompt]; - } - } - else - { - dispatch_group_leave(requestsGroup); - } - } - } - }]; + dispatch_group_enter(dispatchGroup); + [itemProvider loadItemForTypeIdentifier:UTTypeImage options:nil completionHandler:^(id itemProviderItem, NSError *error) { + MXStrongifyAndReturnIfNil(self); + + itemProvider.isLoaded = YES; + + NSData *imageData; + if ([(NSObject *)itemProviderItem isKindOfClass:[NSData class]]) + { + imageData = (NSData*)itemProviderItem; + } + else if ([(NSObject *)itemProviderItem isKindOfClass:[NSURL class]]) + { + NSURL *imageURL = (NSURL*)itemProviderItem; + imageData = [NSData dataWithContentsOfURL:imageURL]; + } + else if ([(NSObject *)itemProviderItem isKindOfClass:[UIImage class]]) + { + // An application can share directly an UIImage. + // The most common case is screenshot sharing without saving to file. + // As screenshot using PNG format when they are saved to file we also use PNG format when saving UIImage to NSData. + UIImage *image = (UIImage*)itemProviderItem; + imageData = UIImagePNGRepresentation(image); + } + + if (imageData) + { + if (areAllAttachmentsImages) + { + [self.pendingImages addObject:imageData]; + [pendingImagesItemProviders addObject:itemProvider]; + } + else + { + CGSize imageSize = [self imageSizeFromImageData:imageData]; + self.imageCompressionMode = ImageCompressionModeNone; + self.actualLargeSize = MAX(imageSize.width, imageSize.height); + + [self sendImageData:imageData withProvider:itemProvider toRoom:room successBlock:^{ + dispatch_group_leave(dispatchGroup); + } failureBlock:requestFailure]; + } + } + else + { + MXLogError(@"[ShareManager] sendContentToRoom: failed to loadItemForTypeIdentifier. Error: %@", error); + dispatch_group_leave(dispatchGroup); + } + + // Only prompt for image resize if all items are images + // Ignore showMediaCompressionPrompt setting due to memory constraints with full size images. + if (areAllAttachmentsImages) + { + if ([self areAttachmentsFullyLoaded]) + { + UIAlertController *compressionPrompt = [self compressionPromptForPendingImagesWithShareBlock:^{ + [self sendImageDatas:self.pendingImages withProviders:pendingImagesItemProviders toRoom:room successBlock:^{ + dispatch_group_leave(dispatchGroup); + } failureBlock:requestFailure]; + }]; + + if (compressionPrompt) + { + [self presentCompressionPrompt:compressionPrompt]; + } + } + else + { + dispatch_group_leave(dispatchGroup); + } + } + }]; } else if ([itemProvider hasItemConformingToTypeIdentifier:UTTypeVideo]) { - dispatch_group_enter(requestsGroup); - - [itemProvider loadItemForTypeIdentifier:UTTypeVideo options:nil completionHandler:^(NSURL *videoLocalUrl, NSError * _Null_unspecified error) { - - // Switch back on the main thread to handle correctly the UI change + dispatch_group_enter(dispatchGroup); + [itemProvider loadItemForTypeIdentifier:UTTypeVideo options:nil completionHandler:^(NSURL *videoLocalUrl, NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ - - if (weakSelf) - { - typeof(self) self = weakSelf; - [self sendVideo:videoLocalUrl - toRoom:room - successBlock:^{ - requestSuccess(item); - } failureBlock:requestFailure]; - } - + MXStrongifyAndReturnIfNil(self); + [self sendVideo:videoLocalUrl toRoom:room successBlock:^{ + dispatch_group_leave(dispatchGroup); + } failureBlock:requestFailure]; }); - }]; } else if ([itemProvider hasItemConformingToTypeIdentifier:UTTypeMovie]) { - dispatch_group_enter(requestsGroup); - - [itemProvider loadItemForTypeIdentifier:UTTypeMovie options:nil completionHandler:^(NSURL *videoLocalUrl, NSError * _Null_unspecified error) { - - // Switch back on the main thread to handle correctly the UI change + dispatch_group_enter(dispatchGroup); + [itemProvider loadItemForTypeIdentifier:UTTypeMovie options:nil completionHandler:^(NSURL *videoLocalUrl, NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ - - if (weakSelf) - { - typeof(self) self = weakSelf; - [self sendVideo:videoLocalUrl - toRoom:room - successBlock:^{ - requestSuccess(item); - } failureBlock:requestFailure]; - } - + MXStrongifyAndReturnIfNil(self); + [self sendVideo:videoLocalUrl toRoom:room successBlock:^{ + dispatch_group_leave(dispatchGroup); + } failureBlock:requestFailure]; }); - - }]; + }]; } } } - dispatch_group_notify(requestsGroup, dispatch_get_main_queue(), ^{ + dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{ [self resetPendingData]; if (firstRequestError) { - if (failureBlock) - { - failureBlock(firstRequestError); - } + failure(firstRequestError); } else { - if (self.completionCallback) - { - self.completionCallback(ShareManagerResultFinished); - } + self.completionCallback(ShareManagerResultFinished); } }); } -- (void)_showFailureAlert:(NSString *)title +- (void)showFailureAlert:(NSString *)title { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleAlert]; @@ -534,145 +438,14 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) return nil; } - UIAlertController *compressionPrompt; BOOL isAPendingImageNotOrientedUp = [self isAPendingImageNotOrientedUp]; NSData *firstImageData = self.pendingImages.firstObject; UIImage *firstImage = [UIImage imageWithData:firstImageData]; - // Get available sizes for this image MXKImageCompressionSizes compressionSizes = [MXKTools availableCompressionSizesForImage:firstImage originalFileSize:firstImageData.length]; - // Apply the compression mode - if (compressionSizes.small.fileSize || compressionSizes.medium.fileSize || compressionSizes.large.fileSize) - { - __weak typeof(self) weakSelf = self; - - compressionPrompt = [UIAlertController alertControllerWithTitle:[MatrixKitL10n attachmentSizePromptTitle] - message:[MatrixKitL10n attachmentSizePromptMessage] - preferredStyle:UIAlertControllerStyleActionSheet]; - - if (compressionSizes.small.fileSize) - { - NSString *fileSizeString = [MXTools fileSizeToString:compressionSizes.small.fileSize]; - - NSString *title = [MatrixKitL10n attachmentSmall:fileSizeString]; - - [compressionPrompt addAction:[UIAlertAction actionWithTitle:title - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - // Send the small image - self.imageCompressionMode = ImageCompressionModeSmall; - - [self logCompressionSizeChoice:compressionSizes.large]; - - if (shareBlock) - { - shareBlock(); - } - } - - }]]; - } - - if (compressionSizes.medium.fileSize) - { - NSString *fileSizeString = [MXTools fileSizeToString:compressionSizes.medium.fileSize]; - - NSString *title = [MatrixKitL10n attachmentMedium:fileSizeString]; - - [compressionPrompt addAction:[UIAlertAction actionWithTitle:title - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - // Send the medium image - self.imageCompressionMode = ImageCompressionModeMedium; - - [self logCompressionSizeChoice:compressionSizes.large]; - - if (shareBlock) - { - shareBlock(); - } - } - - }]]; - } - - // Do not offer the possibility to resize an image with a dimension above kLargeImageSizeMaxDimension, to prevent the risk of memory limit exception. - // TODO: Remove this condition when issue https://github.com/vector-im/riot-ios/issues/2341 will be fixed. - if (compressionSizes.large.fileSize && (MAX(compressionSizes.large.imageSize.width, compressionSizes.large.imageSize.height) <= kLargeImageSizeMaxDimension)) - { - NSString *fileSizeString = [MXTools fileSizeToString:compressionSizes.large.fileSize]; - - NSString *title = [MatrixKitL10n attachmentLarge:fileSizeString]; - - [compressionPrompt addAction:[UIAlertAction actionWithTitle:title - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - // Send the large image - self.imageCompressionMode = ImageCompressionModeLarge; - self.actualLargeSize = compressionSizes.actualLargeSize; - - [self logCompressionSizeChoice:compressionSizes.large]; - - if (shareBlock) - { - shareBlock(); - } - } - - }]]; - } - - // To limit memory consumption, we suggest the original resolution only if the image orientation is up, or if the image size is moderate - if (!isAPendingImageNotOrientedUp || !compressionSizes.large.fileSize) - { - NSString *fileSizeString = [MXTools fileSizeToString:compressionSizes.original.fileSize]; - - NSString *title = [MatrixKitL10n attachmentOriginal:fileSizeString]; - - [compressionPrompt addAction:[UIAlertAction actionWithTitle:title - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - self.imageCompressionMode = ImageCompressionModeNone; - - [self logCompressionSizeChoice:compressionSizes.large]; - if (shareBlock) - { - shareBlock(); - } - } - - }]]; - } - - [compressionPrompt addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] - style:UIAlertActionStyleCancel - handler:nil]]; - - - } - else + if (compressionSizes.small.fileSize == 0 && compressionSizes.medium.fileSize == 0 && compressionSizes.large.fileSize == 0) { if (isAPendingImageNotOrientedUp && self.pendingImages.count > 1) { @@ -685,12 +458,86 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) MXLogDebug(@"[ShareManager] Send %lu image(s) without compression prompt using compression mode: %ld", (unsigned long)self.pendingImages.count, (long)self.imageCompressionMode); - if (shareBlock) - { - shareBlock(); - } + shareBlock(); + + return nil; } + UIAlertController *compressionPrompt = [UIAlertController alertControllerWithTitle:[MatrixKitL10n attachmentSizePromptTitle] + message:[MatrixKitL10n attachmentSizePromptMessage] + preferredStyle:UIAlertControllerStyleActionSheet]; + + if (compressionSizes.small.fileSize) + { + NSString *title = [MatrixKitL10n attachmentSmall:[MXTools fileSizeToString:compressionSizes.small.fileSize]]; + + MXWeakify(self); + [compressionPrompt addAction:[UIAlertAction actionWithTitle:title style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + + self.imageCompressionMode = ImageCompressionModeSmall; + [self logCompressionSizeChoice:compressionSizes.large]; + + shareBlock(); + }]]; + } + + if (compressionSizes.medium.fileSize) + { + NSString *title = [MatrixKitL10n attachmentMedium:[MXTools fileSizeToString:compressionSizes.medium.fileSize]]; + + MXWeakify(self); + [compressionPrompt addAction:[UIAlertAction actionWithTitle:title style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + + self.imageCompressionMode = ImageCompressionModeMedium; + [self logCompressionSizeChoice:compressionSizes.large]; + + shareBlock(); + }]]; + } + + // Do not offer the possibility to resize an image with a dimension above kLargeImageSizeMaxDimension, to prevent the risk of memory limit exception. + // TODO: Remove this condition when issue https://github.com/vector-im/riot-ios/issues/2341 will be fixed. + if (compressionSizes.large.fileSize && (MAX(compressionSizes.large.imageSize.width, compressionSizes.large.imageSize.height) <= kLargeImageSizeMaxDimension)) + { + NSString *title = [MatrixKitL10n attachmentLarge:[MXTools fileSizeToString:compressionSizes.large.fileSize]]; + + MXWeakify(self); + [compressionPrompt addAction:[UIAlertAction actionWithTitle:title style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + + self.imageCompressionMode = ImageCompressionModeLarge; + self.actualLargeSize = compressionSizes.actualLargeSize; + + [self logCompressionSizeChoice:compressionSizes.large]; + + shareBlock(); + }]]; + } + + // To limit memory consumption, we suggest the original resolution only if the image orientation is up, or if the image size is moderate + if (!isAPendingImageNotOrientedUp || !compressionSizes.large.fileSize) + { + NSString *fileSizeString = [MXTools fileSizeToString:compressionSizes.original.fileSize]; + + NSString *title = [MatrixKitL10n attachmentOriginal:fileSizeString]; + + MXWeakify(self); + [compressionPrompt addAction:[UIAlertAction actionWithTitle:title style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + + self.imageCompressionMode = ImageCompressionModeNone; + [self logCompressionSizeChoice:compressionSizes.large]; + + shareBlock(); + }]]; + } + + [compressionPrompt addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] + style:UIAlertActionStyleCancel + handler:nil]]; + return compressionPrompt; } @@ -952,24 +799,15 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) if (!text) { MXLogError(@"[ShareManager] loadItemForTypeIdentifier: failed."); - if (failureBlock) - { - failureBlock(nil); - } + failureBlock(nil); return; } [room sendTextMessage:text success:^(NSString *eventId) { - if (successBlock) - { - successBlock(); - } + successBlock(); } failure:^(NSError *error) { MXLogError(@"[ShareManager] sendTextMessage failed with error %@", error); - if (failureBlock) - { - failureBlock(error); - } + failureBlock(error); }]; } @@ -979,10 +817,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) if (!fileUrl) { MXLogError(@"[ShareManager] loadItemForTypeIdentifier: failed."); - if (failureBlock) - { - failureBlock(nil); - } + failureBlock(nil); return; } @@ -992,16 +827,10 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) CFRelease(uti); [room sendFile:fileUrl mimeType:mimeType localEcho:nil success:^(NSString *eventId) { - if (successBlock) - { - successBlock(); - } + successBlock(); } failure:^(NSError *error) { MXLogError(@"[ShareManager] sendFile failed with error %@", error); - if (failureBlock) - { - failureBlock(error); - } + failureBlock(error); } keepActualFilename:YES]; } @@ -1116,17 +945,10 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) } [room sendImage:finalImageData withImageSize:imageSize mimeType:mimeType andThumbnail:thumbnail localEcho:nil success:^(NSString *eventId) { - if (successBlock) - { - successBlock(); - } + successBlock(); } failure:^(NSError *error) { MXLogError(@"[ShareManager] sendImage failed with error %@", error); - if (failureBlock) - { - failureBlock(error); - } - + failureBlock(error); }]; } @@ -1135,11 +957,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) if (imageDatas.count == 0 || imageDatas.count != itemProviders.count) { MXLogError(@"[ShareManager] sendImages: no images to send."); - - if (failureBlock) - { - failureBlock(nil); - } + failureBlock(nil); return; } @@ -1178,17 +996,11 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) if (firstRequestError) { - if (failureBlock) - { - failureBlock(firstRequestError); - } + failureBlock(firstRequestError); } else { - if (successBlock) - { - successBlock(); - } + successBlock(); } }); } @@ -1216,10 +1028,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) if (!videoLocalUrl) { MXLogError(@"[ShareManager] loadItemForTypeIdentifier: failed."); - if (failureBlock) - { - failureBlock(nil); - } + failureBlock(nil); return; } @@ -1233,16 +1042,10 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) CFRelease(imageRef); [room sendVideoAsset:videoAsset withThumbnail:videoThumbnail localEcho:nil success:^(NSString *eventId) { - if (successBlock) - { - successBlock(); - } + successBlock(); } failure:^(NSError *error) { MXLogError(@"[ShareManager] Failed sending video with error %@", error); - if (failureBlock) - { - failureBlock(error); - } + failureBlock(error); }]; }]; From e3f1bd25a98b3c85eed73e389837a6dd7484f74b Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Fri, 15 Oct 2021 13:09:11 +0300 Subject: [PATCH 4/5] vector-im/element-ios/issues/5009 - Refactored away the NSExtensionContext dependency from the ShareManager. Introduced different ShareItemProviders for the share extension and the main application. Improved item loading error handling. --- Riot/Modules/Room/RoomViewController.m | 20 +- .../ShareItemProviderProtocol.swift | 35 ++ .../SimpleShareItemProvider.swift | 95 ++++ RiotShareExtension/Shared/ShareManager.h | 4 +- RiotShareExtension/Shared/ShareManager.m | 502 +++++++++--------- .../{ => View}/FallbackViewController.h | 0 .../{ => View}/FallbackViewController.m | 0 .../{ => View}/FallbackViewController.xib | 0 .../{ => View}/RecentRoomTableViewCell.h | 0 .../{ => View}/RecentRoomTableViewCell.m | 0 .../{ => View}/RecentRoomTableViewCell.xib | 0 .../{ => View}/RoomsListViewController.h | 0 .../{ => View}/RoomsListViewController.m | 0 .../{ => View}/RoomsListViewController.xib | 0 .../Shared/{ => View}/ShareViewController.h | 0 .../Shared/{ => View}/ShareViewController.m | 0 .../Shared/{ => View}/ShareViewController.xib | 5 +- .../ShareExtensionRootViewController.h | 0 .../ShareExtensionRootViewController.m | 3 +- .../ShareExtensionShareItemProvider.swift | 134 +++++ changelog.d/5009.feature | 1 + 21 files changed, 525 insertions(+), 274 deletions(-) create mode 100644 RiotShareExtension/Shared/ShareItemProvider/ShareItemProviderProtocol.swift create mode 100644 RiotShareExtension/Shared/ShareItemProvider/SimpleShareItemProvider.swift rename RiotShareExtension/Shared/{ => View}/FallbackViewController.h (100%) rename RiotShareExtension/Shared/{ => View}/FallbackViewController.m (100%) rename RiotShareExtension/Shared/{ => View}/FallbackViewController.xib (100%) rename RiotShareExtension/Shared/{ => View}/RecentRoomTableViewCell.h (100%) rename RiotShareExtension/Shared/{ => View}/RecentRoomTableViewCell.m (100%) rename RiotShareExtension/Shared/{ => View}/RecentRoomTableViewCell.xib (100%) rename RiotShareExtension/Shared/{ => View}/RoomsListViewController.h (100%) rename RiotShareExtension/Shared/{ => View}/RoomsListViewController.m (100%) rename RiotShareExtension/Shared/{ => View}/RoomsListViewController.xib (100%) rename RiotShareExtension/Shared/{ => View}/ShareViewController.h (100%) rename RiotShareExtension/Shared/{ => View}/ShareViewController.m (100%) rename RiotShareExtension/Shared/{ => View}/ShareViewController.xib (97%) rename RiotShareExtension/{ => Sources}/ShareExtensionRootViewController.h (100%) rename RiotShareExtension/{ => Sources}/ShareExtensionRootViewController.m (92%) create mode 100644 RiotShareExtension/Sources/ShareExtensionShareItemProvider.swift create mode 100644 changelog.d/5009.feature diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 928b44a88..96b2a2065 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -3198,16 +3198,14 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionForward] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - NSExtensionItem *item = [[NSExtensionItem alloc] init]; - item.attachments = @[[[NSItemProvider alloc] initWithItem:selectedComponent.textMessage typeIdentifier:(__bridge NSString *)kUTTypeText]]; - - self.shareManager = [[ShareManager alloc] initWithItems:@[item]]; + self.shareManager = [[ShareManager alloc] initWithShareItemProvider:[[SimpleShareItemProvider alloc] initWithTextMessage:selectedComponent.textMessage]]; MXWeakify(self); [self.shareManager setCompletionCallback:^(ShareManagerResult result) { MXStrongifyAndReturnIfNil(self); [attachment onShareEnded]; [self dismissViewControllerAnimated:YES completion:nil]; + self.shareManager = nil; }]; [self presentViewController:self.shareManager.mainViewController animated:YES completion:nil]; @@ -3395,31 +3393,25 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; if (attachment.type == MXKAttachmentTypeFile || attachment.type == MXKAttachmentTypeImage || - attachment.type == MXKAttachmentTypeVideo) { + attachment.type == MXKAttachmentTypeVideo || + attachment.type == MXKAttachmentTypeVoiceMessage) { [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionForward] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - - NSDictionary *attachmentTypeToIdentifier = @{@(MXKAttachmentTypeFile): (__bridge NSString *)kUTTypeFileURL, - @(MXKAttachmentTypeImage): (__bridge NSString *)kUTTypeImage, - @(MXKAttachmentTypeVideo): (__bridge NSString *)kUTTypeVideo}; - [self startActivityIndicator]; [attachment prepareShare:^(NSURL *fileURL) { [self stopActivityIndicator]; - NSExtensionItem *item = [[NSExtensionItem alloc] init]; - item.attachments = @[[[NSItemProvider alloc] initWithItem:fileURL typeIdentifier:attachmentTypeToIdentifier[@(attachment.type)]]]; - - self.shareManager = [[ShareManager alloc] initWithItems:@[item]]; + self.shareManager = [[ShareManager alloc] initWithShareItemProvider:[[SimpleShareItemProvider alloc] initWithAttachment:attachment]]; MXWeakify(self); [self.shareManager setCompletionCallback:^(ShareManagerResult result) { MXStrongifyAndReturnIfNil(self); [attachment onShareEnded]; [self dismissViewControllerAnimated:YES completion:nil]; + self.shareManager = nil; }]; [self presentViewController:self.shareManager.mainViewController animated:YES completion:nil]; diff --git a/RiotShareExtension/Shared/ShareItemProvider/ShareItemProviderProtocol.swift b/RiotShareExtension/Shared/ShareItemProvider/ShareItemProviderProtocol.swift new file mode 100644 index 000000000..417fea577 --- /dev/null +++ b/RiotShareExtension/Shared/ShareItemProvider/ShareItemProviderProtocol.swift @@ -0,0 +1,35 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +@objc public enum ShareItemType: UInt { + case fileURL, text, URL, image, video, movie, voiceMessage, unknown +} + +@objc public protocol ShareItemProtocol { + var type: ShareItemType { get } +} + +@objc public protocol ShareItemProviderProtocol { + var items: [ShareItemProtocol] { get } + + func areAllItemsImages() -> Bool + + func areAllItemsLoaded() -> Bool + + func loadItem(_ item: ShareItemProtocol, completion: @escaping (Any?, Error?) -> Void) +} diff --git a/RiotShareExtension/Shared/ShareItemProvider/SimpleShareItemProvider.swift b/RiotShareExtension/Shared/ShareItemProvider/SimpleShareItemProvider.swift new file mode 100644 index 000000000..261c160fe --- /dev/null +++ b/RiotShareExtension/Shared/ShareItemProvider/SimpleShareItemProvider.swift @@ -0,0 +1,95 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +private class SimpleShareItem: ShareItemProtocol { + let attachment: MXKAttachment? + let textMessage: String? + + init(withAttachment attachment: MXKAttachment) { + self.attachment = attachment + self.textMessage = nil + } + + init(withTextMessage textMessage: String) { + self.attachment = nil + self.textMessage = textMessage + } + + var type: ShareItemType { + guard textMessage == nil else { + return .text + } + + guard let attachment = attachment else { + return .unknown + } + + if attachment.type == MXKAttachmentTypeImage { + return .image + } else if attachment.type == MXKAttachmentTypeVideo { + return .video + } else if attachment.type == MXKAttachmentTypeFile { + return .fileURL + } else if attachment.type == MXKAttachmentTypeVoiceMessage { + return .voiceMessage + } else { + return .unknown + } + } +} + +@objc class SimpleShareItemProvider: NSObject, ShareItemProviderProtocol { + + private let attachment: MXKAttachment? + private let textMessage: String? + + let items: [ShareItemProtocol] + + @objc public init(withAttachment attachment: MXKAttachment) { + self.attachment = attachment + self.items = [SimpleShareItem(withAttachment: attachment)]; + self.textMessage = nil + } + + @objc public init(withTextMessage textMessage: String) { + self.textMessage = textMessage + self.items = [SimpleShareItem(withTextMessage: textMessage)]; + self.attachment = nil + } + + func loadItem(_ item: ShareItemProtocol, completion: @escaping (Any?, Error?) -> Void) { + if let textMessage = self.textMessage { + completion(textMessage, nil) + return + } + + attachment?.prepareShare({ url in + completion(url, nil) + }, failure: { error in + completion(nil, error) + }) + } + + func areAllItemsLoaded() -> Bool { + return true + } + + func areAllItemsImages() -> Bool { + return (attachment != nil && attachment?.type == MXKAttachmentTypeImage) + } +} diff --git a/RiotShareExtension/Shared/ShareManager.h b/RiotShareExtension/Shared/ShareManager.h index b81233883..965653d00 100644 --- a/RiotShareExtension/Shared/ShareManager.h +++ b/RiotShareExtension/Shared/ShareManager.h @@ -16,6 +16,8 @@ #import +@protocol ShareItemProviderProtocol; + NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(NSUInteger, ShareManagerResult) { @@ -28,7 +30,7 @@ typedef NS_ENUM(NSUInteger, ShareManagerResult) { @property (nonatomic, copy) void (^completionCallback)(ShareManagerResult); -- (instancetype)initWithItems:(NSArray *)items; +- (instancetype)initWithShareItemProvider:(id)shareItemProvider; - (UIViewController *)mainViewController; diff --git a/RiotShareExtension/Shared/ShareManager.m b/RiotShareExtension/Shared/ShareManager.m index c34b841d1..c5ebeba6b 100644 --- a/RiotShareExtension/Shared/ShareManager.m +++ b/RiotShareExtension/Shared/ShareManager.m @@ -16,7 +16,6 @@ @import MobileCoreServices; -#import "objc/runtime.h" #import #import @@ -43,11 +42,11 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) @interface ShareManager () -@property (nonatomic, strong, readonly) NSArray *extensionItems; +@property (nonatomic, strong, readonly) id shareItemProvider; @property (nonatomic, strong, readonly) ShareViewController *shareViewController; -@property (nonatomic, strong, readonly) NSMutableArray *pendingImages; -@property (nonatomic, strong, readonly) NSMutableDictionary *imageUploadProgresses; +@property (nonatomic, strong, readonly) NSMutableArray *pendingImages; +@property (nonatomic, strong, readonly) NSMutableDictionary *imageUploadProgresses; @property (nonatomic, strong, readonly) id configuration; @property (nonatomic, strong) MXKAccount *userAccount; @@ -61,11 +60,11 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) @implementation ShareManager -- (instancetype)initWithItems:(NSArray *)items +- (instancetype)initWithShareItemProvider:(id)shareItemProvider { - if (self = [super init]) { - - _extensionItems = items; + if (self = [super init]) + { + _shareItemProvider = shareItemProvider; _pendingImages = [NSMutableArray array]; _imageUploadProgresses = [NSMutableDictionary dictionary]; @@ -150,16 +149,8 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) { [self resetPendingData]; - NSString *UTTypeText = (__bridge NSString *)kUTTypeText; - NSString *UTTypeURL = (__bridge NSString *)kUTTypeURL; - NSString *UTTypeImage = (__bridge NSString *)kUTTypeImage; - NSString *UTTypeVideo = (__bridge NSString *)kUTTypeVideo; - NSString *UTTypeFileUrl = (__bridge NSString *)kUTTypeFileURL; - NSString *UTTypeMovie = (__bridge NSString *)kUTTypeMovie; + NSMutableArray > *pendingImagesItemProviders = [NSMutableArray array]; // Used to keep the items associated to pending images (used only when all items are images). - BOOL areAllAttachmentsImages = [self areAllAttachmentsImages]; - NSMutableArray *pendingImagesItemProviders = [NSMutableArray new]; // Used to keep NSItemProvider associated to pending images (used only when all items are images). - __block NSError *firstRequestError = nil; dispatch_group_t dispatchGroup = dispatch_group_create(); @@ -173,149 +164,198 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) }; MXWeakify(self); - for (NSExtensionItem *item in self.extensionItems) + for (id item in self.shareItemProvider.items) { - for (NSItemProvider *itemProvider in item.attachments) - { - if ([itemProvider hasItemConformingToTypeIdentifier:UTTypeFileUrl]) - { - dispatch_group_enter(dispatchGroup); - [itemProvider loadItemForTypeIdentifier:UTTypeFileUrl options:nil completionHandler:^(NSURL *fileUrl, NSError *error) { - dispatch_async(dispatch_get_main_queue(), ^{ - MXStrongifyAndReturnIfNil(self); - [self sendFileWithUrl:fileUrl toRoom:room successBlock:^{ - dispatch_group_leave(dispatchGroup); - } failureBlock:requestFailure]; - }); - - }]; - } - else if ([itemProvider hasItemConformingToTypeIdentifier:UTTypeText]) - { - dispatch_group_enter(dispatchGroup); - [itemProvider loadItemForTypeIdentifier:UTTypeText options:nil completionHandler:^(NSString *text, NSError *error) { - dispatch_async(dispatch_get_main_queue(), ^{ - MXStrongifyAndReturnIfNil(self); - [self sendText:text toRoom:room successBlock:^{ - dispatch_group_leave(dispatchGroup); - } failureBlock:requestFailure]; - }); - - }]; - } - else if ([itemProvider hasItemConformingToTypeIdentifier:UTTypeURL]) - { - dispatch_group_enter(dispatchGroup); - [itemProvider loadItemForTypeIdentifier:UTTypeURL options:nil completionHandler:^(NSURL *url, NSError *error) { - dispatch_async(dispatch_get_main_queue(), ^{ - MXStrongifyAndReturnIfNil(self); - [self sendText:url.absoluteString toRoom:room successBlock:^{ - dispatch_group_leave(dispatchGroup); - } failureBlock:requestFailure]; - }); - }]; - } - else if ([itemProvider hasItemConformingToTypeIdentifier:UTTypeImage]) - { - itemProvider.isLoaded = NO; + if (item.type == ShareItemTypeFileURL) { + dispatch_group_enter(dispatchGroup); + [self.shareItemProvider loadItem:item completion:^(NSURL *url, NSError *error) { + if (error) { + requestFailure(error); + dispatch_group_leave(dispatchGroup); + return; + } - dispatch_group_enter(dispatchGroup); - [itemProvider loadItemForTypeIdentifier:UTTypeImage options:nil completionHandler:^(id itemProviderItem, NSError *error) { + dispatch_async(dispatch_get_main_queue(), ^{ MXStrongifyAndReturnIfNil(self); - - itemProvider.isLoaded = YES; - - NSData *imageData; - if ([(NSObject *)itemProviderItem isKindOfClass:[NSData class]]) + [self sendFileWithUrl:url toRoom:room success:^{ + dispatch_group_leave(dispatchGroup); + } failure:requestFailure]; + }); + }]; + } + + if (item.type == ShareItemTypeText) { + dispatch_group_enter(dispatchGroup); + [self.shareItemProvider loadItem:item completion:^(NSString *text, NSError *error) { + if (error) { + requestFailure(error); + dispatch_group_leave(dispatchGroup); + return; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + MXStrongifyAndReturnIfNil(self); + [self sendText:text toRoom:room success:^{ + dispatch_group_leave(dispatchGroup); + } failure:requestFailure]; + }); + }]; + } + + if (item.type == ShareItemTypeURL) + { + dispatch_group_enter(dispatchGroup); + [self.shareItemProvider loadItem:item completion:^(NSURL *url, NSError *error) { + if (error) { + requestFailure(error); + dispatch_group_leave(dispatchGroup); + return; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + MXStrongifyAndReturnIfNil(self); + [self sendText:url.absoluteString toRoom:room success:^{ + dispatch_group_leave(dispatchGroup); + } failure:requestFailure]; + }); + }]; + } + + if (item.type == ShareItemTypeImage) + { + dispatch_group_enter(dispatchGroup); + [self.shareItemProvider loadItem:item completion:^(id itemProviderItem, NSError *error) { + if (error) { + requestFailure(error); + dispatch_group_leave(dispatchGroup); + return; + } + + NSData *imageData; + if ([(NSObject *)itemProviderItem isKindOfClass:[NSData class]]) + { + imageData = (NSData*)itemProviderItem; + } + else if ([(NSObject *)itemProviderItem isKindOfClass:[NSURL class]]) + { + NSURL *imageURL = (NSURL*)itemProviderItem; + imageData = [NSData dataWithContentsOfURL:imageURL]; + } + else if ([(NSObject *)itemProviderItem isKindOfClass:[UIImage class]]) + { + // An application can share directly an UIImage. + // The most common case is screenshot sharing without saving to file. + // As screenshot using PNG format when they are saved to file we also use PNG format when saving UIImage to NSData. + UIImage *image = (UIImage*)itemProviderItem; + imageData = UIImagePNGRepresentation(image); + } + + MXStrongifyAndReturnIfNil(self); + + if (imageData) + { + if ([self.shareItemProvider areAllItemsImages]) { - imageData = (NSData*)itemProviderItem; + [self.pendingImages addObject:imageData]; + [pendingImagesItemProviders addObject:item]; } - else if ([(NSObject *)itemProviderItem isKindOfClass:[NSURL class]]) + else { - NSURL *imageURL = (NSURL*)itemProviderItem; - imageData = [NSData dataWithContentsOfURL:imageURL]; + CGSize imageSize = [self imageSizeFromImageData:imageData]; + self.imageCompressionMode = ImageCompressionModeNone; + self.actualLargeSize = MAX(imageSize.width, imageSize.height); + + [self sendImageData:imageData withItem:item toRoom:room success:^{ + dispatch_group_leave(dispatchGroup); + } failure:requestFailure]; } - else if ([(NSObject *)itemProviderItem isKindOfClass:[UIImage class]]) + } + else + { + MXLogError(@"[ShareManager] sendContentToRoom: failed to loadItemForTypeIdentifier. Error: %@", error); + dispatch_group_leave(dispatchGroup); + } + + // Only prompt for image resize if all items are images + // Ignore showMediaCompressionPrompt setting due to memory constraints with full size images. + if ([self.shareItemProvider areAllItemsImages]) + { + if ([self.shareItemProvider areAllItemsLoaded]) { - // An application can share directly an UIImage. - // The most common case is screenshot sharing without saving to file. - // As screenshot using PNG format when they are saved to file we also use PNG format when saving UIImage to NSData. - UIImage *image = (UIImage*)itemProviderItem; - imageData = UIImagePNGRepresentation(image); - } - - if (imageData) - { - if (areAllAttachmentsImages) - { - [self.pendingImages addObject:imageData]; - [pendingImagesItemProviders addObject:itemProvider]; - } - else - { - CGSize imageSize = [self imageSizeFromImageData:imageData]; - self.imageCompressionMode = ImageCompressionModeNone; - self.actualLargeSize = MAX(imageSize.width, imageSize.height); - - [self sendImageData:imageData withProvider:itemProvider toRoom:room successBlock:^{ + UIAlertController *compressionPrompt = [self compressionPromptForPendingImagesWithShareBlock:^{ + [self sendImageDatas:self.pendingImages.copy withItems:pendingImagesItemProviders toRoom:room success:^{ dispatch_group_leave(dispatchGroup); - } failureBlock:requestFailure]; + } failure:requestFailure]; + }]; + + if (compressionPrompt) + { + [self presentCompressionPrompt:compressionPrompt]; } } else { - MXLogError(@"[ShareManager] sendContentToRoom: failed to loadItemForTypeIdentifier. Error: %@", error); dispatch_group_leave(dispatchGroup); } - - // Only prompt for image resize if all items are images - // Ignore showMediaCompressionPrompt setting due to memory constraints with full size images. - if (areAllAttachmentsImages) - { - if ([self areAttachmentsFullyLoaded]) - { - UIAlertController *compressionPrompt = [self compressionPromptForPendingImagesWithShareBlock:^{ - [self sendImageDatas:self.pendingImages withProviders:pendingImagesItemProviders toRoom:room successBlock:^{ - dispatch_group_leave(dispatchGroup); - } failureBlock:requestFailure]; - }]; - - if (compressionPrompt) - { - [self presentCompressionPrompt:compressionPrompt]; - } - } - else - { - dispatch_group_leave(dispatchGroup); - } - } - }]; - } - else if ([itemProvider hasItemConformingToTypeIdentifier:UTTypeVideo]) - { - dispatch_group_enter(dispatchGroup); - [itemProvider loadItemForTypeIdentifier:UTTypeVideo options:nil completionHandler:^(NSURL *videoLocalUrl, NSError *error) { - dispatch_async(dispatch_get_main_queue(), ^{ - MXStrongifyAndReturnIfNil(self); - [self sendVideo:videoLocalUrl toRoom:room successBlock:^{ - dispatch_group_leave(dispatchGroup); - } failureBlock:requestFailure]; - }); - }]; - } - else if ([itemProvider hasItemConformingToTypeIdentifier:UTTypeMovie]) - { - dispatch_group_enter(dispatchGroup); - [itemProvider loadItemForTypeIdentifier:UTTypeMovie options:nil completionHandler:^(NSURL *videoLocalUrl, NSError *error) { - dispatch_async(dispatch_get_main_queue(), ^{ - MXStrongifyAndReturnIfNil(self); - [self sendVideo:videoLocalUrl toRoom:room successBlock:^{ - dispatch_group_leave(dispatchGroup); - } failureBlock:requestFailure]; - }); - }]; - } + } + }]; + } + + if (item.type == ShareItemTypeVideo) + { + dispatch_group_enter(dispatchGroup); + [self.shareItemProvider loadItem:item completion:^(NSURL *videoLocalUrl, NSError *error) { + if (error) { + requestFailure(error); + dispatch_group_leave(dispatchGroup); + return; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + MXStrongifyAndReturnIfNil(self); + [self sendVideo:videoLocalUrl toRoom:room success:^{ + dispatch_group_leave(dispatchGroup); + } failure:requestFailure]; + }); + }]; + } + + if (item.type == ShareItemTypeMovie) + { + dispatch_group_enter(dispatchGroup); + [self.shareItemProvider loadItem:item completion:^(NSURL *videoLocalUrl, NSError *error) { + if (error) { + requestFailure(error); + dispatch_group_leave(dispatchGroup); + return; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + MXStrongifyAndReturnIfNil(self); + [self sendVideo:videoLocalUrl toRoom:room success:^{ + dispatch_group_leave(dispatchGroup); + } failure:requestFailure]; + }); + }]; + } + + if (item.type == ShareItemTypeVoiceMessage) + { + dispatch_group_enter(dispatchGroup); + [self.shareItemProvider loadItem:item completion:^(NSURL *fileURL, NSError *error) { + if (error) { + requestFailure(error); + dispatch_group_leave(dispatchGroup); + return; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + MXStrongifyAndReturnIfNil(self); + [self sendVoiceMessage:fileURL toRoom:room success:^{ + dispatch_group_leave(dispatchGroup); + } failure:requestFailure]; + }); + }]; } } @@ -546,59 +586,6 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) [self.shareViewController showProgressIndicator]; } -- (BOOL)areAttachmentsFullyLoaded -{ - for (NSExtensionItem *item in self.extensionItems) - { - for (NSItemProvider *itemProvider in item.attachments) - { - if (itemProvider.isLoaded == NO) - { - return NO; - } - } - } - return YES; -} - -- (BOOL)areAllAttachmentsImages -{ - for (NSExtensionItem *item in self.extensionItems) - { - for (NSItemProvider *itemProvider in item.attachments) - { - if (![itemProvider hasItemConformingToTypeIdentifier:(__bridge NSString *)kUTTypeImage]) - { - return NO; - } - } - } - return YES; -} - -- (NSString*)utiFromImageTypeItemProvider:(NSItemProvider*)itemProvider -{ - NSString *uti; - - NSString *utiPNG = (__bridge NSString *)kUTTypePNG; - NSString *utiJPEG = (__bridge NSString *)kUTTypeJPEG; - - if ([itemProvider hasItemConformingToTypeIdentifier:utiPNG]) - { - uti = utiPNG; - } - else if ([itemProvider hasItemConformingToTypeIdentifier:utiJPEG]) - { - uti = utiJPEG; - } - else - { - uti = itemProvider.registeredTypeIdentifiers.firstObject; - } - - return uti; -} - - (NSString*)utiFromImageData:(NSData*)imageData { CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)imageData, NULL); @@ -793,31 +780,36 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) #pragma mark - Sharing -- (void)sendText:(NSString *)text toRoom:(MXRoom *)room successBlock:(dispatch_block_t)successBlock failureBlock:(void(^)(NSError *error))failureBlock +- (void)sendText:(NSString *)text + toRoom:(MXRoom *)room + success:(dispatch_block_t)success + failure:(void(^)(NSError *error))failure { [self didStartSendingToRoom:room]; if (!text) { - MXLogError(@"[ShareManager] loadItemForTypeIdentifier: failed."); - failureBlock(nil); + MXLogError(@"[ShareManager] Invalid text."); + failure(nil); return; } [room sendTextMessage:text success:^(NSString *eventId) { - successBlock(); + success(); } failure:^(NSError *error) { MXLogError(@"[ShareManager] sendTextMessage failed with error %@", error); - failureBlock(error); + failure(error); }]; } -- (void)sendFileWithUrl:(NSURL *)fileUrl toRoom:(MXRoom *)room successBlock:(dispatch_block_t)successBlock failureBlock:(void(^)(NSError *error))failureBlock +- (void)sendFileWithUrl:(NSURL *)fileUrl toRoom:(MXRoom *)room + success:(dispatch_block_t)success + failure:(void(^)(NSError *error))failure { [self didStartSendingToRoom:room]; if (!fileUrl) { - MXLogError(@"[ShareManager] loadItemForTypeIdentifier: failed."); - failureBlock(nil); + MXLogError(@"[ShareManager] Invalid file url."); + failure(nil); return; } @@ -827,47 +819,39 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) CFRelease(uti); [room sendFile:fileUrl mimeType:mimeType localEcho:nil success:^(NSString *eventId) { - successBlock(); + success(); } failure:^(NSError *error) { MXLogError(@"[ShareManager] sendFile failed with error %@", error); - failureBlock(error); + failure(error); } keepActualFilename:YES]; } -- (void)sendImageData:(NSData *)imageData withProvider:(NSItemProvider*)itemProvider toRoom:(MXRoom *)room successBlock:(dispatch_block_t)successBlock failureBlock:(void(^)(NSError *error))failureBlock +- (void)sendImageData:(NSData *)imageData + withItem:(id)item + toRoom:(MXRoom *)room + success:(dispatch_block_t)success + failure:(void(^)(NSError *error))failure { [self didStartSendingToRoom:room]; NSString *imageUTI; NSString *mimeType; - // Try to get UTI plus mime type from NSItemProvider - imageUTI = [self utiFromImageTypeItemProvider:itemProvider]; - - if (imageUTI) - { - mimeType = [self mimeTypeFromUTI:imageUTI]; - } - if (!mimeType) { - // Try to get UTI plus mime type from image data - imageUTI = [self utiFromImageData:imageData]; - if (imageUTI) { mimeType = [self mimeTypeFromUTI:imageUTI]; } } - // Sanity check if (!mimeType) { - MXLogError(@"[ShareManager] sendImage failed. Cannot determine MIME type of %@", itemProvider); - if (failureBlock) + MXLogError(@"[ShareManager] sendImage failed. Cannot determine MIME type of %@", item); + if (failure) { - failureBlock(nil); + failure(nil); } return; } @@ -945,19 +929,22 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) } [room sendImage:finalImageData withImageSize:imageSize mimeType:mimeType andThumbnail:thumbnail localEcho:nil success:^(NSString *eventId) { - successBlock(); + success(); } failure:^(NSError *error) { MXLogError(@"[ShareManager] sendImage failed with error %@", error); - failureBlock(error); + failure(error); }]; } -- (void)sendImageDatas:(NSMutableArray *)imageDatas withProviders:(NSArray*)itemProviders toRoom:(MXRoom *)room successBlock:(dispatch_block_t)successBlock failureBlock:(void(^)(NSError *error))failureBlock +- (void)sendImageDatas:(NSArray> *)imageDatas + withItems:(NSArray> *)items toRoom:(MXRoom *)room + success:(dispatch_block_t)success + failure:(void(^)(NSError *error))failure { - if (imageDatas.count == 0 || imageDatas.count != itemProviders.count) + if (imageDatas.count == 0 || imageDatas.count != items.count) { MXLogError(@"[ShareManager] sendImages: no images to send."); - failureBlock(nil); + failure(nil); return; } @@ -973,13 +960,9 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) @autoreleasepool { dispatch_group_enter(requestsGroup); - - NSItemProvider *itemProvider = itemProviders[index]; - - [self sendImageData:imageData withProvider:itemProvider toRoom:room successBlock:^{ + [self sendImageData:imageData withItem:items[index] toRoom:room success:^{ dispatch_group_leave(requestsGroup); - } failureBlock:^(NSError *error) { - + } failure:^(NSError *error) { if (error && !firstRequestError) { firstRequestError = error; @@ -996,16 +979,19 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) if (firstRequestError) { - failureBlock(firstRequestError); + failure(firstRequestError); } else { - successBlock(); + success(); } }); } -- (void)sendVideo:(NSURL *)videoLocalUrl toRoom:(MXRoom *)room successBlock:(dispatch_block_t)successBlock failureBlock:(void(^)(NSError *error))failureBlock +- (void)sendVideo:(NSURL *)videoLocalUrl + toRoom:(MXRoom *)room + success:(dispatch_block_t)success + failure:(void(^)(NSError *error))failure { AVURLAsset *videoAsset = [[AVURLAsset alloc] initWithURL:videoLocalUrl options:nil]; @@ -1027,8 +1013,8 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) [self didStartSendingToRoom:room]; if (!videoLocalUrl) { - MXLogError(@"[ShareManager] loadItemForTypeIdentifier: failed."); - failureBlock(nil); + MXLogError(@"[ShareManager] Invalid video file url."); + failure(nil); return; } @@ -1042,31 +1028,35 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) CFRelease(imageRef); [room sendVideoAsset:videoAsset withThumbnail:videoThumbnail localEcho:nil success:^(NSString *eventId) { - successBlock(); + success(); } failure:^(NSError *error) { MXLogError(@"[ShareManager] Failed sending video with error %@", error); - failureBlock(error); + failure(error); }]; }]; [self presentCompressionPrompt:compressionPrompt]; } -@end - - -@implementation NSItemProvider (ShareManager) - -- (void)setIsLoaded:(BOOL)isLoaded +- (void)sendVoiceMessage:(NSURL *)fileUrl + toRoom:(MXRoom *)room + success:(dispatch_block_t)success + failure:(void(^)(NSError *error))failure { - NSNumber *number = @(isLoaded); - objc_setAssociatedObject(self, @selector(isLoaded), number, OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} - -- (BOOL)isLoaded -{ - NSNumber *number = objc_getAssociatedObject(self, @selector(isLoaded)); - return number.boolValue; + [self didStartSendingToRoom:room]; + if (!fileUrl) + { + MXLogError(@"[ShareManager] Invalid voice message file url."); + failure(nil); + return; + } + + [room sendVoiceMessage:fileUrl mimeType:nil duration:0.0 samples:nil localEcho:nil success:^(NSString *eventId) { + success(); + } failure:^(NSError *error) { + MXLogError(@"[ShareManager] sendVoiceMessage failed with error %@", error); + failure(error); + } keepActualFilename:YES]; } @end diff --git a/RiotShareExtension/Shared/FallbackViewController.h b/RiotShareExtension/Shared/View/FallbackViewController.h similarity index 100% rename from RiotShareExtension/Shared/FallbackViewController.h rename to RiotShareExtension/Shared/View/FallbackViewController.h diff --git a/RiotShareExtension/Shared/FallbackViewController.m b/RiotShareExtension/Shared/View/FallbackViewController.m similarity index 100% rename from RiotShareExtension/Shared/FallbackViewController.m rename to RiotShareExtension/Shared/View/FallbackViewController.m diff --git a/RiotShareExtension/Shared/FallbackViewController.xib b/RiotShareExtension/Shared/View/FallbackViewController.xib similarity index 100% rename from RiotShareExtension/Shared/FallbackViewController.xib rename to RiotShareExtension/Shared/View/FallbackViewController.xib diff --git a/RiotShareExtension/Shared/RecentRoomTableViewCell.h b/RiotShareExtension/Shared/View/RecentRoomTableViewCell.h similarity index 100% rename from RiotShareExtension/Shared/RecentRoomTableViewCell.h rename to RiotShareExtension/Shared/View/RecentRoomTableViewCell.h diff --git a/RiotShareExtension/Shared/RecentRoomTableViewCell.m b/RiotShareExtension/Shared/View/RecentRoomTableViewCell.m similarity index 100% rename from RiotShareExtension/Shared/RecentRoomTableViewCell.m rename to RiotShareExtension/Shared/View/RecentRoomTableViewCell.m diff --git a/RiotShareExtension/Shared/RecentRoomTableViewCell.xib b/RiotShareExtension/Shared/View/RecentRoomTableViewCell.xib similarity index 100% rename from RiotShareExtension/Shared/RecentRoomTableViewCell.xib rename to RiotShareExtension/Shared/View/RecentRoomTableViewCell.xib diff --git a/RiotShareExtension/Shared/RoomsListViewController.h b/RiotShareExtension/Shared/View/RoomsListViewController.h similarity index 100% rename from RiotShareExtension/Shared/RoomsListViewController.h rename to RiotShareExtension/Shared/View/RoomsListViewController.h diff --git a/RiotShareExtension/Shared/RoomsListViewController.m b/RiotShareExtension/Shared/View/RoomsListViewController.m similarity index 100% rename from RiotShareExtension/Shared/RoomsListViewController.m rename to RiotShareExtension/Shared/View/RoomsListViewController.m diff --git a/RiotShareExtension/Shared/RoomsListViewController.xib b/RiotShareExtension/Shared/View/RoomsListViewController.xib similarity index 100% rename from RiotShareExtension/Shared/RoomsListViewController.xib rename to RiotShareExtension/Shared/View/RoomsListViewController.xib diff --git a/RiotShareExtension/Shared/ShareViewController.h b/RiotShareExtension/Shared/View/ShareViewController.h similarity index 100% rename from RiotShareExtension/Shared/ShareViewController.h rename to RiotShareExtension/Shared/View/ShareViewController.h diff --git a/RiotShareExtension/Shared/ShareViewController.m b/RiotShareExtension/Shared/View/ShareViewController.m similarity index 100% rename from RiotShareExtension/Shared/ShareViewController.m rename to RiotShareExtension/Shared/View/ShareViewController.m diff --git a/RiotShareExtension/Shared/ShareViewController.xib b/RiotShareExtension/Shared/View/ShareViewController.xib similarity index 97% rename from RiotShareExtension/Shared/ShareViewController.xib rename to RiotShareExtension/Shared/View/ShareViewController.xib index 1718d4501..c6eaf5feb 100644 --- a/RiotShareExtension/Shared/ShareViewController.xib +++ b/RiotShareExtension/Shared/View/ShareViewController.xib @@ -1,9 +1,9 @@ - + - + @@ -55,6 +55,7 @@ + diff --git a/RiotShareExtension/ShareExtensionRootViewController.h b/RiotShareExtension/Sources/ShareExtensionRootViewController.h similarity index 100% rename from RiotShareExtension/ShareExtensionRootViewController.h rename to RiotShareExtension/Sources/ShareExtensionRootViewController.h diff --git a/RiotShareExtension/ShareExtensionRootViewController.m b/RiotShareExtension/Sources/ShareExtensionRootViewController.m similarity index 92% rename from RiotShareExtension/ShareExtensionRootViewController.m rename to RiotShareExtension/Sources/ShareExtensionRootViewController.m index b523d0fb6..c2be442a0 100644 --- a/RiotShareExtension/ShareExtensionRootViewController.m +++ b/RiotShareExtension/Sources/ShareExtensionRootViewController.m @@ -38,7 +38,8 @@ [ThemeService.shared setThemeId:RiotSettings.shared.userInterfaceTheme]; - _shareManager = [[ShareManager alloc] initWithItems:self.extensionContext.inputItems]; + ShareExtensionShareItemProvider *provider = [[ShareExtensionShareItemProvider alloc] initWithExtensionContext:self.extensionContext]; + _shareManager = [[ShareManager alloc] initWithShareItemProvider:provider]; MXWeakify(self); [_shareManager setCompletionCallback:^(ShareManagerResult result) { diff --git a/RiotShareExtension/Sources/ShareExtensionShareItemProvider.swift b/RiotShareExtension/Sources/ShareExtensionShareItemProvider.swift new file mode 100644 index 000000000..e9ec4b6f2 --- /dev/null +++ b/RiotShareExtension/Sources/ShareExtensionShareItemProvider.swift @@ -0,0 +1,134 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import MobileCoreServices + +let UTTypeText = kUTTypeText as String +let UTTypeURL = kUTTypeURL as String +let UTTypeFileUrl = kUTTypeFileURL as String +let UTTypeImage = kUTTypeImage as String +let UTTypeVideo = kUTTypeVideo as String +let UTTypeMovie = kUTTypeMovie as String + +private class ShareExtensionItem: ShareItemProtocol { + let itemProvider: NSItemProvider + + var loaded = false + + init(itemProvider: NSItemProvider) { + self.itemProvider = itemProvider + } + + var type: ShareItemType { + if itemProvider.hasItemConformingToTypeIdentifier(UTTypeText) { + return .text + } else if itemProvider.hasItemConformingToTypeIdentifier(UTTypeURL) { + return .URL + } else if itemProvider.hasItemConformingToTypeIdentifier(UTTypeFileUrl) { + return .fileURL + } else if itemProvider.hasItemConformingToTypeIdentifier(UTTypeImage) { + return .image + } else if itemProvider.hasItemConformingToTypeIdentifier(UTTypeVideo) { + return .video + } else if itemProvider.hasItemConformingToTypeIdentifier(UTTypeMovie) { + return .movie + } + + return .unknown + } +} + +@objcMembers +class ShareExtensionShareItemProvider: NSObject, ShareItemProviderProtocol { + + public let items: [ShareItemProtocol] + + public init(extensionContext: NSExtensionContext) { + + var items: [ShareItemProtocol] = [] + for case let extensionItem as NSExtensionItem in extensionContext.inputItems { + guard let attachments = extensionItem.attachments else { + continue; + } + + for itemProvider in attachments { + items.append(ShareExtensionItem(itemProvider: itemProvider)) + } + } + self.items = items + } + + func areAllItemsLoaded() -> Bool { + for case let item as ShareExtensionItem in self.items { + if !item.loaded { + return false + } + } + + return true + } + + func areAllItemsImages() -> Bool { + for case let item as ShareExtensionItem in self.items { + if item.type != .image { + return false + } + } + + return true + } + + func loadItem(_ item: ShareItemProtocol, completion: @escaping (Any?, Error?) -> Void) { + guard let shareExtensionItem = item as? ShareExtensionItem else { + fatalError("[ShareExtensionShareItemProvider] Unexpected item type.") + } + + let typeIdentifier = typeIdentifierForType(item.type) + + shareExtensionItem.loaded = false + shareExtensionItem.itemProvider.loadItem(forTypeIdentifier: typeIdentifier, options: nil) { result, error in + if error == nil { + shareExtensionItem.loaded = true + } + + completion(result, error) + } + } + + // MARK: - Private + + private func typeIdentifierForType(_ type: ShareItemType) -> String { + switch type { + case .text: + return UTTypeText + case .URL: + return UTTypeURL + case .fileURL: + return UTTypeFileUrl + case .image: + return UTTypeImage + case .video: + return UTTypeVideo + case .movie: + return UTTypeMovie + case .voiceMessage: + return UTTypeFileUrl + default: + return "" + } + } +} diff --git a/changelog.d/5009.feature b/changelog.d/5009.feature new file mode 100644 index 000000000..55e904a3b --- /dev/null +++ b/changelog.d/5009.feature @@ -0,0 +1 @@ +Implemented message forwarding from within the main application. Updated the share extension designs. \ No newline at end of file From 12c167ba6c47e1e96a9d7ea71334923e7585be5d Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Mon, 18 Oct 2021 16:30:32 +0300 Subject: [PATCH 5/5] vector-im/element-ios/issues/5009 - Implemented multi-room forwarding and added various tweaks following code review. --- .../SharedImages.xcassets/Contents.json | 6 +- .../Contents.json | 23 + .../radio-button-default.png | Bin 0 -> 615 bytes .../radio-button-default@2x.png | Bin 0 -> 1153 bytes .../radio-button-default@3x.png | Bin 0 -> 1705 bytes .../Contents.json | 23 + .../radio-button-selected.png | Bin 0 -> 729 bytes .../radio-button-selected@2x.png | Bin 0 -> 1348 bytes .../radio-button-selected@3x.png | Bin 0 -> 2072 bytes Riot/Generated/Images.swift | 2 + Riot/Modules/Room/RoomViewController.m | 62 +- RiotShareExtension/Shared/ShareDataSource.h | 30 +- RiotShareExtension/Shared/ShareDataSource.m | 39 +- .../SimpleShareItemProvider.swift | 20 +- RiotShareExtension/Shared/ShareManager.h | 14 +- RiotShareExtension/Shared/ShareManager.m | 664 +++++++++--------- .../Shared/View/RecentRoomTableViewCell.h | 2 + .../Shared/View/RecentRoomTableViewCell.m | 14 + .../Shared/View/RecentRoomTableViewCell.xib | 31 +- .../Shared/View/RoomsListViewController.m | 17 + .../Shared/View/ShareViewController.h | 7 +- .../Shared/View/ShareViewController.m | 99 ++- .../Shared/View/ShareViewController.xib | 2 +- .../ShareExtensionRootViewController.m | 11 +- .../ShareExtensionShareItemProvider.swift | 38 +- 25 files changed, 620 insertions(+), 484 deletions(-) create mode 100644 Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/Contents.json create mode 100644 Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/radio-button-default.png create mode 100644 Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/radio-button-default@2x.png create mode 100644 Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/radio-button-default@3x.png create mode 100644 Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/Contents.json create mode 100644 Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/radio-button-selected.png create mode 100644 Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/radio-button-selected@2x.png create mode 100644 Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/radio-button-selected@3x.png diff --git a/Riot/Assets/SharedImages.xcassets/Contents.json b/Riot/Assets/SharedImages.xcassets/Contents.json index da4a164c9..73c00596a 100644 --- a/Riot/Assets/SharedImages.xcassets/Contents.json +++ b/Riot/Assets/SharedImages.xcassets/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/Contents.json b/Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/Contents.json new file mode 100644 index 000000000..35812152a --- /dev/null +++ b/Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "radio-button-default.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "radio-button-default@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "radio-button-default@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/radio-button-default.png b/Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/radio-button-default.png new file mode 100644 index 0000000000000000000000000000000000000000..84e419079478efbbd5b03ef22c5b6c750ec45e79 GIT binary patch literal 615 zcmV-t0+{`YP)p&1avqt&2U+LibR&Zl+xe`GLTtQ3+PywU@k_tix&;g`^MM6ms4<&S9I^Y}R9d9{b zK%EKcQpASQUM8d#4c-ifr*#bxy(_7b6JBTJCKQn zP0NNa2Bsa>x6ZHvx_^J7pgJyFQmx(XpuJQ;_Z|-=8;K>$aOeLB1)zI(HASAbVby>S z!ur3f?A=U>EVln}2g%R&*mhCtVjqw*Hz1Qgf} z+5>H)BoZXulr1k{!{6z&!llW9HeBr$(cioPu{07sQ~P;@+d-U$eqP{JSSiq^*JYCK<?p-hInDV`jh3@1+5=9&tATqJFAHJSOt;^8U+O9hf*1T)yaUqy)<{&lLtOv>002ovPDHLkV1lm0 B5dr`J literal 0 HcmV?d00001 diff --git a/Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/radio-button-default@2x.png b/Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/radio-button-default@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7e6083bc319d633e3955789d9d89f19b8123b096 GIT binary patch literal 1153 zcmV-{1b+L8P)iLCFA}0fvWHP*_3J70|ALu!6!02rDREfddRM!(##~sJnxFbxMk* zUKs{z0X81C7NI? zV30v5#VrKt-pWE91)whizTcRcc!ez?#Fr;yvm;;(_3%$?$MY5g^oX7aD8rp8Y)g>g z1%QaudL@#ZfPZ$i!6n=8VaB^55E47y@?0%Xqg(`z&l=x4%z9HY97=&BAiTl!WZ;#{ zP4&WWG-mMouM0S{U~-*T^u zS2)8q{dJ8lX-Tav&VA0(PbLB>#y5o&d*1Rw6@9=zy{yu#Z|mh*NpvM}a^Bb`L#tY= zK!|YDTmEo^0^pxFTrPzV?Q1ab-PNV?*G_Fa5{N(ZzSbfTXpzI{-St_=N+BU2H@&6# z!?bp(1jtpod`jzZK*m3fX(!PXcl@2_+4mPu>x@r@RT#4|IO7$(#WK0laFhPF8ylTD zX>F_o)a=uAe`+tIeXuh~;~$~+m2wX4Wy*D{!_!vZN+6#7XVB8=*W2VlJXVk>EZtkr zpOhuU*4m$hK<0y`CYf z<#ot~{zw!$6sBw7W&ul7dWdaC1j*{!^SWNevIqFvorUQOuC8Z z)?|QD!5rIgr8X~=sFZdB4g;34GZFZ&U?Y%Vopu5a1D3Ik)zFmyN-C2Y5sa}}pjR;o zNO`N37`}ISa1vuvjPy&dVGs~Z*4v*Mch)c>aLS*PCDL%|<`)Q&sKg{TMz;^5sed3CU&(F zSE&aW5|oetEgO_*Swng&fhZD`(ul|X(~E(7B$W;7DhNfywX|$7{UmNVRcL#GGv~YS zpSu_k+JuB>(ArO%eT7s4aXhXIOEf^q9|Q@j3WA4huv0GHF&0vKjJ}jG0201`F>5tm z2?<@Y!LGraw>(eDv^>y?6^WnmX6p;84_y(T<#`BTEr3ZOEx`GneNV{Jk@_QCN=zvS5-$C^cn-#zGCCCHjTG#Nw3?fuu35(zZF6Btggid{gJ?F4ZG?-7)$ zJb2oZt^d3S6;O#lAC6956X>l1 zQ$~caPbr*&q6F}tAuKPCqTaU%6(1jm9JNA;cBtqHyAemb8!Y6{D2RfBd^|pJ<~JPT_w};sH6@M^t@RRp zu6mLW|b2ke%^Mu96#d*KjS`l;w=#Sd#Jj0 zGnVxxT7S3=b3lCf<&8poVHqB=wf*S39(lW%>ZaRZ5l7r=Uc?ci^#@xHcgwPU@We}? zEdK!9G!F8DOn239FlVIa*)t`kG1&^qei1zI5-7lqb#c=$j_L~Ivmh>{Dc!J*%Vrup z$Q4MGwneRM-C3)@CQ}xPq**RzKz@^t73B(~6q=(FyOtz@Fz<}BXEa*4j}1=_ncafqXvRe5Ud`>Zs^X#sWp*>$ zZv~e?3i_s(wWd>ODZCFmFb)JzTp%?qaoz-J0Ih|LmXD~cbuFtvJn0AHKnksMuW!gn zt>IO|;uge91Xn^L%<*k;Pqr2kg}m7%ae zd3%@t8ZLnn`ERdSwf91O5^R>Y^R#@aeB&;wTj>CA&tNDs`DaI<~+P}Wm)10sCq zn&nOu&pUgTk*w_mT7vWxfX%(sc;K3wdp7TAbt!bleOrj7tFAr)gFsxTKHy_&=W>y| z)kxFS*5}s>E9xwRG%2e}PZUYk{i z6>)S3-p}KLec@WMZJbEI1P{Cfk`n<(J4sliounx)o+&hq5yGCY)q9saF4{?q3$nD6 zlxc6Pi)ovnCF7TN9Ts~Qzx-7T9~N|{9k>KXgXrq8u2aVxQ$SsJ}6xek@rylo)iFCL~E zeh1P+Ijy}KSu(ej!vU#(YYel?znT}{0(a4>YHF|ULY?>Ro7&0Jks{snE~>qfDaR_5 zZYx_)bU4{alMcm&l@u0lr|NYAMmy zDa<9c3Uo>4dRq4pX>(Sq%0$t40N<&-Cfbl25$G!M2}h{EC#%xphtp#ij!YQhwZ)y} zIGAX}^^{?D`B$Rd_nl!Idb1ZSSg>Hhg8ATI6MEZVK&@sK00000NkvXXu0mjfI}#^A literal 0 HcmV?d00001 diff --git a/Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/Contents.json b/Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/Contents.json new file mode 100644 index 000000000..a69d70fe6 --- /dev/null +++ b/Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "radio-button-selected.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "radio-button-selected@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "radio-button-selected@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/radio-button-selected.png b/Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/radio-button-selected.png new file mode 100644 index 0000000000000000000000000000000000000000..6a744d6bed5a4753d6cdfde938f85ea02d685d63 GIT binary patch literal 729 zcmV;~0w(>5P)>k%Ly)OFd9QChHvOJpg&f9sjGX}Wtz-^08;5kcp)Bz1`M644*&1^!rBBGGZ$*g(w zY8`V__12!EXd^tOK>#rrKPRGAQ7OkO|J-H!9e8rpVER7?w0nyj2?wL%&lGbJtQlF=O(zo*sHqFo4iMeImS{iI*C6mGNEVa=&3m3LJJvmJL*R8%Mb0?Joqu8eI zb?ewmzN-eOZ<0P*CC#96aoXWb&e@E3ptai9 zWE14%N~^$x{!g-81_>U2AA8(*$ruQwyEQl9-1dmvw0*tTA%u3)D-t{_*;WF9i~uRx zPCp#dz1hLBzitr=Hro-d_?Jnwq}!!uh>)|vNlDH8$n|z<$a{)^ahVuYI-wB^00000 LNkvXXu0mjf1<*je literal 0 HcmV?d00001 diff --git a/Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/radio-button-selected@2x.png b/Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/radio-button-selected@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..67c3bbd64f75d22ed14d368c2a93d7f84aecb799 GIT binary patch literal 1348 zcmV-K1-tr*P)ps1;?uYanlYXH_*o(7+6UJeh=dpVu~kwkuNw$>p+d)FZuN!-XU!~jG7;dN$3RKd9ldF}umU!JYr80bne}WWQVDE(Kha^8 zt|9iNP)Fn1`6Cp7s(0l`S@z7;Am=)N9JEf8o=qg6ms#&iL+nq)Q*D*d$H7Jd6dn!h zXZN<}FsH$P#QBslgOVt}4r-_D`6}CtO{Zm-=sAnBH0Q(oAU7H>*?9ImBIqovx7)|0 zFd2~TjbE>iLVMZ1r#M5>&cTeR0ew#RzPA?S1nQ*_K;JsnzNs)!6QT;Ad8p zdwT2zQI&GWeoQYWkkU}Xgb;?g(mn^8*^8=o8{2x}enTn4$Ei0}Lqse}rQT93q{j$IX~bT@LtCbl0M;5QJM489t;}19_$CIXN{j&A zakWyU2C-$z@joP0{+7jKQIt!v<(|`6Y0=^o}SP5zj>#YW5(k|o$5g*mht%YE` z&We^5bFc(xe|HR#Lsg>-7L4j=y0zFu$P!vHply1piQ6>7Mgo|(!3n$7taswghno%^ zn#Gw9s5w;||B&2?m@U0{OV1itN_?co?x(c=5qBF*YOy3->Tj!4`J@d+_Tj&9sZSi! zT1td#G-s;Htq$stBT<-_$ zjo8qXjx6{33M%OqSAmH?k2c!j2~->bR98u%OOr!e3q~imr2McF0gdFg_$r_YP}AnH z{_Z}{Q@JlRdaDQuYpawHCiJYA#OQTi=QVP#z#_t=LtQ-G2+O}Bv-b<-^0<@d#YoHw z6a?LLqP`6^#Y#Jrg&hjNj`5l`m}g^M@p6UP8f&bPAAbYwO>Yf)G^|Yk0000Fj2CX!EJ`2^l0 zC;^qh+MQ1KV#?Uw*_mDKu5n7=3x#H8clB-e^zR5@j4{R-V~jDrRUlN1T4%Es!uJiO z{RRQvL8OL!Oa%4o^AI5*fB=a42=qoP-Sy$3*Kck1p+ZzbkdUJFH#-Mk1$a1A!>uA!uP}n9Up|`y^n{!md|d?m3|pu7*AJlp6hhEUXM2}4 zthld0u|#dLPDB_Chr`~jTNnN77V6d?PwT$#*OdaXcK3KL|NYd`rQSio=D)4AEf^6a z5frNwO}=p3Yp{=`k2GQX?7>{Eb4RR9{lM_vL z`3iT^XJVf;?fd>o&K`cf{aj*5i_0NE&dI* zJvF#n6*+=Fn{}o9yK-;iB!1?ZlkHu|h)fbBde2J^Oyfkfe>S_G!I~A1slT>2Q&o;| z{&e#ef~k2m24VCv=09tXk8hT*=a{M)Ykk+X_Lk-uryaWUODgB-!x z?4y+wbz^UTcmR%YlAy^B`yU!#8-@efbFx~h*b>4Ia~MGb6y9TpvHsBuXGapL$LbJ( zijF;a*Uf(YUCtncugwwvqOvGfct z*3qh2RcxxW-87LS`%abzp6To?b1?gs-`-}nm`&!=;)M2J&zwASHUfiR*Ly4h$>9cn zC*IJTmP^?NhE_U+D~cMNR^;SdriYo;F?Zga`uWQ zN5DDkMx-Mr$1hEaw0^&sx;AZ)U(V4xiB~}NFm0`o zpjgKZo3p1b_Q3*C&&$oYT=1T>;j$m;$lw?8cT8JvBq)TiVHgVf4!K`(EGx<`vcL1* zDn%cBo}UG9Pf zQ4}@#PZiR01qmPKY?F{wqU1{f)3nCCNh|m<>0_;xLmH#{ss@|=hHZ}IzZ2I2W|tr| z#4w>B24I7NpP_0H-m^{dyrDsYAp0i$d9C~kPN4kYkXT`ArpoQtoNqq)|$^U*a6jtpu6YmI@ADZBxX%Hw*=)HE9x* z_(moo<*)(ME2tU<6Mh{b`1QmA%s~)=PV|^rI_h8nTR;b2sbElaY@4FA!MbYt$GFBc zkjLx1f~O`VFsyPP>U-XIUE3rw1m7t6Zv%`3X>F1Mw0g;LdI7*1@fQbdyO?k^hq-%#AO-ND#Mkn?LDQw$ zDpeI}v`W4X9oy8?8fs8v+((?HNJu)JU0dWmN#6Jf8#ngZx~|H#Tuad!rk9Q7XF{ub zj$C@y5#5yvCO9IlwMo7j@nM$IbDlQ7)ViKy>Dh@OG3%(HV;By&_;xpID&exAIMs}^ zkCI>`$?AP)ap2<=z+OR4TQ(kT?%F2VP#K`tXQ!cm0Xnx?Qr_7YD}9aW$6|vs;RvYf&{LoZ^{R -typedef NS_ENUM(NSInteger, ShareDataSourceMode) -{ - DataSourceModePeople, - DataSourceModeRooms -}; +@class ShareDataSource; +@protocol ShareDataSourceDelegate + +- (void)shareDataSourceDidChangeSelectedRoomIdentifiers:(ShareDataSource *)shareDataSource; + +@end @interface ShareDataSource : MXKRecentsDataSource -- (instancetype)initWithMode:(ShareDataSourceMode)dataSourceMode - fileStore:(MXFileStore *)fileStore - credentials:(MXCredentials *)credentials; +@property (nonatomic, weak) id shareDelegate; -/** - Returns the cell data at the index path - - @param indexPath the index of the cell - @return the MXKRecentCellData instance if it exists - */ -- (MXKRecentCellData *)cellDataAtIndexPath:(NSIndexPath *)indexPath; +@property (nonatomic, strong, readonly) NSSet *selectedRoomIdentifiers; + +- (instancetype)initWithFileStore:(MXFileStore *)fileStore + credentials:(MXCredentials *)credentials; + +- (void)selectRoomWithIdentifier:(NSString *)roomIdentifier animated:(BOOL)animated; + +- (void)deselectRoomWithIdentifier:(NSString *)roomIdentifier animated:(BOOL)animated; @end diff --git a/RiotShareExtension/Shared/ShareDataSource.m b/RiotShareExtension/Shared/ShareDataSource.m index b88a846a4..4bee2a44a 100644 --- a/RiotShareExtension/Shared/ShareDataSource.m +++ b/RiotShareExtension/Shared/ShareDataSource.m @@ -19,27 +19,28 @@ @interface ShareDataSource () -@property (nonatomic, assign, readonly) ShareDataSourceMode dataSourceMode; @property (nonatomic, strong, readonly) MXFileStore *fileStore; @property (nonatomic, strong, readonly) MXCredentials *credentials; @property NSArray *recentCellDatas; @property NSMutableArray *visibleRoomCellDatas; +@property (nonatomic, strong) NSMutableSet *internalSelectedRoomIdentifiers; + @end @implementation ShareDataSource -- (instancetype)initWithMode:(ShareDataSourceMode)dataSourceMode - fileStore:(MXFileStore *)fileStore - credentials:(MXCredentials *)credentials +- (instancetype)initWithFileStore:(MXFileStore *)fileStore + credentials:(MXCredentials *)credentials { if (self = [super init]) { - _dataSourceMode = dataSourceMode; _fileStore = fileStore; _credentials = credentials; + _internalSelectedRoomIdentifiers = [NSMutableSet set]; + [self loadCellData]; } return self; @@ -53,6 +54,25 @@ _visibleRoomCellDatas = nil; } +- (NSSet *)selectedRoomIdentifiers +{ + return self.internalSelectedRoomIdentifiers.copy; +} + +- (void)selectRoomWithIdentifier:(NSString *)roomIdentifier animated:(BOOL)animated +{ + [self.internalSelectedRoomIdentifiers addObject:roomIdentifier]; + + [self.shareDelegate shareDataSourceDidChangeSelectedRoomIdentifiers:self]; +} + +- (void)deselectRoomWithIdentifier:(NSString *)roomIdentifier animated:(BOOL)animated +{ + [self.internalSelectedRoomIdentifiers removeObject:roomIdentifier]; + + [self.shareDelegate shareDataSourceDidChangeSelectedRoomIdentifiers:self]; +} + #pragma mark - Private - (void)loadCellData @@ -66,7 +86,7 @@ for (MXRoomSummary *roomSummary in roomsSummaries) { - if (!roomSummary.hiddenFromUser && ((self.dataSourceMode == DataSourceModeRooms) ^ roomSummary.isDirect)) + if (!roomSummary.hiddenFromUser) { [roomSummary setMatrixSession:session]; @@ -137,6 +157,7 @@ { self.visibleRoomCellDatas = nil; } + [self.delegate dataSource:self didCellChange:nil]; } @@ -160,7 +181,11 @@ { RecentRoomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[RecentRoomTableViewCell defaultReuseIdentifier]]; - [cell render:[self cellDataAtIndexPath:indexPath]]; + MXKRecentCellData *data = [self cellDataAtIndexPath:indexPath]; + + [cell render:data]; + + [cell setCustomSelected:[self.selectedRoomIdentifiers containsObject:data.roomSummary.roomId] animated:NO]; return cell; } diff --git a/RiotShareExtension/Shared/ShareItemProvider/SimpleShareItemProvider.swift b/RiotShareExtension/Shared/ShareItemProvider/SimpleShareItemProvider.swift index 261c160fe..e08435aec 100644 --- a/RiotShareExtension/Shared/ShareItemProvider/SimpleShareItemProvider.swift +++ b/RiotShareExtension/Shared/ShareItemProvider/SimpleShareItemProvider.swift @@ -60,6 +60,12 @@ private class SimpleShareItem: ShareItemProtocol { let items: [ShareItemProtocol] + private override init() { + attachment = nil + textMessage = nil + self.items = [] + } + @objc public init(withAttachment attachment: MXKAttachment) { self.attachment = attachment self.items = [SimpleShareItem(withAttachment: attachment)]; @@ -78,10 +84,18 @@ private class SimpleShareItem: ShareItemProtocol { return } - attachment?.prepareShare({ url in - completion(url, nil) + guard let attachment = attachment else { + fatalError("[SimpleShareItemProvider] Invalid item provider state.") + } + + attachment.prepareShare({ url in + DispatchQueue.main.async { + completion(url, nil) + } }, failure: { error in - completion(nil, error) + DispatchQueue.main.async { + completion(nil, error) + } }) } diff --git a/RiotShareExtension/Shared/ShareManager.h b/RiotShareExtension/Shared/ShareManager.h index 965653d00..bb7245040 100644 --- a/RiotShareExtension/Shared/ShareManager.h +++ b/RiotShareExtension/Shared/ShareManager.h @@ -20,6 +20,11 @@ NS_ASSUME_NONNULL_BEGIN +typedef NS_ENUM(NSUInteger, ShareManagerType) { + ShareManagerTypeSend, + ShareManagerTypeForward, +}; + typedef NS_ENUM(NSUInteger, ShareManagerResult) { ShareManagerResultFinished, ShareManagerResultCancelled, @@ -30,17 +35,12 @@ typedef NS_ENUM(NSUInteger, ShareManagerResult) { @property (nonatomic, copy) void (^completionCallback)(ShareManagerResult); -- (instancetype)initWithShareItemProvider:(id)shareItemProvider; +- (instancetype)initWithShareItemProvider:(id)shareItemProvider + type:(ShareManagerType)type; - (UIViewController *)mainViewController; @end -@interface NSItemProvider (ShareManager) - -@property BOOL isLoaded; - -@end - NS_ASSUME_NONNULL_END diff --git a/RiotShareExtension/Shared/ShareManager.m b/RiotShareExtension/Shared/ShareManager.m index c5ebeba6b..5ca23a2b1 100644 --- a/RiotShareExtension/Shared/ShareManager.m +++ b/RiotShareExtension/Shared/ShareManager.m @@ -31,6 +31,7 @@ #endif static const CGFloat kLargeImageSizeMaxDimension = 2048.0; +static const CGSize kThumbnailSize = {800.0, 600.0}; typedef NS_ENUM(NSInteger, ImageCompressionMode) { @@ -61,6 +62,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) @implementation ShareManager - (instancetype)initWithShareItemProvider:(id)shareItemProvider + type:(ShareManagerType)type { if (self = [super init]) { @@ -91,7 +93,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) [MXLog configure:configuration]; - _shareViewController = [[ShareViewController alloc] initWithType:ShareViewControllerTypeSend + _shareViewController = [[ShareViewController alloc] initWithType:(type == ShareManagerTypeForward ? ShareViewControllerTypeForward : ShareViewControllerTypeSend) currentState:ShareViewControllerAccountStateNotConfigured]; [_shareViewController setDelegate:self]; @@ -101,7 +103,6 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) [NSBundle mxk_setLanguage:language]; [NSBundle mxk_setFallbackLanguage:@"en"]; - // Check the current matrix user. [self checkUserAccount]; } @@ -117,8 +118,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) #pragma mark - ShareViewControllerDelegate -- (void)shareViewControllerDidRequestShare:(ShareViewController *)shareViewController - forRoomIdentifier:(NSString *)roomIdentifier +- (void)shareViewController:(ShareViewController *)shareViewController didRequestShareForRoomIdentifiers:(NSSet *)roomIdentifiers { MXSession *session = [[MXSession alloc] initWithMatrixRestClient:[[MXRestClient alloc] initWithCredentials:self.userAccount.mxCredentials andOnUnrecognizedCertificateBlock:nil]]; [MXFileStore setPreloadOptions:0]; @@ -129,12 +129,22 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) session.crypto.warnOnUnknowDevices = NO; // Do not warn for unknown devices. We have cross-signing now - MXRoom *selectedRoom = [MXRoom loadRoomFromStore:self.fileStore withRoomId:roomIdentifier matrixSession:session]; - [self sendContentToRoom:selectedRoom success:nil failure:^(NSError *error){ + NSMutableArray *rooms = [NSMutableArray array]; + for (NSString *roomIdentifier in roomIdentifiers) { + MXRoom *room = [MXRoom loadRoomFromStore:self.fileStore withRoomId:roomIdentifier matrixSession:session]; + if (room) { + [rooms addObject:room]; + } + } + + [self sendContentToRooms:rooms success:^{ + self.completionCallback(ShareManagerResultFinished); + } failure:^(NSError *error){ [self showFailureAlert:[VectorL10n roomEventFailedToSend]]; }]; + } failure:^(NSError *error) { - MXLogError(@"[ShareManager] Failed preparign matrix session"); + MXLogError(@"[ShareManager] Failed preparing matrix session"); }]; } @@ -145,16 +155,37 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) #pragma mark - Private -- (void)sendContentToRoom:(MXRoom *)room success:(void(^)(void))success failure:(void(^)(NSError *))failure +- (void)showFailureAlert:(NSString *)title +{ + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleAlert]; + + MXWeakify(self); + UIAlertAction *okAction = [UIAlertAction actionWithTitle:[MatrixKitL10n ok] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + MXStrongifyAndReturnIfNil(self); + + if (self.completionCallback) + { + self.completionCallback(ShareManagerResultFailed); + } + }]; + + [alertController addAction:okAction]; + + [self.mainViewController presentViewController:alertController animated:YES completion:nil]; +} + +- (void)sendContentToRooms:(NSArray *)rooms success:(void(^)(void))success failure:(void(^)(NSError *))failure { [self resetPendingData]; - NSMutableArray > *pendingImagesItemProviders = [NSMutableArray array]; // Used to keep the items associated to pending images (used only when all items are images). - __block NSError *firstRequestError = nil; dispatch_group_t dispatchGroup = dispatch_group_create(); - void (^requestFailure)(NSError*) = ^(NSError *requestError) { + void (^requestSuccess)(void) = ^() { + dispatch_group_leave(dispatchGroup); + }; + + void (^requestFailure)(NSError *) = ^(NSError *requestError) { if (requestError && !firstRequestError) { firstRequestError = requestError; @@ -166,68 +197,93 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) MXWeakify(self); for (id item in self.shareItemProvider.items) { + if (item.type == ShareItemTypeText || item.type == ShareItemTypeURL) { + dispatch_group_enter(dispatchGroup); + [self.shareItemProvider loadItem:item completion:^(id item, NSError *error) { + MXStrongifyAndReturnIfNil(self); + + if (error) + { + requestFailure(error); + return; + } + + NSString *text = nil; + if([item isKindOfClass:[NSString class]]) + { + text = item; + } + else if([item isKindOfClass:[NSURL class]]) + { + text = [(NSURL *)item absoluteString]; + } + + if(text.length == 0) + { + requestFailure(nil); + return; + } + + [self sendText:text toRooms:rooms success:requestSuccess failure:requestFailure]; + }]; + } + if (item.type == ShareItemTypeFileURL) { dispatch_group_enter(dispatchGroup); [self.shareItemProvider loadItem:item completion:^(NSURL *url, NSError *error) { - if (error) { + MXStrongifyAndReturnIfNil(self); + + if (error) + { requestFailure(error); - dispatch_group_leave(dispatchGroup); return; } - dispatch_async(dispatch_get_main_queue(), ^{ - MXStrongifyAndReturnIfNil(self); - [self sendFileWithUrl:url toRoom:room success:^{ - dispatch_group_leave(dispatchGroup); - } failure:requestFailure]; - }); + [self sendFileWithUrl:url toRooms:rooms success:requestSuccess failure:requestFailure]; }]; } - if (item.type == ShareItemTypeText) { - dispatch_group_enter(dispatchGroup); - [self.shareItemProvider loadItem:item completion:^(NSString *text, NSError *error) { - if (error) { - requestFailure(error); - dispatch_group_leave(dispatchGroup); - return; - } - - dispatch_async(dispatch_get_main_queue(), ^{ - MXStrongifyAndReturnIfNil(self); - [self sendText:text toRoom:room success:^{ - dispatch_group_leave(dispatchGroup); - } failure:requestFailure]; - }); - }]; - } - - if (item.type == ShareItemTypeURL) + if (item.type == ShareItemTypeVideo || item.type == ShareItemTypeMovie) { dispatch_group_enter(dispatchGroup); - [self.shareItemProvider loadItem:item completion:^(NSURL *url, NSError *error) { - if (error) { + [self.shareItemProvider loadItem:item completion:^(NSURL *videoLocalUrl, NSError *error) { + MXStrongifyAndReturnIfNil(self); + + if (error) + { requestFailure(error); - dispatch_group_leave(dispatchGroup); return; } - dispatch_async(dispatch_get_main_queue(), ^{ - MXStrongifyAndReturnIfNil(self); - [self sendText:url.absoluteString toRoom:room success:^{ - dispatch_group_leave(dispatchGroup); - } failure:requestFailure]; - }); + [self sendVideo:videoLocalUrl toRooms:rooms success:requestSuccess failure:requestFailure]; }]; } + if (item.type == ShareItemTypeVoiceMessage) + { + dispatch_group_enter(dispatchGroup); + [self.shareItemProvider loadItem:item completion:^(NSURL *fileURL, NSError *error) { + MXStrongifyAndReturnIfNil(self); + + if (error) + { + requestFailure(error); + return; + } + + [self sendVoiceMessage:fileURL toRooms:rooms success:requestSuccess failure:requestFailure]; + }]; + } + if (item.type == ShareItemTypeImage) { dispatch_group_enter(dispatchGroup); [self.shareItemProvider loadItem:item completion:^(id itemProviderItem, NSError *error) { - if (error) { + MXStrongifyAndReturnIfNil(self); + + if (error) + { requestFailure(error); - dispatch_group_leave(dispatchGroup); return; } @@ -250,30 +306,23 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) imageData = UIImagePNGRepresentation(image); } - MXStrongifyAndReturnIfNil(self); - - if (imageData) + if (!imageData) { - if ([self.shareItemProvider areAllItemsImages]) - { - [self.pendingImages addObject:imageData]; - [pendingImagesItemProviders addObject:item]; - } - else - { - CGSize imageSize = [self imageSizeFromImageData:imageData]; - self.imageCompressionMode = ImageCompressionModeNone; - self.actualLargeSize = MAX(imageSize.width, imageSize.height); - - [self sendImageData:imageData withItem:item toRoom:room success:^{ - dispatch_group_leave(dispatchGroup); - } failure:requestFailure]; - } + requestFailure(error); + return; + } + + if ([self.shareItemProvider areAllItemsImages]) + { + [self.pendingImages addObject:imageData]; } else { - MXLogError(@"[ShareManager] sendContentToRoom: failed to loadItemForTypeIdentifier. Error: %@", error); - dispatch_group_leave(dispatchGroup); + CGSize imageSize = [self imageSizeFromImageData:imageData]; + self.imageCompressionMode = ImageCompressionModeNone; + self.actualLargeSize = MAX(imageSize.width, imageSize.height); + + [self sendImageData:imageData toRooms:rooms success:requestSuccess failure:requestFailure]; } // Only prompt for image resize if all items are images @@ -283,9 +332,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) if ([self.shareItemProvider areAllItemsLoaded]) { UIAlertController *compressionPrompt = [self compressionPromptForPendingImagesWithShareBlock:^{ - [self sendImageDatas:self.pendingImages.copy withItems:pendingImagesItemProviders toRoom:room success:^{ - dispatch_group_leave(dispatchGroup); - } failure:requestFailure]; + [self sendImageDatas:self.pendingImages.copy toRooms:rooms success:requestSuccess failure:requestFailure]; }]; if (compressionPrompt) @@ -300,63 +347,6 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) } }]; } - - if (item.type == ShareItemTypeVideo) - { - dispatch_group_enter(dispatchGroup); - [self.shareItemProvider loadItem:item completion:^(NSURL *videoLocalUrl, NSError *error) { - if (error) { - requestFailure(error); - dispatch_group_leave(dispatchGroup); - return; - } - - dispatch_async(dispatch_get_main_queue(), ^{ - MXStrongifyAndReturnIfNil(self); - [self sendVideo:videoLocalUrl toRoom:room success:^{ - dispatch_group_leave(dispatchGroup); - } failure:requestFailure]; - }); - }]; - } - - if (item.type == ShareItemTypeMovie) - { - dispatch_group_enter(dispatchGroup); - [self.shareItemProvider loadItem:item completion:^(NSURL *videoLocalUrl, NSError *error) { - if (error) { - requestFailure(error); - dispatch_group_leave(dispatchGroup); - return; - } - - dispatch_async(dispatch_get_main_queue(), ^{ - MXStrongifyAndReturnIfNil(self); - [self sendVideo:videoLocalUrl toRoom:room success:^{ - dispatch_group_leave(dispatchGroup); - } failure:requestFailure]; - }); - }]; - } - - if (item.type == ShareItemTypeVoiceMessage) - { - dispatch_group_enter(dispatchGroup); - [self.shareItemProvider loadItem:item completion:^(NSURL *fileURL, NSError *error) { - if (error) { - requestFailure(error); - dispatch_group_leave(dispatchGroup); - return; - } - - dispatch_async(dispatch_get_main_queue(), ^{ - MXStrongifyAndReturnIfNil(self); - [self sendVoiceMessage:fileURL toRoom:room success:^{ - dispatch_group_leave(dispatchGroup); - } failure:requestFailure]; - }); - }]; - } } dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{ @@ -368,30 +358,11 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) } else { - self.completionCallback(ShareManagerResultFinished); + success(); } }); } -- (void)showFailureAlert:(NSString *)title -{ - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleAlert]; - - MXWeakify(self); - UIAlertAction *okAction = [UIAlertAction actionWithTitle:[MatrixKitL10n ok] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - MXStrongifyAndReturnIfNil(self); - - if (self.completionCallback) - { - self.completionCallback(ShareManagerResultFailed); - } - }]; - - [alertController addAction:okAction]; - - [self.mainViewController presentViewController:alertController animated:YES completion:nil]; -} - - (void)checkUserAccount { // Force account manager to reload account from the local storage. @@ -428,21 +399,14 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) { _fileStore = [[MXFileStore alloc] initWithCredentials:self.userAccount.mxCredentials]; - ShareDataSource *roomDataSource = [[ShareDataSource alloc] initWithMode:DataSourceModeRooms - fileStore:_fileStore - credentials:self.userAccount.mxCredentials]; - - ShareDataSource *peopleDataSource = [[ShareDataSource alloc] initWithMode:DataSourceModePeople - fileStore:_fileStore - credentials:self.userAccount.mxCredentials]; + ShareDataSource *roomDataSource = [[ShareDataSource alloc] initWithFileStore:_fileStore + credentials:self.userAccount.mxCredentials]; [self.shareViewController configureWithState:ShareViewControllerAccountStateConfigured - roomDataSource:roomDataSource - peopleDataSource:peopleDataSource]; + roomDataSource:roomDataSource]; } else { [self.shareViewController configureWithState:ShareViewControllerAccountStateNotConfigured - roomDataSource:nil - peopleDataSource:nil]; + roomDataSource:nil]; } } @@ -581,7 +545,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) return compressionPrompt; } -- (void)didStartSendingToRoom:(MXRoom *)room +- (void)didStartSending { [self.shareViewController showProgressIndicator]; } @@ -738,11 +702,9 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) - (void)presentCompressionPrompt:(UIAlertController *)compressionPrompt { - dispatch_async(dispatch_get_main_queue(), ^{ - [compressionPrompt popoverPresentationController].sourceView = self.mainViewController.view; - [compressionPrompt popoverPresentationController].sourceRect = self.mainViewController.view.frame; - [self.mainViewController presentViewController:compressionPrompt animated:YES completion:nil]; - }); + [compressionPrompt popoverPresentationController].sourceView = self.mainViewController.view; + [compressionPrompt popoverPresentationController].sourceRect = self.mainViewController.view.frame; + [self.mainViewController presentViewController:compressionPrompt animated:YES completion:nil]; } #pragma mark - Notifications @@ -781,11 +743,11 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) #pragma mark - Sharing - (void)sendText:(NSString *)text - toRoom:(MXRoom *)room + toRooms:(NSArray *)rooms success:(dispatch_block_t)success failure:(void(^)(NSError *error))failure { - [self didStartSendingToRoom:room]; + [self didStartSending]; if (!text) { MXLogError(@"[ShareManager] Invalid text."); @@ -793,19 +755,34 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) return; } - [room sendTextMessage:text success:^(NSString *eventId) { - success(); - } failure:^(NSError *error) { - MXLogError(@"[ShareManager] sendTextMessage failed with error %@", error); - failure(error); - }]; + __block NSError *error = nil; + dispatch_group_t dispatchGroup = dispatch_group_create(); + for (MXRoom *room in rooms) { + dispatch_group_enter(dispatchGroup); + [room sendTextMessage:text success:^(NSString *eventId) { + dispatch_group_leave(dispatchGroup); + } failure:^(NSError *innerError) { + MXLogError(@"[ShareManager] sendTextMessage failed with error %@", error); + error = innerError; + dispatch_group_leave(dispatchGroup); + }]; + } + + dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{ + if(error) { + failure(error); + } else { + success(); + } + }); } -- (void)sendFileWithUrl:(NSURL *)fileUrl toRoom:(MXRoom *)room - success:(dispatch_block_t)success - failure:(void(^)(NSError *error))failure +- (void)sendFileWithUrl:(NSURL *)fileUrl + toRooms:(NSArray *)rooms + success:(dispatch_block_t)success + failure:(void(^)(NSError *error))failure { - [self didStartSendingToRoom:room]; + [self didStartSending]; if (!fileUrl) { MXLogError(@"[ShareManager] Invalid file url."); @@ -818,21 +795,185 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) mimeType = [self mimeTypeFromUTI:(__bridge NSString *)uti]; CFRelease(uti); - [room sendFile:fileUrl mimeType:mimeType localEcho:nil success:^(NSString *eventId) { - success(); - } failure:^(NSError *error) { - MXLogError(@"[ShareManager] sendFile failed with error %@", error); - failure(error); - } keepActualFilename:YES]; + __block NSError *error = nil; + dispatch_group_t dispatchGroup = dispatch_group_create(); + for (MXRoom *room in rooms) { + dispatch_group_enter(dispatchGroup); + [room sendFile:fileUrl mimeType:mimeType localEcho:nil success:^(NSString *eventId) { + dispatch_group_leave(dispatchGroup); + } failure:^(NSError *innerError) { + MXLogError(@"[ShareManager] sendFile failed with error %@", innerError); + error = innerError; + dispatch_group_leave(dispatchGroup); + } keepActualFilename:YES]; + } + + dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{ + if(error) { + failure(error); + } else { + success(); + } + }); +} + +- (void)sendVideo:(NSURL *)videoLocalUrl + toRooms:(NSArray *)rooms + success:(dispatch_block_t)success + failure:(void(^)(NSError *error))failure +{ + AVURLAsset *videoAsset = [[AVURLAsset alloc] initWithURL:videoLocalUrl options:nil]; + + MXWeakify(self); + + // Ignore showMediaCompressionPrompt setting due to memory constraints when encrypting large videos. + UIAlertController *compressionPrompt = [MXKTools videoConversionPromptForVideoAsset:videoAsset withCompletion:^(NSString *presetName) { + MXStrongifyAndReturnIfNil(self); + + // If the preset name is nil, the user cancelled. + if (!presetName) + { + return; + } + + // Set the chosen video conversion preset. + [MXSDKOptions sharedInstance].videoConversionPresetName = presetName; + + [self didStartSending]; + if (!videoLocalUrl) + { + MXLogError(@"[ShareManager] Invalid video file url."); + failure(nil); + return; + } + + // Retrieve the video frame at 1 sec to define the video thumbnail + AVAssetImageGenerator *assetImageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:videoAsset]; + assetImageGenerator.appliesPreferredTrackTransform = YES; + CMTime time = CMTimeMake(1, 1); + CGImageRef imageRef = [assetImageGenerator copyCGImageAtTime:time actualTime:NULL error:nil]; + // Finalize video attachment + UIImage *videoThumbnail = [[UIImage alloc] initWithCGImage:imageRef]; + CFRelease(imageRef); + + __block NSError *error = nil; + dispatch_group_t dispatchGroup = dispatch_group_create(); + for (MXRoom *room in rooms) { + dispatch_group_enter(dispatchGroup); + [room sendVideoAsset:videoAsset withThumbnail:videoThumbnail localEcho:nil success:^(NSString *eventId) { + dispatch_group_leave(dispatchGroup); + } failure:^(NSError *innerError) { + MXLogError(@"[ShareManager] Failed sending video with error %@", innerError); + error = innerError; + dispatch_group_leave(dispatchGroup); + }]; + } + + dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{ + if(error) { + failure(error); + } else { + success(); + } + }); + }]; + + [self presentCompressionPrompt:compressionPrompt]; +} + +- (void)sendVoiceMessage:(NSURL *)fileUrl + toRooms:(NSArray *)rooms + success:(dispatch_block_t)success + failure:(void(^)(NSError *error))failure +{ + [self didStartSending]; + if (!fileUrl) + { + MXLogError(@"[ShareManager] Invalid voice message file url."); + failure(nil); + return; + } + + __block NSError *error = nil; + dispatch_group_t dispatchGroup = dispatch_group_create(); + for (MXRoom *room in rooms) { + dispatch_group_enter(dispatchGroup); + [room sendVoiceMessage:fileUrl mimeType:nil duration:0.0 samples:nil localEcho:nil success:^(NSString *eventId) { + dispatch_group_leave(dispatchGroup); + } failure:^(NSError *innerError) { + MXLogError(@"[ShareManager] sendVoiceMessage failed with error %@", error); + error = innerError; + dispatch_group_leave(dispatchGroup); + } keepActualFilename:YES]; + } + + dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{ + if(error) { + failure(error); + } else { + success(); + } + }); +} + +- (void)sendImageDatas:(NSArray> *)imageDatas + toRooms:(NSArray *)rooms + success:(dispatch_block_t)success + failure:(void(^)(NSError *error))failure +{ + if (imageDatas.count == 0) + { + MXLogError(@"[ShareManager] sendImages: no images to send."); + failure(nil); + return; + } + + [self didStartSending]; + + dispatch_group_t requestsGroup = dispatch_group_create(); + __block NSError *firstRequestError; + + NSUInteger index = 0; + + for (NSData *imageData in imageDatas) + { + @autoreleasepool + { + dispatch_group_enter(requestsGroup); + [self sendImageData:imageData toRooms:rooms success:^{ + dispatch_group_leave(requestsGroup); + } failure:^(NSError *error) { + if (error && !firstRequestError) + { + firstRequestError = error; + } + + dispatch_group_leave(requestsGroup); + }]; + } + + index++; + } + + dispatch_group_notify(requestsGroup, dispatch_get_main_queue(), ^{ + + if (firstRequestError) + { + failure(firstRequestError); + } + else + { + success(); + } + }); } - (void)sendImageData:(NSData *)imageData - withItem:(id)item - toRoom:(MXRoom *)room + toRooms:(NSArray *)rooms success:(dispatch_block_t)success failure:(void(^)(NSError *error))failure { - [self didStartSendingToRoom:room]; + [self didStartSending]; NSString *imageUTI; NSString *mimeType; @@ -848,7 +989,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) if (!mimeType) { - MXLogError(@"[ShareManager] sendImage failed. Cannot determine MIME type of %@", item); + MXLogError(@"[ShareManager] sendImage failed. Cannot determine MIME type ."); if (failure) { failure(nil); @@ -921,142 +1062,33 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) imageSize = [self imageSizeFromImageData:imageData]; } - UIImage *thumbnail = nil; - // Thumbnail is useful only in case of encrypted room - if (room.summary.isEncrypted) - { - thumbnail = [MXKTools resizeImageWithData:imageData toFitInSize:CGSizeMake(800, 600)]; - } - - [room sendImage:finalImageData withImageSize:imageSize mimeType:mimeType andThumbnail:thumbnail localEcho:nil success:^(NSString *eventId) { - success(); - } failure:^(NSError *error) { - MXLogError(@"[ShareManager] sendImage failed with error %@", error); - failure(error); - }]; -} - -- (void)sendImageDatas:(NSArray> *)imageDatas - withItems:(NSArray> *)items toRoom:(MXRoom *)room - success:(dispatch_block_t)success - failure:(void(^)(NSError *error))failure -{ - if (imageDatas.count == 0 || imageDatas.count != items.count) - { - MXLogError(@"[ShareManager] sendImages: no images to send."); - failure(nil); - return; - } - - [self didStartSendingToRoom:room]; - - dispatch_group_t requestsGroup = dispatch_group_create(); - __block NSError *firstRequestError; - - NSUInteger index = 0; - - for (NSData *imageData in imageDatas) - { - @autoreleasepool - { - dispatch_group_enter(requestsGroup); - [self sendImageData:imageData withItem:items[index] toRoom:room success:^{ - dispatch_group_leave(requestsGroup); - } failure:^(NSError *error) { - if (error && !firstRequestError) - { - firstRequestError = error; - } + __block NSError *error = nil; + dispatch_group_t dispatchGroup = dispatch_group_create(); + for (MXRoom *room in rooms) { - dispatch_group_leave(requestsGroup); - }]; + UIImage *thumbnail = nil; + if (room.summary.isEncrypted) // Thumbnail is useful only in case of encrypted room + { + thumbnail = [MXKTools resizeImageWithData:imageData toFitInSize:kThumbnailSize]; } - index++; + dispatch_group_enter(dispatchGroup); + [room sendImage:finalImageData withImageSize:imageSize mimeType:mimeType andThumbnail:thumbnail localEcho:nil success:^(NSString *eventId) { + dispatch_group_leave(dispatchGroup); + } failure:^(NSError *innerError) { + MXLogError(@"[ShareManager] sendImage failed with error %@", error); + error = innerError; + dispatch_group_leave(dispatchGroup); + }]; } - dispatch_group_notify(requestsGroup, dispatch_get_main_queue(), ^{ - - if (firstRequestError) - { - failure(firstRequestError); - } - else - { + dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{ + if(error) { + failure(error); + } else { success(); } }); } -- (void)sendVideo:(NSURL *)videoLocalUrl - toRoom:(MXRoom *)room - success:(dispatch_block_t)success - failure:(void(^)(NSError *error))failure -{ - AVURLAsset *videoAsset = [[AVURLAsset alloc] initWithURL:videoLocalUrl options:nil]; - - MXWeakify(self); - - // Ignore showMediaCompressionPrompt setting due to memory constraints when encrypting large videos. - UIAlertController *compressionPrompt = [MXKTools videoConversionPromptForVideoAsset:videoAsset withCompletion:^(NSString *presetName) { - MXStrongifyAndReturnIfNil(self); - - // If the preset name is nil, the user cancelled. - if (!presetName) - { - return; - } - - // Set the chosen video conversion preset. - [MXSDKOptions sharedInstance].videoConversionPresetName = presetName; - - [self didStartSendingToRoom:room]; - if (!videoLocalUrl) - { - MXLogError(@"[ShareManager] Invalid video file url."); - failure(nil); - return; - } - - // Retrieve the video frame at 1 sec to define the video thumbnail - AVAssetImageGenerator *assetImageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:videoAsset]; - assetImageGenerator.appliesPreferredTrackTransform = YES; - CMTime time = CMTimeMake(1, 1); - CGImageRef imageRef = [assetImageGenerator copyCGImageAtTime:time actualTime:NULL error:nil]; - // Finalize video attachment - UIImage *videoThumbnail = [[UIImage alloc] initWithCGImage:imageRef]; - CFRelease(imageRef); - - [room sendVideoAsset:videoAsset withThumbnail:videoThumbnail localEcho:nil success:^(NSString *eventId) { - success(); - } failure:^(NSError *error) { - MXLogError(@"[ShareManager] Failed sending video with error %@", error); - failure(error); - }]; - }]; - - [self presentCompressionPrompt:compressionPrompt]; -} - -- (void)sendVoiceMessage:(NSURL *)fileUrl - toRoom:(MXRoom *)room - success:(dispatch_block_t)success - failure:(void(^)(NSError *error))failure -{ - [self didStartSendingToRoom:room]; - if (!fileUrl) - { - MXLogError(@"[ShareManager] Invalid voice message file url."); - failure(nil); - return; - } - - [room sendVoiceMessage:fileUrl mimeType:nil duration:0.0 samples:nil localEcho:nil success:^(NSString *eventId) { - success(); - } failure:^(NSError *error) { - MXLogError(@"[ShareManager] sendVoiceMessage failed with error %@", error); - failure(error); - } keepActualFilename:YES]; -} - @end diff --git a/RiotShareExtension/Shared/View/RecentRoomTableViewCell.h b/RiotShareExtension/Shared/View/RecentRoomTableViewCell.h index 959ca786e..63eb213f7 100644 --- a/RiotShareExtension/Shared/View/RecentRoomTableViewCell.h +++ b/RiotShareExtension/Shared/View/RecentRoomTableViewCell.h @@ -20,4 +20,6 @@ + (CGFloat)cellHeight; +- (void)setCustomSelected:(BOOL)selected animated:(BOOL)animated; + @end diff --git a/RiotShareExtension/Shared/View/RecentRoomTableViewCell.m b/RiotShareExtension/Shared/View/RecentRoomTableViewCell.m index 5abb551d9..4dec0815a 100644 --- a/RiotShareExtension/Shared/View/RecentRoomTableViewCell.m +++ b/RiotShareExtension/Shared/View/RecentRoomTableViewCell.m @@ -30,6 +30,7 @@ @property (weak, nonatomic) IBOutlet MXKImageView *avatarImageView; @property (weak, nonatomic) IBOutlet UILabel *roomTitleLabel; @property (weak, nonatomic) IBOutlet UIImageView *encryptedRoomIcon; +@property (weak, nonatomic) IBOutlet UIButton *selectionButton; @end @@ -56,6 +57,12 @@ self.roomTitleLabel.textColor = ThemeService.shared.theme.textPrimaryColor; self.contentView.backgroundColor = ThemeService.shared.theme.backgroundColor; + + [self.selectionButton setImage:[UIImage imageNamed:@"radio-button-default"] forState:UIControlStateNormal]; + [self.selectionButton setImage:[UIImage imageNamed:@"radio-button-selected"] forState:UIControlStateSelected]; + + [self.selectionButton setTitle:@"" forState:UIControlStateNormal]; + [self.selectionButton setTitle:@"" forState:UIControlStateSelected]; } - (void)layoutSubviews @@ -92,4 +99,11 @@ return 74; } +- (void)setCustomSelected:(BOOL)selected animated:(BOOL)animated +{ + [UIView animateWithDuration:(animated ? 0.25f : 0.0f) animations:^{ + [self.selectionButton setSelected:selected]; + }]; +} + @end diff --git a/RiotShareExtension/Shared/View/RecentRoomTableViewCell.xib b/RiotShareExtension/Shared/View/RecentRoomTableViewCell.xib index 80e07581e..fe39d2a67 100644 --- a/RiotShareExtension/Shared/View/RecentRoomTableViewCell.xib +++ b/RiotShareExtension/Shared/View/RecentRoomTableViewCell.xib @@ -1,9 +1,9 @@ - + - + @@ -25,12 +25,9 @@ - + diff --git a/RiotShareExtension/Shared/View/RoomsListViewController.m b/RiotShareExtension/Shared/View/RoomsListViewController.m index 8f387a4d4..414e0364a 100644 --- a/RiotShareExtension/Shared/View/RoomsListViewController.m +++ b/RiotShareExtension/Shared/View/RoomsListViewController.m @@ -18,6 +18,7 @@ #import "RoomsListViewController.h" #import "RecentRoomTableViewCell.h" +#import "ShareDataSource.h" #import "RecentCellData.h" #import "ThemeService.h" @@ -140,6 +141,22 @@ return [RecentRoomTableViewCell cellHeight]; } +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + + NSString *roomIdentifier = [self.dataSource cellDataAtIndexPath:indexPath].roomSummary.roomId; + + ShareDataSource *dataSource = (ShareDataSource *)self.dataSource; + if ([dataSource.selectedRoomIdentifiers containsObject:roomIdentifier]) { + [dataSource deselectRoomWithIdentifier:roomIdentifier animated:YES]; + } else { + [dataSource selectRoomWithIdentifier:roomIdentifier animated:YES]; + } + + [self.recentsTableView reloadData]; +} + #pragma mark - MXKDataSourceDelegate - (Class)cellViewClassForCellData:(MXKCellData*)cellData diff --git a/RiotShareExtension/Shared/View/ShareViewController.h b/RiotShareExtension/Shared/View/ShareViewController.h index edf5ce348..d45a66049 100644 --- a/RiotShareExtension/Shared/View/ShareViewController.h +++ b/RiotShareExtension/Shared/View/ShareViewController.h @@ -33,9 +33,7 @@ typedef NS_ENUM(NSUInteger, ShareViewControllerAccountState) { @protocol ShareViewControllerDelegate -- (void)shareViewControllerDidRequestShare:(ShareViewController *)shareViewController - forRoomIdentifier:(NSString *)roomIdentifier; - +- (void)shareViewController:(ShareViewController *)shareViewController didRequestShareForRoomIdentifiers:(NSSet *)roomIdentifiers; - (void)shareViewControllerDidRequestDismissal:(ShareViewController *)shareViewController; @end @@ -48,8 +46,7 @@ typedef NS_ENUM(NSUInteger, ShareViewControllerAccountState) { currentState:(ShareViewControllerAccountState)state; - (void)configureWithState:(ShareViewControllerAccountState)state - roomDataSource:(nullable ShareDataSource *)roomDataSource - peopleDataSource:(nullable ShareDataSource *)peopleDataSource; + roomDataSource:(nullable ShareDataSource *)roomDataSource; - (void)showProgressIndicator; diff --git a/RiotShareExtension/Shared/View/ShareViewController.m b/RiotShareExtension/Shared/View/ShareViewController.m index 148069665..cfe4f2a87 100644 --- a/RiotShareExtension/Shared/View/ShareViewController.m +++ b/RiotShareExtension/Shared/View/ShareViewController.m @@ -15,10 +15,9 @@ */ #import "ShareViewController.h" -#import "SegmentedViewController.h" +#import "ShareDataSource.h" #import "RoomsListViewController.h" #import "FallbackViewController.h" -#import "ShareDataSource.h" #import "ThemeService.h" @@ -28,13 +27,16 @@ #import "Riot-Swift.h" #endif -@interface ShareViewController () +@interface ShareViewController () @property (nonatomic, assign, readonly) ShareViewControllerType type; @property (nonatomic, assign) ShareViewControllerAccountState state; + +@property (nonatomic, strong) RoomsListViewController *roomListViewController; @property (nonatomic, strong) ShareDataSource *roomDataSource; -@property (nonatomic, strong) ShareDataSource *peopleDataSource; + +@property (nonatomic, strong) FallbackViewController *fallbackViewController; @property (nonatomic, weak) IBOutlet UIView *masterContainerView; @property (nonatomic, weak) IBOutlet UIButton *cancelButton; @@ -42,8 +44,6 @@ @property (nonatomic, weak) IBOutlet UIButton *shareButton; @property (nonatomic, weak) IBOutlet UIView *contentView; -@property (nonatomic, strong) SegmentedViewController *segmentedViewController; - @property (nonatomic, strong) MXKPieChartHUD *hudView; @end @@ -76,17 +76,17 @@ [self.cancelButton setTitle:[VectorL10n cancel] forState:UIControlStateNormal]; [self.shareButton setTintColor:ThemeService.shared.theme.tintColor]; + [self.shareButton setEnabled:NO]; - [self configureWithState:self.state roomDataSource:self.roomDataSource peopleDataSource:self.peopleDataSource]; + [self configureWithState:self.state roomDataSource:self.roomDataSource]; } - (void)configureWithState:(ShareViewControllerAccountState)state roomDataSource:(ShareDataSource *)roomDataSource - peopleDataSource:(ShareDataSource *)peopleDataSource { self.state = state; self.roomDataSource = roomDataSource; - self.peopleDataSource = peopleDataSource; + self.roomDataSource.shareDelegate = self; if (!self.isViewLoaded) { return; @@ -110,19 +110,11 @@ [self.hudView setProgress:progress]; } -#pragma mark - MXKRecentListViewControllerDelegate +#pragma mark - ShareDataSourceDelegate -- (void)recentListViewController:(MXKRecentListViewController *)recentListViewController - didSelectRoom:(NSString *)roomId - inMatrixSession:(MXSession *)mxSession +- (void)shareDataSourceDidChangeSelectedRoomIdentifiers:(ShareDataSource *)shareDataSource { - [self.delegate shareViewControllerDidRequestShare:self forRoomIdentifier:roomId]; -} - -- (void)recentListViewController:(MXKRecentListViewController *)recentListViewController - didSelectSuggestedRoom:(MXSpaceChildInfo *)childInfo -{ - [self.delegate shareViewControllerDidRequestShare:self forRoomIdentifier:childInfo.childRoomId]; + self.shareButton.enabled = (shareDataSource.selectedRoomIdentifiers.count > 0); } #pragma mark - Private @@ -133,60 +125,53 @@ if (self.state == ShareViewControllerAccountStateConfigured) { - self.titleLabel.text = [VectorL10n sendTo:@""]; - [self.shareButton setTitle:[VectorL10n roomEventActionForward] forState:UIControlStateNormal]; - [self configureSegmentedViewController]; + [self.shareButton setHidden:NO]; + + if (self.type == ShareViewControllerTypeSend) { + [self.titleLabel setText:[VectorL10n sendTo:@""]]; + [self.shareButton setTitle:[VectorL10n sendTo:@""] forState:UIControlStateNormal]; + } else { + [self.titleLabel setText:[VectorL10n roomEventActionForward]]; + [self.shareButton setTitle:[VectorL10n roomEventActionForward] forState:UIControlStateNormal]; + } } else { - self.titleLabel.text = [AppInfo.current displayName]; [self configureFallbackViewController]; + [self.shareButton setHidden:NO]; + + self.titleLabel.text = [AppInfo.current displayName]; } } - (void)configureSegmentedViewController { - RoomsListViewController *roomsViewController = [RoomsListViewController recentListViewController]; - [roomsViewController displayList:self.roomDataSource]; - [roomsViewController setDelegate:self]; - - RoomsListViewController *peopleViewController = [RoomsListViewController recentListViewController]; - [peopleViewController setDelegate:self]; - [peopleViewController displayList:self.peopleDataSource]; - - self.segmentedViewController = [SegmentedViewController segmentedViewController]; - [self.segmentedViewController initWithTitles:@[[VectorL10n titleRooms], [VectorL10n titlePeople]] - viewControllers:@[roomsViewController, peopleViewController] defaultSelected:0]; - - [self addChildViewController:self.segmentedViewController]; - [self.contentView vc_addSubViewMatchingParent:self.segmentedViewController.view]; - [self.segmentedViewController didMoveToParentViewController:self]; + self.roomListViewController = [RoomsListViewController recentListViewController]; + [self.roomListViewController displayList:self.roomDataSource]; + + [self addChildViewController:self.roomListViewController]; + [self.contentView vc_addSubViewMatchingParent:self.roomListViewController.view]; + [self.roomListViewController didMoveToParentViewController:self]; } - (void)configureFallbackViewController { - FallbackViewController *fallbackVC = [FallbackViewController new]; - [self addChildViewController:fallbackVC]; - [self.contentView vc_addSubViewMatchingParent:fallbackVC.view]; - [fallbackVC didMoveToParentViewController:self]; + self.fallbackViewController = [FallbackViewController new]; + [self addChildViewController:self.fallbackViewController]; + [self.contentView vc_addSubViewMatchingParent:self.fallbackViewController.view]; + [self.fallbackViewController didMoveToParentViewController:self]; } - (void)resetContentView { - NSArray *subviews = self.contentView.subviews; - for (UIView *subview in subviews) - { - [subview removeFromSuperview]; - } + [self.roomListViewController willMoveToParentViewController:nil]; + [self.roomListViewController.view removeFromSuperview]; + [self.roomListViewController removeFromParentViewController]; - if (self.segmentedViewController) - { - [self.segmentedViewController removeFromParentViewController]; - - [self.segmentedViewController destroy]; - self.segmentedViewController = nil; - } + [self.fallbackViewController willMoveToParentViewController:nil]; + [self.fallbackViewController.view removeFromSuperview]; + [self.fallbackViewController removeFromParentViewController]; } #pragma mark - Actions @@ -198,7 +183,11 @@ - (IBAction)onShareButtonTap:(UIButton *)sender { + if (self.roomDataSource.selectedRoomIdentifiers.count == 0) { + return; + } + [self.delegate shareViewController:self didRequestShareForRoomIdentifiers:self.roomDataSource.selectedRoomIdentifiers]; } @end diff --git a/RiotShareExtension/Shared/View/ShareViewController.xib b/RiotShareExtension/Shared/View/ShareViewController.xib index c6eaf5feb..ece4f812b 100644 --- a/RiotShareExtension/Shared/View/ShareViewController.xib +++ b/RiotShareExtension/Shared/View/ShareViewController.xib @@ -42,7 +42,7 @@ -