@hortemo/expo-photos 6.0.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -1
- package/build/PHImage.d.ts +27 -0
- package/build/PHImage.d.ts.map +1 -0
- package/build/PHImage.js +6 -0
- package/build/PHImage.js.map +1 -0
- package/build/index.d.ts +2 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -1
- package/build/index.js.map +1 -1
- package/ios/ExpoPhotos.podspec +1 -2
- package/ios/ExpoPhotosModule.swift +24 -0
- package/ios/PHImageView.swift +137 -0
- package/package.json +1 -11
- package/src/PHImage.tsx +32 -0
- package/src/index.ts +2 -2
- package/build/PHImageSource.d.ts +0 -32
- package/build/PHImageSource.d.ts.map +0 -1
- package/build/PHImageSource.js +0 -46
- package/build/PHImageSource.js.map +0 -1
- package/ios/ExpoPhotosImageLoader.swift +0 -187
- package/src/PHImageSource.tsx +0 -86
package/README.md
CHANGED
|
@@ -19,10 +19,25 @@ npm install @hortemo/expo-photos
|
|
|
19
19
|
- `authorizationStatus(accessLevel: PHAccessLevel): Promise<PHAuthorizationStatus>`
|
|
20
20
|
- `requestAuthorization(accessLevel: PHAccessLevel): Promise<PHAuthorizationStatus>`
|
|
21
21
|
- `presentLimitedLibraryPicker(): Promise<void>`
|
|
22
|
-
- `createPHImageSource(options: PHImageSourceOptions): ImageSourcePropType | undefined`
|
|
23
22
|
|
|
24
23
|
### Components
|
|
25
24
|
|
|
25
|
+
#### PHImage
|
|
26
|
+
|
|
27
|
+
A native image component that displays photos from the Photos library using `PHImageManager`.
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { PHImage, PHImageContentMode } from "@hortemo/expo-photos";
|
|
31
|
+
|
|
32
|
+
<PHImage
|
|
33
|
+
localIdentifier={asset.localIdentifier}
|
|
34
|
+
contentMode={PHImageContentMode.aspectFill}
|
|
35
|
+
resizeMode={PHImageRequestOptionsResizeMode.Fast}
|
|
36
|
+
deliveryMode={PHImageRequestOptionsDeliveryMode.HighQualityFormat}
|
|
37
|
+
style={{ width: 300, height: 300 }}
|
|
38
|
+
/>
|
|
39
|
+
```
|
|
40
|
+
|
|
26
41
|
#### PHVideo
|
|
27
42
|
|
|
28
43
|
A video player component that streams videos from the library.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ViewProps } from "react-native";
|
|
2
|
+
import { PHImageContentMode, PHImageRequestOptionsDeliveryMode, PHImageRequestOptionsResizeMode } from "./ExpoPhotos.types";
|
|
3
|
+
export interface PHImageLoadEvent {
|
|
4
|
+
size: {
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
};
|
|
8
|
+
scale: number;
|
|
9
|
+
}
|
|
10
|
+
export interface PHImageErrorEvent {
|
|
11
|
+
message: string;
|
|
12
|
+
}
|
|
13
|
+
export interface PHImageProps extends ViewProps {
|
|
14
|
+
localIdentifier: string;
|
|
15
|
+
isNetworkAccessAllowed?: boolean;
|
|
16
|
+
deliveryMode?: PHImageRequestOptionsDeliveryMode;
|
|
17
|
+
resizeMode?: PHImageRequestOptionsResizeMode;
|
|
18
|
+
contentMode?: PHImageContentMode;
|
|
19
|
+
onLoad?: (event: {
|
|
20
|
+
nativeEvent: PHImageLoadEvent;
|
|
21
|
+
}) => void;
|
|
22
|
+
onError?: (event: {
|
|
23
|
+
nativeEvent: PHImageErrorEvent;
|
|
24
|
+
}) => void;
|
|
25
|
+
}
|
|
26
|
+
export declare function PHImage(props: PHImageProps): import("react").JSX.Element;
|
|
27
|
+
//# sourceMappingURL=PHImage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PHImage.d.ts","sourceRoot":"","sources":["../src/PHImage.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EACL,kBAAkB,EAClB,iCAAiC,EACjC,+BAA+B,EAChC,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAa,SAAQ,SAAS;IAC7C,eAAe,EAAE,MAAM,CAAC;IACxB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,YAAY,CAAC,EAAE,iCAAiC,CAAC;IACjD,UAAU,CAAC,EAAE,+BAA+B,CAAC;IAC7C,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,WAAW,EAAE,gBAAgB,CAAA;KAAE,KAAK,IAAI,CAAC;IAC5D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,WAAW,EAAE,iBAAiB,CAAA;KAAE,KAAK,IAAI,CAAC;CAC/D;AAID,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,+BAE1C"}
|
package/build/PHImage.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PHImage.js","sourceRoot":"","sources":["../src/PHImage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AA2B7D,MAAM,iBAAiB,GAAG,wBAAwB,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC;AAEhF,MAAM,UAAU,OAAO,CAAC,KAAmB;IACzC,OAAO,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,EAAG,CAAC;AAC1C,CAAC","sourcesContent":["import { requireNativeViewManager } from \"expo-modules-core\";\nimport { ViewProps } from \"react-native\";\nimport {\n PHImageContentMode,\n PHImageRequestOptionsDeliveryMode,\n PHImageRequestOptionsResizeMode,\n} from \"./ExpoPhotos.types\";\n\nexport interface PHImageLoadEvent {\n size: { width: number; height: number };\n scale: number;\n}\n\nexport interface PHImageErrorEvent {\n message: string;\n}\n\nexport interface PHImageProps extends ViewProps {\n localIdentifier: string;\n isNetworkAccessAllowed?: boolean;\n deliveryMode?: PHImageRequestOptionsDeliveryMode;\n resizeMode?: PHImageRequestOptionsResizeMode;\n contentMode?: PHImageContentMode;\n onLoad?: (event: { nativeEvent: PHImageLoadEvent }) => void;\n onError?: (event: { nativeEvent: PHImageErrorEvent }) => void;\n}\n\nconst NativePHImageView = requireNativeViewManager(\"ExpoPhotos\", \"PHImageView\");\n\nexport function PHImage(props: PHImageProps) {\n return <NativePHImageView {...props} />;\n}\n"]}
|
package/build/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { default } from "./ExpoPhotosModule";
|
|
2
2
|
export * from "./ExpoPhotos.types";
|
|
3
|
-
export {
|
|
4
|
-
export type {
|
|
3
|
+
export { PHImage } from "./PHImage";
|
|
4
|
+
export type { PHImageProps, PHImageLoadEvent, PHImageErrorEvent } from "./PHImage";
|
|
5
5
|
export { PHVideo } from "./PHVideo";
|
|
6
6
|
export type { PHVideoProps } from "./PHVideo";
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACnF,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC"}
|
package/build/index.js
CHANGED
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC","sourcesContent":["export { default } from \"./ExpoPhotosModule\";\nexport * from \"./ExpoPhotos.types\";\nexport { PHImage } from \"./PHImage\";\nexport type { PHImageProps, PHImageLoadEvent, PHImageErrorEvent } from \"./PHImage\";\nexport { PHVideo } from \"./PHVideo\";\nexport type { PHVideoProps } from \"./PHVideo\";\n"]}
|
package/ios/ExpoPhotos.podspec
CHANGED
|
@@ -14,11 +14,10 @@ Pod::Spec.new do |s|
|
|
|
14
14
|
s.swift_version = '5.9'
|
|
15
15
|
s.source = { git: package['repository'] || 'https://github.com/hortemo/expo-photos' }
|
|
16
16
|
s.static_framework = true
|
|
17
|
+
s.frameworks = 'Photos','PhotosUI'
|
|
17
18
|
|
|
18
19
|
s.dependency 'ExpoModulesCore'
|
|
19
|
-
s.dependency 'React-Core'
|
|
20
20
|
s.dependency 'SDWebImageWebPCoder', '~> 0.14'
|
|
21
21
|
|
|
22
22
|
s.source_files = '**/*.{h,m,swift}'
|
|
23
|
-
s.requires_arc = true
|
|
24
23
|
end
|
|
@@ -305,6 +305,30 @@ public final class ExpoPhotos: Module {
|
|
|
305
305
|
}
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
+
View(PHImageView.self) {
|
|
309
|
+
Prop("localIdentifier") { (view: PHImageView, localIdentifier: String?) in
|
|
310
|
+
view.localIdentifier = localIdentifier
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
Prop("isNetworkAccessAllowed") { (view: PHImageView, isNetworkAccessAllowed: Bool?) in
|
|
314
|
+
view.isNetworkAccessAllowed = isNetworkAccessAllowed ?? true
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
Prop("deliveryMode") { (view: PHImageView, deliveryMode: PHImageRequestOptionsDeliveryMode?) in
|
|
318
|
+
view.deliveryMode = deliveryMode ?? .opportunistic
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
Prop("resizeMode") { (view: PHImageView, resizeMode: PHImageRequestOptionsResizeMode?) in
|
|
322
|
+
view.resizeMode = resizeMode ?? .none
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
Prop("contentMode") { (view: PHImageView, contentMode: PHImageContentMode?) in
|
|
326
|
+
view.imageContentMode = contentMode ?? .aspectFill
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
Events("onLoad", "onError")
|
|
330
|
+
}
|
|
331
|
+
|
|
308
332
|
View(PHVideoView.self) {
|
|
309
333
|
Prop("localIdentifier") { (view: PHVideoView, localIdentifier: String?) in
|
|
310
334
|
view.localIdentifier = localIdentifier
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import Photos
|
|
3
|
+
import UIKit
|
|
4
|
+
|
|
5
|
+
public final class PHImageView: ExpoView {
|
|
6
|
+
let onLoad = EventDispatcher()
|
|
7
|
+
let onError = EventDispatcher()
|
|
8
|
+
|
|
9
|
+
private var currentRequestId: PHImageRequestID?
|
|
10
|
+
private var imageView: UIImageView
|
|
11
|
+
|
|
12
|
+
var localIdentifier: String? {
|
|
13
|
+
didSet {
|
|
14
|
+
if localIdentifier != oldValue {
|
|
15
|
+
loadImage()
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
var isNetworkAccessAllowed: Bool = true {
|
|
21
|
+
didSet {
|
|
22
|
+
if isNetworkAccessAllowed != oldValue {
|
|
23
|
+
loadImage()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
var deliveryMode: PHImageRequestOptionsDeliveryMode = .opportunistic {
|
|
29
|
+
didSet {
|
|
30
|
+
if deliveryMode != oldValue {
|
|
31
|
+
loadImage()
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
var resizeMode: PHImageRequestOptionsResizeMode = .none {
|
|
37
|
+
didSet {
|
|
38
|
+
if resizeMode != oldValue {
|
|
39
|
+
loadImage()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
var imageContentMode: PHImageContentMode = .aspectFill {
|
|
45
|
+
didSet {
|
|
46
|
+
if imageContentMode != oldValue {
|
|
47
|
+
imageView.contentMode = imageContentMode == .aspectFit ? .scaleAspectFit : .scaleAspectFill
|
|
48
|
+
loadImage()
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public required init(appContext: AppContext? = nil) {
|
|
54
|
+
imageView = UIImageView()
|
|
55
|
+
super.init(appContext: appContext)
|
|
56
|
+
imageView.clipsToBounds = true
|
|
57
|
+
imageView.contentMode = imageContentMode == .aspectFit ? .scaleAspectFit : .scaleAspectFill
|
|
58
|
+
addSubview(imageView)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public override func layoutSubviews() {
|
|
62
|
+
super.layoutSubviews()
|
|
63
|
+
imageView.frame = bounds
|
|
64
|
+
loadImage()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public override func willMove(toWindow newWindow: UIWindow?) {
|
|
68
|
+
super.willMove(toWindow: newWindow)
|
|
69
|
+
if newWindow == nil {
|
|
70
|
+
cleanup()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private func loadImage() {
|
|
75
|
+
cleanup()
|
|
76
|
+
|
|
77
|
+
guard let localIdentifier = localIdentifier else { return }
|
|
78
|
+
guard bounds.width > 0 && bounds.height > 0 else { return }
|
|
79
|
+
|
|
80
|
+
guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil).firstObject else {
|
|
81
|
+
onError(["message": "Asset not found for identifier: \(localIdentifier)"])
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let options = PHImageRequestOptions()
|
|
86
|
+
options.isNetworkAccessAllowed = isNetworkAccessAllowed
|
|
87
|
+
options.deliveryMode = deliveryMode
|
|
88
|
+
options.resizeMode = resizeMode
|
|
89
|
+
|
|
90
|
+
let scale = window?.screen.scale ?? UIScreen.main.scale
|
|
91
|
+
let targetSize = CGSize(width: bounds.width * scale, height: bounds.height * scale)
|
|
92
|
+
|
|
93
|
+
currentRequestId = PHImageManager.default().requestImage(
|
|
94
|
+
for: asset,
|
|
95
|
+
targetSize: targetSize,
|
|
96
|
+
contentMode: imageContentMode,
|
|
97
|
+
options: options
|
|
98
|
+
) { [weak self] image, info in
|
|
99
|
+
guard let self = self else { return }
|
|
100
|
+
|
|
101
|
+
DispatchQueue.main.async {
|
|
102
|
+
let isCancelled = (info?[PHImageCancelledKey] as? NSNumber)?.boolValue ?? false
|
|
103
|
+
if isCancelled { return }
|
|
104
|
+
|
|
105
|
+
if let error = info?[PHImageErrorKey] as? Error {
|
|
106
|
+
self.onError(["message": error.localizedDescription])
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
guard let image = image else {
|
|
111
|
+
self.onError(["message": "Failed to load image"])
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
self.imageView.image = image
|
|
116
|
+
|
|
117
|
+
let isDegraded = (info?[PHImageResultIsDegradedKey] as? NSNumber)?.boolValue ?? false
|
|
118
|
+
if !isDegraded {
|
|
119
|
+
self.onLoad([
|
|
120
|
+
"size": [
|
|
121
|
+
"width": image.size.width,
|
|
122
|
+
"height": image.size.height,
|
|
123
|
+
],
|
|
124
|
+
"scale": image.scale,
|
|
125
|
+
])
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private func cleanup() {
|
|
132
|
+
if let requestId = currentRequestId {
|
|
133
|
+
PHImageManager.default().cancelImageRequest(requestId)
|
|
134
|
+
currentRequestId = nil
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hortemo/expo-photos",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0",
|
|
4
4
|
"description": "Expo module exposing advanced iOS Photos APIs.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -38,15 +38,5 @@
|
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"expo": "*",
|
|
40
40
|
"react-native": "*"
|
|
41
|
-
},
|
|
42
|
-
"codegenConfig": {
|
|
43
|
-
"name": "expo-photos",
|
|
44
|
-
"type": "modules",
|
|
45
|
-
"jsSrcsDir": "./src",
|
|
46
|
-
"ios": {
|
|
47
|
-
"modulesConformingToProtocol": {
|
|
48
|
-
"RCTImageURLLoader": "ExpoPhotosImageLoader"
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
41
|
}
|
|
52
42
|
}
|
package/src/PHImage.tsx
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { requireNativeViewManager } from "expo-modules-core";
|
|
2
|
+
import { ViewProps } from "react-native";
|
|
3
|
+
import {
|
|
4
|
+
PHImageContentMode,
|
|
5
|
+
PHImageRequestOptionsDeliveryMode,
|
|
6
|
+
PHImageRequestOptionsResizeMode,
|
|
7
|
+
} from "./ExpoPhotos.types";
|
|
8
|
+
|
|
9
|
+
export interface PHImageLoadEvent {
|
|
10
|
+
size: { width: number; height: number };
|
|
11
|
+
scale: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface PHImageErrorEvent {
|
|
15
|
+
message: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PHImageProps extends ViewProps {
|
|
19
|
+
localIdentifier: string;
|
|
20
|
+
isNetworkAccessAllowed?: boolean;
|
|
21
|
+
deliveryMode?: PHImageRequestOptionsDeliveryMode;
|
|
22
|
+
resizeMode?: PHImageRequestOptionsResizeMode;
|
|
23
|
+
contentMode?: PHImageContentMode;
|
|
24
|
+
onLoad?: (event: { nativeEvent: PHImageLoadEvent }) => void;
|
|
25
|
+
onError?: (event: { nativeEvent: PHImageErrorEvent }) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const NativePHImageView = requireNativeViewManager("ExpoPhotos", "PHImageView");
|
|
29
|
+
|
|
30
|
+
export function PHImage(props: PHImageProps) {
|
|
31
|
+
return <NativePHImageView {...props} />;
|
|
32
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { default } from "./ExpoPhotosModule";
|
|
2
2
|
export * from "./ExpoPhotos.types";
|
|
3
|
-
export {
|
|
4
|
-
export type {
|
|
3
|
+
export { PHImage } from "./PHImage";
|
|
4
|
+
export type { PHImageProps, PHImageLoadEvent, PHImageErrorEvent } from "./PHImage";
|
|
5
5
|
export { PHVideo } from "./PHVideo";
|
|
6
6
|
export type { PHVideoProps } from "./PHVideo";
|
package/build/PHImageSource.d.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { ImageSourcePropType } from "react-native";
|
|
2
|
-
import { PHImageContentMode, PHImageRequestOptionsDeliveryMode, PHImageRequestOptionsResizeMode, RequestImageTargetSize } from "./ExpoPhotos.types";
|
|
3
|
-
export interface PHImageSourceOptions {
|
|
4
|
-
localIdentifier: string;
|
|
5
|
-
targetSize?: RequestImageTargetSize;
|
|
6
|
-
resizeMode?: PHImageRequestOptionsResizeMode;
|
|
7
|
-
deliveryMode?: PHImageRequestOptionsDeliveryMode;
|
|
8
|
-
contentMode?: PHImageContentMode;
|
|
9
|
-
isNetworkAccessAllowed?: boolean;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Creates a source object for React Native's Image component from a Photos library asset identifier.
|
|
13
|
-
*
|
|
14
|
-
* @param options - Configuration for image source, including the local identifier
|
|
15
|
-
* @returns A source object that can be used with the Image component's `source` prop, or undefined if no identifier is provided
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```tsx
|
|
19
|
-
* import { Image } from 'react-native';
|
|
20
|
-
* import { createPHImageSource } from '@hortemo/expo-photos';
|
|
21
|
-
*
|
|
22
|
-
* <Image
|
|
23
|
-
* source={createPHImageSource({
|
|
24
|
-
* localIdentifier: asset.localIdentifier,
|
|
25
|
-
* targetSize: { width: 200, height: 200 },
|
|
26
|
-
* contentMode: PHImageContentMode.aspectFill,
|
|
27
|
-
* })}
|
|
28
|
-
* />
|
|
29
|
-
* ```
|
|
30
|
-
*/
|
|
31
|
-
export declare function createPHImageSource(options: PHImageSourceOptions): ImageSourcePropType;
|
|
32
|
-
//# sourceMappingURL=PHImageSource.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PHImageSource.d.ts","sourceRoot":"","sources":["../src/PHImageSource.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EACL,kBAAkB,EAClB,iCAAiC,EACjC,+BAA+B,EAC/B,sBAAsB,EACvB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,oBAAoB;IACnC,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,sBAAsB,CAAC;IACpC,UAAU,CAAC,EAAE,+BAA+B,CAAC;IAC7C,YAAY,CAAC,EAAE,iCAAiC,CAAC;IACjD,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAwBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,oBAAoB,GAC5B,mBAAmB,CAwBrB"}
|
package/build/PHImageSource.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
const buildQueryString = (params) => {
|
|
2
|
-
const entries = Object.entries(params).filter(([, value]) => value !== undefined);
|
|
3
|
-
if (entries.length === 0) {
|
|
4
|
-
return "";
|
|
5
|
-
}
|
|
6
|
-
return ("?" +
|
|
7
|
-
entries
|
|
8
|
-
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value ?? "")}`)
|
|
9
|
-
.join("&"));
|
|
10
|
-
};
|
|
11
|
-
/**
|
|
12
|
-
* Creates a source object for React Native's Image component from a Photos library asset identifier.
|
|
13
|
-
*
|
|
14
|
-
* @param options - Configuration for image source, including the local identifier
|
|
15
|
-
* @returns A source object that can be used with the Image component's `source` prop, or undefined if no identifier is provided
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```tsx
|
|
19
|
-
* import { Image } from 'react-native';
|
|
20
|
-
* import { createPHImageSource } from '@hortemo/expo-photos';
|
|
21
|
-
*
|
|
22
|
-
* <Image
|
|
23
|
-
* source={createPHImageSource({
|
|
24
|
-
* localIdentifier: asset.localIdentifier,
|
|
25
|
-
* targetSize: { width: 200, height: 200 },
|
|
26
|
-
* contentMode: PHImageContentMode.aspectFill,
|
|
27
|
-
* })}
|
|
28
|
-
* />
|
|
29
|
-
* ```
|
|
30
|
-
*/
|
|
31
|
-
export function createPHImageSource(options) {
|
|
32
|
-
const { localIdentifier, resizeMode, deliveryMode, contentMode, isNetworkAccessAllowed, } = options;
|
|
33
|
-
const targetSize = options.targetSize && typeof options.targetSize === "object"
|
|
34
|
-
? `${options.targetSize.width}x${options.targetSize.height}`
|
|
35
|
-
: options.targetSize;
|
|
36
|
-
const query = buildQueryString({
|
|
37
|
-
localIdentifier,
|
|
38
|
-
targetSize,
|
|
39
|
-
resizeMode,
|
|
40
|
-
deliveryMode,
|
|
41
|
-
contentMode,
|
|
42
|
-
isNetworkAccessAllowed,
|
|
43
|
-
});
|
|
44
|
-
return { uri: `expo-photos://${query}` };
|
|
45
|
-
}
|
|
46
|
-
//# sourceMappingURL=PHImageSource.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PHImageSource.js","sourceRoot":"","sources":["../src/PHImageSource.tsx"],"names":[],"mappings":"AAiBA,MAAM,gBAAgB,GAAG,CACvB,MAA6D,EACrD,EAAE;IACV,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAC3C,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CACnC,CAAC;IAEF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,CACL,GAAG;QACH,OAAO;aACJ,GAAG,CACF,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CACf,GAAG,kBAAkB,CAAC,GAAG,CAAC,IAAI,kBAAkB,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAClE;aACA,IAAI,CAAC,GAAG,CAAC,CACb,CAAC;AACJ,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAA6B;IAE7B,MAAM,EACJ,eAAe,EACf,UAAU,EACV,YAAY,EACZ,WAAW,EACX,sBAAsB,GACvB,GAAG,OAAO,CAAC;IAEZ,MAAM,UAAU,GACd,OAAO,CAAC,UAAU,IAAI,OAAO,OAAO,CAAC,UAAU,KAAK,QAAQ;QAC1D,CAAC,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,MAAM,EAAE;QAC5D,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC;IAEzB,MAAM,KAAK,GAAG,gBAAgB,CAAC;QAC7B,eAAe;QACf,UAAU;QACV,UAAU;QACV,YAAY;QACZ,WAAW;QACX,sBAAsB;KACvB,CAAC,CAAC;IAEH,OAAO,EAAE,GAAG,EAAE,iBAAiB,KAAK,EAAE,EAAE,CAAC;AAC3C,CAAC","sourcesContent":["import type { ImageSourcePropType } from \"react-native\";\nimport {\n PHImageContentMode,\n PHImageRequestOptionsDeliveryMode,\n PHImageRequestOptionsResizeMode,\n RequestImageTargetSize,\n} from \"./ExpoPhotos.types\";\n\nexport interface PHImageSourceOptions {\n localIdentifier: string;\n targetSize?: RequestImageTargetSize;\n resizeMode?: PHImageRequestOptionsResizeMode;\n deliveryMode?: PHImageRequestOptionsDeliveryMode;\n contentMode?: PHImageContentMode;\n isNetworkAccessAllowed?: boolean;\n}\n\nconst buildQueryString = (\n params: Record<string, string | number | boolean | undefined>\n): string => {\n const entries = Object.entries(params).filter(\n ([, value]) => value !== undefined\n );\n\n if (entries.length === 0) {\n return \"\";\n }\n\n return (\n \"?\" +\n entries\n .map(\n ([key, value]) =>\n `${encodeURIComponent(key)}=${encodeURIComponent(value ?? \"\")}`\n )\n .join(\"&\")\n );\n};\n\n/**\n * Creates a source object for React Native's Image component from a Photos library asset identifier.\n *\n * @param options - Configuration for image source, including the local identifier\n * @returns A source object that can be used with the Image component's `source` prop, or undefined if no identifier is provided\n *\n * @example\n * ```tsx\n * import { Image } from 'react-native';\n * import { createPHImageSource } from '@hortemo/expo-photos';\n *\n * <Image\n * source={createPHImageSource({\n * localIdentifier: asset.localIdentifier,\n * targetSize: { width: 200, height: 200 },\n * contentMode: PHImageContentMode.aspectFill,\n * })}\n * />\n * ```\n */\nexport function createPHImageSource(\n options: PHImageSourceOptions\n): ImageSourcePropType {\n const {\n localIdentifier,\n resizeMode,\n deliveryMode,\n contentMode,\n isNetworkAccessAllowed,\n } = options;\n\n const targetSize =\n options.targetSize && typeof options.targetSize === \"object\"\n ? `${options.targetSize.width}x${options.targetSize.height}`\n : options.targetSize;\n\n const query = buildQueryString({\n localIdentifier,\n targetSize,\n resizeMode,\n deliveryMode,\n contentMode,\n isNetworkAccessAllowed,\n });\n\n return { uri: `expo-photos://${query}` };\n}\n"]}
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
#if canImport(React)
|
|
2
|
-
import Foundation
|
|
3
|
-
import Photos
|
|
4
|
-
import React
|
|
5
|
-
import UIKit
|
|
6
|
-
|
|
7
|
-
@objc(ExpoPhotosImageLoader)
|
|
8
|
-
class ExpoPhotosImageLoader: NSObject, RCTBridgeModule, RCTImageURLLoader {
|
|
9
|
-
static func moduleName() -> String! {
|
|
10
|
-
return "ExpoPhotosImageLoader"
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
static func requiresMainQueueSetup() -> Bool {
|
|
14
|
-
return false
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// MARK: - Helper Functions
|
|
18
|
-
|
|
19
|
-
private static func queryItems(from url: URL) -> [String: String] {
|
|
20
|
-
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
|
|
21
|
-
let queryItems = components.queryItems else {
|
|
22
|
-
return [:]
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
var result: [String: String] = [:]
|
|
26
|
-
for item in queryItems {
|
|
27
|
-
if let value = item.value {
|
|
28
|
-
result[item.name] = value
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return result
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// MARK: - RCTImageURLLoader
|
|
35
|
-
|
|
36
|
-
func canLoadImageURL(_ requestURL: URL) -> Bool {
|
|
37
|
-
return requestURL.scheme?.caseInsensitiveCompare("expo-photos") == .orderedSame
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
func loaderPriority() -> Float {
|
|
41
|
-
return 1.0
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
func loadImage(
|
|
45
|
-
for imageURL: URL,
|
|
46
|
-
size: CGSize,
|
|
47
|
-
scale: CGFloat,
|
|
48
|
-
resizeMode: RCTResizeMode,
|
|
49
|
-
progressHandler: RCTImageLoaderProgressBlock!,
|
|
50
|
-
partialLoadHandler: RCTImageLoaderPartialLoadBlock!,
|
|
51
|
-
completionHandler: @escaping RCTImageLoaderCompletionBlock
|
|
52
|
-
) -> RCTImageLoaderCancellationBlock! {
|
|
53
|
-
|
|
54
|
-
guard imageURL.scheme?.caseInsensitiveCompare("expo-photos") == .orderedSame else {
|
|
55
|
-
completionHandler(RCTErrorWithMessage("Unsupported image URL scheme for ExpoPhotos"), nil)
|
|
56
|
-
return {}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
let queryParams = Self.queryItems(from: imageURL)
|
|
60
|
-
|
|
61
|
-
guard let localIdentifier = queryParams["localIdentifier"], !localIdentifier.isEmpty else {
|
|
62
|
-
completionHandler(RCTErrorWithMessage("Missing localIdentifier query parameter"), nil)
|
|
63
|
-
return {}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
let assets = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil)
|
|
67
|
-
guard let asset = assets.firstObject else {
|
|
68
|
-
completionHandler(RCTErrorWithMessage("Failed to fetch PHAsset with local identifier \(localIdentifier)"), nil)
|
|
69
|
-
return {}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
let imageOptions = PHImageRequestOptions()
|
|
73
|
-
|
|
74
|
-
var targetSize = size.applying(CGAffineTransform(scaleX: scale, y: scale))
|
|
75
|
-
|
|
76
|
-
if let targetSizeString = queryParams["targetSize"] {
|
|
77
|
-
if targetSizeString.caseInsensitiveCompare("PHImageManagerMaximumSize") == .orderedSame {
|
|
78
|
-
targetSize = PHImageManagerMaximumSize
|
|
79
|
-
} else {
|
|
80
|
-
let components = targetSizeString.lowercased().components(separatedBy: "x")
|
|
81
|
-
guard components.count == 2,
|
|
82
|
-
let width = Double(components[0]),
|
|
83
|
-
let height = Double(components[1]) else {
|
|
84
|
-
completionHandler(RCTErrorWithMessage("Invalid targetSize format. Expected WIDTHxHEIGHT or PHImageManagerMaximumSize."), nil)
|
|
85
|
-
return {}
|
|
86
|
-
}
|
|
87
|
-
targetSize = CGSize(width: width, height: height)
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if let deliveryModeString = queryParams["deliveryMode"] {
|
|
92
|
-
guard let deliveryMode = PHImageRequestOptionsDeliveryMode.from(queryParam: deliveryModeString) else {
|
|
93
|
-
completionHandler(RCTErrorWithMessage("Invalid deliveryMode value."), nil)
|
|
94
|
-
return {}
|
|
95
|
-
}
|
|
96
|
-
imageOptions.deliveryMode = deliveryMode
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if let resizeModeString = queryParams["resizeMode"] {
|
|
100
|
-
guard let resizeMode = PHImageRequestOptionsResizeMode.from(queryParam: resizeModeString) else {
|
|
101
|
-
completionHandler(RCTErrorWithMessage("Invalid resizeMode value."), nil)
|
|
102
|
-
return {}
|
|
103
|
-
}
|
|
104
|
-
imageOptions.resizeMode = resizeMode
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if let isNetworkAccessAllowedString = queryParams["isNetworkAccessAllowed"] {
|
|
108
|
-
imageOptions.isNetworkAccessAllowed = Bool.from(queryParam: isNetworkAccessAllowedString)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
var contentMode: PHImageContentMode = .default
|
|
112
|
-
if let contentModeString = queryParams["contentMode"] {
|
|
113
|
-
guard let _contentMode = PHImageContentMode.from(queryParam: contentModeString) else {
|
|
114
|
-
completionHandler(RCTErrorWithMessage("Invalid contentMode value."), nil)
|
|
115
|
-
return {}
|
|
116
|
-
}
|
|
117
|
-
contentMode = _contentMode
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if let progressHandler = progressHandler {
|
|
121
|
-
imageOptions.progressHandler = { progress, error, stop, info in
|
|
122
|
-
let multiplier: Int64 = 1_000_000
|
|
123
|
-
progressHandler(Int64(progress * Double(multiplier)), multiplier)
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
let requestID = PHImageManager.default().requestImage(
|
|
128
|
-
for: asset,
|
|
129
|
-
targetSize: targetSize,
|
|
130
|
-
contentMode: contentMode,
|
|
131
|
-
options: imageOptions
|
|
132
|
-
) { result, info in
|
|
133
|
-
guard let info = info else {
|
|
134
|
-
return
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
let isCancelled = (info[PHImageCancelledKey] as? NSNumber)?.boolValue ?? false
|
|
138
|
-
let isDegraded = (info[PHImageResultIsDegradedKey] as? NSNumber)?.boolValue ?? false
|
|
139
|
-
|
|
140
|
-
if isCancelled {
|
|
141
|
-
return
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if let result = result {
|
|
145
|
-
if isDegraded {
|
|
146
|
-
partialLoadHandler?(result)
|
|
147
|
-
} else {
|
|
148
|
-
completionHandler(nil, result)
|
|
149
|
-
}
|
|
150
|
-
} else if let error = info[PHImageErrorKey] as? Error {
|
|
151
|
-
completionHandler(error, nil)
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return {
|
|
156
|
-
PHImageManager.default().cancelImageRequest(requestID)
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// MARK: - RawRepresentable Extension
|
|
162
|
-
|
|
163
|
-
extension RawRepresentable where RawValue == Int {
|
|
164
|
-
static func from(queryParam string: String?) -> Self? {
|
|
165
|
-
guard let string = string, !string.isEmpty else {
|
|
166
|
-
return nil
|
|
167
|
-
}
|
|
168
|
-
guard let intValue = Int(string) else {
|
|
169
|
-
return nil
|
|
170
|
-
}
|
|
171
|
-
return Self(rawValue: intValue)
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// MARK: - Bool Extension
|
|
176
|
-
|
|
177
|
-
extension Bool {
|
|
178
|
-
static func from(queryParam string: String?) -> Bool {
|
|
179
|
-
guard let string = string, !string.isEmpty else {
|
|
180
|
-
return false
|
|
181
|
-
}
|
|
182
|
-
return string.lowercased() == "true"
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
#endif
|
|
187
|
-
|
package/src/PHImageSource.tsx
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import type { ImageSourcePropType } from "react-native";
|
|
2
|
-
import {
|
|
3
|
-
PHImageContentMode,
|
|
4
|
-
PHImageRequestOptionsDeliveryMode,
|
|
5
|
-
PHImageRequestOptionsResizeMode,
|
|
6
|
-
RequestImageTargetSize,
|
|
7
|
-
} from "./ExpoPhotos.types";
|
|
8
|
-
|
|
9
|
-
export interface PHImageSourceOptions {
|
|
10
|
-
localIdentifier: string;
|
|
11
|
-
targetSize?: RequestImageTargetSize;
|
|
12
|
-
resizeMode?: PHImageRequestOptionsResizeMode;
|
|
13
|
-
deliveryMode?: PHImageRequestOptionsDeliveryMode;
|
|
14
|
-
contentMode?: PHImageContentMode;
|
|
15
|
-
isNetworkAccessAllowed?: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const buildQueryString = (
|
|
19
|
-
params: Record<string, string | number | boolean | undefined>
|
|
20
|
-
): string => {
|
|
21
|
-
const entries = Object.entries(params).filter(
|
|
22
|
-
([, value]) => value !== undefined
|
|
23
|
-
);
|
|
24
|
-
|
|
25
|
-
if (entries.length === 0) {
|
|
26
|
-
return "";
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
"?" +
|
|
31
|
-
entries
|
|
32
|
-
.map(
|
|
33
|
-
([key, value]) =>
|
|
34
|
-
`${encodeURIComponent(key)}=${encodeURIComponent(value ?? "")}`
|
|
35
|
-
)
|
|
36
|
-
.join("&")
|
|
37
|
-
);
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Creates a source object for React Native's Image component from a Photos library asset identifier.
|
|
42
|
-
*
|
|
43
|
-
* @param options - Configuration for image source, including the local identifier
|
|
44
|
-
* @returns A source object that can be used with the Image component's `source` prop, or undefined if no identifier is provided
|
|
45
|
-
*
|
|
46
|
-
* @example
|
|
47
|
-
* ```tsx
|
|
48
|
-
* import { Image } from 'react-native';
|
|
49
|
-
* import { createPHImageSource } from '@hortemo/expo-photos';
|
|
50
|
-
*
|
|
51
|
-
* <Image
|
|
52
|
-
* source={createPHImageSource({
|
|
53
|
-
* localIdentifier: asset.localIdentifier,
|
|
54
|
-
* targetSize: { width: 200, height: 200 },
|
|
55
|
-
* contentMode: PHImageContentMode.aspectFill,
|
|
56
|
-
* })}
|
|
57
|
-
* />
|
|
58
|
-
* ```
|
|
59
|
-
*/
|
|
60
|
-
export function createPHImageSource(
|
|
61
|
-
options: PHImageSourceOptions
|
|
62
|
-
): ImageSourcePropType {
|
|
63
|
-
const {
|
|
64
|
-
localIdentifier,
|
|
65
|
-
resizeMode,
|
|
66
|
-
deliveryMode,
|
|
67
|
-
contentMode,
|
|
68
|
-
isNetworkAccessAllowed,
|
|
69
|
-
} = options;
|
|
70
|
-
|
|
71
|
-
const targetSize =
|
|
72
|
-
options.targetSize && typeof options.targetSize === "object"
|
|
73
|
-
? `${options.targetSize.width}x${options.targetSize.height}`
|
|
74
|
-
: options.targetSize;
|
|
75
|
-
|
|
76
|
-
const query = buildQueryString({
|
|
77
|
-
localIdentifier,
|
|
78
|
-
targetSize,
|
|
79
|
-
resizeMode,
|
|
80
|
-
deliveryMode,
|
|
81
|
-
contentMode,
|
|
82
|
-
isNetworkAccessAllowed,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
return { uri: `expo-photos://${query}` };
|
|
86
|
-
}
|