diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 63b974866e..4dd0b28edb 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/Images.swift b/Riot/Generated/Images.swift index 78b460c56e..c681c2fbeb 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -225,6 +225,7 @@ internal struct ImageAsset { internal typealias Image = UIImage #endif + @available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.7, *) internal var image: Image { let bundle = BundleToken.bundle #if os(iOS) || os(tvOS) @@ -236,13 +237,25 @@ internal struct ImageAsset { let image = Image(named: name) #endif guard let result = image else { - fatalError("Unable to load image named \(name).") + fatalError("Unable to load image asset named \(name).") } return result } + + #if os(iOS) || os(tvOS) + @available(iOS 8.0, tvOS 9.0, *) + internal func image(compatibleWith traitCollection: UITraitCollection) -> Image { + let bundle = BundleToken.bundle + guard let result = Image(named: name, in: bundle, compatibleWith: traitCollection) else { + fatalError("Unable to load image asset named \(name).") + } + return result + } + #endif } internal extension ImageAsset.Image { + @available(iOS 8.0, tvOS 9.0, watchOS 2.0, *) @available(macOS, deprecated, message: "This initializer is unsafe on macOS, please use the ImageAsset.image property") convenience init!(asset: ImageAsset) { diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index f23c03d2ac..2b8768a866 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 0853efe94b..a1dbffe143 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 075a3f57fc..2896df8472 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 @@ @interface RoomViewController () documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:fileURL]; @@ -3324,10 +3351,8 @@ - (void)showAdditionalActionsMenuForEvent:(MXEvent*)selectedEvent inCell:(id80%), it must be declared here diff --git a/RiotShareExtension/Managers/ShareExtensionManager.h b/RiotShareExtension/Managers/ShareExtensionManager.h index 042d55fb0f..e8dc215e6e 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; - -/** - The share app extension’s primary view controller. - */ -@property (nonatomic) SharePresentingViewController *primaryViewController; - -/** - 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; +@property (nonatomic, copy) void (^completionCallback)(ShareExtensionManagerResult); -/** - 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; +- (instancetype)initWithShareExtensionContext:(NSExtensionContext *)shareExtensionContext + extensionItems:(NSArray *)extensionItems; -/** - 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 ea58a7d346..6d2ab3cea3 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 () + +@property (nonatomic, strong, readonly) NSExtensionContext *shareExtensionContext; +@property (nonatomic, strong, readonly) NSArray *extensionItems; -@interface ShareExtensionManager () +@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, readwrite) MXKAccount *userAccount; +@property (nonatomic, strong) MXKAccount *userAccount; +@property (nonatomic, strong) MXFileStore *fileStore; -@property (nonatomic) NSMutableArray *pendingImages; -@property (nonatomic) NSMutableDictionary *imageUploadProgresses; -@property (nonatomic) ImageCompressionMode imageCompressionMode; -@property (nonatomic) CGFloat actualLargeSize; +@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 @@ + (instancetype)sharedManager } [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; - } - } - - 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]; + _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]; } - // 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"]; + return self.shareViewController; +} + +#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]; - // Check the current matrix user. - [self checkUserAccount]; + 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)sendContentToRoom:(MXRoom *)room failureBlock:(void(^)(NSError *error))failureBlock +- (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 @@ - (void)sendContentToRoom:(MXRoom *)room failureBlock:(void(^)(NSError *error))f __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 @@ - (void)sendContentToRoom:(MXRoom *)room failureBlock:(void(^)(NSError *error))f { 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 @@ - (void)sendContentToRoom:(MXRoom *)room failureBlock:(void(^)(NSError *error))f 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 @@ - (void)sendContentToRoom:(MXRoom *)room failureBlock:(void(^)(NSError *error))f if (compressionPrompt) { - [self.delegate shareExtensionManager:self showImageCompressionPrompt:compressionPrompt]; + [self presentCompressionPrompt:compressionPrompt]; } } else @@ -356,44 +365,44 @@ - (void)sendContentToRoom:(MXRoom *)room failureBlock:(void(^)(NSError *error))f 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 @@ - (void)sendContentToRoom:(MXRoom *)room failureBlock:(void(^)(NSError *error))f } 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); } - } - return NO; + }]; + + [alertController addAction:okAction]; + + [self.mainViewController presentViewController:alertController animated:YES completion:nil]; } -- (void)terminateExtensionCanceled:(BOOL)canceled +- (void)checkUserAccount { - if (canceled) + // Force account manager to reload account from the local storage. + [[MXKAccountManager sharedManager] forceReloadAccounts]; + + if (self.userAccount) { - [self.shareExtensionContext cancelRequestWithError:[NSError errorWithDomain:@"MXUserCancelErrorDomain" code:4201 userInfo:nil]]; + // 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; + } } - else + + if (!self.userAccount) { - [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 @@ - (UIAlertController *)compressionPromptForPendingImagesWithShareBlock:(void(^)( - (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 @@ - (BOOL)areAttachmentsFullyLoaded - (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 @@ - (void)logMemoryUsage } } +- (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 @@ - (void)onMediaLoaderStateDidChange:(NSNotification *)notification 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 @@ - (void)sendVideo:(NSURL *)videoLocalUrl toRoom:(MXRoom *)room successBlock:(dis 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 @@ - (void)sendVideo:(NSURL *)videoLocalUrl toRoom:(MXRoom *)room successBlock:(dis }]; }]; - [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 ddee2a064a..c4f26c2ef3 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 74f81b4a01..57412f705c 100644 --- a/RiotShareExtension/Modules/Fallback/FallbackViewController.m +++ b/RiotShareExtension/Modules/Fallback/FallbackViewController.m @@ -42,10 +42,4 @@ - (void)viewDidLoad 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 c9eff12d0b..e0868b6ad2 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 0000000000..ad5178c417 --- /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 ef57eba3c0..0000000000 --- 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 2a50bfdee8..6d62631da4 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 e841769eac..802425ebcb 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 @@ @interface ShareDataSource () @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)destroy - (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 b8a50f7e48..bee26845bf 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" -@interface RoomsListViewController : MXKRecentListViewController +@class RoomsListViewController; -@property (copy) void (^failureBlock)(void); +@interface RoomsListViewController : MXKRecentListViewController @end diff --git a/RiotShareExtension/Modules/Share/Listing/RoomsListViewController.m b/RiotShareExtension/Modules/Share/Listing/RoomsListViewController.m index 3f6e67b2e5..7a9e198f01 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 @@ - (void)setKeyboardHeight:(CGFloat)keyboardHeight 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 @@ - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPa 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 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView } } -#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 90550a99e2..5abb551d93 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 74e7af78ad..edf5ce348e 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 af4796e727..21cce2d00a 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 configureViews]; + [self.shareButton setTintColor:ThemeService.shared.theme.tintColor]; + + [self configureWithState:self.state roomDataSource:self.roomDataSource peopleDataSource:self.peopleDataSource]; } -- (void)destroy +- (void)configureWithState:(ShareViewControllerAccountState)state + roomDataSource:(ShareDataSource *)roomDataSource + peopleDataSource:(ShareDataSource *)peopleDataSource { - [super destroy]; + self.state = state; + self.roomDataSource = roomDataSource; + self.peopleDataSource = peopleDataSource; - if (self.shareExtensionManagerDidUpdateAccountDataObserver) - { - [[NSNotificationCenter defaultCenter] removeObserver:self.shareExtensionManagerDidUpdateAccountDataObserver]; - self.shareExtensionManagerDidUpdateAccountDataObserver = nil; + if (!self.isViewLoaded) { + return; } - [self resetContentView]; + [self configureViews]; } -- (void)resetContentView +#pragma mark - MXKRecentListViewControllerDelegate + +- (void)recentListViewController:(MXKRecentListViewController *)recentListViewController + didSelectRoom:(NSString *)roomId + inMatrixSession:(MXSession *)mxSession { - // 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:roomId]; +} + +- (void)recentListViewController:(MXKRecentListViewController *)recentListViewController + didSelectSuggestedRoom:(MXSpaceChildInfo *)childInfo +{ + [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 04a1316c87..1718d45010 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 f0a8761668..fce6e4c3f0 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 d3bf536b24..9d1bfc7d9c 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 9e61389f12..81e952ff5c 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