diff --git a/CHANGES.rst b/CHANGES.rst index 7daae55b1..1a2b6a02a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -18,6 +18,7 @@ Improvements: * Migrate to Swift 5.0. * Reactions: Update quick reactions (#2459). * Message Editing: Handle reply edition (#2492). + * RoomVC: Add ability to upload a file that comes from outside the app’s sandbox (#2019). Bug fix: * Device Verification: Fix user display name and device id colors in dark theme diff --git a/Podfile b/Podfile index fbab2ca18..652529aff 100644 --- a/Podfile +++ b/Podfile @@ -61,6 +61,7 @@ abstract_target 'RiotPods' do pod 'GBDeviceInfo', '~> 5.2.0' pod 'Reusable', '~> 4.1' + pod 'SwiftUTI', :git => 'https://github.com/speramusinc/SwiftUTI.git', :branch => 'master' # Piwik for analytics pod 'MatomoTracker', '~> 6.0.1' @@ -99,6 +100,11 @@ post_install do |installer| # Plus the app does not enable it target.build_configurations.each do |config| config.build_settings['ENABLE_BITCODE'] = 'NO' + + # Force SwiftUTI Swift version to 5.0 (as there is no code changes to perform for SwiftUTI fork using Swift 4.2) + if target.name.include? 'SwiftUTI' + config.build_settings['SWIFT_VERSION'] = '5.0' + end end end end diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 18d956eb1..ff0f9d47f 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -287,6 +287,7 @@ "room_event_failed_to_send" = "Failed to send"; "room_action_send_photo_or_video" = "Send photo or video"; "room_action_send_sticker" = "Send sticker"; +"room_action_send_file" = "Send file"; "room_action_reply" = "Reply"; "room_replacement_information" = "This room has been replaced and is no longer active."; "room_replacement_link" = "The conversation continues here."; @@ -900,4 +901,6 @@ "device_verification_emoji_folder" = "Folder"; "device_verification_emoji_pin" = "Pin"; - +// MARK: File upload +"file_upload_error_title" = "File upload"; +"file_upload_error_unsupported_file_type_message" = "File type not supported."; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 2caf52f43..e38028ae3 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -910,6 +910,14 @@ internal enum VectorL10n { internal static func eventFormatterWidgetRemoved(_ p1: String, _ p2: String) -> String { return VectorL10n.tr("Vector", "event_formatter_widget_removed", p1, p2) } + /// File upload + internal static var fileUploadErrorTitle: String { + return VectorL10n.tr("Vector", "file_upload_error_title") + } + /// File type not supported. + internal static var fileUploadErrorUnsupportedFileTypeMessage: String { + return VectorL10n.tr("Vector", "file_upload_error_unsupported_file_type_message") + } /// To continue using the %@ homeserver you must review and agree to the terms and conditions. internal static func gdprConsentNotGivenAlertMessage(_ p1: String) -> String { return VectorL10n.tr("Vector", "gdpr_consent_not_given_alert_message", p1) @@ -1350,6 +1358,10 @@ internal enum VectorL10n { internal static var roomActionReply: String { return VectorL10n.tr("Vector", "room_action_reply") } + /// Send file + internal static var roomActionSendFile: String { + return VectorL10n.tr("Vector", "room_action_send_file") + } /// Send photo or video internal static var roomActionSendPhotoOrVideo: String { return VectorL10n.tr("Vector", "room_action_send_photo_or_video") diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.h b/Riot/Modules/Room/DataSources/RoomDataSource.h index cfc62b45d..1c7dfda5a 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.h +++ b/Riot/Modules/Room/DataSources/RoomDataSource.h @@ -46,4 +46,20 @@ */ - (Widget *)jitsiWidget; +/** + Send a video to the room. + Note: Move this method to MatrixKit when MatrixKit project will handle Swift module. + + While sending, a fake event will be echoed in the messages list. + Once complete, this local echo will be replaced by the event saved by the homeserver. + + @param videoLocalURL the local filesystem path of the video to send. + @param success A block object called when the operation succeeds. It returns + the event id of the event generated on the homeserver + @param failure A block object called when the operation fails. + */ +- (void)sendVideo:(NSURL*)videoLocalURL + success:(void (^)(NSString *eventId))success + failure:(void (^)(NSError *error))failure; + @end diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index 75208dbfc..fa7034b45 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -555,6 +555,14 @@ return jitsiWidget; } +- (void)sendVideo:(NSURL*)videoLocalURL + success:(void (^)(NSString *eventId))success + failure:(void (^)(NSError *error))failure +{ + UIImage *videoThumbnail = [MXKVideoThumbnailGenerator.shared generateThumbnailFrom:videoLocalURL]; + [self sendVideo:videoLocalURL withThumbnail:videoThumbnail success:success failure:failure]; +} + #pragma mark - BubbleReactionsViewModelDelegate - (void)bubbleReactionsViewModel:(BubbleReactionsViewModel *)viewModel didAddReaction:(MXReactionCount *)reactionCount forEventId:(NSString *)eventId diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index b0d4f54c1..5b574c56f 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -124,7 +124,7 @@ #import "Riot-Swift.h" @interface RoomViewController () + ReactionsMenuViewModelCoordinatorDelegate, EditHistoryCoordinatorBridgePresenterDelegate, MXKDocumentPickerPresenterDelegate> { // The expanded header ExpandedRoomTitleView *expandedHeader; @@ -222,6 +222,7 @@ @property (nonatomic, strong) MXKErrorAlertPresentation *errorPresenter; @property (nonatomic, strong) NSString *textMessageBeforeEditing; @property (nonatomic, strong) EditHistoryCoordinatorBridgePresenter *editHistoryPresenter; +@property (nonatomic, strong) MXKDocumentPickerPresenter *documentPickerPresenter; @end @@ -3356,6 +3357,17 @@ } } +- (void)roomInputToolbarViewDidTapFileUpload:(MXKRoomInputToolbarView *)toolbarView +{ + MXKDocumentPickerPresenter *documentPickerPresenter = [MXKDocumentPickerPresenter new]; + documentPickerPresenter.delegate = self; + + NSArray *allowedUTIs = @[MXKUTI.data]; + [documentPickerPresenter presentDocumentPickerWith:allowedUTIs from:self animated:YES completion:nil]; + + self.documentPickerPresenter = documentPickerPresenter; +} + #pragma mark - RoomParticipantsViewControllerDelegate - (void)roomParticipantsViewController:(RoomParticipantsViewController *)roomParticipantsViewController mention:(MXRoomMember*)member @@ -5343,5 +5355,51 @@ self.editHistoryPresenter = nil; } +#pragma mark - DocumentPickerPresenterDelegate + +- (void)documentPickerPresenterWasCancelled:(MXKDocumentPickerPresenter *)presenter +{ + self.documentPickerPresenter = nil; +} + +- (void)documentPickerPresenter:(MXKDocumentPickerPresenter *)presenter didPickDocumentsAt:(NSURL *)url +{ + self.documentPickerPresenter = nil; + + MXKUTI *fileUTI = [[MXKUTI alloc] initWithLocalFileURL:url]; + NSString *mimeType = fileUTI.mimeType; + + if (fileUTI.isImage) + { + NSData *imageData = [[NSData alloc] initWithContentsOfURL:url]; + + [self.roomDataSource sendImage:imageData mimeType:mimeType success:nil failure:^(NSError *error) { + // Nothing to do. The image is marked as unsent in the room history by the datasource + NSLog(@"[MXKRoomViewController] sendImage failed."); + }]; + } + else if (fileUTI.isVideo) + { + [(RoomDataSource*)self.roomDataSource sendVideo:url success:nil failure:^(NSError *error) { + // Nothing to do. The video is marked as unsent in the room history by the datasource + NSLog(@"[MXKRoomViewController] sendVideo failed."); + }]; + } + else if (fileUTI.isFile) + { + [self.roomDataSource sendFile:url mimeType:mimeType success:nil failure:^(NSError *error) { + // Nothing to do. The file is marked as unsent in the room history by the datasource + NSLog(@"[MXKRoomViewController] sendFile failed."); + }]; + } + else + { + NSLog(@"[MXKRoomViewController] File upload using MIME type %@ is not supported.", mimeType); + + [[AppDelegate theDelegate] showAlertWithTitle:NSLocalizedStringFromTable(@"file_upload_error_title", @"Vector", nil) + message:NSLocalizedStringFromTable(@"file_upload_error_unsupported_file_type_message", @"Vector", nil)]; + } +} + @end diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h index c548f48cf..fdff609e1 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h @@ -38,6 +38,13 @@ typedef enum : NSUInteger */ - (void)roomInputToolbarViewPresentStickerPicker:(MXKRoomInputToolbarView*)toolbarView; +/** + Tells the delegate that the user wants to send external files. + + @param toolbarView the room input toolbar view + */ +- (void)roomInputToolbarViewDidTapFileUpload:(MXKRoomInputToolbarView*)toolbarView; + @end /** diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m index 2b9ff11e0..f09f0ba8f 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m @@ -349,6 +349,19 @@ } }]]; + + [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_action_send_file", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->actionSheet = nil; + + [self.delegate roomInputToolbarViewDidTapFileUpload:self]; + } + }]]; [actionSheet addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] style:UIAlertActionStyleCancel