conversations-classic-ios/ConversationsClassic/AppCore/Middlewares/SharingMiddleware.swift
2024-07-14 12:48:04 +02:00

131 lines
6 KiB
Swift

import AVFoundation
import Combine
import Foundation
import Photos
import UIKit
final class SharingMiddleware {
static let shared = SharingMiddleware()
func middleware(state: AppState, action: AppAction) -> AnyPublisher<AppAction, Never> {
switch action {
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
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().async {
let ids = items
.filter { $0.thumbnail == nil }
.map { $0.id }
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
image?.scaleAndCropImage(toExampleSize: CGSize(width: Const.galleryGridSize, height: Const.galleryGridSize)) { image in
if let image {
let data = image.jpegData(compressionQuality: 1.0) ?? Data()
store.dispatch(.sharingAction(.thumbnailUpdated(data: data, id: asset.localIdentifier)))
}
}
}
} else if asset.mediaType == .video {
PHImageManager.default().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)) { image in
if let image {
let data = image.jpegData(compressionQuality: 1.0) ?? Data()
store.dispatch(.sharingAction(.thumbnailUpdated(data: data, id: asset.localIdentifier)))
}
}
} catch {
print("Failed to create thumbnail image")
}
}
}
}
}
}
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()
}
default:
return Empty().eraseToAnyPublisher()
}
}
}