react-native-attachment-kit 1.0.2 → 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 +121 -0
- package/README.md +446 -206
- package/android/build.gradle +17 -0
- package/android/src/main/libs/rnattachmentkit-release.aar +0 -0
- package/android/src/newarch/java/com/rnattachmentkit/RNAttachmentKitModule.kt +587 -22
- package/android/src/oldarch/java/com/rnattachmentkit/RNAttachmentKitModule.kt +592 -21
- package/ios/RNAttachmentKit.m +62 -0
- package/ios/RNAttachmentKit.swift +725 -29
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Assets.car +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Headers/RNAttachmentKit-Swift.h +51 -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 +51876 -18835
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios.private.swiftinterface +310 -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 +310 -5
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/RNAttachmentKit +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_crop_reflect~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_crop_reflect~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_crop_rotate~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_crop_rotate~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_delete~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_delete~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_forward~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_forward~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_reward~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_reward~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_back~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_back~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_blur~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_blur~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_check_small~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_check_small~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_check~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_check~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_close~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_close~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_crop~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_crop~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_drawing~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_drawing~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_effect~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_effect~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_emoji~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_emoji~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_eraser~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_eraser~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_mosaic~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_mosaic~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_text~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_text~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_text_add~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_text_add~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/_CodeSignature/CodeDirectory +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/_CodeSignature/CodeResources +195 -63
- 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 +102 -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 +51876 -18835
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +310 -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 +310 -5
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.abi.json +51876 -18835
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +310 -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 +310 -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/ToolbarIcons/ic_crop_reflect~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_crop_reflect~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_crop_rotate~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_crop_rotate~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_delete~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_delete~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_forward~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_forward~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_reward~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_reward~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_back~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_back~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_blur~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_blur~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_check_small~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_check_small~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_check~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_check~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_close~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_close~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_crop~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_crop~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_drawing~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_drawing~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_effect~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_effect~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_emoji~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_emoji~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_eraser~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_eraser~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_mosaic~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_mosaic~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_text~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_text~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_text_add~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_text_add~light.webp +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 +205 -73
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_crop_reflect~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_crop_reflect~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_crop_rotate~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_crop_rotate~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_drawing_delete~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_drawing_delete~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_drawing_forward~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_drawing_forward~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_drawing_reward~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_drawing_reward~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_back~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_back~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_blur~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_blur~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_check_small~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_check_small~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_check~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_check~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_close~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_close~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_crop~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_crop~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_drawing~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_drawing~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_effect~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_effect~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_emoji~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_emoji~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_eraser~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_eraser~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_mosaic~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_mosaic~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_text~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_text~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_text_add~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_text_add~light.webp +0 -0
- package/package.json +1 -1
- package/react-native-attachment-kit.podspec +1 -1
- package/src/NativeAttachmentKit.ts +15 -0
- package/src/index.d.ts +5 -56
- package/src/index.ts +301 -37
- package/src/types.ts +380 -11
package/README.md
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
# React Native Attachment Kit 🚀
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A fully native attachment toolkit for React Native chat apps: a Telegram-style photo picker with an attach menu (Gallery / Location / File), an image editor, a sticky image viewer, a "all photos" media gallery, and everything a **location message** needs — map thumbnails, reverse geocoding and a full-screen map viewer.
|
|
4
|
+
|
|
5
|
+
---
|
|
4
6
|
|
|
5
7
|
## ✨ Features
|
|
6
|
-
|
|
7
|
-
- **
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
8
|
+
|
|
9
|
+
- **Pure native UI** (Swift / Kotlin) — smooth on both platforms, New Architecture (Turbo Modules) and old architecture supported.
|
|
10
|
+
- **Photo picker** — bottom-sheet gallery, single or multi selection, albums, camera cell, optional editor step, profile (1:1) crop mode.
|
|
11
|
+
- **Attach menu** — Gallery / **Location** / **File** tabs under the gallery (Telegram style).
|
|
12
|
+
- Location: map with a center pin, "send my current location", place search, "Share my location" switch, map snapshot image.
|
|
13
|
+
- File: system file picker, files copied into the app's temp folder.
|
|
14
|
+
- **Image editor** — crop, rotate, filters, drawing, mosaic, text, caption.
|
|
15
|
+
- **Image viewer** — paging, pinch-to-zoom, thumbnails, save / share / delete actions, and a **sticky** open/close animation from the tapped thumbnail.
|
|
16
|
+
- **Location messages**
|
|
17
|
+
- `renderLocationThumbnail` — draws a chat-bubble map thumbnail on the receiving device from coordinates only (cached).
|
|
18
|
+
- `reverseGeocode` — coordinates → address in the app's language with the device geocoder (free, cached, throttle-safe).
|
|
19
|
+
- `openMapViewer` — full-screen map with the sender's marker, distance · address card and Directions; the bubble image grows into the map and shrinks back on close.
|
|
20
|
+
- **Media gallery** — native grid for thousands of photos (3×3 / 4×4 / 5×5), date scroller, opens the viewer from a cell.
|
|
21
|
+
- **Theming** — theme color, light / dark / system appearance, 59 UI languages.
|
|
14
22
|
|
|
15
23
|
---
|
|
16
24
|
|
|
@@ -30,308 +38,540 @@ A highly customizable, high-performance, and purely native image picker and edit
|
|
|
30
38
|
|
|
31
39
|
---
|
|
32
40
|
|
|
41
|
+
## 📋 Requirements
|
|
42
|
+
|
|
43
|
+
| | |
|
|
44
|
+
|---|---|
|
|
45
|
+
| iOS | 15.1+ (Apple Maps / MapKit — no API key) |
|
|
46
|
+
| Android | minSdk 24, compileSdk 34. Google Maps — **the app supplies its own key** (see below) |
|
|
47
|
+
| React Native | New Architecture (Turbo Modules) or old architecture |
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
33
51
|
## 📦 Installation
|
|
34
52
|
|
|
35
53
|
```bash
|
|
36
|
-
# using yarn
|
|
37
54
|
yarn add react-native-attachment-kit
|
|
38
|
-
|
|
39
|
-
# or using npm
|
|
55
|
+
# or
|
|
40
56
|
npm install react-native-attachment-kit
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
If you are using iOS, run pod install:
|
|
44
57
|
|
|
45
|
-
```bash
|
|
46
58
|
cd ios && pod install
|
|
47
59
|
```
|
|
48
60
|
|
|
49
|
-
|
|
50
|
-
|
|
61
|
+
Rebuild the app after installing or updating (the module ships compiled native code — a Metro reload is not enough).
|
|
62
|
+
|
|
63
|
+
### iOS — `Info.plist`
|
|
51
64
|
|
|
52
65
|
```xml
|
|
53
66
|
<key>NSCameraUsageDescription</key>
|
|
54
|
-
<string>We need access to your camera to take photos
|
|
67
|
+
<string>We need access to your camera to take photos.</string>
|
|
55
68
|
<key>NSPhotoLibraryUsageDescription</key>
|
|
56
69
|
<string>We need access to your photo library to select images.</string>
|
|
57
70
|
<key>NSPhotoLibraryAddUsageDescription</key>
|
|
58
|
-
<string>We need access to save
|
|
71
|
+
<string>We need access to save photos to your library.</string>
|
|
72
|
+
<!-- Attach menu → Location ("send my current location", my-location dot) -->
|
|
73
|
+
<key>NSLocationWhenInUseUsageDescription</key>
|
|
74
|
+
<string>We use your location to share it in a chat.</string>
|
|
59
75
|
```
|
|
60
76
|
|
|
77
|
+
### Android — Google Maps key
|
|
78
|
+
|
|
79
|
+
The Location tab, the map viewer and `renderLocationThumbnail` use Google Maps. The library ships **no key**; add your own to the app's `AndroidManifest.xml`:
|
|
80
|
+
|
|
81
|
+
```xml
|
|
82
|
+
<application>
|
|
83
|
+
<meta-data android:name="com.google.android.geo.API_KEY" android:value="YOUR_KEY"/>
|
|
84
|
+
</application>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Enable on the key: **Maps SDK for Android**, and **Places API (New)** for place search in the Location tab (Autocomplete (New) + Place Details Essentials only; on failure it falls back to the device `Geocoder`). Addresses shown while dragging the map and `reverseGeocode` use only the free device `Geocoder`.
|
|
88
|
+
|
|
89
|
+
### Android — permissions merged from the library
|
|
90
|
+
|
|
91
|
+
`INTERNET`, `ACCESS_NETWORK_STATE`, `READ_MEDIA_IMAGES` / `READ_EXTERNAL_STORAGE`, `WRITE_EXTERNAL_STORAGE` (Android 9 and below, viewer "save"), `CAMERA`, `ACCESS_FINE_LOCATION`, `ACCESS_COARSE_LOCATION`. Remove any you don't use with `tools:node="remove"`. Runtime prompts are shown by the module when needed. Dependencies `play-services-maps:19.0.0` and `play-services-location:21.3.0` come with the library.
|
|
92
|
+
|
|
61
93
|
---
|
|
62
94
|
|
|
63
|
-
## 🛠
|
|
64
|
-
|
|
65
|
-
```
|
|
66
|
-
import AttachmentKit from 'react-native-attachment-kit'
|
|
67
|
-
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const handleOpenPicker = async () => {
|
|
78
|
-
try {
|
|
79
|
-
const result = await AttachmentKit.openGallery({
|
|
80
|
-
maxSelection: 10,
|
|
81
|
-
themeColor: '#FF6B35',
|
|
82
|
-
enableEditor: true,
|
|
83
|
-
profileMode: false,
|
|
84
|
-
});
|
|
85
|
-
console.log('Selected Images:', result);
|
|
86
|
-
} catch (error) {
|
|
87
|
-
console.error(error);
|
|
88
|
-
}
|
|
89
|
-
};
|
|
95
|
+
## 🛠 Quick start
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import AttachmentKit from 'react-native-attachment-kit'
|
|
99
|
+
|
|
100
|
+
// Once at app start
|
|
101
|
+
await AttachmentKit.init('YOUR_LICENSE_KEY', { // '' = evaluation (editor watermark)
|
|
102
|
+
languageCode: 'ko', // any app language code; see Localization
|
|
103
|
+
themeColor: '#10b981',
|
|
104
|
+
appearance: 'system', // 'system' | 'light' | 'dark'
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
// Pick photos
|
|
108
|
+
const images = await AttachmentKit.openGallery({ maxSelection: 10, enableEditor: true })
|
|
90
109
|
```
|
|
91
110
|
|
|
92
|
-
###
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const openViewer = () => {
|
|
101
|
-
imageRef.current?.measure((x, y, width, height, pageX, pageY) => {
|
|
102
|
-
AttachmentKit.openViewer({
|
|
103
|
-
images: ['https://example.com/photo1.jpg', 'https://example.com/photo2.jpg'],
|
|
104
|
-
initialIndex: 0,
|
|
105
|
-
themeColor: '#FF6B35',
|
|
106
|
-
// Sticky transition: pass the thumbnail's screen coordinates
|
|
107
|
-
sourceRect: { x: pageX, y: pageY, width, height },
|
|
108
|
-
sourceBorderRadius: 12,
|
|
109
|
-
sourceBorderCorners: ['topLeft', 'topRight', 'bottomLeft', 'bottomRight'],
|
|
110
|
-
// Android: use overlay viewer to avoid AppState blips
|
|
111
|
-
useOverlayViewer: true,
|
|
112
|
-
onPageSelected: (index) => {
|
|
113
|
-
console.log('Viewing image at index:', index);
|
|
114
|
-
},
|
|
115
|
-
onViewerOpened: () => {
|
|
116
|
-
// Android only: called when the open animation finishes.
|
|
117
|
-
// Safe place to run background scroll corrections.
|
|
118
|
-
console.log('Viewer fully open');
|
|
119
|
-
},
|
|
120
|
-
onViewerWillClose: () => {
|
|
121
|
-
console.log('Viewer is closing');
|
|
122
|
-
},
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
};
|
|
111
|
+
### Global defaults
|
|
112
|
+
|
|
113
|
+
`init` options become defaults for every screen. Change them any time (e.g. when the user changes the app theme or language) — only the values you pass change:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
AttachmentKit.setDefaults({ appearance: 'dark', languageCode: 'en' })
|
|
117
|
+
AttachmentKit.getDefaults() // { languageCode, themeColor, appearance }
|
|
126
118
|
```
|
|
127
119
|
|
|
120
|
+
Every `open*` / `render*` call also accepts `themeColor`, `languageCode` and `appearance` to override per call.
|
|
121
|
+
|
|
122
|
+
### Localization
|
|
123
|
+
|
|
124
|
+
The UI is translated into 59 languages (ISO 639-1):
|
|
125
|
+
`af ar be bg bn ca cs cy da de el en eo es et fa fi fr ga gl gu he hi hr ht hu id is it ja ka kn ko lt lv mk mr ms mt nl no pl pt ro ru sk sl sq sv sw ta te th tl tr uk ur vi zh`
|
|
126
|
+
|
|
127
|
+
- Region/script variants are normalized for the UI (`"pt-BR"` → `pt`, `"zh-Hans"` → `zh`, `"iw"` → `he`, `"nb"` → `no`, `"fil"` → `tl`); unsupported codes fall back to English. `zh` is Simplified Chinese.
|
|
128
|
+
- Addresses (`reverseGeocode`, map viewer) use the code **as given** (e.g. `'zh-Hant'`), since geocoders support more locales than the UI.
|
|
129
|
+
- `normalizeLanguageCode()` and `SUPPORTED_LANGUAGE_CODES` are exported.
|
|
130
|
+
|
|
128
131
|
---
|
|
129
132
|
|
|
130
|
-
## 📖 API
|
|
133
|
+
## 📖 API reference
|
|
131
134
|
|
|
132
|
-
###
|
|
135
|
+
### Photo picker
|
|
133
136
|
|
|
134
|
-
|
|
137
|
+
#### `openGallery(options?: GalleryOptions): Promise<ImageResult[]>`
|
|
135
138
|
|
|
136
|
-
|
|
139
|
+
Opens the gallery bottom sheet. Resolves with the picked images (`[]` when the user sent a location or files from the attach menu, or closed it). Calling it while a gallery is open rejects with `E_ALREADY_OPEN`.
|
|
140
|
+
|
|
141
|
+
| Option | Type | Default | Description |
|
|
142
|
+
|---|---|---|---|
|
|
143
|
+
| `maxSelection` | `number` | `1` | Maximum number of photos. |
|
|
144
|
+
| `selectionMode` | `'single' \| 'multi'` | auto | `'single'`: tap returns immediately (with editor: tap → edit → send). `'multi'`: numbered checks + Done, even with `maxSelection: 1`. Omitted: single when `autoCloseOnSelect` or `maxSelection <= 1`. |
|
|
145
|
+
| `autoCloseOnSelect` | `boolean` | `false` | Single mode: close right after the tap. |
|
|
146
|
+
| `enableEditor` | `boolean` | `false` | Open the editor after selection. |
|
|
147
|
+
| `profileMode` | `boolean` | `false` | 1:1 (circle) crop for profile photos. |
|
|
148
|
+
| `maxWidth` / `maxHeight` | `number` | `1024` | Output images are scaled down to fit. |
|
|
149
|
+
| `outputFormat` | `'webp' \| 'jpg' \| 'png'` | `'webp'` | Output file format. (Not in the TS type yet — pass it with a cast.) |
|
|
150
|
+
| `themeColor` / `languageCode` / `appearance` | | defaults | Per-call overrides. |
|
|
151
|
+
| `doneButtonText` / `allItemsText` / `selectedItemsText` / `recentsAlbumText` | `string` | translated | Label overrides. |
|
|
152
|
+
| `asyncProcessing` | `boolean` | `false` | Event-driven sending: each item is delivered through `onImageProcessed` as soon as it is ready (the Promise still resolves with everything at the end), and videos show no progress HUD — draw progress in your own UI. See [Sending progress](#sending-progress-events). |
|
|
153
|
+
| `onSelectionChange` | `(e: SelectionChangeEvent) => void` | — | `{ selectedCount, maxSelection }` on every change. |
|
|
154
|
+
| `onProcessingStart` | `(e: ProcessingStartEvent) => void` | — | Once, right after Send, before any processing. |
|
|
155
|
+
| `onImageProcessed` | `(e) => void` | — | Per item `{ index, total, image }` as soon as it is ready. |
|
|
156
|
+
| `mediaType` | `'photo' \| 'all' \| 'video'` | `'photo'` | `'all'` mixes photos and videos. |
|
|
157
|
+
| `video` | `VideoOptions` | — | `maxDurationSec`, `maxFileSizeMB`, `qualities`, `defaultQuality`, `codec`, `onProgress`, `showProgressHUD`. |
|
|
158
|
+
| `openDuration` / `closeDuration` | `number` (ms) | native | Animation durations. |
|
|
159
|
+
| `showAttachMenu` … | | | Attach menu options — see [Attach menu](#attach-menu-location--file). |
|
|
160
|
+
|
|
161
|
+
`quality` is accepted for compatibility but currently ignored (output quality is fixed).
|
|
162
|
+
|
|
163
|
+
#### `ImageResult`
|
|
164
|
+
|
|
165
|
+
| Field | Type | Description |
|
|
137
166
|
|---|---|---|
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
141
|
-
| `
|
|
167
|
+
| `originalUri` | `string` | Original asset (`ph://` or `file://`). |
|
|
168
|
+
| `originalFileUri?` | `string` | Original-resolution file (`file://`). |
|
|
169
|
+
| `originalWidth` / `originalHeight` | `number` | Original size. |
|
|
170
|
+
| `uri?` | `string` | Resized / edited output (`file://`). |
|
|
171
|
+
| `width?` / `height?` | `number` | Output size. |
|
|
172
|
+
| `type` | `string` | MIME type. |
|
|
173
|
+
| `caption?` | `string` | Caption typed in the editor. |
|
|
174
|
+
| `fileName?` / `fileExtension?` / `fileSize?` | | Output file info (bytes). |
|
|
175
|
+
|
|
176
|
+
#### `closeGallery(): Promise<boolean>`
|
|
177
|
+
Closes the picker from code. `true` if it was open.
|
|
178
|
+
|
|
179
|
+
#### `addSelectionChangeListener(listener): () => void`
|
|
180
|
+
Selection events outside `openGallery`; returns an unsubscribe function.
|
|
142
181
|
|
|
143
|
-
|
|
182
|
+
#### Sending progress (events)
|
|
144
183
|
|
|
145
|
-
|
|
184
|
+
A chat can show every sent item at once and fill it in as it finishes (photos are resized, videos are encoded), the same way for photos and videos:
|
|
185
|
+
|
|
186
|
+
1. **`onProcessingStart`** `{ total, items }`: once, right after Send. Each item has `index`, `mediaType` (`'image' | 'video'`), `originalUri`, and the expected output `width` / `height` (for the aspect ratio). Videos also have `duration` (seconds, after trim) and `thumbnailUri` / `thumbnailWidth` / `thumbnailHeight` (a poster jpg with crop and drawing applied). Create the placeholders here.
|
|
187
|
+
2. **`onVideoProgress`** `{ index, total, progress }`: video encoding 0…1 (at most every 1% / 0.1 s). Draw a progress ring on that placeholder.
|
|
188
|
+
3. **`onImageProcessed`** `{ index, total, image }`: the item is ready. Replace the placeholder with `image.uri`. A video the app cancelled comes as `{ error: 'CANCELLED', cancelled: true }`; a failed one has `error`.
|
|
146
189
|
|
|
147
190
|
```ts
|
|
148
|
-
|
|
191
|
+
const sub = emitter.addListener('onProcessingStart', ({ items }) => addPlaceholders(items))
|
|
192
|
+
const prog = emitter.addListener('onVideoProgress', ({ index, progress }) => setProgress(index, progress))
|
|
193
|
+
const done = emitter.addListener('onImageProcessed', ({ index, image }) => fillPlaceholder(index, image))
|
|
194
|
+
await AttachmentKit.openGallery({ mediaType: 'all', asyncProcessing: true, enableEditor: true })
|
|
195
|
+
// or pass onProcessingStart / onImageProcessed / video.onProgress as options
|
|
149
196
|
```
|
|
150
197
|
|
|
198
|
+
- **HUD:** `video.showProgressHUD` sets the full-screen progress HUD. The default is `true`, but `false` when `asyncProcessing: true`.
|
|
199
|
+
- **Cancel:** without the HUD, cancel from your UI with `AttachmentKit.cancelVideoProcessing(index?, processingId?)`. No `index` cancels all remaining videos; no `processingId` targets the latest send. The rest keep going, and the cancelled item is reported as above. It returns `false` when nothing is in progress.
|
|
200
|
+
- **Sending again while a video encodes:** the gallery can be opened again as soon as `onProcessingStart` arrives; you don't have to wait for the Promise, which still resolves with everything at the end. Every event carries `processingId`, so an app that listens with global listeners should use it to tell sends apart (`index` restarts at 0 for each send). Callbacks passed in the options are already filtered to their own send.
|
|
201
|
+
|
|
151
202
|
---
|
|
152
203
|
|
|
153
|
-
###
|
|
204
|
+
### Attach menu (Location / File)
|
|
154
205
|
|
|
155
|
-
|
|
206
|
+
```ts
|
|
207
|
+
const images = await AttachmentKit.openGallery({
|
|
208
|
+
showAttachMenu: true,
|
|
209
|
+
captureLocationImage: false, // send coordinates only; receivers draw the thumbnail
|
|
210
|
+
onLocationSelected: (location) => sendLocationMessage(location),
|
|
211
|
+
onFilesSelected: (files, failed) => sendFiles(files),
|
|
212
|
+
})
|
|
213
|
+
// images is [] when a location or files were sent
|
|
214
|
+
```
|
|
156
215
|
|
|
157
216
|
| Option | Type | Default | Description |
|
|
158
217
|
|---|---|---|---|
|
|
159
|
-
| `
|
|
160
|
-
| `
|
|
161
|
-
| `
|
|
162
|
-
| `
|
|
163
|
-
| `
|
|
164
|
-
| `
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
218
|
+
| `showAttachMenu` | `boolean` | `false` | Show the Gallery / Location / File bar. |
|
|
219
|
+
| `onLocationSelected` | `(location: AttachLocationResult) => void` | — | Location sent from the Location tab. |
|
|
220
|
+
| `onFilesSelected` | `(files: AttachFileResult[], failed?: AttachFileFailure[]) => void` | — | Files sent from the File tab. `failed` lists files that could not be copied (omitted when all succeeded). |
|
|
221
|
+
| `captureLocationImage` | `boolean` | `true` | `false` = no map snapshot; the location is sent immediately (no `image*` fields, everything else is returned). |
|
|
222
|
+
| `attachMenuSinkIntoNav` | `number` (dp) | `12` | **Android** — how far the bar sinks into the navigation-bar area (`0` = right above it). |
|
|
223
|
+
| `attachMenuMinBottomMargin` | `number` (dp) | `8` | **Android** — minimum gap to the screen bottom. |
|
|
224
|
+
|
|
225
|
+
Both callbacks fire before `openGallery` resolves; their listeners are removed when it settles.
|
|
226
|
+
|
|
227
|
+
**Location tab**
|
|
228
|
+
- Map with a center pin: drag the map to choose a point (address from the device geocoder), tap a place, or "Send my current location" (accuracy shown).
|
|
229
|
+
- Search: iOS Apple Maps search; Android Places Autocomplete (New) with a session token + Place Details Essentials (location only) on pick, falling back to the device `Geocoder`.
|
|
230
|
+
- Full-screen map, map type (standard / hybrid), my-location button, light/dark map style.
|
|
231
|
+
- **"Share my location"** switch (off by default, remembered on the device): only affects the snapshot image — my-location dot drawn and the area widened so both points fit.
|
|
232
|
+
|
|
233
|
+
**`AttachLocationResult`**
|
|
234
|
+
|
|
235
|
+
| Field | Type | Description |
|
|
236
|
+
|---|---|---|
|
|
237
|
+
| `latitude`, `longitude` | `number` | Sent point. |
|
|
238
|
+
| `accuracy?` | `number` (m) | Only when the current location was sent. |
|
|
239
|
+
| `title?` | `string` | Place name when a place was picked. |
|
|
240
|
+
| `address?` | `string` | Address (no country name). |
|
|
241
|
+
| `isCurrentLocation` | `boolean` | `false` = dragged pin or place. |
|
|
242
|
+
| `markerType?` | `'current' \| 'pin' \| 'place'` | Marker icon — pass it to the thumbnail / map viewer so they draw the same marker. |
|
|
243
|
+
| `placeCategory?` | `string` | **iOS** — `MKPointOfInterestCategory` of a place (category icon). |
|
|
244
|
+
| `imageSpanMeters?` | `number` | Real distance covered by the image width — pass it as `spanMeters` to keep the same scale. Returned even with `captureLocationImage: false`. |
|
|
245
|
+
| `imageUri?`, `imagePath?`, `imageWidth?`, `imageHeight?` | | Map snapshot: portrait **480×1040 webp** (9:19.5), the point exactly centered, marker drawn. Omitted when `captureLocationImage: false` or the capture fails. |
|
|
246
|
+
| `includesMyLocation?` | `boolean` | Whether my-location was drawn into the snapshot ("Share my location"). |
|
|
247
|
+
| `currentLatitude?`, `currentLongitude?`, `currentAccuracy?` | `number` | The device's own location at send time (when known), independent of what was sent. |
|
|
248
|
+
|
|
249
|
+
**`AttachFileResult`** — `{ uri (file://), path, name, size (bytes), mimeType }`
|
|
250
|
+
**`AttachFileFailure`** — `{ name, error }`
|
|
251
|
+
|
|
252
|
+
**File tab** — iOS Files picker, Android system picker (`ACTION_OPEN_DOCUMENT`, multiple, any type). Files are **copied** to the app's temp folder; copy or upload them promptly (see [Caches & temp files](#-caches--temp-files)).
|
|
173
253
|
|
|
174
254
|
---
|
|
175
255
|
|
|
176
|
-
###
|
|
256
|
+
### Image editor
|
|
177
257
|
|
|
178
|
-
|
|
258
|
+
#### `openEditor(options: EditorOptions): Promise<ImageResult>`
|
|
179
259
|
|
|
180
260
|
| Option | Type | Description |
|
|
181
261
|
|---|---|---|
|
|
182
|
-
| `uri` | `string` |
|
|
183
|
-
| `editedFileUri
|
|
184
|
-
| `
|
|
185
|
-
| `
|
|
186
|
-
| `openDuration` / `closeDuration` | `number` |
|
|
262
|
+
| `uri` | `string` | Image to edit (`originalUri` from a picker result). |
|
|
263
|
+
| `editedFileUri?` | `string` | A previous edit to continue from. |
|
|
264
|
+
| `maxWidth` / `maxHeight` | `number` | Output bounds. |
|
|
265
|
+
| `themeColor` / `appearance` | | Per-call overrides. |
|
|
266
|
+
| `openDuration` / `closeDuration` | `number` (ms) | Animation durations. |
|
|
267
|
+
|
|
268
|
+
Rejects with `E_ALREADY_OPEN` while an editor is open. Without a license key the editor output carries a watermark.
|
|
187
269
|
|
|
188
270
|
---
|
|
189
271
|
|
|
190
|
-
###
|
|
272
|
+
### Image viewer
|
|
191
273
|
|
|
192
|
-
|
|
274
|
+
#### `openViewer(options: ViewerOptions): Promise<void>`
|
|
193
275
|
|
|
194
|
-
|
|
276
|
+
```ts
|
|
277
|
+
const ref = useRef<View>(null)
|
|
278
|
+
|
|
279
|
+
ref.current?.measure((x, y, width, height, pageX, pageY) => {
|
|
280
|
+
AttachmentKit.openViewer({
|
|
281
|
+
images: ['https://example.com/1.jpg', 'https://example.com/2.jpg'],
|
|
282
|
+
initialIndex: 0,
|
|
283
|
+
sourceRect: { x: pageX, y: pageY, width, height }, // sticky open/close
|
|
284
|
+
sourceBorderRadius: 12,
|
|
285
|
+
useOverlayViewer: true, // Android: recommended
|
|
286
|
+
onPageSelected: async (index) => {
|
|
287
|
+
const rect = await measureThumbnail(index)
|
|
288
|
+
if (rect) AttachmentKit.updateSourceRect(rect) // close back into the right thumbnail
|
|
289
|
+
},
|
|
290
|
+
})
|
|
291
|
+
})
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**Basic**
|
|
195
295
|
|
|
196
296
|
| Option | Type | Default | Description |
|
|
197
297
|
|---|---|---|---|
|
|
198
|
-
| `images` | `string[]` | **required** |
|
|
199
|
-
| `
|
|
200
|
-
| `
|
|
201
|
-
| `
|
|
202
|
-
| `title` | `string` | — |
|
|
203
|
-
| `
|
|
298
|
+
| `images` | `string[]` | **required** | Image URIs (`https://`, `file://`, `ph://`). |
|
|
299
|
+
| `initialIndex` | `number` | `0` | First page. |
|
|
300
|
+
| `placeholderImages` | `string[]` | — | Low-res placeholders shown while the full image loads (same order). |
|
|
301
|
+
| `cacheFiles` | `(string \| null)[]` | — | Local cache paths per image: if the file exists it is shown without network, otherwise the downloaded bytes are saved there. |
|
|
302
|
+
| `title` | `string` | — | Toolbar title. |
|
|
303
|
+
| `themeColor` / `appearance` / `languageCode` | | defaults | Per-call overrides. |
|
|
304
|
+
| `animationType` / `closeAnimationType` | `'zoom' \| 'fade' \| 'slide'` | `zoom` with `sourceRect`, else `slide` | Open / close animation. |
|
|
305
|
+
| `openDuration` / `closeDuration` | `number` (ms) | native | Animation durations. |
|
|
204
306
|
|
|
205
|
-
|
|
307
|
+
**Sticky transition**
|
|
206
308
|
|
|
207
|
-
|
|
309
|
+
| Option | Type | Description |
|
|
310
|
+
|---|---|---|
|
|
311
|
+
| `sourceRect` | `SourceRect` | On-screen rect of the tapped thumbnail (`measure` page coordinates). |
|
|
312
|
+
| `sourceBorderRadius` | `number` | Thumbnail corner radius (animates to 0 and back). |
|
|
313
|
+
| `sourceBorderCorners` | `('topLeft' \| 'topRight' \| 'bottomLeft' \| 'bottomRight')[]` | Which corners are rounded (chat-bubble groups). `[]` = none. |
|
|
314
|
+
| `sourceBackgroundColor` | `string` | Background behind the thumbnail during the animation. |
|
|
315
|
+
| `hideSourceImage` | `boolean` | A native mask hides the source thumbnail while the viewer is open. |
|
|
316
|
+
|
|
317
|
+
**Actions**
|
|
208
318
|
|
|
209
319
|
| Option | Type | Description |
|
|
210
320
|
|---|---|---|
|
|
211
|
-
| `
|
|
212
|
-
| `
|
|
213
|
-
| `
|
|
214
|
-
| `
|
|
215
|
-
| `
|
|
321
|
+
| `showActionButtons` | `boolean` (default `true`) | Save / share / delete buttons in the top bar. |
|
|
322
|
+
| `saveAllText` / `saveOneText` / `shareAllText` / `shareOneText` / `actionCancelText` | `string` | Action-sheet labels for groups. |
|
|
323
|
+
| `deleteConfirmTitle` / `deleteConfirmMessage` / `deleteConfirmGroupMessage` / `deleteButtonText` | `string` | Delete-confirm texts (translated by default). |
|
|
324
|
+
| `deleteOptions` | `{ key, label, destructive? }[]` | Several delete choices (e.g. "Delete for everyone" / "Delete for me"); the chosen `key` comes back as `option`. |
|
|
325
|
+
| `onImageDeleted` | `({ index, url, option? }) => void` | The user confirmed delete. The module does not delete anything — the app does. |
|
|
216
326
|
|
|
217
|
-
|
|
327
|
+
**Events**
|
|
218
328
|
|
|
219
|
-
| Option |
|
|
220
|
-
|
|
221
|
-
| `onPageSelected
|
|
222
|
-
| `onViewerWillClose
|
|
223
|
-
| `onViewerOpened
|
|
329
|
+
| Option | Platform | Description |
|
|
330
|
+
|---|---|---|
|
|
331
|
+
| `onPageSelected(index)` | both | Page changed. |
|
|
332
|
+
| `onViewerWillClose()` | both | Close animation starts. |
|
|
333
|
+
| `onViewerOpened()` | Android | Open animation finished (safe moment for background scroll corrections). |
|
|
224
334
|
|
|
225
|
-
|
|
335
|
+
**Android viewer modes**
|
|
226
336
|
|
|
227
|
-
|
|
337
|
+
| Option | Description |
|
|
338
|
+
|---|---|
|
|
339
|
+
| (default) | Separate Activity — `AppState` briefly reports `background`. |
|
|
340
|
+
| `useDialogViewer: true` | DialogFragment — no `AppState` blip, but background RN layout changes may not render until it closes. |
|
|
341
|
+
| `useOverlayViewer: true` | ⭐ View on top of the host Activity's window — no `AppState` blip, background changes render in real time. Wins over `useDialogViewer`. |
|
|
228
342
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
343
|
+
#### `updateSourceRect(rect: SourceRect & { sourceBorderRadius?, sourceBorderCorners? }): Promise<void>`
|
|
344
|
+
Updates where the viewer closes to (e.g. after paging to another thumbnail or scrolling).
|
|
345
|
+
|
|
346
|
+
#### `injectImageCache(url: string, localPath: string): Promise<boolean>`
|
|
347
|
+
Seeds the viewer's image cache with a local file for a remote URL, so it opens instantly without network.
|
|
233
348
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
> - `useDialogViewer: true`: DialogFragment — no `AppState` blip, but background layout updates may be deferred.
|
|
237
|
-
> - `useOverlayViewer: true`: Overlay View — no `AppState` blip, background layout updates work in real time. ✅
|
|
349
|
+
#### `SourceRect`
|
|
350
|
+
`{ x: number, y: number, width: number, height: number }` — screen / window coordinates in dp (pt).
|
|
238
351
|
|
|
239
352
|
---
|
|
240
353
|
|
|
241
|
-
###
|
|
354
|
+
### Location messages
|
|
355
|
+
|
|
356
|
+
Recommended flow for a chat app that sends **coordinates only** (`captureLocationImage: false`):
|
|
357
|
+
|
|
358
|
+
1. **Sender**: `openGallery({ showAttachMenu: true, captureLocationImage: false, onLocationSelected })` → send `latitude`, `longitude`, `title?`, `markerType`, `placeCategory?`, `imageSpanMeters`, and `currentLatitude/Longitude` only if `includesMyLocation`.
|
|
359
|
+
2. **Receiver bubble**: `renderLocationThumbnail(...)` → show `uri` in the bubble (e.g. 170×128, `resizeMode="cover"`); `reverseGeocode(...)` → show the address in the app's language.
|
|
360
|
+
3. **Tap**: `openMapViewer({ ..., sourceRect, sourceImageUri: thumb.uri })` → the bubble image grows into the map.
|
|
361
|
+
|
|
362
|
+
```tsx
|
|
363
|
+
const bubbleRef = useRef<View>(null)
|
|
364
|
+
|
|
365
|
+
// bubble
|
|
366
|
+
const thumb = await AttachmentKit.renderLocationThumbnail({
|
|
367
|
+
latitude, longitude, markerType, placeCategory, spanMeters: imageSpanMeters,
|
|
368
|
+
width: 170, aspect: 'device', // screen-ratio image, shown cropped in a 4:3 bubble
|
|
369
|
+
maxWidth: 512, maxHeight: 1152, // sharp on 3× screens
|
|
370
|
+
})
|
|
371
|
+
const { address } = await AttachmentKit.reverseGeocode({ latitude, longitude })
|
|
372
|
+
|
|
373
|
+
// tap
|
|
374
|
+
bubbleRef.current?.measureInWindow((x, y, width, height) => {
|
|
375
|
+
AttachmentKit.openMapViewer({
|
|
376
|
+
latitude, longitude, title, markerType, placeCategory, spanMeters: imageSpanMeters,
|
|
377
|
+
sourceRect: { x, y, width, height },
|
|
378
|
+
sourceImageUri: thumb.uri, // pass the uri as returned
|
|
379
|
+
sourceCornerRadius: 16,
|
|
380
|
+
})
|
|
381
|
+
})
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
#### `renderLocationThumbnail(options: LocationThumbnailOptions): Promise<LocationThumbnailResult>`
|
|
385
|
+
|
|
386
|
+
Draws a map thumbnail on this device from the location info alone, in this device's theme, and caches it on disk.
|
|
387
|
+
|
|
388
|
+
| Option | Type | Description |
|
|
389
|
+
|---|---|---|
|
|
390
|
+
| `latitude`, `longitude` | `number` | **Required.** |
|
|
391
|
+
| `width` | `number` (dp/pt) | **Required.** Output px = width × screen scale (capped by `maxWidth/maxHeight`). |
|
|
392
|
+
| `height` / `aspect` | `number` / `'square' \| 'portrait' \| 'device'` | Height directly, or from the width: `square` 1:1, `portrait` 9:19.5 (same as the sent snapshot), `device` this screen's ratio (the image then fills the screen exactly in the map viewer). |
|
|
393
|
+
| `maxWidth` / `maxHeight` | `number` (px) | Scale down to fit. Both omitted → 512 × 512; only one given → the other is not limited. |
|
|
394
|
+
| `markerType`, `placeCategory` | | Marker icon (as `AttachLocationResult`). Android has no per-category icons. |
|
|
395
|
+
| `spanMeters` | `number` | Distance covered by the width (`imageSpanMeters`). Default 400. |
|
|
396
|
+
| `myLatitude`, `myLongitude` | `number` | Only if the sender shared their location (`includesMyLocation`): draws the dot and widens the area. |
|
|
397
|
+
| `themeColor`, `appearance` | | Marker color, light/dark map (`system` = current app/device mode). |
|
|
242
398
|
|
|
243
|
-
|
|
399
|
+
Result: `{ uri: 'file://…webp', path, width, height (px), cached }`.
|
|
244
400
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
401
|
+
- Cached by coordinates, output size, theme, marker, span, color and my-location; identical requests in flight are merged. A theme change renders once more, then that is cached too.
|
|
402
|
+
- Errors: `E_INVALID_ARGS`, `E_THUMBNAIL_FAILED` (no tiles — e.g. offline — or a 10 s timeout). Failures are not cached.
|
|
403
|
+
- The renderer also stores the same map without the marker; the map viewer uses it so the marker keeps the right size while the bubble grows into the map.
|
|
404
|
+
|
|
405
|
+
#### `reverseGeocode(options: ReverseGeocodeOptions): Promise<ReverseGeocodeResult>`
|
|
406
|
+
|
|
407
|
+
Coordinates → address with the **device geocoder** (iOS `CLGeocoder`, Android `Geocoder`) — free, no key.
|
|
408
|
+
|
|
409
|
+
```ts
|
|
410
|
+
const { address, shortAddress, cached } = await AttachmentKit.reverseGeocode({
|
|
411
|
+
latitude: 37.5665, longitude: 126.978,
|
|
412
|
+
languageCode: 'ko', // optional — defaults to the init/setDefaults languageCode, as given
|
|
413
|
+
})
|
|
252
414
|
```
|
|
253
415
|
|
|
254
|
-
|
|
416
|
+
- `address`: full address (Location-tab format, no country). `shortAddress`: street / neighbourhood + number. Both `null` when there is no address (e.g. open sea) — a **resolve**, not a reject.
|
|
417
|
+
- Disk cache, key `round(lat,5)|round(lng,5)|languageCode`; "no result" is cached too. 1000 entries max, trimmed to 800 least recently used. A hit resolves with `cached: true` in a few ms.
|
|
418
|
+
- Same-key requests are merged; the geocoder is called **one request at a time** (Apple allows ~50 per app per minute). 8 s timeout per call.
|
|
419
|
+
- Rejects (never cached): `E_INVALID_COORDINATE`, `E_GEOCODE_UNAVAILABLE` (Android device without a geocoder), `E_GEOCODE_THROTTLED` (retry later; on iOS new requests are rejected right away until the throttle resets), `E_GEOCODE_TIMEOUT`, `E_GEOCODE_NETWORK` (offline), `E_GEOCODE_FAILED`.
|
|
420
|
+
- Logs never contain coordinates or addresses.
|
|
421
|
+
- iOS data limits (Apple Maps): Korean addresses come without the district (`중구`), and Korea has only Korean/English data, so `ja`, `zh-Hans`, … return Korean there. Android (Google) includes the district.
|
|
255
422
|
|
|
256
|
-
|
|
423
|
+
#### `openMapViewer(options: MapViewerOptions): Promise<void>`
|
|
257
424
|
|
|
258
|
-
|
|
425
|
+
Full-screen map (Android Google Maps, iOS MapKit) with the sender's marker, a card (title, distance · address), map type / my-location buttons and an optional **Directions** button. Resolves when the viewer closes.
|
|
426
|
+
|
|
427
|
+
| Option | Type | Description |
|
|
428
|
+
|---|---|---|
|
|
429
|
+
| `latitude`, `longitude` | `number` | **Required.** |
|
|
430
|
+
| `title?`, `address?` | `string` | Without `address` it is filled with `reverseGeocode` (same cache and language). |
|
|
431
|
+
| `markerType?`, `placeCategory?` | | Same marker as the sender. |
|
|
432
|
+
| `spanMeters?` | `number` | Initial zoom = same scale as the bubble image (`imageSpanMeters`). The point is exactly at the screen center. |
|
|
433
|
+
| `showDirections?` | `boolean` (default `true`) | Android: chooser of installed map apps (`geo:`). iOS: Apple Maps / Google Maps, plus Kakao Map / Naver Map when `languageCode` is `ko`. |
|
|
434
|
+
| `sourceRect?` | `SourceRect` | Bubble rect (`measureInWindow` values). Sticky open/close; omitted → fade. |
|
|
435
|
+
| `sourceImageUri?` | `string` | Bubble image (`file://` or `https://`). May have a different aspect than `sourceRect` (screen-ratio image in a 4:3 bubble): it is always drawn center-cropped, so the first frame equals the bubble, then the cropped top/bottom unfold; closing crops back. Not loaded within ~1.5 s → opens without it. |
|
|
436
|
+
| `sourceCornerRadius?` | `number` (default 12) | Bubble corner radius. |
|
|
437
|
+
| `themeColor?`, `languageCode?`, `appearance?` | | Per-call overrides. |
|
|
438
|
+
|
|
439
|
+
#### `updateMapViewerSourceRect(rect: SourceRect | null): Promise<void>`
|
|
440
|
+
While the map viewer is open, updates where it closes to (e.g. the chat list scrolled). `null` → the original `sourceRect`.
|
|
441
|
+
|
|
442
|
+
#### `clearLocationThumbnailCache()` / `clearReverseGeocodeCache(): Promise<void>`
|
|
443
|
+
Delete the thumbnail / address caches (e.g. on logout).
|
|
259
444
|
|
|
260
445
|
---
|
|
261
446
|
|
|
262
|
-
###
|
|
447
|
+
### Media gallery ("all photos")
|
|
263
448
|
|
|
264
|
-
|
|
449
|
+
#### `openMediaGallery(options: MediaGalleryOptions): Promise<{ galleryId }>`
|
|
265
450
|
|
|
266
|
-
|
|
267
|
-
const unsubscribe = AttachmentKit.addSelectionChangeListener((event) => {
|
|
268
|
-
console.log(`${event.selectedCount} / ${event.maxSelection} selected`);
|
|
269
|
-
});
|
|
451
|
+
A native grid built for thousands of photos. It opens like the picker (same bottom sheet: 60% → drag up to expand, drag down to close), with a date quick-scroller. Tapping a photo opens the image viewer with the sticky zoom from that cell; the viewer pages through every loaded photo and closes back into the cell you were viewing. Resolves when the screen is shown.
|
|
270
452
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
453
|
+
- **Native data source (recommended)**: implement `MediaGalleryDataSource` in your app's native code and register it with `MediaGalleryRegistry.register("name", source)` (iOS Swift / Android Kotlin). Pass `dataSource: "name"` and `dataSourceParams`. Pages are loaded natively, no JS round trip. Built-in demo: `dataSource: "turboDemo", dataSourceParams: { count: 10000 }`.
|
|
454
|
+
- **JS data source**: pass `initialItems`, `hasMore`, `nextCursor`; answer `onNeedPage` with `appendGalleryItems({ galleryId, items, nextCursor, hasMore })`.
|
|
455
|
+
- `MediaGalleryItem`: `{ id, groupId?, timestamp?, thumbUri? (file:// only — no base64), imageUri, cacheFile?, isMine?, width?, height? }`.
|
|
456
|
+
- `title` (required), `gridSize: '3x3' | '4x4' | '5x5'` (default until the user picks one; the user's choice is saved on the device and wins), `spacing`, `theme`, `appearance`, `languageCode`, `emptyTitle`, `emptySubtitle`, `countFormat`.
|
|
457
|
+
- `viewer`: `{ themeColor, deleteOptionsForMine, deleteOptionsForOthers, titleFormat: 'date' | 'none', …openViewer text options }`.
|
|
458
|
+
- Events: `onItemDeleted({ galleryId, id, groupId, option })` (the module already removed that group from the screen — delete it in the app), `onNeedPage`, `onGridSizeChanged`, `onClosed`.
|
|
459
|
+
- Other: `closeMediaGallery(galleryId)`, `removeGalleryItems({ galleryId, groupIds })`, `isMediaGallerySupported()`.
|
|
460
|
+
- Android: the sheet is a view on top of the host Activity (no Activity / Dialog window); system back closes it.
|
|
274
461
|
|
|
275
462
|
---
|
|
276
463
|
|
|
277
|
-
###
|
|
464
|
+
### AI (generate & edit)
|
|
278
465
|
|
|
279
|
-
|
|
466
|
+
The gallery's ✦ button (AI image generation) and the editor's 6th tool **AI** (AI photo editing) use Google Gemini. They are **off until you call `setAIConfig({ enabled: true, ... })`**. In apps other than ZemTalk the AI screens are chat-only: generate shows an aspect-ratio bar and the chat input, edit shows the photo and the chat input, and the text is sent to the model as typed. The style/option menus, sample prompts, recommendation chips and recent works are ZemTalk-only.
|
|
280
467
|
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
|
|
468
|
+
```ts
|
|
469
|
+
await AttachmentKit.setAIConfig({
|
|
470
|
+
enabled: true,
|
|
471
|
+
endpoint: "https://api.example.com/ai/generate", // your server: forwards the Gemini generateContent body with your key
|
|
472
|
+
headers: { Authorization: `Bearer ${token}` }, // header X-AI-Model carries the model name
|
|
473
|
+
points: 1200, // current points
|
|
474
|
+
freeCount: 27, // subscribers: free uses left (e.g. 30 per month — your server decides); used before points
|
|
475
|
+
costPerUse: 100, // points per use (0 = free / unlimited)
|
|
476
|
+
highQualityCost: 150, // points per use with Quality "High (1K)" (default: costPerUse)
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
const offUsage = AttachmentKit.addAIUsageListener(e => {
|
|
480
|
+
// { kind: "generate" | "edit", usedFreeCount, pointsDeducted, highQuality, points, freeCount }
|
|
481
|
+
api.reportAIUsage(e)
|
|
482
|
+
})
|
|
483
|
+
const offCharge = AttachmentKit.addAIChargeRequiredListener(e => {
|
|
484
|
+
// { kind, required, points, freeCount } — the AI screen has closed
|
|
485
|
+
AttachmentKit.closeGallery(); navigation.navigate("PointShop")
|
|
486
|
+
})
|
|
284
487
|
```
|
|
285
488
|
|
|
286
|
-
|
|
489
|
+
- Only the values you pass change; call it again whenever the balance changes (after a top-up, after a server sync). An open AI screen updates at once.
|
|
490
|
+
- Order of use: free uses → points → when points are short the user sees "Not enough points · Top up" (Top up → `onAIChargeRequired`, AI screen closes).
|
|
491
|
+
- Only successful requests are charged (failures, cancel and safety blocks are free). The module lowers its own copy and sends `onAIUsage`; your server stays the source of truth.
|
|
492
|
+
- The AI screen title shows "N free left" or "points · cost per use".
|
|
493
|
+
- `apiKey` exists for testing only — never ship a Gemini key in the app.
|
|
287
494
|
|
|
288
|
-
|
|
495
|
+
#### Your backend (the `endpoint`)
|
|
289
496
|
|
|
290
|
-
|
|
497
|
+
The module never needs a Gemini key in the app. Point `endpoint` at **your own server**; it only has to pass the request through to Gemini with your key.
|
|
291
498
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
499
|
+
**Request the module sends**
|
|
500
|
+
|
|
501
|
+
```
|
|
502
|
+
POST {endpoint}
|
|
503
|
+
Content-Type: application/json
|
|
504
|
+
X-AI-Model: gemini-3.1-flash-image ← model to call (image: gemini-3.1-flash-image / gemini-3.1-flash-lite-image, photo analysis: gemini-3.5-flash-lite)
|
|
505
|
+
…plus every header you passed in setAIConfig({ headers }) ← e.g. Authorization
|
|
506
|
+
|
|
507
|
+
{ …Gemini generateContent request body… }
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
**What your server does**
|
|
511
|
+
|
|
512
|
+
1. Check the user (your `headers`) and that `X-AI-Model` is a model you allow.
|
|
513
|
+
2. Forward the body unchanged to `https://generativelanguage.googleapis.com/v1beta/models/{X-AI-Model}:generateContent` with header `x-goog-api-key: <your key>`.
|
|
514
|
+
3. Return Gemini's status code and JSON body **as is**.
|
|
515
|
+
|
|
516
|
+
```js
|
|
517
|
+
// Minimal Express example
|
|
518
|
+
app.post('/ai/generate', auth, express.json({ limit: '20mb' }), async (req, res) => {
|
|
519
|
+
const model = req.get('X-AI-Model');
|
|
520
|
+
if (!ALLOWED_MODELS.includes(model)) return res.status(400).json({ code: 'MODEL_NOT_ALLOWED' });
|
|
521
|
+
const r = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`, {
|
|
522
|
+
method: 'POST',
|
|
523
|
+
headers: { 'Content-Type': 'application/json', 'x-goog-api-key': process.env.GEMINI_API_KEY },
|
|
524
|
+
body: JSON.stringify(req.body),
|
|
525
|
+
});
|
|
526
|
+
res.status(r.status).type('application/json').send(await r.text());
|
|
527
|
+
});
|
|
297
528
|
```
|
|
298
529
|
|
|
530
|
+
**Billing (optional, on your server)** — the module's `points` / `freeCount` are only a display copy. If you charge, do it on the server: reserve before calling Gemini, confirm only when the response contains an image, refund otherwise (failures and safety blocks should be free). Then send the new balance back with `setAIConfig` (e.g. in `addAIUsageListener`).
|
|
531
|
+
|
|
532
|
+
**Errors** — any status other than 200 is a failed attempt (nothing is deducted in the module). To show your own reason, answer in Gemini's error shape: `{ "error": { "message": "…" } }`. The "Not enough points · Top up" prompt comes from the module's own copy of `points` / `freeCount`, so keep it in sync with `setAIConfig`.
|
|
533
|
+
|
|
534
|
+
**Security** — keep the key only on the server, allow-list models, limit the body size, and rate-limit per user (photo analysis requests carry no image output and are easy to abuse if left unlimited).
|
|
535
|
+
|
|
299
536
|
---
|
|
300
537
|
|
|
301
|
-
###
|
|
538
|
+
### Misc
|
|
302
539
|
|
|
303
|
-
|
|
540
|
+
| API | Description |
|
|
541
|
+
|---|---|
|
|
542
|
+
| `getDefaultAnimationConfig()` | Native default durations (ms): `{ galleryOpen, galleryClose, editorOpen, editorClose, viewerOpen, viewerClose }`. |
|
|
543
|
+
| `isUsingTurboModule` / `isUsingLegacyModule` | Which architecture is in use. |
|
|
544
|
+
| `normalizeLanguageCode(code)` / `SUPPORTED_LANGUAGE_CODES` | UI language helpers. |
|
|
545
|
+
| `normalizeAppearance(value)` | `'light' \| 'dark'`, anything else → `'system'`. |
|
|
304
546
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
|
310
|
-
|
|
311
|
-
| `
|
|
312
|
-
| `
|
|
313
|
-
| `
|
|
314
|
-
| `fileName` | `string` | File name of the output image. |
|
|
315
|
-
| `fileExtension` | `string` | File extension (e.g., `"jpg"`). |
|
|
316
|
-
| `fileSize` | `number` | File size in bytes. |
|
|
547
|
+
---
|
|
548
|
+
|
|
549
|
+
## ⚠️ Error codes
|
|
550
|
+
|
|
551
|
+
| Code | Where |
|
|
552
|
+
|---|---|
|
|
553
|
+
| `E_ALREADY_OPEN` | `openGallery` / `openEditor` while one is already open. |
|
|
554
|
+
| `E_INVALID_ARGS`, `E_THUMBNAIL_FAILED` | `renderLocationThumbnail`. |
|
|
555
|
+
| `E_INVALID_COORDINATE`, `E_GEOCODE_UNAVAILABLE`, `E_GEOCODE_THROTTLED`, `E_GEOCODE_TIMEOUT`, `E_GEOCODE_NETWORK`, `E_GEOCODE_FAILED` | `reverseGeocode` (type `ReverseGeocodeErrorCode`). |
|
|
317
556
|
|
|
318
557
|
---
|
|
319
558
|
|
|
320
|
-
|
|
559
|
+
## 🗂 Caches & temp files
|
|
321
560
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
```
|
|
561
|
+
| What | iOS | Android | Lifetime |
|
|
562
|
+
|---|---|---|---|
|
|
563
|
+
| Files from the File tab | `tmp/AttachmentKitFiles/<uuid>/<name>` | `cache/AttachmentKitFiles/<uuid>/<name>` | Deleted after 24 h (next gallery open); the OS may purge earlier — copy/upload promptly. |
|
|
564
|
+
| Location snapshots | `tmp/AttachmentKitLocation/` | `cache/AttachmentKitLocation/` | Deleted after 24 h. |
|
|
565
|
+
| Location thumbnails | `Library/Caches/RNAttachmentKit/LocationThumbs/` | `cache/RNAttachmentKitLocationThumbs/` | 300 max → 250, least recently used. `clearLocationThumbnailCache()`. |
|
|
566
|
+
| Reverse-geocode results | `Library/Caches/RNAttachmentKit/Geocode/` | `cache/RNAttachmentKitGeocode/` | 1000 max → 800, least recently used. `clearReverseGeocodeCache()`. |
|
|
330
567
|
|
|
331
568
|
---
|
|
332
569
|
|
|
333
570
|
## 📜 License & Contact
|
|
334
571
|
|
|
335
|
-
|
|
572
|
+
`react-native-attachment-kit` is released under the **MIT License**. Removing the editor watermark requires a license key passed to `init()`.
|
|
573
|
+
|
|
574
|
+
🛒 **License**: [https://license.rnturboimagepicker.usomnia.co.kr](https://license.rnturboimagepicker.usomnia.co.kr)
|
|
575
|
+
📧 **Contact**: [contact@usomnia.co.kr](mailto:contact@usomnia.co.kr)
|
|
336
576
|
|
|
337
|
-
|
|
577
|
+
See `CHANGELOG.md` for version history.
|