conversations-classic-ios/ConversationsClassic/AppCore/Middlewares/SharingMiddleware.swift

355 lines
16 KiB
Swift
Raw Normal View History

2024-07-10 17:49:36 +00:00
import AVFoundation
2024-07-10 14:13:47 +00:00
import Combine
import Foundation
2024-07-10 17:49:36 +00:00
import Photos
import UIKit
2024-07-10 14:13:47 +00:00
final class SharingMiddleware {
static let shared = SharingMiddleware()
2024-07-10 17:49:36 +00:00
// swiftlint:disable:next function_body_length
2024-07-10 14:13:47 +00:00
func middleware(state _: AppState, action: AppAction) -> AnyPublisher<AppAction, Never> {
switch action {
2024-07-10 17:49:36 +00:00
case .sharingAction(.checkCameraAccess):
return Future<AppAction, Never> { 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<AppAction, Never> { 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<AppAction, Never> { 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<AppAction, Never> { 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()
2024-07-10 14:13:47 +00:00
default:
return Empty().eraseToAnyPublisher()
}
}
}
2024-07-10 17:49:36 +00:00
// 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
// }
// })
// }
// }
// }
// }