first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,153 @@
import Photos
import Dispatch
import SDWebImage
import ExpoModulesCore
/**
A custom loader for assets from the Photo Library. It handles all urls with the `ph` scheme.
*/
final class PhotoLibraryAssetLoader: NSObject, SDImageLoader {
// MARK: - SDImageLoader
func canRequestImage(for url: URL?) -> Bool {
return isPhotoLibraryAssetUrl(url)
}
func requestImage(
with url: URL?,
options: SDWebImageOptions = [],
context: SDWebImageContext?,
progress progressBlock: SDImageLoaderProgressBlock?,
completed completedBlock: SDImageLoaderCompletedBlock? = nil
) -> SDWebImageOperation? {
guard isPhotoLibraryStatusAuthorized() else {
let error = makeNSError(description: "Unauthorized access to the Photo Library")
completedBlock?(nil, nil, error, false)
return nil
}
let operation = PhotoLibraryAssetLoaderOperation()
DispatchQueue.global(qos: .userInitiated).async {
guard let url = url, let assetLocalIdentifier = assetLocalIdentifier(fromUrl: url) else {
let error = makeNSError(description: "Unable to obtain the asset identifier from the url: '\(String(describing: url?.absoluteString))'")
completedBlock?(nil, nil, error, false)
return
}
guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [assetLocalIdentifier], options: .none).firstObject else {
let error = makeNSError(description: "Asset with identifier '\(assetLocalIdentifier)' not found in the Photo Library")
completedBlock?(nil, nil, error, false)
return
}
operation.requestId = requestAsset(
asset,
url: url,
context: context,
progressBlock: progressBlock,
completedBlock: completedBlock
)
}
return operation
}
func shouldBlockFailedURL(with url: URL, error: Error) -> Bool {
// The lack of permission is one of the reasons of failed request,
// but in that single case we don't want to blacklist the url as
// the permission might be granted later and then the retry should be possible.
return isPhotoLibraryStatusAuthorized()
}
}
/**
Returns a bool value whether the given url references the Photo Library asset.
*/
internal func isPhotoLibraryAssetUrl(_ url: URL?) -> Bool {
return url?.scheme == "ph"
}
/**
Returns the local identifier of the asset from the given `ph://` url.
These urls have the form of "ph://26687849-33F9-4402-8EC0-A622CD011D70",
where the asset local identifier is used as the host part.
*/
private func assetLocalIdentifier(fromUrl url: URL) -> String? {
return url.host
}
/**
Checks whether the app is authorized to read the Photo Library.
*/
private func isPhotoLibraryStatusAuthorized() -> Bool {
let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
return status == .authorized || status == .limited
}
/**
Requests the image of the given asset object and returns the request identifier.
*/
private func requestAsset(
_ asset: PHAsset,
url: URL,
context: SDWebImageContext?,
progressBlock: SDImageLoaderProgressBlock?,
completedBlock: SDImageLoaderCompletedBlock?
) -> PHImageRequestID {
let options = PHImageRequestOptions()
options.isSynchronous = false
options.version = .current
options.deliveryMode = .highQualityFormat
options.resizeMode = .fast
options.normalizedCropRect = .zero
options.isNetworkAccessAllowed = true
if let progressBlock = progressBlock {
options.progressHandler = { progress, _, _, _ in
// The `progress` is a double from 0.0 to 1.0, but the loader needs integers so we map it to 0...100 range
let progressPercentage = Int((progress * 100.0).rounded())
progressBlock(progressPercentage, 100, url)
}
}
var targetSize = PHImageManagerMaximumSize
// We compute the minimal size required to display the image to avoid having to downsample it later
if let scale = context?[ImageView.screenScaleKey] as? Double,
let containerSize = context?[ImageView.frameSizeKey] as? CGSize,
let contentFit = context?[ImageView.contentFitKey] as? ContentFit {
targetSize = idealSize(
contentPixelSize: CGSize(width: asset.pixelWidth, height: asset.pixelHeight),
containerSize: containerSize,
scale: scale,
contentFit: contentFit
).rounded(.up) * scale
}
return PHImageManager.default().requestImage(
for: asset,
targetSize: targetSize,
contentMode: .aspectFit,
options: options,
resultHandler: { image, info in
// This value can be `true` only when network access is allowed and the photo is stored in the iCloud.
let isDegraded: Bool = info?[PHImageResultIsDegradedKey] as? Bool ?? false
completedBlock?(image, nil, nil, !isDegraded)
}
)
}
/**
Loader operation specialized for the Photo Library by keeping the request identifier.
*/
private class PhotoLibraryAssetLoaderOperation: NSObject, SDWebImageOperation {
var canceled: Bool = false
var requestId: PHImageRequestID?
// MARK: - SDWebImageOperation
func cancel() {
if let requestId = requestId {
PHImageManager.default().cancelImageRequest(requestId)
}
canceled = true
}
}