// // PhotoKitDependency.swift // RWPureSwift // // Created by Christopher Hotchkiss on 10/13/25. // import AppTypes import Dao import Dependencies import DependenciesMacros import Photos #if canImport(UIKit) import UIKit #endif @DependencyClient public struct PhotoKitAlbums: Sendable { public var libraryAccess: @Sendable () -> PHAuthorizationStatus = { .denied } public var openPhotoSettings: @Sendable () async -> () public var requestAuthorization: @Sendable () async -> () public var availableAlbums: @Sendable () async -> PHFetchResultCollection? public var loadAlbumAssets: @Sendable (AlbumLocalId) async -> [PHAsset]? /// First album whose title matches the synced descriptor, if any. public var findAlbum: @Sendable (AlbumDescriptor) async -> AlbumLocalId? /// Descriptor for a locally-cached id; nil when the album no longer exists. public var albumDescriptor: @Sendable (AlbumLocalId) async -> AlbumDescriptor? } extension PhotoKitAlbums: DependencyKey { public static var liveValue: Self { return Self( libraryAccess: { return PHPhotoLibrary.authorizationStatus(for: .readWrite) }, openPhotoSettings: { @Dependency(\.fireAndForget) var fireAndForget await fireAndForget { @MainActor in #if targetEnvironment(macCatalyst) let url = "x-apple.systempreferences:com.apple.preference.security?Privacy_Photos" await UIApplication.shared.open(URL(string: url)!) #elseif canImport(UIKit) let url = UIApplication.openSettingsURLString UIApplication.shared.open(URL(string: url)!) #endif } }, requestAuthorization: { await PHPhotoLibrary.requestAuthorization(for: .readWrite) }, availableAlbums: { if PHPhotoLibrary.authorizationStatus(for: .readWrite) != .authorized { return nil } let albums = PHAssetCollection.fetchAssetCollections( with: PHAssetCollectionType.album, subtype: PHAssetCollectionSubtype.any, options: baseFetchOptions()) return PHFetchResultCollection(fetchResult: albums) }, loadAlbumAssets: {albumId in if PHPhotoLibrary.authorizationStatus(for: .readWrite) != .authorized { return nil } let result = await Task { guard let fetchAlbumObj = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId.rawValue], options: baseFetchOptions()).firstObject else { return nil as [PHAsset]? } let fetchResult = PHAsset.fetchAssets(in: fetchAlbumObj, options: albumContentsFetchOptions()) // The caching image manager demands an array so we can't leverage the fetchresult var contents: [PHAsset] = [] fetchResult.enumerateObjects({ obj, _, _ in contents.append(obj) }) contents.shuffle() return contents }.value return result }, findAlbum: { descriptor in if PHPhotoLibrary.authorizationStatus(for: .readWrite) != .authorized { return nil } let albums = PHAssetCollection.fetchAssetCollections( with: PHAssetCollectionType.album, subtype: PHAssetCollectionSubtype.any, options: baseFetchOptions()) var match: AlbumLocalId? albums.enumerateObjects { album, _, stop in if album.localizedTitle == descriptor.rawValue { match = AlbumLocalId(album.localIdentifier) stop.pointee = true } } return match }, albumDescriptor: { albumId in if PHPhotoLibrary.authorizationStatus(for: .readWrite) != .authorized { return nil } return PHAssetCollection.fetchAssetCollections( withLocalIdentifiers: [albumId.rawValue], options: baseFetchOptions() ).firstObject?.localizedTitle.map(AlbumDescriptor.init(rawValue:)) } ) } } extension PhotoKitAlbums: TestDependencyKey { public static let testValue = Self() public static var previewValue: Self { return Self( libraryAccess: { return .authorized }, openPhotoSettings: {}, requestAuthorization: {}, availableAlbums: { let albums = [ PHAssetCollectionMock(title: "Day at Park"), PHAssetCollectionMock(title: "Cats") ] return PHFetchResultCollectionMock(albums) }, loadAlbumAssets: { _ in return [ //TODO Add some placeholders ] }, findAlbum: { _ in return nil }, albumDescriptor: { _ in return nil } ) } } extension DependencyValues { public var photoKitAlbums: PhotoKitAlbums { get { self[PhotoKitAlbums.self] } set { self[PhotoKitAlbums.self] = newValue } } }