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))))
|
|
|
|
}
|
|
|
|
|
|
|
|
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))))
|
|
|
|
}
|
|
|
|
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|