react-native-attachment-kit 1.0.5 → 1.0.6
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/CHANGELOG.md +10 -0
- package/README.md +100 -3
- package/android/build.gradle +14 -0
- package/android/src/main/libs/rnattachmentkit-release.aar +0 -0
- package/android/src/newarch/java/com/rnattachmentkit/RNAttachmentKitModule.kt +280 -13
- package/android/src/oldarch/java/com/rnattachmentkit/RNAttachmentKitModule.kt +282 -13
- package/ios/RNAttachmentKit.m +18 -0
- package/ios/RNAttachmentKit.swift +421 -9
- package/ios/RNAttachmentKit.xcframework/Info.plist +5 -5
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Assets.car +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Headers/RNAttachmentKit-Swift.h +21 -5
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Info.plist +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios.abi.json +33275 -7562
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios.private.swiftinterface +193 -5
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios.swiftinterface +193 -5
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/RNAttachmentKit +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/_CodeSignature/CodeDirectory +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/_CodeSignature/CodeResources +15 -15
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/_CodeSignature/CodeSignature +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Assets.car +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Headers/RNAttachmentKit-Swift.h +42 -10
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Info.plist +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.abi.json +33275 -7562
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +193 -5
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface +193 -5
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.abi.json +33275 -7562
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +193 -5
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +193 -5
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/RNAttachmentKit +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/_CodeSignature/CodeDirectory +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/_CodeSignature/CodeResources +25 -25
- package/package.json +1 -1
- package/react-native-attachment-kit.podspec +1 -1
- package/src/NativeAttachmentKit.ts +5 -0
- package/src/index.ts +118 -7
- package/src/types.ts +173 -0
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<dict>
|
|
7
7
|
<key>Assets.car</key>
|
|
8
8
|
<data>
|
|
9
|
-
|
|
9
|
+
RhTH3FdNHWutYB3O0httBcss21E=
|
|
10
10
|
</data>
|
|
11
11
|
<key>Base.lproj/LaunchScreen.storyboardc/01J-lp-oVM-view-Ze5-6b-2t3.nib</key>
|
|
12
12
|
<data>
|
|
@@ -22,51 +22,51 @@
|
|
|
22
22
|
</data>
|
|
23
23
|
<key>Headers/RNAttachmentKit-Swift.h</key>
|
|
24
24
|
<data>
|
|
25
|
-
|
|
25
|
+
SA2Uv/xUxXBayyyXE8H2RjgdHQA=
|
|
26
26
|
</data>
|
|
27
27
|
<key>Info.plist</key>
|
|
28
28
|
<data>
|
|
29
|
-
|
|
29
|
+
VGVHsdYWxmrpkpVKbSFeY69ecnY=
|
|
30
30
|
</data>
|
|
31
31
|
<key>Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.abi.json</key>
|
|
32
32
|
<data>
|
|
33
|
-
|
|
33
|
+
pQhFtv1R/nVZA5UMRIUtRAYUPfE=
|
|
34
34
|
</data>
|
|
35
35
|
<key>Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface</key>
|
|
36
36
|
<data>
|
|
37
|
-
|
|
37
|
+
QViRQdPRlGSnejc+FkklUFb1xzo=
|
|
38
38
|
</data>
|
|
39
39
|
<key>Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.swiftdoc</key>
|
|
40
40
|
<data>
|
|
41
|
-
|
|
41
|
+
13waPrbWcS1Vv7tMDqh9IORrbTU=
|
|
42
42
|
</data>
|
|
43
43
|
<key>Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface</key>
|
|
44
44
|
<data>
|
|
45
|
-
|
|
45
|
+
QViRQdPRlGSnejc+FkklUFb1xzo=
|
|
46
46
|
</data>
|
|
47
47
|
<key>Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.swiftmodule</key>
|
|
48
48
|
<data>
|
|
49
|
-
|
|
49
|
+
srdAd3yXJz+UBhI0hq2En+76JBQ=
|
|
50
50
|
</data>
|
|
51
51
|
<key>Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.abi.json</key>
|
|
52
52
|
<data>
|
|
53
|
-
|
|
53
|
+
pQhFtv1R/nVZA5UMRIUtRAYUPfE=
|
|
54
54
|
</data>
|
|
55
55
|
<key>Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface</key>
|
|
56
56
|
<data>
|
|
57
|
-
|
|
57
|
+
4Bhyu9pqG8QPG8JDzN15Es69LgQ=
|
|
58
58
|
</data>
|
|
59
59
|
<key>Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.swiftdoc</key>
|
|
60
60
|
<data>
|
|
61
|
-
|
|
61
|
+
9aHuRlxlcUm5c6hoS03l0x+rd+8=
|
|
62
62
|
</data>
|
|
63
63
|
<key>Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.swiftinterface</key>
|
|
64
64
|
<data>
|
|
65
|
-
|
|
65
|
+
4Bhyu9pqG8QPG8JDzN15Es69LgQ=
|
|
66
66
|
</data>
|
|
67
67
|
<key>Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.swiftmodule</key>
|
|
68
68
|
<data>
|
|
69
|
-
|
|
69
|
+
GRAT4UVHZ+7RlMTxrfKaS0xKzRo=
|
|
70
70
|
</data>
|
|
71
71
|
<key>Modules/module.modulemap</key>
|
|
72
72
|
<data>
|
|
@@ -227,7 +227,7 @@
|
|
|
227
227
|
<dict>
|
|
228
228
|
<key>hash2</key>
|
|
229
229
|
<data>
|
|
230
|
-
|
|
230
|
+
0N1A4LGshpc4ca3QAtjAqMx7M6KC4+eiQAKHcKRnUB4=
|
|
231
231
|
</data>
|
|
232
232
|
</dict>
|
|
233
233
|
<key>Base.lproj/LaunchScreen.storyboardc/01J-lp-oVM-view-Ze5-6b-2t3.nib</key>
|
|
@@ -255,77 +255,77 @@
|
|
|
255
255
|
<dict>
|
|
256
256
|
<key>hash2</key>
|
|
257
257
|
<data>
|
|
258
|
-
|
|
258
|
+
CIsiqD4QUiiFinw84KecZtVO9t4RF+3C362HoLNyZiA=
|
|
259
259
|
</data>
|
|
260
260
|
</dict>
|
|
261
261
|
<key>Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.abi.json</key>
|
|
262
262
|
<dict>
|
|
263
263
|
<key>hash2</key>
|
|
264
264
|
<data>
|
|
265
|
-
|
|
265
|
+
1zvKPqPypcR4T+4Rom2iyPxya6nAybDNWacrlzCtmHk=
|
|
266
266
|
</data>
|
|
267
267
|
</dict>
|
|
268
268
|
<key>Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface</key>
|
|
269
269
|
<dict>
|
|
270
270
|
<key>hash2</key>
|
|
271
271
|
<data>
|
|
272
|
-
|
|
272
|
+
bQBBzPNGBXPENE5stiQHn43HpKljGLBGsmytx74osv8=
|
|
273
273
|
</data>
|
|
274
274
|
</dict>
|
|
275
275
|
<key>Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.swiftdoc</key>
|
|
276
276
|
<dict>
|
|
277
277
|
<key>hash2</key>
|
|
278
278
|
<data>
|
|
279
|
-
|
|
279
|
+
c5sCwczWvgTgfE4P3/QZXLg4Ju1YyiBkHe8mlkacRzs=
|
|
280
280
|
</data>
|
|
281
281
|
</dict>
|
|
282
282
|
<key>Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface</key>
|
|
283
283
|
<dict>
|
|
284
284
|
<key>hash2</key>
|
|
285
285
|
<data>
|
|
286
|
-
|
|
286
|
+
bQBBzPNGBXPENE5stiQHn43HpKljGLBGsmytx74osv8=
|
|
287
287
|
</data>
|
|
288
288
|
</dict>
|
|
289
289
|
<key>Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.swiftmodule</key>
|
|
290
290
|
<dict>
|
|
291
291
|
<key>hash2</key>
|
|
292
292
|
<data>
|
|
293
|
-
+
|
|
293
|
+
jN0azSsNiu9msGjnni3J+edf0PMv4n919UpLDnJhFfU=
|
|
294
294
|
</data>
|
|
295
295
|
</dict>
|
|
296
296
|
<key>Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.abi.json</key>
|
|
297
297
|
<dict>
|
|
298
298
|
<key>hash2</key>
|
|
299
299
|
<data>
|
|
300
|
-
|
|
300
|
+
1zvKPqPypcR4T+4Rom2iyPxya6nAybDNWacrlzCtmHk=
|
|
301
301
|
</data>
|
|
302
302
|
</dict>
|
|
303
303
|
<key>Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface</key>
|
|
304
304
|
<dict>
|
|
305
305
|
<key>hash2</key>
|
|
306
306
|
<data>
|
|
307
|
-
|
|
307
|
+
ruMrsqQu7c5vBjEKp/hzHz82QST/UnwbWCTHD3uGgqM=
|
|
308
308
|
</data>
|
|
309
309
|
</dict>
|
|
310
310
|
<key>Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.swiftdoc</key>
|
|
311
311
|
<dict>
|
|
312
312
|
<key>hash2</key>
|
|
313
313
|
<data>
|
|
314
|
-
|
|
314
|
+
l+6rzGzrcNz6C1xQixmxJnTj6bp8QFLvi01FhSTniOA=
|
|
315
315
|
</data>
|
|
316
316
|
</dict>
|
|
317
317
|
<key>Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.swiftinterface</key>
|
|
318
318
|
<dict>
|
|
319
319
|
<key>hash2</key>
|
|
320
320
|
<data>
|
|
321
|
-
|
|
321
|
+
ruMrsqQu7c5vBjEKp/hzHz82QST/UnwbWCTHD3uGgqM=
|
|
322
322
|
</data>
|
|
323
323
|
</dict>
|
|
324
324
|
<key>Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.swiftmodule</key>
|
|
325
325
|
<dict>
|
|
326
326
|
<key>hash2</key>
|
|
327
327
|
<data>
|
|
328
|
-
|
|
328
|
+
EqFFC6hS93F/fIA+wRumib7y+P0lyAk64livq+vjDuE=
|
|
329
329
|
</data>
|
|
330
330
|
</dict>
|
|
331
331
|
<key>Modules/module.modulemap</key>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-attachment-kit",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Native attachment kit for React Native — gallery picker, editor, image viewer and media gallery (Turbo Module)",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -26,7 +26,7 @@ Pod::Spec.new do |s|
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
s.vendored_frameworks = "ios/RNAttachmentKit.xcframework"
|
|
29
|
-
s.frameworks = "MapKit", "CoreLocation", "UniformTypeIdentifiers" #
|
|
29
|
+
s.frameworks = "MapKit", "CoreLocation", "UniformTypeIdentifiers", "Vision", "CryptoKit", "CoreMotion" # 위치·파일 탭 / AI 편집 사진 분석·캐시 / 카메라 기기 방향
|
|
30
30
|
|
|
31
31
|
# s.dependency "SDWebImageWebPCoder"
|
|
32
32
|
|
|
@@ -7,6 +7,8 @@ export interface Spec extends TurboModule {
|
|
|
7
7
|
openEditor(options: Object): Promise<Object>;
|
|
8
8
|
openViewer(options: Object): Promise<void>;
|
|
9
9
|
closeGallery(): Promise<boolean>;
|
|
10
|
+
/** 동영상 변환 취소. index -1 = 남은 동영상 전부, processingId "" = 가장 최근 처리 */
|
|
11
|
+
cancelVideoProcessing(index: number, processingId: string): Promise<boolean>;
|
|
10
12
|
|
|
11
13
|
// RCTEventEmitter 필수 규격 (Turbo Module에서 이벤트 방출 시 필수)
|
|
12
14
|
addListener(eventName: string): void;
|
|
@@ -26,6 +28,9 @@ export interface Spec extends TurboModule {
|
|
|
26
28
|
reverseGeocode(options: Object): Promise<Object>;
|
|
27
29
|
clearReverseGeocodeCache(): Promise<void>;
|
|
28
30
|
|
|
31
|
+
// AI 기능 설정·잔액 (넘긴 값만 바뀜). 사용·충전 요청은 onAIUsage / onAIChargeRequired 이벤트
|
|
32
|
+
setAIConfig(config: Object): Promise<void>;
|
|
33
|
+
|
|
29
34
|
// 미디어 갤러리 (사진 모아보기)
|
|
30
35
|
openMediaGallery(options: Object): Promise<Object>;
|
|
31
36
|
closeMediaGallery(galleryId: string): Promise<void>;
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NativeModules, Platform, TurboModuleRegistry, NativeEventEmitter, DeviceEventEmitter } from "react-native"
|
|
2
|
-
import type { ImageResult, GalleryOptions, EditorOptions, SelectionChangeEvent, ViewerOptions, MediaGalleryOptions, MediaGalleryAppendPayload, MediaGalleryRemovePayload, Appearance } from "./types"
|
|
2
|
+
import type { ImageResult, GalleryOptions, EditorOptions, SelectionChangeEvent, ViewerOptions, MediaGalleryOptions, MediaGalleryAppendPayload, MediaGalleryRemovePayload, Appearance, AIConfig, AIUsageEvent, AIChargeRequiredEvent } from "./types"
|
|
3
3
|
|
|
4
4
|
const LINKING_ERROR = `The package 'react-native-attachment-kit' doesn't seem to be linked. Make sure: \n\n` + Platform.select({ ios: "- You have run 'pod install'\n", default: "" }) + "- You rebuilt the app after installing the package\n" + "- You are not using Expo Go\n"
|
|
5
5
|
|
|
@@ -78,6 +78,8 @@ function alreadyOpenError(what: string): Error {
|
|
|
78
78
|
* Android pickImagesPromise) 중복 호출하면 앞 호출의 Promise 가 영영 끝나지 않습니다 → JS 에서 먼저 막습니다.
|
|
79
79
|
*/
|
|
80
80
|
let _galleryOpen = false
|
|
81
|
+
/** 어느 openGallery 호출이 _galleryOpen 을 잡고 있는지 — 처리 시작(onProcessingStart)에 먼저 풀고, 늦게 끝난 앞 호출이 새 호출의 잠금을 풀지 않게 */
|
|
82
|
+
let _galleryOpenToken = 0
|
|
81
83
|
/** openEditor 도 Android 가 editImagePromise 하나만 보관하므로 같은 보호가 필요 */
|
|
82
84
|
let _editorOpen = false
|
|
83
85
|
/**
|
|
@@ -86,6 +88,8 @@ let _editorOpen = false
|
|
|
86
88
|
*/
|
|
87
89
|
let _asyncImgProcessedSub: { remove: () => void } | null = null
|
|
88
90
|
|
|
91
|
+
export type { AIConfig, AIUsageEvent, AIChargeRequiredEvent } from "./types"
|
|
92
|
+
export type { VideoOptions, VideoProgressEvent, ViewerVideo, ProcessingStartEvent, ProcessingStartItem } from "./types"
|
|
89
93
|
export type { SelectionChangeEvent, Appearance, AttachLocationResult, MapViewerOptions, ReverseGeocodeOptions, ReverseGeocodeResult, ReverseGeocodeErrorCode, AttachFileResult, AttachFileFailure, LocationThumbnailOptions, LocationThumbnailResult } from "./types"
|
|
90
94
|
export type { MediaGalleryItem, MediaGalleryOptions, MediaGalleryGridSize, MediaGalleryGridSizeChangedEvent, MediaGalleryTheme, MediaGalleryViewerOptions, MediaGalleryDeleteOption, MediaGalleryItemDeletedEvent, MediaGalleryNeedPageEvent, MediaGalleryClosedEvent, MediaGalleryAppendPayload, MediaGalleryRemovePayload } from "./types"
|
|
91
95
|
|
|
@@ -115,6 +119,23 @@ export interface RNAttachmentKit {
|
|
|
115
119
|
/** reverseGeocode 캐시 비우기 */
|
|
116
120
|
clearReverseGeocodeCache(): Promise<void>
|
|
117
121
|
closeGallery(): Promise<boolean>
|
|
122
|
+
/**
|
|
123
|
+
* processingId: 어느 보내기인지 (onProcessingStart 의 값). 생략하면 가장 최근 보내기
|
|
124
|
+
*
|
|
125
|
+
* 보내는 중인 동영상 변환 취소 (HUD 없이 말풍선에서 취소할 때). index 생략 = 남은 동영상 전부.
|
|
126
|
+
* 취소된 동영상은 onImageProcessed·결과에 { error: "CANCELLED", cancelled: true } 로 오고 나머지는 계속 처리됩니다.
|
|
127
|
+
* 진행 중인 처리가 없으면 false
|
|
128
|
+
*/
|
|
129
|
+
cancelVideoProcessing(index?: number, processingId?: string): Promise<boolean>
|
|
130
|
+
/**
|
|
131
|
+
* AI 기능 on/off · 포인트 · 무료 횟수 · 1회 차감 포인트 (넘긴 값만 바뀜). 앱 시작 때 한 번, 잔액이 바뀔 때마다(충전·서버 동기화) 다시 호출.
|
|
132
|
+
* AI 화면이 열려 있으면 바로 반영됩니다.
|
|
133
|
+
*/
|
|
134
|
+
setAIConfig(config: AIConfig): Promise<void>
|
|
135
|
+
/** AI 를 한 번 쓸 때마다 (무료 횟수 사용 또는 포인트 차감) — 앱이 서버에 반영. 해제 함수를 돌려줌 */
|
|
136
|
+
addAIUsageListener(listener: (event: AIUsageEvent) => void): () => void
|
|
137
|
+
/** 포인트가 모자라 사용자가 '충전하기'를 눌렀을 때 — 앱이 충전 화면으로 안내 (필요하면 closeGallery() 로 시트도 닫기). 해제 함수를 돌려줌 */
|
|
138
|
+
addAIChargeRequiredListener(listener: (event: AIChargeRequiredEvent) => void): () => void
|
|
118
139
|
addSelectionChangeListener(listener: (event: SelectionChangeEvent) => void): () => void
|
|
119
140
|
getDefaultAnimationConfig(): Promise<{ galleryOpen: number, galleryClose: number, editorOpen: number, editorClose: number, viewerOpen: number, viewerClose: number }>
|
|
120
141
|
injectImageCache(urlString: string, localPath: string): Promise<boolean>
|
|
@@ -167,6 +188,22 @@ const AttachmentKit: RNAttachmentKit = {
|
|
|
167
188
|
throw alreadyOpenError("Gallery")
|
|
168
189
|
}
|
|
169
190
|
_galleryOpen = true
|
|
191
|
+
const myToken = ++_galleryOpenToken
|
|
192
|
+
// 갤러리 화면이 닫히고 처리가 시작되면(onProcessingStart) 바로 잠금을 풂 — 동영상을 변환하는 동안에도
|
|
193
|
+
// 다시 열어 보낼 수 있게. Promise 는 지금처럼 처리가 다 끝나야 resolve
|
|
194
|
+
let myProcessingId: string | null = null
|
|
195
|
+
const subscriptionOpenLock = eventEmitter.addListener("onProcessingStart", (e: any) => {
|
|
196
|
+
if (myProcessingId) return
|
|
197
|
+
myProcessingId = typeof e?.processingId === "string" ? e.processingId : ""
|
|
198
|
+
if (_galleryOpenToken === myToken) _galleryOpen = false
|
|
199
|
+
})
|
|
200
|
+
/** 이 호출의 이벤트인지 — processingId 가 붙은 이벤트는 내 처리의 것만, 안 붙은(옛 네이티브·사진 1장 편집) 이벤트는 처리 ID 를 모를 때만 */
|
|
201
|
+
const isMine = (e: any) => {
|
|
202
|
+
const id = e?.processingId
|
|
203
|
+
if (typeof id !== "string" || id === "") return !myProcessingId
|
|
204
|
+
return myProcessingId === id
|
|
205
|
+
}
|
|
206
|
+
const mine = <T,>(fn?: (e: T) => void) => fn ? (e: T) => { if (isMine(e)) fn(e) } : undefined
|
|
170
207
|
|
|
171
208
|
// 콜백 이벤트 임시 리스너 등록
|
|
172
209
|
let subscriptionSelChange: any = null
|
|
@@ -182,16 +219,33 @@ const AttachmentKit: RNAttachmentKit = {
|
|
|
182
219
|
try { _asyncImgProcessedSub.remove() } catch (e) { /* noop */ }
|
|
183
220
|
_asyncImgProcessedSub = null
|
|
184
221
|
}
|
|
185
|
-
subscriptionImgProcessed = eventEmitter.addListener("onImageProcessed", options.onImageProcessed)
|
|
222
|
+
subscriptionImgProcessed = eventEmitter.addListener("onImageProcessed", mine(options.onImageProcessed)!)
|
|
186
223
|
_asyncImgProcessedSub = subscriptionImgProcessed
|
|
187
224
|
} else {
|
|
188
|
-
subscriptionImgProcessed = eventEmitter.addListener("onImageProcessed", options.onImageProcessed)
|
|
225
|
+
subscriptionImgProcessed = eventEmitter.addListener("onImageProcessed", mine(options.onImageProcessed)!)
|
|
189
226
|
}
|
|
190
227
|
}
|
|
191
228
|
let subscriptionLocation: any = null
|
|
192
229
|
if (options.onLocationSelected) {
|
|
193
230
|
subscriptionLocation = eventEmitter.addListener("onLocationSelected", options.onLocationSelected)
|
|
194
231
|
}
|
|
232
|
+
// 동영상 변환 진행률 (video.onProgress)
|
|
233
|
+
let subscriptionVideoProgress: any = null
|
|
234
|
+
if (options.video?.onProgress) {
|
|
235
|
+
subscriptionVideoProgress = eventEmitter.addListener("onVideoProgress", mine(options.video.onProgress)!)
|
|
236
|
+
}
|
|
237
|
+
// 처리 시작 (보낸 직후 한 번, 결과보다 먼저)
|
|
238
|
+
let subscriptionProcessingStart: any = null
|
|
239
|
+
if (options.onProcessingStart) {
|
|
240
|
+
// 내 처리 시작만 (앞 보내기의 시작 이벤트는 이 호출 전에 이미 지나감)
|
|
241
|
+
const onStart = options.onProcessingStart
|
|
242
|
+
let started = false
|
|
243
|
+
subscriptionProcessingStart = eventEmitter.addListener("onProcessingStart", (e: any) => {
|
|
244
|
+
if (started) return
|
|
245
|
+
started = true
|
|
246
|
+
onStart(e)
|
|
247
|
+
})
|
|
248
|
+
}
|
|
195
249
|
let subscriptionFiles: any = null
|
|
196
250
|
if (options.onFilesSelected) {
|
|
197
251
|
const onFiles = options.onFilesSelected
|
|
@@ -201,19 +255,26 @@ const AttachmentKit: RNAttachmentKit = {
|
|
|
201
255
|
|
|
202
256
|
try {
|
|
203
257
|
// 이벤트 콜백은 네이티브로 전달하지 않음
|
|
204
|
-
const { onSelectionChange, onImageProcessed, onLocationSelected, onFilesSelected, ...restOptions } = options
|
|
205
|
-
const nativeOptions = {
|
|
258
|
+
const { onSelectionChange, onImageProcessed, onLocationSelected, onFilesSelected, onProcessingStart, video, ...restOptions } = options
|
|
259
|
+
const nativeOptions: any = {
|
|
206
260
|
themeColor: _defaultThemeColor,
|
|
207
261
|
languageCode: _defaultLanguageCode,
|
|
208
262
|
appearance: _defaultAppearance,
|
|
209
263
|
...restOptions,
|
|
210
264
|
}
|
|
265
|
+
if (video) {
|
|
266
|
+
// onProgress 콜백은 네이티브로 넘기지 않음
|
|
267
|
+
const { onProgress, ...videoRest } = video
|
|
268
|
+
nativeOptions.video = videoRest
|
|
269
|
+
}
|
|
211
270
|
nativeOptions.languageCode = normalizeLanguageCode(nativeOptions.languageCode)
|
|
212
271
|
nativeOptions.appearance = normalizeAppearance(nativeOptions.appearance)
|
|
213
272
|
const result = await RNAttachmentKitModule.openGallery(nativeOptions)
|
|
214
273
|
return result
|
|
215
274
|
} finally {
|
|
216
|
-
|
|
275
|
+
// 처리 시작 때 이미 풀었고 그 뒤 새 호출이 잡았을 수 있음 → 내 잠금일 때만
|
|
276
|
+
if (_galleryOpenToken === myToken) _galleryOpen = false
|
|
277
|
+
subscriptionOpenLock.remove()
|
|
217
278
|
// 갤러리가 닫히면 onSelectionChange 리스너 제거
|
|
218
279
|
if (subscriptionSelChange) {
|
|
219
280
|
subscriptionSelChange.remove()
|
|
@@ -225,6 +286,12 @@ const AttachmentKit: RNAttachmentKit = {
|
|
|
225
286
|
if (subscriptionFiles) {
|
|
226
287
|
subscriptionFiles.remove()
|
|
227
288
|
}
|
|
289
|
+
if (subscriptionVideoProgress) {
|
|
290
|
+
subscriptionVideoProgress.remove()
|
|
291
|
+
}
|
|
292
|
+
if (subscriptionProcessingStart) {
|
|
293
|
+
subscriptionProcessingStart.remove()
|
|
294
|
+
}
|
|
228
295
|
// asyncProcessing 모드가 아닐 때만 onImageProcessed 리스너 제거 (async 모드에선 백그라운드 처리를 위해 유지해야 함)
|
|
229
296
|
// 호출부에서 수동으로 제거하거나, 최종 완료 시 정리하는 구조가 필요할 수 있음
|
|
230
297
|
if (!options.asyncProcessing && subscriptionImgProcessed) {
|
|
@@ -380,7 +447,22 @@ const AttachmentKit: RNAttachmentKit = {
|
|
|
380
447
|
mergedOptions.addressLanguageCode = options.languageCode || _rawLanguageCode || _defaultLanguageCode
|
|
381
448
|
mergedOptions.languageCode = normalizeLanguageCode(mergedOptions.languageCode)
|
|
382
449
|
mergedOptions.appearance = normalizeAppearance(mergedOptions.appearance)
|
|
383
|
-
|
|
450
|
+
// 함수는 네이티브로 못 넘김 — 닫기 시작은 이벤트로 받음 (Android 는 RCTDeviceEventEmitter 로도 올 수 있어 둘 다 듣고 한 번만)
|
|
451
|
+
const onWillClose = options.onViewerWillClose
|
|
452
|
+
delete mergedOptions.onViewerWillClose
|
|
453
|
+
let willCloseSent = false
|
|
454
|
+
const willCloseHandler = (e: any) => {
|
|
455
|
+
if (willCloseSent) return
|
|
456
|
+
willCloseSent = true
|
|
457
|
+
onWillClose?.({ durationMs: typeof e?.durationMs === 'number' ? e.durationMs : 0 })
|
|
458
|
+
}
|
|
459
|
+
const subs: any[] = [eventEmitter.addListener("onMapViewerWillClose", willCloseHandler)]
|
|
460
|
+
if (Platform.OS === "android") subs.push(DeviceEventEmitter.addListener("onMapViewerWillClose", willCloseHandler))
|
|
461
|
+
try {
|
|
462
|
+
await RNAttachmentKitModule.openMapViewer(mergedOptions)
|
|
463
|
+
} finally {
|
|
464
|
+
subs.forEach(s => s.remove())
|
|
465
|
+
}
|
|
384
466
|
},
|
|
385
467
|
|
|
386
468
|
updateMapViewerSourceRect: async (rect: import("./types").SourceRect | null): Promise<void> => {
|
|
@@ -439,6 +521,35 @@ const AttachmentKit: RNAttachmentKit = {
|
|
|
439
521
|
return RNAttachmentKitModule.closeGallery()
|
|
440
522
|
},
|
|
441
523
|
|
|
524
|
+
cancelVideoProcessing: async (index?: number, processingId?: string): Promise<boolean> => {
|
|
525
|
+
if (!RNAttachmentKitModule) {
|
|
526
|
+
throw new Error(LINKING_ERROR)
|
|
527
|
+
}
|
|
528
|
+
if (typeof RNAttachmentKitModule.cancelVideoProcessing !== "function") return false // 옛 네이티브 빌드
|
|
529
|
+
return RNAttachmentKitModule.cancelVideoProcessing(typeof index === "number" && index >= 0 ? index : -1, processingId ?? "")
|
|
530
|
+
},
|
|
531
|
+
|
|
532
|
+
setAIConfig: async (config: AIConfig): Promise<void> => {
|
|
533
|
+
if (!RNAttachmentKitModule) {
|
|
534
|
+
throw new Error(LINKING_ERROR)
|
|
535
|
+
}
|
|
536
|
+
if (typeof RNAttachmentKitModule.setAIConfig !== "function") return // 옛 네이티브 빌드
|
|
537
|
+
// undefined 는 "바꾸지 않음" 이므로 빼고 보냄 (null 은 "비움")
|
|
538
|
+
const payload: Record<string, unknown> = {}
|
|
539
|
+
Object.entries(config ?? {}).forEach(([k, v]) => { if (v !== undefined) payload[k] = v })
|
|
540
|
+
return RNAttachmentKitModule.setAIConfig(payload)
|
|
541
|
+
},
|
|
542
|
+
|
|
543
|
+
addAIUsageListener: (listener: (event: AIUsageEvent) => void) => {
|
|
544
|
+
const subscription = eventEmitter.addListener("onAIUsage", listener as any)
|
|
545
|
+
return () => subscription.remove()
|
|
546
|
+
},
|
|
547
|
+
|
|
548
|
+
addAIChargeRequiredListener: (listener: (event: AIChargeRequiredEvent) => void) => {
|
|
549
|
+
const subscription = eventEmitter.addListener("onAIChargeRequired", listener as any)
|
|
550
|
+
return () => subscription.remove()
|
|
551
|
+
},
|
|
552
|
+
|
|
442
553
|
addSelectionChangeListener: (listener: (event: SelectionChangeEvent) => void) => {
|
|
443
554
|
const subscription = eventEmitter.addListener("onSelectionChange", listener)
|
|
444
555
|
|
package/src/types.ts
CHANGED
|
@@ -20,6 +20,94 @@ export interface ImageResult {
|
|
|
20
20
|
fileName?: string // 파일명
|
|
21
21
|
fileExtension?: string // 확장자
|
|
22
22
|
fileSize?: number // 파일 크기 (bytes)
|
|
23
|
+
/** "image"(기본) | "video" — mediaType: "all"/"video" 로 열었을 때 동영상이면 "video" */
|
|
24
|
+
mediaType?: "image" | "video"
|
|
25
|
+
/** 동영상: 길이(초, 소수) */
|
|
26
|
+
duration?: number
|
|
27
|
+
/** 동영상: 포스터 썸네일 file:// (video.posterFormat 형식 — 기본 webp, 긴 변 512) */
|
|
28
|
+
thumbnailUri?: string
|
|
29
|
+
thumbnailWidth?: number
|
|
30
|
+
thumbnailHeight?: number
|
|
31
|
+
/** 동영상: 전송본 코덱·평균 비트레이트(bps) */
|
|
32
|
+
videoCodec?: "h264" | "hevc" | string
|
|
33
|
+
videoBitrate?: number
|
|
34
|
+
/** 편집(trim/crop/overlay/음소거/품질 변경)했는지. false 면 원본을 그대로(리먹스) 보낸 것 */
|
|
35
|
+
edited?: boolean
|
|
36
|
+
/** 동영상 변환에 실패했을 때 (uri 없음). 사진·정상 동영상엔 없음. 앱이 취소했으면 "CANCELLED" */
|
|
37
|
+
error?: string
|
|
38
|
+
/** 앱이 cancelVideoProcessing 으로 취소한 동영상 (uri 없음) */
|
|
39
|
+
cancelled?: boolean
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* onProcessingStart — 갤러리에서 보낸 직후, 처리(사진 리사이즈·동영상 변환)를 시작하기 전에 한 번.
|
|
44
|
+
* 앱은 이걸로 말풍선 자리를 먼저 만들고(비율·포스터·길이), 동영상은 onVideoProgress 로 진행률을 그린 뒤
|
|
45
|
+
* onImageProcessed(index 같음)가 오면 실제 파일로 바꿉니다.
|
|
46
|
+
*/
|
|
47
|
+
export interface ProcessingStartEvent {
|
|
48
|
+
/**
|
|
49
|
+
* 이번 보내기의 ID. onVideoProgress / onImageProcessed 에도 같은 값이 붙음.
|
|
50
|
+
* 동영상을 변환하는 동안에도 갤러리를 다시 열어 또 보낼 수 있으므로, 이벤트를 전역 리스너로 받는 앱은 이 값으로 구분
|
|
51
|
+
*/
|
|
52
|
+
processingId: string
|
|
53
|
+
total: number
|
|
54
|
+
items: ProcessingStartItem[]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface ProcessingStartItem {
|
|
58
|
+
index: number
|
|
59
|
+
mediaType: "image" | "video"
|
|
60
|
+
/** ph://… (iOS) / content://… (Android) */
|
|
61
|
+
originalUri?: string
|
|
62
|
+
/** 예상 결과 크기 (사진: 리사이즈 후, 동영상: 품질·크롭 반영). 비율용 */
|
|
63
|
+
width: number
|
|
64
|
+
height: number
|
|
65
|
+
/** 동영상: 보낼 길이(초, 구간 반영) */
|
|
66
|
+
duration?: number
|
|
67
|
+
/** 동영상: 포스터 file:// (video.posterFormat 형식 — 기본 webp, 크롭·그리기 반영) — 변환이 끝나기 전 말풍선에 */
|
|
68
|
+
thumbnailUri?: string
|
|
69
|
+
thumbnailWidth?: number
|
|
70
|
+
thumbnailHeight?: number
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** 동영상 옵션 (GalleryOptions.video). mediaType 이 "video"/"all" 일 때만 의미 */
|
|
74
|
+
export interface VideoOptions {
|
|
75
|
+
/** 고를 수 있는 최대 길이(초). 기본 600 (10분). 넘는 동영상은 누르면 안내만. 0 = 제한 없음 */
|
|
76
|
+
maxDurationSec?: number
|
|
77
|
+
/** 전송 결과 최대 용량(MB). 기본 100. 추정치가 넘으면 더 낮은 품질로 자동 조정. 0 = 제한 없음 */
|
|
78
|
+
maxFileSizeMB?: number
|
|
79
|
+
/** 해상도 단계 (세로 px). 기본 [360, 480, 720, 1080]. 원본보다 큰 단계는 쓰지 않음(원본 유지) */
|
|
80
|
+
qualities?: number[]
|
|
81
|
+
/** 기본 품질 (세로 px). 기본 720. 원본이 더 작으면 원본 그대로 */
|
|
82
|
+
defaultQuality?: number
|
|
83
|
+
/** 구간 자르기 허용. 기본 true (편집 화면이 생기는 P2 부터) */
|
|
84
|
+
allowTrim?: boolean
|
|
85
|
+
/** 음소거 버튼 표시. 기본 true (P2 부터) */
|
|
86
|
+
allowMute?: boolean
|
|
87
|
+
/** 코덱. 기본 "h264". "hevc" 는 용량이 작지만 수신 기기 호환을 확인한 경우만 */
|
|
88
|
+
codec?: "h264" | "hevc"
|
|
89
|
+
/**
|
|
90
|
+
* 동영상 포스터(썸네일) 파일 형식. 기본 "webp" (jpg 보다 작음). "jpg" 로 하면 예전처럼 jpg.
|
|
91
|
+
* onProcessingStart / onImageProcessed 의 thumbnailUri 확장자가 이 값을 따름 (webp 인코딩이 안 되는 드문 경우엔 jpg 로 대신하니, 확장자는 uri 로 확인)
|
|
92
|
+
*/
|
|
93
|
+
posterFormat?: "webp" | "jpg"
|
|
94
|
+
/** 전송 전 변환 진행률 (동영상마다 0~1, 1% 이상 바뀔 때·최소 0.1초 간격) */
|
|
95
|
+
onProgress?: (e: VideoProgressEvent) => void
|
|
96
|
+
/**
|
|
97
|
+
* 변환하는 동안 화면 가운데 진행 HUD(취소 버튼 포함)를 띄울지.
|
|
98
|
+
* 기본: asyncProcessing 이 아니면 true, asyncProcessing: true 면 false —
|
|
99
|
+
* 이벤트(onProcessingStart / onVideoProgress / onImageProcessed)로 말풍선에 진행 상황을 그리는 앱은 HUD 없이 씁니다.
|
|
100
|
+
* HUD 가 없을 때 취소는 AttachmentKit.cancelVideoProcessing(index)
|
|
101
|
+
*/
|
|
102
|
+
showProgressHUD?: boolean
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface VideoProgressEvent {
|
|
106
|
+
index: number
|
|
107
|
+
total: number
|
|
108
|
+
progress: number
|
|
109
|
+
/** 어느 보내기의 진행률인지 (onProcessingStart 의 processingId) */
|
|
110
|
+
processingId?: string
|
|
23
111
|
}
|
|
24
112
|
|
|
25
113
|
export interface SourceRect {
|
|
@@ -33,7 +121,10 @@ export interface GalleryOptions {
|
|
|
33
121
|
maxSelection?: number
|
|
34
122
|
maxWidth?: number // 최대 width
|
|
35
123
|
maxHeight?: number // 최대 height
|
|
124
|
+
/** 기본 "photo" = 사진만(동영상 권한도 요청 안 함). "all" = 사진+동영상 날짜순 혼합, "video" = 동영상만 */
|
|
36
125
|
mediaType?: "photo" | "video" | "all"
|
|
126
|
+
/** 동영상 옵션 (mediaType 이 video/all 일 때) */
|
|
127
|
+
video?: VideoOptions
|
|
37
128
|
quality?: number
|
|
38
129
|
allItemsText?: string
|
|
39
130
|
selectedItemsText?: string
|
|
@@ -61,6 +152,8 @@ export interface GalleryOptions {
|
|
|
61
152
|
asyncProcessing?: boolean // 완전 비동기 이벤트 리턴 여부
|
|
62
153
|
onSelectionChange?: (event: SelectionChangeEvent) => void
|
|
63
154
|
onImageProcessed?: (event: any) => void
|
|
155
|
+
/** 처리 시작 (보낸 직후 한 번) — 모든 자리의 미디어 종류·예상 크기·동영상 포스터. ProcessingStartEvent 참고 */
|
|
156
|
+
onProcessingStart?: (event: ProcessingStartEvent) => void
|
|
64
157
|
/**
|
|
65
158
|
* 갤러리 아래에 첨부 메뉴(갤러리 / 위치 / 파일)를 보여줍니다. 기본 false.
|
|
66
159
|
* 위치 탭: iOS 는 Apple 지도(키 필요 없음), Android 는 Google 지도 — 앱의 AndroidManifest 에
|
|
@@ -266,12 +359,29 @@ export interface MapViewerOptions {
|
|
|
266
359
|
sourceImageUri?: string
|
|
267
360
|
/** 말풍선 이미지 라운드 (dp/pt, 기본 12) */
|
|
268
361
|
sourceCornerRadius?: number
|
|
362
|
+
/**
|
|
363
|
+
* 닫기 애니메이션이 "시작"될 때 (durationMs = 닫기 애니메이션 길이). 프로미스는 다 닫힌 뒤에 끝나므로,
|
|
364
|
+
* 키보드 복귀처럼 닫힘과 동시에 하고 싶은 일은 여기서. Android 는 이 시점에 뷰어 창이 입력 포커스를 내려놓음
|
|
365
|
+
*/
|
|
366
|
+
onViewerWillClose?: (e: { durationMs: number }) => void
|
|
269
367
|
/** 마커·버튼 색 (기본: setDefaults 의 themeColor) */
|
|
270
368
|
themeColor?: string
|
|
271
369
|
languageCode?: LanguageCode
|
|
272
370
|
appearance?: Appearance
|
|
273
371
|
}
|
|
274
372
|
|
|
373
|
+
/** 뷰어의 동영상 페이지 정보 (ViewerOptions.videos) */
|
|
374
|
+
export interface ViewerVideo {
|
|
375
|
+
/** http(s) 또는 file:// */
|
|
376
|
+
uri: string
|
|
377
|
+
/** 초. 모르면 생략 (불러온 뒤 채움) */
|
|
378
|
+
duration?: number
|
|
379
|
+
/** 절대 경로(file:// 없음). 파일이 있으면 그걸로 재생, 없으면 스트리밍하고 다 받으면 이 경로에 저장 */
|
|
380
|
+
cacheFile?: string
|
|
381
|
+
width?: number
|
|
382
|
+
height?: number
|
|
383
|
+
}
|
|
384
|
+
|
|
275
385
|
export interface ViewerOptions {
|
|
276
386
|
images: string[]
|
|
277
387
|
placeholderImages?: string[]
|
|
@@ -281,6 +391,12 @@ export interface ViewerOptions {
|
|
|
281
391
|
* (앱이 캐시를 한 곳에서 관리하기 위한 옵션 — 항목이 null/빈 문자열이면 해당 사진은 기존 방식)
|
|
282
392
|
*/
|
|
283
393
|
cacheFiles?: (string | null)[]
|
|
394
|
+
/**
|
|
395
|
+
* images 와 같은 순서. 항목이 있으면 그 페이지는 동영상 (null/생략 = 사진) — cacheFiles 와 같은 "평행 배열".
|
|
396
|
+
* 동영상 페이지에서 images[i] 는 포스터(썸네일) 이미지로 쓰입니다 (열기·닫기 애니메이션, 로딩 중 표시).
|
|
397
|
+
* 지원: iOS 뷰어, Android 는 useOverlayViewer: true 일 때 (그 외 Android 뷰어는 포스터만 보임)
|
|
398
|
+
*/
|
|
399
|
+
videos?: (ViewerVideo | null)[]
|
|
284
400
|
initialIndex?: number
|
|
285
401
|
themeColor?: string // Added themeColor for Android
|
|
286
402
|
/**
|
|
@@ -414,6 +530,14 @@ export interface MediaGalleryItem {
|
|
|
414
530
|
isMine?: boolean
|
|
415
531
|
width?: number
|
|
416
532
|
height?: number
|
|
533
|
+
/** 생략 = 'image'. 'video' 면 imageUri/thumbUri 는 포스터, 재생은 videoUri */
|
|
534
|
+
mediaType?: 'image' | 'video'
|
|
535
|
+
/** 동영상 원본 (http(s) 또는 file://) */
|
|
536
|
+
videoUri?: string
|
|
537
|
+
/** 동영상 캐시 경로 (ViewerVideo.cacheFile 과 같은 의미) */
|
|
538
|
+
videoCacheFile?: string
|
|
539
|
+
/** 초 — 그리드 칸의 "▶ 0:12" 배지 */
|
|
540
|
+
duration?: number
|
|
417
541
|
}
|
|
418
542
|
|
|
419
543
|
export interface MediaGalleryDeleteOption {
|
|
@@ -534,3 +658,52 @@ export interface MediaGalleryRemovePayload {
|
|
|
534
658
|
galleryId: string
|
|
535
659
|
groupIds: string[]
|
|
536
660
|
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* AI 기능 (갤러리의 AI 생성 · 편집 화면의 AI 편집) 설정과 잔액. `AttachmentKit.setAIConfig` 로 넘기며, 넘긴 값만 바뀝니다.
|
|
664
|
+
* 앱 서버가 진짜 장부이고 모듈은 화면 표시·사용 가능 판단용 사본을 들고 있습니다.
|
|
665
|
+
* 사용 순서: 구독자의 남은 무료 횟수(freeCount, 예: 월 30회) → 포인트(points, 1회 costPerUse) → 모자라면 충전 안내(onAIChargeRequired).
|
|
666
|
+
* 성공한 요청만 차감되고(실패·취소·안전 차단은 무료), 차감할 때마다 onAIUsage 가 옵니다.
|
|
667
|
+
*/
|
|
668
|
+
export interface AIConfig {
|
|
669
|
+
/** AI 기능 on/off. 기본 false — 꺼져 있으면 갤러리·편집 화면에 AI 버튼이 보이지 않음 */
|
|
670
|
+
enabled?: boolean
|
|
671
|
+
/** 현재 포인트 */
|
|
672
|
+
points?: number
|
|
673
|
+
/** 구독자의 남은 무료 횟수 (예: 월 30회 중 남은 것 — 기간·횟수는 앱/서버가 정함). 0 보다 크면 포인트보다 먼저 씀 */
|
|
674
|
+
freeCount?: number
|
|
675
|
+
/** 1회 차감 포인트. 0 이면 과금 없음 (무제한) */
|
|
676
|
+
costPerUse?: number
|
|
677
|
+
/** 화질 옵션 "고화질(1K)" 1회 차감 포인트. 생략/null 이면 costPerUse */
|
|
678
|
+
highQualityCost?: number | null
|
|
679
|
+
/** 운영: 앱 서버(프록시) 주소. 모듈이 Gemini generateContent 본문을 이 주소로 보내고 서버가 키를 붙여 전달 (헤더 X-AI-Model = 모델 이름) */
|
|
680
|
+
endpoint?: string | null
|
|
681
|
+
/** endpoint 로 보낼 때 붙일 헤더 (예: { Authorization: "Bearer …" }) */
|
|
682
|
+
headers?: Record<string, string>
|
|
683
|
+
/** 테스트 전용 Gemini 키 (앱에 넣어 배포하지 말 것). endpoint 가 있으면 무시 */
|
|
684
|
+
apiKey?: string | null
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/** AI 를 한 번 성공적으로 썼을 때 (서버에 차감을 반영하는 데 사용) */
|
|
688
|
+
export interface AIUsageEvent {
|
|
689
|
+
/** "generate" = 갤러리 AI 생성, "edit" = 편집 화면 AI 편집 */
|
|
690
|
+
kind: "generate" | "edit"
|
|
691
|
+
/** 무료 횟수를 썼으면 true (이때 pointsDeducted = 0) */
|
|
692
|
+
usedFreeCount: boolean
|
|
693
|
+
/** 이번에 차감한 포인트 */
|
|
694
|
+
pointsDeducted: number
|
|
695
|
+
/** 고화질(1K)로 만들었는지 */
|
|
696
|
+
highQuality: boolean
|
|
697
|
+
/** 차감 후 모듈이 들고 있는 값 */
|
|
698
|
+
points: number
|
|
699
|
+
freeCount: number
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/** 포인트가 모자라 사용자가 '충전하기'를 눌렀을 때 (모듈은 AI 화면을 닫음 → 앱이 충전 화면으로 안내) */
|
|
703
|
+
export interface AIChargeRequiredEvent {
|
|
704
|
+
kind: "generate" | "edit"
|
|
705
|
+
/** 1회에 필요한 포인트 */
|
|
706
|
+
required: number
|
|
707
|
+
points: number
|
|
708
|
+
freeCount: number
|
|
709
|
+
}
|