import AVFoundation import Combine import Foundation import Photos import UIKit final class SharingMiddleware { static let shared = SharingMiddleware() // swiftlint:disable:next function_body_length func middleware(state: AppState, action: AppAction) -> AnyPublisher { switch action { // MARK: - Camera and Gallery Access case .sharingAction(.checkCameraAccess): return Future { promise in let status = AVCaptureDevice.authorizationStatus(for: .video) switch status { case .authorized: promise(.success(.sharingAction(.setCameraAccess(true)))) case .notDetermined: AVCaptureDevice.requestAccess(for: .video) { granted in promise(.success(.sharingAction(.setCameraAccess(granted)))) } case .denied, .restricted: promise(.success(.sharingAction(.setCameraAccess(false)))) @unknown default: promise(.success(.sharingAction(.setCameraAccess(false)))) } } .eraseToAnyPublisher() case .sharingAction(.checkGalleryAccess): return Future { promise in let status = PHPhotoLibrary.authorizationStatus() switch status { case .authorized, .limited: promise(.success(.sharingAction(.setGalleryAccess(true)))) case .notDetermined: PHPhotoLibrary.requestAuthorization { status in promise(.success(.sharingAction(.setGalleryAccess(status == .authorized)))) } case .denied, .restricted: promise(.success(.sharingAction(.setGalleryAccess(false)))) @unknown default: promise(.success(.sharingAction(.setGalleryAccess(false)))) } } .eraseToAnyPublisher() case .fileAction(.itemsFromGalleryFetched(let items)): return Just(.sharingAction(.galleryItemsUpdated(items: items))) .eraseToAnyPublisher() // MARK: - Sharing case .sharingAction(.shareMedia(let ids)): return Future { promise in let assets = PHAsset.fetchAssets(withLocalIdentifiers: ids, options: nil) assets.enumerateObjects { asset, _, _ in if asset.mediaType == .image { PHImageManager.default().requestImage( for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .aspectFill, options: nil ) { image, _ in if let data = image?.jpegData(compressionQuality: 1.0) { DispatchQueue.main.async { let newMessageId = UUID().uuidString store.dispatch(.fileAction(.copyFileForUploading( messageId: newMessageId, fileData: data, thumbnailData: store.state.sharingState.galleryItems.first(where: { $0.id == asset.localIdentifier })?.thumbnail ))) } } } } else if asset.mediaType == .video { let options = PHVideoRequestOptions() options.version = .original options.deliveryMode = .highQualityFormat PHImageManager.default().requestAVAsset(forVideo: asset, options: options) { avAsset, _, _ in guard let urlAsset = avAsset as? AVURLAsset else { return } let exporter = AVAssetExportSession(asset: urlAsset, presetName: AVAssetExportPresetHighestQuality) exporter?.outputFileType = .mp4 let outputURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".mp4") exporter?.outputURL = outputURL exporter?.exportAsynchronously { switch exporter?.status { case .completed: if let data = try? Data(contentsOf: outputURL) { DispatchQueue.main.async { let newMessageId = UUID().uuidString store.dispatch(.fileAction(.copyFileForUploading( messageId: newMessageId, fileData: data, thumbnailData: nil ))) } } default: break } } } } } promise(.success(.empty)) } .eraseToAnyPublisher() case .sharingAction(.cameraCaptured(let media, let type)): print("Camera captured: \(media.count)") return Empty().eraseToAnyPublisher() case .sharingAction(.shareLocation(let lat, let lon)): if let chat = state.conversationsState.currentChat { let msg = "geo:\(lat),\(lon)" return Just(.conversationAction(.sendMessage(from: chat.account, to: chat.participant, body: msg))) .eraseToAnyPublisher() } else { return Empty().eraseToAnyPublisher() } case .sharingAction(.shareDocuments(let data)): print("Sharing documents: \(data.count)") return Empty().eraseToAnyPublisher() case .sharingAction(.shareContact(let jid)): if let chat = state.conversationsState.currentChat { let msg = "contact:\(jid)" return Just(.conversationAction(.sendMessage(from: chat.account, to: chat.participant, body: msg))) .eraseToAnyPublisher() } else { return Empty().eraseToAnyPublisher() } default: return Empty().eraseToAnyPublisher() } } }