import AVFoundation import Combine import Foundation import Photos import UIKit final class SharingMiddleware { static let shared = SharingMiddleware() func middleware(state _: AppState, action: AppAction) -> AnyPublisher { switch action { 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 .sharingAction(.fetchGallery): return Future { promise in let fetchOptions = PHFetchOptions() fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] let assets = PHAsset.fetchAssets(with: fetchOptions) var items: [SharingGalleryItem] = [] assets.enumerateObjects { asset, _, _ in if asset.mediaType == .image { items.append(.init(id: asset.localIdentifier, type: .photo)) } else if asset.mediaType == .video { items.append(.init(id: asset.localIdentifier, type: .video)) } } promise(.success(.sharingAction(.galleryFetched(items)))) } .eraseToAnyPublisher() case .sharingAction(.galleryFetched(let items)): DispatchQueue.global(qos: .background).async { // for item in items { // switch item.type { // case .photo: // self.makePhotoThumbnail(assetId: PHAsset.fetchAssets(withLocalIdentifiers: [item.id], options: nil).firstObject!) { data in // if let data { // DispatchQueue.main.async { // store.dispatch(.sharingAction(.thumbnailUpdated(data, item.id))) // } // } // } // // case .video: // self.fetchVideo(asset: PHAsset.fetchAssets(withLocalIdentifiers: [item.id], options: nil).firstObject!) { newItem in // if let newItem { // newItems.append(newItem) // } // } // } // } } return Empty().eraseToAnyPublisher() default: return Empty().eraseToAnyPublisher() } } } private extension SharingMiddleware { func makePhotoThumbnail(asset: PHAsset, completion: @escaping (Data?) -> Void) { PHImageManager.default().requestImage( for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .aspectFill, options: nil ) { _, _ in // guard let image = image?.scaleAndCropImage(toExampleSize: CGSize(width: Const.galleryGridSize, height: Const.galleryGridSize)) else { // completion(nil) // return // } // let data = image.jpegData(compressionQuality: 1.0) ?? Data() // completion(.init(id: asset.localIdentifier, type: .photo, thumbnail: data)) completion(.init(id: asset.localIdentifier, type: .photo, thumbnail: Data())) } } func fetchVideo(asset: PHAsset, completion: @escaping (SharingGalleryItem?) -> Void) { PHImageManager.default().requestAVAsset(forVideo: asset, options: nil) { _, _, _ in // if let avAsset { // let imageGenerator = AVAssetImageGenerator(asset: avAsset) // imageGenerator.appliesPreferredTrackTransform = true // let time = CMTimeMake(value: 1, timescale: 2) // do { // let imageRef = try imageGenerator.copyCGImage(at: time, actualTime: nil) // let thumbnail = UIImage(cgImage: imageRef) // guard let image = thumbnail.scaleAndCropImage(toExampleSize: CGSize(width: Const.galleryGridSize, height: Const.galleryGridSize)) else { // completion(nil) // return // } // let data = image.jpegData(compressionQuality: 1.0) ?? Data() // completion(.init(id: asset.localIdentifier, type: .video, thumbnail: data, duration: asset.duration.minAndSec)) // } catch { // print("Failed to create thumbnail image") // completion(nil) // } // } else { // completion(nil) // } completion(.init(id: asset.localIdentifier, type: .photo, thumbnail: Data())) } } } // case .sharingAction(.fetchGallery): // return Future { promise in // DispatchQueue.global(qos: .background).async { // let fetchOptions = PHFetchOptions() // fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] // let assets = PHAsset.fetchAssets(with: fetchOptions) // // let manager = PHImageManager.default() // let option = PHImageRequestOptions() // option.isSynchronous = true // // var items: [SharingGalleryItem] = [] // let group = DispatchGroup() // assets.enumerateObjects { asset, _, _ in // if asset.mediaType == .image { // group.enter() // manager.requestImage( // for: asset, // targetSize: PHImageManagerMaximumSize, // contentMode: .aspectFill, // options: option // ) { image, _ in // if image != nil { // items.append(.init(id: asset.localIdentifier, type: .photo)) // group.leave() // } // } // } else if asset.mediaType == .video { // group.enter() // manager.requestAVAsset(forVideo: asset, options: nil) { avAsset, _, _ in // if avAsset != nil { // items.append(.init(id: asset.localIdentifier, type: .video, duration: asset.duration.minAndSec)) // group.leave() // } // } // } // } // group.notify(queue: .main) { // promise(.success(.sharingAction(.galleryFetched(items)))) // } // } // } // .eraseToAnyPublisher() // // case .sharingAction(.galleryFetched(let items)): // return Future { promise in // DispatchQueue.global(qos: .background).async { // let ids = items // .filter { $0.thumbnail == nil } // .map { $0.id } // let assets = PHAsset.fetchAssets(withLocalIdentifiers: ids, options: nil) // // var newItems: [SharingGalleryItem] = [] // let group = DispatchGroup() // // assets.enumerateObjects { asset, _, _ in // let manager = PHImageManager.default() // group.enter() // if asset.mediaType == .image { // manager.requestImage( // for: asset, // targetSize: PHImageManagerMaximumSize, // contentMode: .aspectFill, // options: nil // ) { image, _ in // image?.scaleAndCropImage(toExampleSize: CGSize(width: Const.galleryGridSize, height: Const.galleryGridSize)) { image in // if let image { // let data = image.jpegData(compressionQuality: 1.0) ?? Data() // newItems.append(.init(id: asset.localIdentifier, type: .photo, thumbnail: data)) // } // group.leave() // } // } // } else if asset.mediaType == .video { // manager.requestAVAsset(forVideo: asset, options: nil) { avAsset, _, _ in // if let avAsset { // let imageGenerator = AVAssetImageGenerator(asset: avAsset) // imageGenerator.appliesPreferredTrackTransform = true // let time = CMTimeMake(value: 1, timescale: 2) // do { // let imageRef = try imageGenerator.copyCGImage(at: time, actualTime: nil) // let thumbnail = UIImage(cgImage: imageRef) // thumbnail.scaleAndCropImage(toExampleSize: CGSize(width: Const.galleryGridSize, height: Const.galleryGridSize), completion: { image in // if let image { // let data = image.jpegData(compressionQuality: 1.0) ?? Data() // newItems.append(.init(id: asset.localIdentifier, type: .video, thumbnail: data, duration: asset.duration.minAndSec)) // } // group.leave() // }) // } catch { // print("Failed to create thumbnail image") // group.leave() // } // } else { // group.leave() // } // } // } else { // group.leave() // } // } // group.notify(queue: .main) { // promise(.success(.sharingAction(.thumbnailUpdated(newItems)))) // } // } // } // .eraseToAnyPublisher()