remindwall / RWPureSwift / Sources / PhotoKitAsync / PhotoKitAlbums.swift
Back to remindwall — Current RemindWall
//
// 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<PHAssetCollection>?
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<PHAssetCollection>(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 }
}
}