mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-17 23:18:27 +02:00
Merge branch 'feature/5365_photos_picker' into 'develop'
MESSENGER-5365 photos picker See merge request bwmessenger/bundesmessenger/bundesmessenger-ios!302
This commit is contained in:
@@ -713,6 +713,10 @@ class BWIBuildSettings: NSObject {
|
||||
// MARK: Backup restore
|
||||
var showMatrixIDinRoomInfoScreen = false
|
||||
|
||||
|
||||
// MARK: New photos picker API
|
||||
var useNewPhotosPickerAPI = true
|
||||
|
||||
// MARK: Contacts List
|
||||
var showContactIdentifierInDetailRow = true
|
||||
|
||||
|
||||
@@ -335,7 +335,7 @@ typedef enum : NSUInteger
|
||||
@param selectedVideo an AVAsset that represents the video to send.
|
||||
@param isPhotoLibraryAsset tell whether the video has been selected from user's photos library.
|
||||
*/
|
||||
- (void)sendSelectedVideoAsset:(AVAsset*)selectedVideo isPhotoLibraryAsset:(BOOL)isPhotoLibraryAsset;
|
||||
- (BOOL)sendSelectedVideoAsset:(AVAsset*)selectedVideo isPhotoLibraryAsset:(BOOL)isPhotoLibraryAsset;
|
||||
|
||||
/**
|
||||
Handle multiple media attachments according to the compression mode.
|
||||
|
||||
@@ -779,7 +779,8 @@ NSString* MXKFileSizes_description(MXKFileSizes sizes)
|
||||
[self sendSelectedVideoAsset:videoAsset isPhotoLibraryAsset:isPhotoLibraryAsset];
|
||||
}
|
||||
|
||||
- (void)sendSelectedVideoAsset:(AVAsset*)selectedVideo isPhotoLibraryAsset:(BOOL)isPhotoLibraryAsset
|
||||
// bwi: added boolean return value to inform the caller if the asset could be sent to the content repository
|
||||
- (BOOL)sendSelectedVideoAsset:(AVAsset*)selectedVideo isPhotoLibraryAsset:(BOOL)isPhotoLibraryAsset
|
||||
{
|
||||
// Check condition before saving this media in user's library
|
||||
if (_enableAutoSaving && !isPhotoLibraryAsset)
|
||||
@@ -797,21 +798,32 @@ NSString* MXKFileSizes_description(MXKFileSizes sizes)
|
||||
|
||||
if ([self.delegate respondsToSelector:@selector(roomInputToolbarView:sendVideoAsset:withThumbnail:)])
|
||||
{
|
||||
if (![selectedVideo isKindOfClass:[AVURLAsset class]]) {
|
||||
MXLogDebug(@"sendSelectedVideoAsset failed because asset is not an AVURLAsset");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Retrieve the video frame at 1 sec to define the video thumbnail
|
||||
AVAssetImageGenerator *assetImageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:selectedVideo];
|
||||
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);
|
||||
|
||||
[self.delegate roomInputToolbarView:self sendVideoAsset:selectedVideo withThumbnail:videoThumbnail];
|
||||
if(imageRef) {
|
||||
// Finalize video attachment
|
||||
UIImage* videoThumbnail = [[UIImage alloc] initWithCGImage:imageRef];
|
||||
CFRelease(imageRef);
|
||||
|
||||
[self.delegate roomInputToolbarView:self sendVideoAsset:selectedVideo withThumbnail:videoThumbnail];
|
||||
return true;
|
||||
} else {
|
||||
MXLogDebug(@"sendSelectedVideoAsset failed because imageRef is nil");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MXLogDebug(@"[RoomInputToolbarView] Attach video is not supported");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
import PhotosUI
|
||||
|
||||
@objc protocol SingleImagePickerPresenterDelegate: AnyObject {
|
||||
func singleImagePickerPresenter(_ presenter: SingleImagePickerPresenter, didSelectImageData imageData: Data, withUTI uti: MXKUTI?)
|
||||
@@ -122,12 +123,26 @@ final class SingleImagePickerPresenter: NSObject {
|
||||
guard let presentingViewController = self.presentingViewController else {
|
||||
return
|
||||
}
|
||||
|
||||
let mediaPickerPresenter = MediaPickerCoordinatorBridgePresenter(session: self.session, mediaUTIs: [.image], allowsMultipleSelection: false)
|
||||
mediaPickerPresenter.delegate = self
|
||||
|
||||
mediaPickerPresenter.present(from: presentingViewController, sourceView: sourceView, sourceRect: sourceRect, animated: animated)
|
||||
self.mediaPickerPresenter = mediaPickerPresenter
|
||||
|
||||
if BWIBuildSettings.shared.useNewPhotosPickerAPI {
|
||||
var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
|
||||
configuration.filter = PHPickerFilter.any(of: [.images, .videos])
|
||||
configuration.preferredAssetRepresentationMode = .compatible
|
||||
configuration.selection = .default
|
||||
configuration.selectionLimit = 1
|
||||
configuration.preselectedAssetIdentifiers = []
|
||||
|
||||
let pickerViewController = PHPickerViewController(configuration: configuration)
|
||||
pickerViewController.delegate = self
|
||||
|
||||
presentingViewController.present(pickerViewController, animated: true)
|
||||
} else {
|
||||
let mediaPickerPresenter = MediaPickerCoordinatorBridgePresenter(session: self.session, mediaUTIs: [.image], allowsMultipleSelection: false)
|
||||
mediaPickerPresenter.delegate = self
|
||||
|
||||
mediaPickerPresenter.present(from: presentingViewController, sourceView: sourceView, sourceRect: sourceRect, animated: animated)
|
||||
self.mediaPickerPresenter = mediaPickerPresenter
|
||||
}
|
||||
}
|
||||
|
||||
private func removeImage() {
|
||||
@@ -170,3 +185,30 @@ extension SingleImagePickerPresenter: MediaPickerCoordinatorBridgePresenterDeleg
|
||||
self.delegate?.singleImagePickerPresenterDidCancel(self)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - PHPickerViewControllerDelegate (BWI)
|
||||
extension SingleImagePickerPresenter: PHPickerViewControllerDelegate {
|
||||
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
|
||||
picker.dismiss(animated: true)
|
||||
|
||||
guard let itemProvider = results.first?.itemProvider else {
|
||||
self.delegate?.singleImagePickerPresenterDidCancel(self)
|
||||
return
|
||||
}
|
||||
|
||||
if itemProvider.canLoadObject(ofClass: UIImage.self) {
|
||||
itemProvider.loadObject(ofClass: UIImage.self) { image, error in
|
||||
DispatchQueue.main.async {
|
||||
if let image = image as? UIImage, let imageData = image.pngData() {
|
||||
let uti = MXKUTI(mimeType: "image/png")
|
||||
self.delegate?.singleImagePickerPresenter(self, didSelectImageData: imageData, withUTI: uti)
|
||||
} else {
|
||||
self.delegate?.singleImagePickerPresenterDidCancel(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.delegate?.singleImagePickerPresenterDidCancel(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3560,13 +3560,35 @@ static const CGFloat kCellVisibilityMinimumHeight = 8.0;
|
||||
- (void)roomInputToolbarView:(MXKRoomInputToolbarView*)toolbarView sendVideoAsset:(AVAsset*)videoAsset withThumbnail:(UIImage*)videoThumbnail
|
||||
{
|
||||
// Let the datasource send it and manage the local echo
|
||||
[roomDataSource sendVideoAsset:videoAsset withThumbnail:videoThumbnail success:nil failure:^(NSError *error)
|
||||
{
|
||||
[roomDataSource sendVideoAsset:videoAsset withThumbnail:videoThumbnail success:^(NSString *eventId) {
|
||||
// bwi: 5365
|
||||
NSURL *url = [(AVURLAsset *)videoAsset URL];
|
||||
if(url) {
|
||||
[self deleteFileAtURL: url];
|
||||
}
|
||||
} failure:^(NSError *error) {
|
||||
// Nothing to do. The video is marked as unsent in the room history by the datasource
|
||||
MXLogDebug(@"[MXKRoomViewController] sendVideo failed.");
|
||||
// bwi: 5365
|
||||
NSURL *url = [(AVURLAsset *)videoAsset URL];
|
||||
if(url) {
|
||||
[self deleteFileAtURL: url];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)deleteFileAtURL:(NSURL *)url {
|
||||
if (url == nil) {
|
||||
return;
|
||||
}
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSError *error = nil;
|
||||
if (![fileManager removeItemAtURL:url error:&error]) {
|
||||
MXLogDebug(@"Cannot delete file at %@", [url absoluteString])
|
||||
MXLogDebug(@"%@", [error localizedDescription])
|
||||
}
|
||||
}
|
||||
|
||||
- (void)roomInputToolbarView:(MXKRoomInputToolbarView*)toolbarView sendFile:(NSURL *)fileLocalURL withMimeType:(NSString*)mimetype
|
||||
{
|
||||
// Let the datasource send it and manage the local echo
|
||||
|
||||
@@ -89,6 +89,9 @@
|
||||
|
||||
#import "GeneratedInterface-Swift.h"
|
||||
|
||||
#import <PhotosUI/PhotosUI.h>
|
||||
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
|
||||
|
||||
NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNotification";
|
||||
NSNotificationName const RoomGroupCallTileTappedNotification = @"RoomGroupCallTileTappedNotification";
|
||||
const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
||||
@@ -100,7 +103,7 @@ static CGSize kThreadListBarButtonItemImageSize;
|
||||
@interface RoomViewController () <UISearchBarDelegate, UIGestureRecognizerDelegate, UIScrollViewAccessibilityDelegate, RoomTitleViewTapGestureDelegate, MXKRoomMemberDetailsViewControllerDelegate, ContactsTableViewControllerDelegate, MXServerNoticesDelegate, RoomContextualMenuViewControllerDelegate,
|
||||
ReactionsMenuViewModelCoordinatorDelegate, EditHistoryCoordinatorBridgePresenterDelegate, MXKDocumentPickerPresenterDelegate, EmojiPickerCoordinatorBridgePresenterDelegate,
|
||||
ReactionHistoryCoordinatorBridgePresenterDelegate, CameraPresenterDelegate, MediaPickerCoordinatorBridgePresenterDelegate,
|
||||
RoomDataSourceDelegate, RoomCreationModalCoordinatorBridgePresenterDelegate, RoomInfoCoordinatorBridgePresenterDelegate, DialpadViewControllerDelegate, RemoveJitsiWidgetViewDelegate, VoiceMessageControllerDelegate, SpaceDetailPresenterDelegate, CompletionSuggestionCoordinatorBridgeDelegate, ThreadsCoordinatorBridgePresenterDelegate, ThreadsBetaCoordinatorBridgePresenterDelegate, MXThreadingServiceDelegate, RoomParticipantsInviteCoordinatorBridgePresenterDelegate, RoomInputToolbarViewDelegate, ComposerCreateActionListBridgePresenterDelegate>
|
||||
RoomDataSourceDelegate, RoomCreationModalCoordinatorBridgePresenterDelegate, RoomInfoCoordinatorBridgePresenterDelegate, DialpadViewControllerDelegate, RemoveJitsiWidgetViewDelegate, VoiceMessageControllerDelegate, SpaceDetailPresenterDelegate, CompletionSuggestionCoordinatorBridgeDelegate, ThreadsCoordinatorBridgePresenterDelegate, ThreadsBetaCoordinatorBridgePresenterDelegate, MXThreadingServiceDelegate, RoomParticipantsInviteCoordinatorBridgePresenterDelegate, RoomInputToolbarViewDelegate, ComposerCreateActionListBridgePresenterDelegate, PHPickerViewControllerDelegate>
|
||||
{
|
||||
|
||||
// The preview header
|
||||
@@ -2334,9 +2337,24 @@ static CGSize kThreadListBarButtonItemImageSize;
|
||||
sourceView = self.inputToolbarView;
|
||||
}
|
||||
|
||||
[mediaPickerPresenter presentFrom:self sourceView:sourceView sourceRect:sourceView.bounds animated:YES];
|
||||
|
||||
self.mediaPickerPresenter = mediaPickerPresenter;
|
||||
if(BWIBuildSettings.shared.useNewPhotosPickerAPI) {
|
||||
PHPhotoLibrary *sharedPhotoLibrary = [PHPhotoLibrary sharedPhotoLibrary];
|
||||
PHPickerConfiguration *configuration = [[PHPickerConfiguration alloc] initWithPhotoLibrary:sharedPhotoLibrary];
|
||||
configuration.filter = [PHPickerFilter anyFilterMatchingSubfilters:@[PHPickerFilter.imagesFilter, PHPickerFilter.videosFilter]];
|
||||
configuration.preferredAssetRepresentationMode = PHPickerConfigurationAssetRepresentationModeCompatible;
|
||||
configuration.selection = PHPickerConfigurationSelectionDefault;
|
||||
configuration.selectionLimit = 1;
|
||||
configuration.preselectedAssetIdentifiers = @[];
|
||||
|
||||
PHPickerViewController *pickerViewController = [[PHPickerViewController alloc] initWithConfiguration:configuration];
|
||||
pickerViewController.delegate = self;
|
||||
|
||||
[self presentViewController:pickerViewController animated:YES completion:nil];
|
||||
} else {
|
||||
[mediaPickerPresenter presentFrom:self sourceView:sourceView sourceRect:sourceView.bounds animated:YES];
|
||||
|
||||
self.mediaPickerPresenter = mediaPickerPresenter;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showRoomCreationModal
|
||||
@@ -2675,6 +2693,32 @@ static CGSize kThreadListBarButtonItemImageSize;
|
||||
}
|
||||
}
|
||||
|
||||
// bwi: 5365
|
||||
- (void)sendVideoAssetWithoutCompression:(AVAsset *)videoAsset isPhotoLibraryAsset:(BOOL)isPhotoLibraryAsset
|
||||
{
|
||||
// Create before sending the message in case of a discussion (direct chat)
|
||||
[self createDiscussionIfNeeded:^(BOOL readyToSend) {
|
||||
if (readyToSend && [self inputToolbarConformsToToolbarViewProtocol])
|
||||
{
|
||||
if(![self.inputToolbarView sendSelectedVideoAsset:videoAsset isPhotoLibraryAsset:isPhotoLibraryAsset]) {
|
||||
NSURL *url = [(AVURLAsset *)videoAsset URL];
|
||||
if(url) {
|
||||
[self deleteFileAtURL:url];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if([videoAsset isKindOfClass:[AVURLAsset class]]) {
|
||||
NSURL *url = [(AVURLAsset *)videoAsset URL];
|
||||
[self deleteFileAtURL:url];
|
||||
}
|
||||
}
|
||||
NSURL *url = [(AVURLAsset *)videoAsset URL];
|
||||
if(url) {
|
||||
[self deleteFileAtURL:url];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)showRoomWithId:(NSString*)roomId
|
||||
{
|
||||
if (self.delegate)
|
||||
@@ -8031,6 +8075,113 @@ static CGSize kThreadListBarButtonItemImageSize;
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - PHPickerViewControllerDelegate
|
||||
|
||||
- (void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray<PHPickerResult *> *)results
|
||||
{
|
||||
[picker dismissViewControllerAnimated:YES completion:nil];
|
||||
|
||||
if (results.firstObject) {
|
||||
NSItemProvider *itemProvider = results.firstObject.itemProvider;
|
||||
|
||||
if ([itemProvider canLoadObjectOfClass:[UIImage class]]) {
|
||||
[itemProvider loadObjectOfClass:[UIImage class] completionHandler:^(id _Nullable object, NSError * _Nullable error) {
|
||||
if (error) {
|
||||
MXLogDebug(@"Uploading photo from photo library failed: %@", error);
|
||||
} else {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if ([object isKindOfClass:[UIImage class]]) {
|
||||
UIImage *image = object;
|
||||
[self uploadImage:image];
|
||||
}
|
||||
});
|
||||
}
|
||||
}];
|
||||
} else if ([itemProvider hasItemConformingToTypeIdentifier:@"public.movie"]) {
|
||||
[itemProvider loadFileRepresentationForTypeIdentifier:@"public.movie" completionHandler:^(NSURL *videoURL, NSError *error) {
|
||||
if (error) {
|
||||
MXLogDebug(@"Uploading video from photo library failed: %@", error);
|
||||
} else {
|
||||
// First make a copy of the video in the temp directory because the video at videoURL
|
||||
// is a copy by the iOS photo picker and will be dismissed when this completion block ends
|
||||
NSURL *videoCopy = [self makeVideoCopy:videoURL];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self uploadVideo:videoCopy];
|
||||
});
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)uploadImage:(UIImage *)image {
|
||||
[self createDiscussionIfNeeded:^(BOOL readyToSend) {
|
||||
if (readyToSend && [self inputToolbarConformsToToolbarViewProtocol])
|
||||
{
|
||||
NSData *imageData = [image dataForPNGRepresentation];
|
||||
if(imageData) {
|
||||
[self.inputToolbarView sendSelectedImage:imageData
|
||||
withMimeType:@"image/png"
|
||||
andCompressionMode:MediaCompressionHelper.defaultCompressionMode
|
||||
isPhotoLibraryAsset:YES];
|
||||
} else {
|
||||
MXLogDebug(@"Uploading photo from photo library failed because imageData is nil")
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)uploadVideo:(NSURL *)url {
|
||||
if(url) {
|
||||
NSLog(@"uploadVideo: %@", [url absoluteString]);
|
||||
|
||||
AVURLAsset *asset = [AVURLAsset assetWithURL:url];
|
||||
if(asset) {
|
||||
[self sendVideoAssetWithoutCompression:asset isPhotoLibraryAsset:YES];
|
||||
} else {
|
||||
MXLogDebug(@"Uploading video from photo library failed because asset is nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSURL *)makeVideoCopy:(NSURL *)sourceURL {
|
||||
if (sourceURL == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *sourceFilename = [sourceURL lastPathComponent];
|
||||
|
||||
NSUUID *uuid = [NSUUID UUID];
|
||||
NSString *uuidString = [uuid UUIDString];
|
||||
|
||||
NSString *destinationFilename = [NSString stringWithFormat:@"%@-%@", uuidString, sourceFilename];
|
||||
|
||||
NSURL *tempDirURL = [NSURL fileURLWithPath:NSTemporaryDirectory()];
|
||||
NSURL *destinationURL = [tempDirURL URLByAppendingPathComponent:destinationFilename];
|
||||
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSError *error = nil;
|
||||
if ([fileManager copyItemAtURL:sourceURL toURL:destinationURL error:&error]) {
|
||||
MXLogDebug(@"Successfully copied video to a temp destination %@", [destinationURL absoluteString]);
|
||||
return destinationURL;
|
||||
} else {
|
||||
MXLogDebug(@"Failed to make a temp copy of the video file: %@", error);
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)deleteFileAtURL:(NSURL *)url {
|
||||
if (url == nil) {
|
||||
return;
|
||||
}
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSError *error = nil;
|
||||
if (![fileManager removeItemAtURL:url error:&error]) {
|
||||
MXLogDebug(@"Cannot delete file at %@", [url absoluteString])
|
||||
MXLogDebug(@"%@", [error localizedDescription])
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - RoomCreationModalCoordinatorBridgePresenter
|
||||
|
||||
- (void)roomCreationModalCoordinatorBridgePresenterDelegateDidComplete:(RoomCreationModalCoordinatorBridgePresenter *)coordinatorBridgePresenter
|
||||
|
||||
Reference in New Issue
Block a user