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 { 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)))) // DispatchQueue.main.async { // self.isCameraAccessGranted = 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)))) // DispatchQueue.main.async { // self.isGalleryAccessGranted = status == .authorized // if self.isGalleryAccessGranted { // self.fetchGallery() // } // } } case .denied, .restricted: promise(.success(.sharingAction(.setGalleryAccess(false)))) @unknown default: promise(.success(.sharingAction(.setGalleryAccess(false)))) } } .eraseToAnyPublisher() 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) assets.enumerateObjects { asset, _, _ in let manager = PHImageManager.default() 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() DispatchQueue.main.async { store.dispatch(.sharingAction(.thumbnailUpdated(data, asset.localIdentifier))) } } } } } 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() DispatchQueue.main.async { store.dispatch(.sharingAction(.thumbnailUpdated(data, asset.localIdentifier))) } } }) } catch { print("Failed to create thumbnail image") } } } } } } promise(.success(.empty)) } .eraseToAnyPublisher() default: return Empty().eraseToAnyPublisher() } } } // private func fetchGallery() { // 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 // // assets.enumerateObjects { asset, _, _ in // if asset.mediaType == .image { // manager.requestImage( // for: asset, // targetSize: PHImageManagerMaximumSize, // contentMode: .aspectFill, // options: option // ) { image, _ in // image?.scaleAndCropImage(toExampleSize: CGSize(width: gridSize, height: gridSize), completion: { image in // if let image { // DispatchQueue.main.async { // self.thumbnails.append(ThumbnailView(id: asset.localIdentifier, image: image, gridSize: gridSize, selected: $selectedMedia)) // } // } // }) // } // } 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: gridSize, height: gridSize), completion: { image in // if let image { // DispatchQueue.main.async { // self.thumbnails.append(ThumbnailView(id: asset.localIdentifier, image: image, gridSize: gridSize, selected: $selectedMedia, duration: asset.duration.minAndSec)) // } // } // }) // } catch { // print("Failed to create thumbnail image") // } // } // } // } // } // } // private func sendGalleryMedia(ids _: [String]) { // var media: [AttachmentItem] = [] // let dispatchGroup = DispatchGroup() // // let asset = PHAsset.fetchAssets(withLocalIdentifiers: ids, options: nil) // asset.enumerateObjects { asset, _, _ in // dispatchGroup.enter() // if asset.mediaType == .image { // let manager = PHImageManager.default() // let option = PHImageRequestOptions() // option.isSynchronous = true // // manager.requestImage( // for: asset, // targetSize: PHImageManagerMaximumSize, // contentMode: .aspectFill, // options: option // ) { image, _ in // if let image { // let data = image.jpegData(compressionQuality: 1.0) ?? Data() // media.append(.init(type: .image, data: data, string: "")) // } // dispatchGroup.leave() // } // } else if asset.mediaType == .video { // let manager = PHImageManager.default() // let option = PHVideoRequestOptions() // option.version = .current // option.deliveryMode = .highQualityFormat // // manager.requestAVAsset(forVideo: asset, options: option) { avAsset, _, _ in // if let avAsset { // let url = (avAsset as? AVURLAsset)?.url // let data = try? Data(contentsOf: url ?? URL(fileURLWithPath: "")) // media.append(.init(type: .movie, data: data ?? Data(), string: "")) // } // dispatchGroup.leave() // } // } // } // dispatchGroup.notify(queue: .main) { // store.dispatch(.conversationAction(.sendAttachment(.init( // id: UUID().uuidString, // items: media // )))) // } // } // } // // private struct ThumbnailView: Identifiable, View { // let id: String // let gridSize: CGFloat // let duration: String? // // @State private var image: UIImage // @State private var ready = false // @State private var selected = false // @Binding var selectedMedia: [SelectedMedia] // // init(id: String, image: UIImage, gridSize: CGFloat, selected: Binding<[SelectedMedia]>, duration: String? = nil) { // self.id = id // self.image = image // self.gridSize = gridSize // _selectedMedia = selected // self.duration = duration // } // // var body: some View { // if ready { // ZStack { // Image(uiImage: image) // .resizable() // .aspectRatio(contentMode: .fill) // .frame(width: gridSize, height: gridSize) // .clipped() // if let duration { // VStack { // Spacer() // HStack { // Spacer() // Text(duration) // .foregroundColor(.Material.Text.white) // .font(.sub1) // .shadow(color: .black, radius: 2) // .padding(4) // } // } // } // if selected { // VStack { // HStack { // Spacer() // Circle() // .frame(width: 30, height: 30) // .shadow(color: .black, radius: 2) // .foregroundColor(.Material.Shape.white) // .overlay { // Image(systemName: "checkmark") // .foregroundColor(.Material.Elements.active) // .font(.body3) // } // .padding(4) // } // Spacer() // } // } // } // .onTapGesture { // withAnimation { // selected.toggle() // if selected { // selectedMedia.append(SelectedMedia(id: id)) // } else { // selectedMedia.removeAll { $0.id == id } // } // } // } // } else { // ZStack { // Rectangle() // .fill(Color.Material.Background.light) // .overlay { // ProgressView() // } // .frame(width: gridSize, height: gridSize) // } // .onAppear { // image.scaleAndCropImage(toExampleSize: CGSize(width: gridSize, height: gridSize), completion: { image in // if let image { // self.image = image // ready = true // } // }) // } // } // } // }