diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 9284e4008d..ffa85f7e98 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -3167,16 +3167,14 @@ - (void)showAdditionalActionsMenuForEvent:(MXEvent*)selectedEvent inCell:(id 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 0000000000..261c160fef --- /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 b812338836..965653d006 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 c34b841d18..c5ebeba6b3 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 @@ @interface ShareManager () @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 @@ - (void)sendContentToRoom:(MXRoom *)room success:(void(^)(void))success failure: { [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 @@ - (void)sendContentToRoom:(MXRoom *)room success:(void(^)(void))success failure: }; MXWeakify(self); - for (NSExtensionItem *item in self.extensionItems) + for (id item in self.shareItemProvider.items) { - for (NSItemProvider *itemProvider in item.attachments) + 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_async(dispatch_get_main_queue(), ^{ + MXStrongifyAndReturnIfNil(self); + [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) { - 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; + 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]]) - { - imageData = (NSData*)itemProviderItem; - } - else if ([(NSObject *)itemProviderItem isKindOfClass:[NSURL class]]) + [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]) { - NSURL *imageURL = (NSURL*)itemProviderItem; - imageData = [NSData dataWithContentsOfURL:imageURL]; + [self.pendingImages addObject:imageData]; + [pendingImagesItemProviders addObject:item]; } - else if ([(NSObject *)itemProviderItem isKindOfClass:[UIImage class]]) + else { - // 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); + 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]; } - - if (imageData) + } + 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]) { - 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 @@ - (void)didStartSendingToRoom:(MXRoom *)room [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 @@ - (void)didReceiveMemoryWarning:(NSNotification*)notification #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 @@ - (void)sendFileWithUrl:(NSURL *)fileUrl toRoom:(MXRoom *)room successBlock:(dis 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 @@ - (void)sendImageData:(NSData *)imageData withProvider:(NSItemProvider*)itemProv } [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 @@ - (void)sendImageDatas:(NSMutableArray *)imageDatas withProviders:(NSArray*)item @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 @@ - (void)sendImageDatas:(NSMutableArray *)imageDatas withProviders:(NSArray*)item 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 @@ - (void)sendVideo:(NSURL *)videoLocalUrl toRoom:(MXRoom *)room successBlock:(dis [self didStartSendingToRoom:room]; if (!videoLocalUrl) { - MXLogError(@"[ShareManager] loadItemForTypeIdentifier: failed."); - failureBlock(nil); + MXLogError(@"[ShareManager] Invalid video file url."); + failure(nil); return; } @@ -1042,31 +1028,35 @@ - (void)sendVideo:(NSURL *)videoLocalUrl toRoom:(MXRoom *)room successBlock:(dis 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 1718d45010..c6eaf5febb 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 b523d0fb66..c2be442a01 100644 --- a/RiotShareExtension/ShareExtensionRootViewController.m +++ b/RiotShareExtension/Sources/ShareExtensionRootViewController.m @@ -38,7 +38,8 @@ - (void)viewDidLoad [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 0000000000..e9ec4b6f29 --- /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 "" + } + } +}