import Foundation import UIKit import SwiftSignalKit import Photos import AVFoundation class MediaAssetsContext: NSObject, PHPhotoLibraryChangeObserver { private let assetType: PHAssetMediaType? private var registeredChangeObserver = false private let changeSink = ValuePipe() private let mediaAccessSink = ValuePipe() private let cameraAccessSink = ValuePipe() init(assetType: PHAssetMediaType?) { self.assetType = assetType super.init() if PHPhotoLibrary.authorizationStatus() == .authorized { PHPhotoLibrary.shared().register(self) self.registeredChangeObserver = true } } deinit { if self.registeredChangeObserver { PHPhotoLibrary.shared().unregisterChangeObserver(self) } } func photoLibraryDidChange(_ changeInstance: PHChange) { self.changeSink.putNext(changeInstance) } func fetchAssets(_ collection: PHAssetCollection) -> Signal, NoError> { let options = PHFetchOptions() if let assetType = self.assetType { options.predicate = NSPredicate(format: "mediaType = %d", assetType.rawValue) } let initialFetchResult = PHAsset.fetchAssets(in: collection, options: options) let fetchResult = Atomic>(value: initialFetchResult) return .single(initialFetchResult) |> then( self.changeSink.signal() |> mapToSignal { change in if let updatedFetchResult = change.changeDetails(for: fetchResult.with { $0 })?.fetchResultAfterChanges { let _ = fetchResult.modify { _ in return updatedFetchResult } return .single(updatedFetchResult) } else { return .complete() } } ) } func fetchAssetsCollections(_ type: PHAssetCollectionType) -> Signal, NoError> { let initialFetchResult = PHAssetCollection.fetchAssetCollections(with: type, subtype: .any, options: nil) let fetchResult = Atomic>(value: initialFetchResult) return .single(initialFetchResult) |> then( self.changeSink.signal() |> mapToSignal { change in if let updatedFetchResult = change.changeDetails(for: fetchResult.with { $0 })?.fetchResultAfterChanges { let _ = fetchResult.modify { _ in return updatedFetchResult } return .single(updatedFetchResult) } else { return .complete() } } ) } func recentAssets() -> Signal?, NoError> { let collections = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .smartAlbumUserLibrary, options: nil) if let collection = collections.firstObject { return fetchAssets(collection) |> map(Optional.init) } else { return .single(nil) } } func mediaAccess() -> Signal { let initialStatus: PHAuthorizationStatus if #available(iOS 14.0, *) { initialStatus = PHPhotoLibrary.authorizationStatus(for: .readWrite) } else { initialStatus = PHPhotoLibrary.authorizationStatus() } return .single(initialStatus) |> then( self.mediaAccessSink.signal() ) } func requestMediaAccess() -> Void { PHPhotoLibrary.requestAuthorization { [weak self] status in self?.mediaAccessSink.putNext(status) } } func cameraAccess() -> Signal { #if targetEnvironment(simulator) return .single(.authorized) #else if UIImagePickerController.isSourceTypeAvailable(.camera) { return .single(AVCaptureDevice.authorizationStatus(for: .video)) |> then( self.cameraAccessSink.signal() ) } else { return .single(nil) } #endif } func requestCameraAccess() -> Void { AVCaptureDevice.requestAccess(for: .video, completionHandler: { [weak self] result in if result { self?.cameraAccessSink.putNext(.authorized) } else { self?.cameraAccessSink.putNext(.denied) } }) } }