Ilya Laktyushin e923acd4c0 Various fixes
2023-07-17 16:45:47 +02:00

132 lines
4.7 KiB
Swift

import Foundation
import UIKit
import SwiftSignalKit
import Photos
import AVFoundation
public final class MediaAssetsContext: NSObject, PHPhotoLibraryChangeObserver {
private let assetType: PHAssetMediaType?
private var registeredChangeObserver = false
private let changeSink = ValuePipe<PHChange>()
private let mediaAccessSink = ValuePipe<PHAuthorizationStatus>()
private let cameraAccessSink = ValuePipe<AVAuthorizationStatus?>()
public init(assetType: PHAssetMediaType? = nil) {
self.assetType = assetType
super.init()
if PHPhotoLibrary.authorizationStatus() == .authorized {
PHPhotoLibrary.shared().register(self)
self.registeredChangeObserver = true
}
}
deinit {
if self.registeredChangeObserver {
PHPhotoLibrary.shared().unregisterChangeObserver(self)
}
}
public func photoLibraryDidChange(_ changeInstance: PHChange) {
self.changeSink.putNext(changeInstance)
}
public func fetchAssets(_ collection: PHAssetCollection) -> Signal<PHFetchResult<PHAsset>, 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<PHFetchResult<PHAsset>>(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()
}
}
)
}
public func fetchAssetsCollections(_ type: PHAssetCollectionType) -> Signal<PHFetchResult<PHAssetCollection>, NoError> {
let initialFetchResult = PHAssetCollection.fetchAssetCollections(with: type, subtype: .any, options: nil)
let fetchResult = Atomic<PHFetchResult<PHAssetCollection>>(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()
}
}
)
}
public func recentAssets() -> Signal<PHFetchResult<PHAsset>?, 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)
}
}
public func mediaAccess() -> Signal<PHAuthorizationStatus, NoError> {
let initialStatus: PHAuthorizationStatus
if #available(iOS 14.0, *) {
initialStatus = PHPhotoLibrary.authorizationStatus(for: .readWrite)
} else {
initialStatus = PHPhotoLibrary.authorizationStatus()
}
return .single(initialStatus)
|> then(
self.mediaAccessSink.signal()
)
}
public func requestMediaAccess(completion: @escaping () -> Void = {}) -> Void {
PHPhotoLibrary.requestAuthorization { [weak self] status in
Queue.mainQueue().async {
completion()
}
self?.mediaAccessSink.putNext(status)
}
}
public func cameraAccess() -> Signal<AVAuthorizationStatus?, NoError> {
#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
}
public func requestCameraAccess() -> Void {
AVCaptureDevice.requestAccess(for: .video, completionHandler: { [weak self] result in
if result {
self?.cameraAccessSink.putNext(.authorized)
} else {
self?.cameraAccessSink.putNext(.denied)
}
})
}
}