355 lines
16 KiB
Swift
355 lines
16 KiB
Swift
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<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))))
|
|
// 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()
|
|
|
|
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
|
|
// }
|
|
// })
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|