2017-08-17 15:28:08 +00:00
/ *
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
2017-09-08 15:02:45 +00:00
distributed under the License is distributed on an "AS IS" BASIS ,
2017-08-17 15:28:08 +00:00
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 .
* /
2017-08-10 22:38:47 +00:00
# import "ShareExtensionManager.h"
2017-08-25 09:55:37 +00:00
# import "SharePresentingViewController.h"
2017-11-23 15:09:19 +00:00
# import < MatrixKit / MatrixKit . h >
2017-08-10 22:38:47 +00:00
@ import MobileCoreServices ;
2017-08-22 15:52:32 +00:00
# import "objc/runtime.h"
2019-01-04 12:38:31 +00:00
# include < MatrixSDK / MXUIKitBackgroundModeHandler . h >
2019-04-02 12:42:43 +00:00
# import < mach / mach . h >
2020-07-28 14:53:55 +00:00
# import "RiotShareExtension-Swift.h"
2017-08-10 22:38:47 +00:00
2017-09-29 08:37:54 +00:00
NSString * const kShareExtensionManagerDidUpdateAccountDataNotification = @ "kShareExtensionManagerDidUpdateAccountDataNotification" ;
2017-08-21 20:33:06 +00:00
2019-03-21 11:50:02 +00:00
static const CGFloat kLargeImageSizeMaxDimension = 2048.0 ;
2017-08-16 20:01:54 +00:00
typedef NS_ENUM ( NSInteger , ImageCompressionMode )
{
ImageCompressionModeNone ,
ImageCompressionModeSmall ,
ImageCompressionModeMedium ,
ImageCompressionModeLarge
} ;
2017-09-08 15:02:45 +00:00
2017-08-16 20:01:54 +00:00
@ interface ShareExtensionManager ( )
2017-09-08 15:02:45 +00:00
@ property ( nonatomic , readwrite ) MXKAccount * userAccount ;
2017-08-26 11:54:25 +00:00
@ property ( nonatomic ) NSMutableArray < NSData * > * pendingImages ;
@ property ( nonatomic ) NSMutableDictionary < NSString * , NSNumber * > * imageUploadProgresses ;
@ property ( nonatomic ) ImageCompressionMode imageCompressionMode ;
@ property ( nonatomic ) CGFloat actualLargeSize ;
2017-08-16 20:01:54 +00:00
@ end
2017-08-10 22:38:47 +00:00
@ implementation ShareExtensionManager
# pragma mark - Lifecycle
+ ( instancetype ) sharedManager
{
static ShareExtensionManager * sharedInstance = nil ;
static dispatch_once _t onceToken ;
dispatch_once ( & onceToken , ^ {
2017-09-29 08:37:54 +00:00
2017-08-10 22:38:47 +00:00
sharedInstance = [ [ self alloc ] init ] ;
2017-08-21 20:33:06 +00:00
2017-08-22 15:52:32 +00:00
sharedInstance . pendingImages = [ NSMutableArray array ] ;
sharedInstance . imageUploadProgresses = [ NSMutableDictionary dictionary ] ;
2017-08-22 15:56:30 +00:00
2018-11-10 13:28:08 +00:00
[ [ NSNotificationCenter defaultCenter ] addObserver : sharedInstance selector : @ selector ( onMediaLoaderStateDidChange : ) name : kMXMediaLoaderStateDidChangeNotification object : nil ] ;
2017-08-21 20:33:06 +00:00
2017-09-08 15:02:45 +00:00
// 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 ] ;
2019-03-11 17:04:42 +00:00
// Add observer to handle memory warning
2019-04-02 12:20:05 +00:00
[ NSNotificationCenter . defaultCenter addObserver : sharedInstance selector : @ selector ( didReceiveMemoryWarning : ) name : UIApplicationDidReceiveMemoryWarningNotification object : nil ] ;
2019-03-11 17:04:42 +00:00
2020-07-28 14:53:55 +00:00
// Set static application settings
2020-07-29 08:13:43 +00:00
sharedInstance -> _configuration = [ CommonConfiguration new ] ;
2020-07-29 08:03:37 +00:00
[ sharedInstance . configuration setupSettings ] ;
2021-06-03 08:30:07 +00:00
2017-10-20 12:57:51 +00:00
// NSLog -> console . log file when not debugging the app
2021-06-03 08:30:07 +00:00
MXLogConfiguration * configuration = [ [ MXLogConfiguration alloc ] init ] ;
configuration . logLevel = MXLogLevelVerbose ;
configuration . logFilesSizeLimit = 0 ;
configuration . maxLogFilesCount = 10 ;
configuration . subLogName = @ "share" ;
// Redirect NSLogs to files only if we are not debugging
if ( ! isatty ( STDERR_FILENO ) ) {
configuration . redirectLogsToFiles = YES ;
2017-10-20 12:57:51 +00:00
}
2021-06-03 08:30:07 +00:00
[ MXLog configure : configuration ] ;
2017-08-10 22:38:47 +00:00
} ) ;
return sharedInstance ;
}
2017-09-08 15:02:45 +00:00
- ( 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 ;
}
}
2017-09-29 08:37:54 +00:00
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 ;
}
2017-09-08 15:02:45 +00:00
if ( self . userAccount )
{
2017-09-29 08:37:54 +00:00
_fileStore = [ [ MXFileStore alloc ] initWithCredentials : self . userAccount . mxCredentials ] ;
2017-09-08 15:02:45 +00:00
}
2017-09-29 08:37:54 +00:00
// Post notification
[ [ NSNotificationCenter defaultCenter ] postNotificationName : kShareExtensionManagerDidUpdateAccountDataNotification object : self . userAccount userInfo : nil ] ;
2017-09-08 15:02:45 +00:00
}
2017-08-14 11:25:02 +00:00
# pragma mark - Public
2017-08-21 20:33:06 +00:00
- ( void ) setShareExtensionContext : ( NSExtensionContext * ) shareExtensionContext
{
_shareExtensionContext = shareExtensionContext ;
2017-09-29 08:37:54 +00:00
2017-12-27 13:00:16 +00:00
// 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" ] ;
2017-09-29 08:37:54 +00:00
// Check the current matrix user .
[ self checkUserAccount ] ;
2017-08-21 20:33:06 +00:00
}
2017-09-29 08:37:54 +00:00
- ( void ) sendContentToRoom : ( MXRoom * ) room failureBlock : ( void ( ^ ) ( NSError * error ) ) failureBlock
2017-08-10 22:38:47 +00:00
{
2019-02-08 13:43:21 +00:00
[ self resetPendingData ] ;
2017-08-10 22:38:47 +00:00
NSString * UTTypeText = ( __bridge NSString * ) kUTTypeText ;
NSString * UTTypeURL = ( __bridge NSString * ) kUTTypeURL ;
NSString * UTTypeImage = ( __bridge NSString * ) kUTTypeImage ;
NSString * UTTypeVideo = ( __bridge NSString * ) kUTTypeVideo ;
2017-08-14 11:25:02 +00:00
NSString * UTTypeFileUrl = ( __bridge NSString * ) kUTTypeFileURL ;
NSString * UTTypeMovie = ( __bridge NSString * ) kUTTypeMovie ;
2017-08-10 22:38:47 +00:00
2019-02-08 10:04:51 +00:00
BOOL areAllAttachmentsImages = [ self areAllAttachmentsImages ] ;
2019-02-08 13:43:21 +00:00
NSMutableArray < NSItemProvider * > * pendingImagesItemProviders = [ NSMutableArray new ] ; // Used to keep NSItemProvider associated to pending images ( used only when all items are images ) .
2019-02-08 10:04:51 +00:00
__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 ) ;
} ;
void ( ^ requestFailure ) ( NSError * ) = ^ ( NSError * requestError ) {
if ( requestError && ! firstRequestError )
{
firstRequestError = requestError ;
}
dispatch_group _leave ( requestsGroup ) ;
} ;
2017-08-24 10:55:35 +00:00
2019-02-08 13:43:21 +00:00
__weak typeof ( self ) weakSelf = self ;
2017-08-10 22:38:47 +00:00
for ( NSExtensionItem * item in self . shareExtensionContext . inputItems )
{
for ( NSItemProvider * itemProvider in item . attachments )
{
2017-08-14 11:25:02 +00:00
if ( [ itemProvider hasItemConformingToTypeIdentifier : UTTypeFileUrl ] )
{
2019-02-08 10:04:51 +00:00
dispatch_group _enter ( requestsGroup ) ;
2017-08-14 11:25:02 +00:00
[ itemProvider loadItemForTypeIdentifier : UTTypeFileUrl options : nil completionHandler : ^ ( NSURL * fileUrl , NSError * _Null _unspecified error ) {
2017-08-24 08:47:27 +00:00
// Switch back on the main thread to handle correctly the UI change
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
2019-02-08 10:04:51 +00:00
[ self sendFileWithUrl : fileUrl
toRoom : room
successBlock : ^ {
requestSuccess ( item ) ;
} failureBlock : requestFailure ] ;
2017-08-24 08:47:27 +00:00
}
} ) ;
2017-08-14 11:25:02 +00:00
} ] ;
}
else if ( [ itemProvider hasItemConformingToTypeIdentifier : UTTypeText ] )
2017-08-10 22:38:47 +00:00
{
2019-02-08 10:04:51 +00:00
dispatch_group _enter ( requestsGroup ) ;
2017-08-10 22:38:47 +00:00
[ itemProvider loadItemForTypeIdentifier : UTTypeText options : nil completionHandler : ^ ( NSString * text , NSError * _Null _unspecified error ) {
2017-08-24 08:47:27 +00:00
// Switch back on the main thread to handle correctly the UI change
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
2019-02-08 10:04:51 +00:00
[ self sendText : text
toRoom : room
successBlock : ^ {
requestSuccess ( item ) ;
} failureBlock : requestFailure ] ;
2017-08-24 08:47:27 +00:00
}
} ) ;
2017-08-10 22:38:47 +00:00
} ] ;
}
else if ( [ itemProvider hasItemConformingToTypeIdentifier : UTTypeURL ] )
{
2019-02-08 10:04:51 +00:00
dispatch_group _enter ( requestsGroup ) ;
2017-08-10 22:38:47 +00:00
[ itemProvider loadItemForTypeIdentifier : UTTypeURL options : nil completionHandler : ^ ( NSURL * url , NSError * _Null _unspecified error ) {
2017-08-24 08:47:27 +00:00
// Switch back on the main thread to handle correctly the UI change
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
2019-02-08 10:04:51 +00:00
[ self sendText : url . absoluteString
toRoom : room
successBlock : ^ {
requestSuccess ( item ) ;
} failureBlock : requestFailure ] ;
2017-08-24 08:47:27 +00:00
}
} ) ;
2017-08-10 22:38:47 +00:00
} ] ;
}
else if ( [ itemProvider hasItemConformingToTypeIdentifier : UTTypeImage ] )
{
2019-02-08 10:04:51 +00:00
dispatch_group _enter ( requestsGroup ) ;
2017-08-22 15:52:32 +00:00
itemProvider . isLoaded = NO ;
2019-01-04 12:45:41 +00:00
[ itemProvider loadItemForTypeIdentifier : UTTypeImage options : nil completionHandler : ^ ( id < NSSecureCoding > _Nullable itemProviderItem , NSError * _Null _unspecified error )
2017-08-10 22:38:47 +00:00
{
2017-08-17 15:28:08 +00:00
if ( weakSelf )
{
2018-01-22 18:36:51 +00:00
typeof ( self ) self = weakSelf ;
2017-08-22 15:52:32 +00:00
itemProvider . isLoaded = YES ;
2019-01-04 12:45:41 +00:00
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 ] ] )
{
2019-02-06 15:05:36 +00:00
// 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 .
2019-01-04 12:45:41 +00:00
UIImage * image = ( UIImage * ) itemProviderItem ;
2019-02-06 15:05:36 +00:00
imageData = UIImagePNGRepresentation ( image ) ;
2019-01-04 12:45:41 +00:00
}
2018-03-08 15:48:33 +00:00
if ( imageData )
{
2019-02-08 13:43:21 +00:00
if ( areAllAttachmentsImages )
{
[ self . pendingImages addObject : imageData ] ;
[ pendingImagesItemProviders addObject : itemProvider ] ;
}
else
2019-02-08 10:04:51 +00:00
{
CGSize imageSize = [ self imageSizeFromImageData : imageData ] ;
self . imageCompressionMode = ImageCompressionModeNone ;
self . actualLargeSize = MAX ( imageSize . width , imageSize . height ) ;
2019-02-08 13:43:21 +00:00
[ self sendImageData : imageData
withProvider : itemProvider
toRoom : room
successBlock : ^ {
requestSuccess ( item ) ;
} failureBlock : requestFailure ] ;
2019-02-08 10:04:51 +00:00
}
2018-03-08 15:48:33 +00:00
}
else
{
2021-06-03 08:30:07 +00:00
MXLogDebug ( @ "[ShareExtensionManager] sendContentToRoom: failed to loadItemForTypeIdentifier. Error: %@" , error ) ;
2019-02-08 10:04:51 +00:00
dispatch_group _leave ( requestsGroup ) ;
2018-03-08 15:48:33 +00:00
}
2017-08-22 15:52:32 +00:00
2021-08-23 11:38:04 +00:00
// Only prompt for image resize if all items are images
// Ignore showMediaCompressionPrompt setting due to memory constraints with full size images .
if ( areAllAttachmentsImages )
2017-08-22 15:52:32 +00:00
{
2019-02-08 13:43:21 +00:00
if ( [ self areAttachmentsFullyLoaded ] )
2018-02-05 14:36:56 +00:00
{
2019-02-08 13:43:21 +00:00
UIAlertController * compressionPrompt = [ self compressionPromptForPendingImagesWithShareBlock : ^ {
[ self sendImageDatas : self . pendingImages
withProviders : pendingImagesItemProviders
toRoom : room
successBlock : ^ {
requestSuccess ( item ) ;
} failureBlock : requestFailure ] ;
} ] ;
if ( compressionPrompt )
{
[ self . delegate shareExtensionManager : self showImageCompressionPrompt : compressionPrompt ] ;
}
}
else
{
dispatch_group _leave ( requestsGroup ) ;
2018-02-05 14:36:56 +00:00
}
2017-08-22 15:52:32 +00:00
}
2017-08-17 15:28:08 +00:00
}
2017-08-10 22:38:47 +00:00
} ] ;
}
else if ( [ itemProvider hasItemConformingToTypeIdentifier : UTTypeVideo ] )
{
2019-02-08 10:04:51 +00:00
dispatch_group _enter ( requestsGroup ) ;
2017-08-24 08:47:27 +00:00
[ 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 ;
2019-02-08 10:04:51 +00:00
[ self sendVideo : videoLocalUrl
toRoom : room
successBlock : ^ {
requestSuccess ( item ) ;
} failureBlock : requestFailure ] ;
2017-08-24 08:47:27 +00:00
}
} ) ;
2017-08-14 11:25:02 +00:00
} ] ;
}
else if ( [ itemProvider hasItemConformingToTypeIdentifier : UTTypeMovie ] )
{
2019-02-08 10:04:51 +00:00
dispatch_group _enter ( requestsGroup ) ;
2017-08-24 08:47:27 +00:00
[ 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 ;
2019-02-08 10:04:51 +00:00
[ self sendVideo : videoLocalUrl
toRoom : room
successBlock : ^ {
requestSuccess ( item ) ;
} failureBlock : requestFailure ] ;
2017-08-24 08:47:27 +00:00
}
} ) ;
2017-08-10 22:38:47 +00:00
} ] ;
}
}
}
2019-02-08 10:04:51 +00:00
dispatch_group _notify ( requestsGroup , dispatch_get _main _queue ( ) , ^ {
[ self resetPendingData ] ;
if ( firstRequestError )
{
if ( failureBlock )
{
failureBlock ( firstRequestError ) ;
}
}
else
{
[ self completeRequestReturningItems : returningExtensionItems completionHandler : nil ] ;
}
} ) ;
2017-08-10 22:38:47 +00:00
}
2017-08-16 20:01:54 +00:00
- ( BOOL ) hasImageTypeContent
{
for ( NSExtensionItem * item in self . shareExtensionContext . inputItems )
{
for ( NSItemProvider * itemProvider in item . attachments )
{
if ( [ itemProvider hasItemConformingToTypeIdentifier : ( __bridge NSString * ) kUTTypeImage ] )
{
return YES ;
}
}
}
return NO ;
}
2017-08-17 22:27:56 +00:00
- ( void ) terminateExtensionCanceled : ( BOOL ) canceled
2017-08-10 22:38:47 +00:00
{
2017-08-17 22:27:56 +00:00
if ( canceled )
{
[ self . shareExtensionContext cancelRequestWithError : [ NSError errorWithDomain : @ "MXUserCancelErrorDomain" code : 4201 userInfo : nil ] ] ;
}
else
{
[ self . shareExtensionContext cancelRequestWithError : [ NSError errorWithDomain : @ "MXFailureErrorDomain" code : 500 userInfo : nil ] ] ;
}
2017-08-25 09:55:37 +00:00
[ self . primaryViewController destroy ] ;
self . primaryViewController = nil ;
2019-01-04 12:57:35 +00:00
// 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" ] ;
2017-08-10 22:38:47 +00:00
}
2017-11-09 14:59:42 +00:00
# pragma mark - Private
- ( void ) resetPendingData
2017-09-15 15:17:03 +00:00
{
2017-11-09 14:59:42 +00:00
[ self . pendingImages removeAllObjects ] ;
[ self . imageUploadProgresses removeAllObjects ] ;
2017-09-15 15:17:03 +00:00
}
2017-08-25 09:55:37 +00:00
- ( void ) completeRequestReturningItems : ( nullable NSArray * ) items completionHandler : ( void ( ^ __nullable ) ( BOOL expired ) ) completionHandler ;
{
[ self . shareExtensionContext completeRequestReturningItems : items completionHandler : completionHandler ] ;
[ self . primaryViewController destroy ] ;
self . primaryViewController = nil ;
2019-01-04 12:57:35 +00:00
// 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" ] ;
}
2019-01-04 12:52:26 +00:00
- ( BOOL ) isAPendingImageNotOrientedUp
{
BOOL isAPendingImageNotOrientedUp = NO ;
for ( NSData * imageData in self . pendingImages )
{
2019-02-06 15:05:36 +00:00
if ( [ self isImageOrientationNotUpOrUndeterminedForImageData : imageData ] )
2019-01-04 12:52:26 +00:00
{
2019-02-06 15:05:36 +00:00
isAPendingImageNotOrientedUp = YES ;
break ;
2019-01-04 12:52:26 +00:00
}
}
return isAPendingImageNotOrientedUp ;
2017-08-25 09:55:37 +00:00
}
2019-01-04 12:52:26 +00:00
// TODO : When select multiple images :
// - Enhance prompt to display sum of all file sizes for each compression .
// - Find a way to choose compression sizes for all images .
- ( UIAlertController * ) compressionPromptForPendingImagesWithShareBlock : ( void ( ^ ) ( void ) ) shareBlock
2017-08-16 20:01:54 +00:00
{
2019-01-04 12:52:26 +00:00
if ( ! self . pendingImages . count )
{
return nil ;
}
2017-08-16 20:01:54 +00:00
UIAlertController * compressionPrompt ;
2019-01-04 12:52:26 +00:00
BOOL isAPendingImageNotOrientedUp = [ self isAPendingImageNotOrientedUp ] ;
NSData * firstImageData = self . pendingImages . firstObject ;
UIImage * firstImage = [ UIImage imageWithData : firstImageData ] ;
2017-08-16 20:01:54 +00:00
2017-08-24 10:59:46 +00:00
// Get available sizes for this image
2019-01-04 12:52:26 +00:00
MXKImageCompressionSizes compressionSizes = [ MXKTools availableCompressionSizesForImage : firstImage originalFileSize : firstImageData . length ] ;
2017-08-16 20:01:54 +00:00
// Apply the compression mode
if ( compressionSizes . small . fileSize || compressionSizes . medium . fileSize || compressionSizes . large . fileSize )
{
__weak typeof ( self ) weakSelf = self ;
compressionPrompt = [ UIAlertController alertControllerWithTitle : [ NSBundle mxk_localizedStringForKey : @ "attachment_size_prompt" ] message : nil preferredStyle : UIAlertControllerStyleActionSheet ] ;
if ( compressionSizes . small . fileSize )
{
2021-08-27 15:15:46 +00:00
NSString * fileSizeString = [ MXTools fileSizeToString : compressionSizes . small . fileSize ] ;
2017-08-16 20:01:54 +00:00
2021-08-27 15:15:46 +00:00
NSString * title = [ NSString stringWithFormat : [ NSBundle mxk_localizedStringForKey : @ "attachment_small" ] , fileSizeString ] ;
2017-08-16 20:01:54 +00:00
[ compressionPrompt addAction : [ UIAlertAction actionWithTitle : title
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
// Send the small image
self . imageCompressionMode = ImageCompressionModeSmall ;
2019-04-02 13:01:50 +00:00
[ self logCompressionSizeChoice : compressionSizes . large ] ;
2017-08-16 20:01:54 +00:00
if ( shareBlock )
{
shareBlock ( ) ;
}
}
} ] ] ;
}
if ( compressionSizes . medium . fileSize )
{
2021-08-27 15:15:46 +00:00
NSString * fileSizeString = [ MXTools fileSizeToString : compressionSizes . medium . fileSize ] ;
2017-08-16 20:01:54 +00:00
2021-08-27 15:15:46 +00:00
NSString * title = [ NSString stringWithFormat : [ NSBundle mxk_localizedStringForKey : @ "attachment_medium" ] , fileSizeString ] ;
2017-08-16 20:01:54 +00:00
[ compressionPrompt addAction : [ UIAlertAction actionWithTitle : title
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
// Send the medium image
self . imageCompressionMode = ImageCompressionModeMedium ;
2019-04-02 13:01:50 +00:00
[ self logCompressionSizeChoice : compressionSizes . large ] ;
2017-08-16 20:01:54 +00:00
if ( shareBlock )
{
shareBlock ( ) ;
}
}
} ] ] ;
}
2019-03-21 11:50:02 +00:00
// 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 ) )
2017-08-16 20:01:54 +00:00
{
2021-08-27 15:15:46 +00:00
NSString * fileSizeString = [ MXTools fileSizeToString : compressionSizes . large . fileSize ] ;
2017-08-16 20:01:54 +00:00
2021-08-27 15:15:46 +00:00
NSString * title = [ NSString stringWithFormat : [ NSBundle mxk_localizedStringForKey : @ "attachment_large" ] , fileSizeString ] ;
2017-08-16 20:01:54 +00:00
[ 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 ;
2019-04-02 13:01:50 +00:00
[ self logCompressionSizeChoice : compressionSizes . large ] ;
2017-08-16 20:01:54 +00:00
if ( shareBlock )
{
shareBlock ( ) ;
}
}
} ] ] ;
}
2017-08-24 10:59:46 +00:00
// To limit memory consumption , we suggest the original resolution only if the image orientation is up , or if the image size is moderate
2019-01-04 12:52:26 +00:00
if ( ! isAPendingImageNotOrientedUp || ! compressionSizes . large . fileSize )
2017-08-24 10:59:46 +00:00
{
2021-08-27 15:15:46 +00:00
NSString * fileSizeString = [ MXTools fileSizeToString : compressionSizes . original . fileSize ] ;
2017-08-24 10:59:46 +00:00
2021-08-27 15:15:46 +00:00
NSString * title = [ NSString stringWithFormat : [ NSBundle mxk_localizedStringForKey : @ "attachment_original" ] , fileSizeString ] ;
2017-08-24 10:59:46 +00:00
[ compressionPrompt addAction : [ UIAlertAction actionWithTitle : title
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action ) {
2017-08-16 20:01:54 +00:00
2017-08-24 10:59:46 +00:00
if ( weakSelf )
2017-08-16 20:01:54 +00:00
{
2017-08-24 10:59:46 +00:00
typeof ( self ) self = weakSelf ;
self . imageCompressionMode = ImageCompressionModeNone ;
2019-04-02 13:01:50 +00:00
[ self logCompressionSizeChoice : compressionSizes . large ] ;
2017-08-24 10:59:46 +00:00
if ( shareBlock )
{
shareBlock ( ) ;
}
2017-08-16 20:01:54 +00:00
}
2017-08-24 10:59:46 +00:00
} ] ] ;
}
2017-08-16 20:01:54 +00:00
[ compressionPrompt addAction : [ UIAlertAction actionWithTitle : [ NSBundle mxk_localizedStringForKey : @ "cancel" ]
2018-01-19 02:43:28 +00:00
style : UIAlertActionStyleCancel
2019-01-04 12:52:26 +00:00
handler : nil ] ] ;
2017-08-16 20:01:54 +00:00
}
2017-08-24 10:59:46 +00:00
else
{
2019-01-04 12:52:26 +00:00
if ( isAPendingImageNotOrientedUp && self . pendingImages . count > 1 )
{
self . imageCompressionMode = ImageCompressionModeSmall ;
}
else
{
self . imageCompressionMode = ImageCompressionModeNone ;
}
2021-06-03 08:30:07 +00:00
MXLogDebug ( @ "[ShareExtensionManager] Send %lu image(s) without compression prompt using compression mode: %ld" , ( unsigned long ) self . pendingImages . count , ( long ) self . imageCompressionMode ) ;
2019-04-02 13:01:50 +00:00
2017-08-24 10:59:46 +00:00
if ( shareBlock )
{
shareBlock ( ) ;
}
}
2017-08-16 20:01:54 +00:00
return compressionPrompt ;
}
2017-08-31 13:51:49 +00:00
- ( void ) didStartSendingToRoom : ( MXRoom * ) room
2017-08-20 21:39:08 +00:00
{
if ( [ self . delegate respondsToSelector : @ selector ( shareExtensionManager : didStartSendingContentToRoom : ) ] )
{
2017-08-31 13:51:49 +00:00
[ self . delegate shareExtensionManager : self didStartSendingContentToRoom : room ] ;
2017-08-20 21:39:08 +00:00
}
}
2017-08-22 15:52:32 +00:00
- ( BOOL ) areAttachmentsFullyLoaded
{
for ( NSExtensionItem * item in self . shareExtensionContext . inputItems )
{
for ( NSItemProvider * itemProvider in item . attachments )
{
if ( itemProvider . isLoaded = = NO )
{
return NO ;
}
}
}
return YES ;
}
2019-02-08 10:04:51 +00:00
- ( BOOL ) areAllAttachmentsImages
{
for ( NSExtensionItem * item in self . shareExtensionContext . inputItems )
{
for ( NSItemProvider * itemProvider in item . attachments )
{
if ( ! [ itemProvider hasItemConformingToTypeIdentifier : ( __bridge NSString * ) kUTTypeImage ] )
{
return NO ;
}
}
}
return YES ;
}
2019-02-06 15:05:36 +00:00
- ( 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 ) ;
NSString * uti = ( NSString * ) CGImageSourceGetType ( imageSource ) ;
CFRelease ( imageSource ) ;
return uti ;
}
- ( NSString * ) mimeTypeFromUTI : ( NSString * ) uti
{
return ( __bridge _transfer NSString * ) UTTypeCopyPreferredTagWithClass ( ( __bridge CFStringRef ) uti , kUTTagClassMIMEType ) ;
}
- ( BOOL ) isResizingSupportedForImageData : ( NSData * ) imageData
{
NSString * imageUTI = [ self utiFromImageData : imageData ] ;
return [ self isResizingSupportedForUTI : imageUTI ] ;
}
- ( BOOL ) isResizingSupportedForUTI : ( NSString * ) imageUTI
{
if ( [ imageUTI isEqualToString : ( __bridge NSString * ) kUTTypePNG ] || [ imageUTI isEqualToString : ( __bridge NSString * ) kUTTypeJPEG ] )
{
return YES ;
}
return NO ;
}
- ( CGSize ) imageSizeFromImageData : ( NSData * ) imageData
{
CGFloat width = 0.0 f ;
CGFloat height = 0.0 f ;
CGImageSourceRef imageSource = CGImageSourceCreateWithData ( ( CFDataRef ) imageData , NULL ) ;
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex ( imageSource , 0 , NULL ) ;
CFRelease ( imageSource ) ;
if ( imageProperties ! = NULL )
{
CFNumberRef widthNumber = CFDictionaryGetValue ( imageProperties , kCGImagePropertyPixelWidth ) ;
CFNumberRef heightNumber = CFDictionaryGetValue ( imageProperties , kCGImagePropertyPixelHeight ) ;
CFNumberRef orientationNumber = CFDictionaryGetValue ( imageProperties , kCGImagePropertyOrientation ) ;
if ( widthNumber ! = NULL )
{
CFNumberGetValue ( widthNumber , kCFNumberCGFloatType , & width ) ;
}
if ( heightNumber ! = NULL )
{
CFNumberGetValue ( heightNumber , kCFNumberCGFloatType , & height ) ;
}
// Check orientation and flip size if required
if ( orientationNumber ! = NULL )
{
int orientation ;
CFNumberGetValue ( orientationNumber , kCFNumberIntType , & orientation ) ;
// For orientation from kCGImagePropertyOrientationLeftMirrored to kCGImagePropertyOrientationLeft flip size
if ( orientation >= 5 )
{
CGFloat tempWidth = width ;
width = height ;
height = tempWidth ;
}
}
CFRelease ( imageProperties ) ;
}
return CGSizeMake ( width , height ) ;
}
- ( NSNumber * ) cgImageimageOrientationNumberFromImageData : ( NSData * ) imageData
{
NSNumber * orientationNumber ;
CGImageSourceRef imageSource = CGImageSourceCreateWithData ( ( CFDataRef ) imageData , NULL ) ;
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex ( imageSource , 0 , NULL ) ;
CFRelease ( imageSource ) ;
if ( imageProperties ! = NULL )
{
CFNumberRef orientationNum = CFDictionaryGetValue ( imageProperties , kCGImagePropertyOrientation ) ;
// Check orientation and flip size if required
if ( orientationNum ! = NULL )
{
orientationNumber = ( __bridge NSNumber * ) orientationNum ;
}
CFRelease ( imageProperties ) ;
}
return orientationNumber ;
}
- ( BOOL ) isImageOrientationNotUpOrUndeterminedForImageData : ( NSData * ) imageData
{
BOOL isImageNotOrientedUp = YES ;
NSNumber * cgImageOrientationNumber = [ self cgImageimageOrientationNumberFromImageData : imageData ] ;
if ( cgImageOrientationNumber && cgImageOrientationNumber . unsignedIntegerValue = = ( NSUInteger ) kCGImagePropertyOrientationUp )
{
isImageNotOrientedUp = NO ;
}
return isImageNotOrientedUp ;
}
2019-04-02 13:01:50 +00:00
- ( void ) logCompressionSizeChoice : ( MXKImageCompressionSize ) compressionSize
{
NSString * fileSize = [ MXTools fileSizeToString : compressionSize . fileSize round : NO ] ;
NSUInteger imageWidth = compressionSize . imageSize . width ;
NSUInteger imageHeight = compressionSize . imageSize . height ;
2021-06-03 08:30:07 +00:00
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 ) ;
2019-04-02 13:01:50 +00:00
}
2019-04-02 12:42:43 +00:00
// Log memory usage .
// NOTE : This result may not be reliable for all iOS versions ( see https : // forums . developer . apple . com / thread / 64665 for more information ) .
- ( void ) logMemoryUsage
{
struct task_basic _info basicInfo ;
mach_msg _type _number _t size = TASK_BASIC _INFO _COUNT ;
kern_return _t kerr = task_info ( mach_task _self ( ) ,
TASK_BASIC _INFO ,
( task_info _t ) & basicInfo ,
& size ) ;
vm_size _t memoryUsedInBytes = basicInfo . resident_size ;
CGFloat memoryUsedInMegabytes = memoryUsedInBytes / ( 1024 * 1024 ) ;
if ( kerr = = KERN_SUCCESS )
{
2021-06-03 08:30:07 +00:00
MXLogDebug ( @ "[ShareExtensionManager] Memory in use (in MB): %f" , memoryUsedInMegabytes ) ;
2019-04-02 12:42:43 +00:00
}
else
{
2021-06-03 08:30:07 +00:00
MXLogDebug ( @ "[ShareExtensionManager] Error with task_info(): %s" , mach_error _string ( kerr ) ) ;
2019-04-02 12:42:43 +00:00
}
}
2017-08-16 20:01:54 +00:00
# pragma mark - Notifications
2018-11-10 13:28:08 +00:00
- ( void ) onMediaLoaderStateDidChange : ( NSNotification * ) notification
2017-08-16 20:01:54 +00:00
{
2018-11-10 13:28:08 +00:00
MXMediaLoader * loader = ( MXMediaLoader * ) notification . object ;
// Consider only upload progress
switch ( loader . state ) {
case MXMediaLoaderStateUploadInProgress :
2017-08-22 15:52:32 +00:00
{
2018-11-10 13:28:08 +00:00
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 )
{
totalProgress + = progress . floatValue / totalImagesCount ;
}
[ self . delegate shareExtensionManager : self mediaUploadProgress : totalProgress ] ;
}
break ;
2017-08-22 15:52:32 +00:00
}
2018-11-10 13:28:08 +00:00
default :
break ;
2017-08-16 20:01:54 +00:00
}
}
2019-03-11 17:04:42 +00:00
- ( void ) didReceiveMemoryWarning : ( NSNotification * ) notification
{
2021-06-03 08:30:07 +00:00
MXLogDebug ( @ "[ShareExtensionManager] Did receive memory warning" ) ;
2019-04-02 12:42:43 +00:00
[ self logMemoryUsage ] ;
2019-03-11 17:04:42 +00:00
}
2017-08-14 11:25:02 +00:00
# pragma mark - Sharing
2019-02-08 10:04:51 +00:00
- ( void ) sendText : ( NSString * ) text toRoom : ( MXRoom * ) room successBlock : ( dispatch_block _t ) successBlock failureBlock : ( void ( ^ ) ( NSError * error ) ) failureBlock
2017-08-14 11:25:02 +00:00
{
2017-08-31 13:51:49 +00:00
[ self didStartSendingToRoom : room ] ;
2017-08-14 11:25:02 +00:00
if ( ! text )
{
2021-06-03 08:30:07 +00:00
MXLogDebug ( @ "[ShareExtensionManager] loadItemForTypeIdentifier: failed." ) ;
2017-08-14 11:25:02 +00:00
if ( failureBlock )
{
2017-09-29 08:37:54 +00:00
failureBlock ( nil ) ;
2017-08-14 11:25:02 +00:00
}
return ;
}
2017-08-17 15:28:08 +00:00
2017-08-31 13:51:49 +00:00
[ room sendTextMessage : text success : ^ ( NSString * eventId ) {
2019-02-08 10:04:51 +00:00
if ( successBlock )
2017-08-17 15:28:08 +00:00
{
2019-02-08 10:04:51 +00:00
successBlock ( ) ;
2017-08-17 15:28:08 +00:00
}
2017-08-14 11:25:02 +00:00
} failure : ^ ( NSError * error ) {
2021-06-03 08:30:07 +00:00
MXLogDebug ( @ "[ShareExtensionManager] sendTextMessage failed." ) ;
2017-08-14 11:25:02 +00:00
if ( failureBlock )
{
2017-09-29 08:37:54 +00:00
failureBlock ( error ) ;
2017-08-14 11:25:02 +00:00
}
} ] ;
}
2019-02-08 10:04:51 +00:00
- ( void ) sendFileWithUrl : ( NSURL * ) fileUrl toRoom : ( MXRoom * ) room successBlock : ( dispatch_block _t ) successBlock failureBlock : ( void ( ^ ) ( NSError * error ) ) failureBlock
2017-08-14 11:25:02 +00:00
{
2017-08-31 13:51:49 +00:00
[ self didStartSendingToRoom : room ] ;
2017-08-14 11:25:02 +00:00
if ( ! fileUrl )
{
2021-06-03 08:30:07 +00:00
MXLogDebug ( @ "[ShareExtensionManager] loadItemForTypeIdentifier: failed." ) ;
2017-08-14 11:25:02 +00:00
if ( failureBlock )
{
2017-09-29 08:37:54 +00:00
failureBlock ( nil ) ;
2017-08-14 11:25:02 +00:00
}
return ;
}
2017-08-21 22:31:43 +00:00
NSString * mimeType ;
CFStringRef uti = UTTypeCreatePreferredIdentifierForTag ( kUTTagClassFilenameExtension , ( __bridge CFStringRef ) [ fileUrl pathExtension ] , NULL ) ;
2019-02-06 15:05:36 +00:00
mimeType = [ self mimeTypeFromUTI : ( __bridge NSString * ) uti ] ;
2017-08-21 22:31:43 +00:00
CFRelease ( uti ) ;
2017-08-17 15:28:08 +00:00
2017-08-31 13:51:49 +00:00
[ room sendFile : fileUrl mimeType : mimeType localEcho : nil success : ^ ( NSString * eventId ) {
2019-02-08 10:04:51 +00:00
if ( successBlock )
2017-08-17 15:28:08 +00:00
{
2019-02-08 10:04:51 +00:00
successBlock ( ) ;
2017-08-17 15:28:08 +00:00
}
2017-08-14 11:25:02 +00:00
} failure : ^ ( NSError * error ) {
2021-06-03 08:30:07 +00:00
MXLogDebug ( @ "[ShareExtensionManager] sendFile failed." ) ;
2017-08-14 11:25:02 +00:00
if ( failureBlock )
{
2017-09-29 08:37:54 +00:00
failureBlock ( error ) ;
2017-08-14 11:25:02 +00:00
}
2017-08-31 13:51:49 +00:00
} keepActualFilename : YES ] ;
2017-08-14 11:25:02 +00:00
}
2019-02-08 13:43:21 +00:00
- ( void ) sendImageData : ( NSData * ) imageData withProvider : ( NSItemProvider * ) itemProvider toRoom : ( MXRoom * ) room successBlock : ( dispatch_block _t ) successBlock failureBlock : ( void ( ^ ) ( NSError * error ) ) failureBlock
2017-08-14 11:25:02 +00:00
{
2019-02-08 13:43:21 +00:00
[ self didStartSendingToRoom : room ] ;
NSString * imageUTI ;
NSString * mimeType ;
// Try to get UTI plus mime type from NSItemProvider
imageUTI = [ self utiFromImageTypeItemProvider : itemProvider ] ;
if ( imageUTI )
2019-01-04 12:55:46 +00:00
{
2019-02-08 13:43:21 +00:00
mimeType = [ self mimeTypeFromUTI : imageUTI ] ;
}
if ( ! mimeType )
{
// Try to get UTI plus mime type from image data
2019-01-04 12:55:46 +00:00
2019-02-08 13:43:21 +00:00
imageUTI = [ self utiFromImageData : imageData ] ;
if ( imageUTI )
{
mimeType = [ self mimeTypeFromUTI : imageUTI ] ;
}
}
// Sanity check
if ( ! mimeType )
{
2021-06-03 08:30:07 +00:00
MXLogDebug ( @ "[ShareExtensionManager] sendImage failed. Cannot determine MIME type of %@" , itemProvider ) ;
2019-01-04 12:55:46 +00:00
if ( failureBlock )
{
failureBlock ( nil ) ;
}
return ;
}
2019-02-08 13:43:21 +00:00
CGSize imageSize ;
NSData * finalImageData ;
2017-11-09 14:59:42 +00:00
2019-02-08 13:43:21 +00:00
// Only resize JPEG or PNG files
if ( [ self isResizingSupportedForUTI : imageUTI ] )
2017-08-14 11:25:02 +00:00
{
2019-02-08 13:43:21 +00:00
UIImage * convertedImage ;
CGSize newImageSize ;
switch ( self . imageCompressionMode ) {
case ImageCompressionModeSmall :
newImageSize = CGSizeMake ( MXKTOOLS_SMALL _IMAGE _SIZE , MXKTOOLS_SMALL _IMAGE _SIZE ) ;
break ;
case ImageCompressionModeMedium :
newImageSize = CGSizeMake ( MXKTOOLS_MEDIUM _IMAGE _SIZE , MXKTOOLS_MEDIUM _IMAGE _SIZE ) ;
break ;
case ImageCompressionModeLarge :
newImageSize = CGSizeMake ( self . actualLargeSize , self . actualLargeSize ) ;
break ;
default :
newImageSize = CGSizeZero ;
break ;
}
if ( CGSizeEqualToSize ( newImageSize , CGSizeZero ) )
2017-08-14 11:25:02 +00:00
{
2019-02-08 13:43:21 +00:00
// No resize to make
// Make sure the uploaded image orientation is up
if ( [ self isImageOrientationNotUpOrUndeterminedForImageData : imageData ] )
2017-11-09 14:59:42 +00:00
{
2019-02-08 13:43:21 +00:00
UIImage * image = [ UIImage imageWithData : imageData ] ;
convertedImage = [ MXKTools forceImageOrientationUp : image ] ;
2017-11-09 14:59:42 +00:00
}
2019-02-08 13:43:21 +00:00
}
else
{
// Resize the image and set image in right orientation too
convertedImage = [ MXKTools resizeImageWithData : imageData toFitInSize : newImageSize ] ;
}
if ( convertedImage )
{
if ( [ imageUTI isEqualToString : ( __bridge NSString * ) kUTTypePNG ] )
2017-08-22 15:52:32 +00:00
{
2019-02-08 13:43:21 +00:00
finalImageData = UIImagePNGRepresentation ( convertedImage ) ;
2017-08-22 15:52:32 +00:00
}
2019-02-08 13:43:21 +00:00
else if ( [ imageUTI isEqualToString : ( __bridge NSString * ) kUTTypeJPEG ] )
2019-01-04 12:55:46 +00:00
{
2019-02-08 13:43:21 +00:00
finalImageData = UIImageJPEGRepresentation ( convertedImage , 0.9 ) ;
2019-01-04 12:55:46 +00:00
}
2017-08-22 15:52:32 +00:00
2019-02-08 13:43:21 +00:00
imageSize = convertedImage . size ;
}
else
{
finalImageData = imageData ;
imageSize = [ self imageSizeFromImageData : imageData ] ;
}
}
else
{
finalImageData = imageData ;
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 ) {
if ( successBlock )
{
successBlock ( ) ;
}
} failure : ^ ( NSError * error ) {
2021-06-03 08:30:07 +00:00
MXLogDebug ( @ "[ShareExtensionManager] sendImage failed." ) ;
2019-02-08 13:43:21 +00:00
if ( failureBlock )
{
failureBlock ( error ) ;
}
} ] ;
}
- ( void ) sendImageDatas : ( NSMutableArray * ) imageDatas withProviders : ( NSArray * ) itemProviders toRoom : ( MXRoom * ) room successBlock : ( dispatch_block _t ) successBlock failureBlock : ( void ( ^ ) ( NSError * error ) ) failureBlock
{
if ( imageDatas . count = = 0 || imageDatas . count ! = itemProviders . count )
{
2021-06-03 08:30:07 +00:00
MXLogDebug ( @ "[ShareExtensionManager] sendImages: no images to send." ) ;
2019-02-08 13:43:21 +00:00
if ( failureBlock )
{
failureBlock ( 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 ) ;
2019-02-06 15:05:36 +00:00
2019-02-08 13:43:21 +00:00
NSItemProvider * itemProvider = itemProviders [ index ] ;
[ self sendImageData : imageData withProvider : itemProvider toRoom : room successBlock : ^ {
dispatch_group _leave ( requestsGroup ) ;
} failureBlock : ^ ( NSError * error ) {
2019-02-06 15:05:36 +00:00
2019-02-08 13:43:21 +00:00
if ( error && ! firstRequestError )
2019-02-06 15:05:36 +00:00
{
2019-02-08 13:43:21 +00:00
firstRequestError = error ;
2019-02-06 15:05:36 +00:00
}
2019-02-08 13:43:21 +00:00
dispatch_group _leave ( requestsGroup ) ;
} ] ;
}
index + + ;
}
dispatch_group _notify ( requestsGroup , dispatch_get _main _queue ( ) , ^ {
if ( firstRequestError )
{
if ( failureBlock )
2019-02-06 15:05:36 +00:00
{
2019-02-08 13:43:21 +00:00
failureBlock ( firstRequestError ) ;
2019-02-06 15:05:36 +00:00
}
2019-02-08 13:43:21 +00:00
}
else
{
if ( successBlock )
2017-08-22 15:52:32 +00:00
{
2019-02-08 13:43:21 +00:00
successBlock ( ) ;
2017-08-22 15:52:32 +00:00
}
2019-01-04 12:55:46 +00:00
}
2019-02-08 13:43:21 +00:00
} ) ;
2017-08-14 11:25:02 +00:00
}
2019-02-08 10:04:51 +00:00
- ( void ) sendVideo : ( NSURL * ) videoLocalUrl toRoom : ( MXRoom * ) room successBlock : ( dispatch_block _t ) successBlock failureBlock : ( void ( ^ ) ( NSError * error ) ) failureBlock
2017-08-14 11:25:02 +00:00
{
2021-07-09 09:26:02 +00:00
AVURLAsset * videoAsset = [ [ AVURLAsset alloc ] initWithURL : videoLocalUrl options : nil ] ;
2021-08-16 19:01:35 +00:00
MXWeakify ( self ) ;
2021-08-23 11:38:04 +00:00
// Ignore showMediaCompressionPrompt setting due to memory constraints when encrypting large videos .
2021-08-16 19:01:35 +00:00
UIAlertController * compressionPrompt = [ MXKTools videoConversionPromptForVideoAsset : videoAsset withCompletion : ^ ( NSString * _Nullable presetName ) {
MXStrongifyAndReturnIfNil ( self ) ;
// If the preset name is nil , the user cancelled .
if ( ! presetName )
2017-08-17 15:28:08 +00:00
{
2021-08-16 19:01:35 +00:00
return ;
2017-08-17 15:28:08 +00:00
}
2021-08-16 19:01:35 +00:00
// Set the chosen video conversion preset .
[ MXSDKOptions sharedInstance ] . videoConversionPresetName = presetName ;
[ self didStartSendingToRoom : room ] ;
if ( ! videoLocalUrl )
2017-08-14 11:25:02 +00:00
{
2021-08-16 19:01:35 +00:00
MXLogDebug ( @ "[ShareExtensionManager] loadItemForTypeIdentifier: failed." ) ;
if ( failureBlock )
{
failureBlock ( nil ) ;
}
return ;
2017-08-14 11:25:02 +00:00
}
2021-08-16 19:01:35 +00:00
// 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 ) {
if ( successBlock )
{
successBlock ( ) ;
}
} failure : ^ ( NSError * error ) {
MXLogDebug ( @ "[ShareExtensionManager] sendVideo failed." ) ;
if ( failureBlock )
{
failureBlock ( error ) ;
}
} ] ;
2017-08-14 11:25:02 +00:00
} ] ;
2021-08-16 19:01:35 +00:00
[ self . delegate shareExtensionManager : self showImageCompressionPrompt : compressionPrompt ] ;
2017-08-14 11:25:02 +00:00
}
2017-08-10 22:38:47 +00:00
@ end
2017-08-22 15:52:32 +00:00
@ implementation NSItemProvider ( ShareExtensionManager )
- ( void ) setIsLoaded : ( BOOL ) isLoaded
{
2019-01-07 23:24:11 +00:00
NSNumber * number = @ ( isLoaded ) ;
2017-08-22 15:52:32 +00:00
objc_setAssociatedObject ( self , @ selector ( isLoaded ) , number , OBJC_ASSOCIATION _RETAIN _NONATOMIC ) ;
}
- ( BOOL ) isLoaded
{
NSNumber * number = objc_getAssociatedObject ( self , @ selector ( isLoaded ) ) ;
return number . boolValue ;
}
@ end