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.
Files changed (147) hide show
  1. package/CHANGELOG.md +121 -0
  2. package/README.md +446 -206
  3. package/android/build.gradle +17 -0
  4. package/android/src/main/libs/rnattachmentkit-release.aar +0 -0
  5. package/android/src/newarch/java/com/rnattachmentkit/RNAttachmentKitModule.kt +587 -22
  6. package/android/src/oldarch/java/com/rnattachmentkit/RNAttachmentKitModule.kt +592 -21
  7. package/ios/RNAttachmentKit.m +62 -0
  8. package/ios/RNAttachmentKit.swift +725 -29
  9. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Assets.car +0 -0
  10. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Headers/RNAttachmentKit-Swift.h +51 -5
  11. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Info.plist +0 -0
  12. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios.abi.json +51876 -18835
  13. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios.private.swiftinterface +310 -5
  14. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  15. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios.swiftinterface +310 -5
  16. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/RNAttachmentKit +0 -0
  17. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_crop_reflect~dark.webp +0 -0
  18. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_crop_reflect~light.webp +0 -0
  19. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_crop_rotate~dark.webp +0 -0
  20. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_crop_rotate~light.webp +0 -0
  21. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_delete~dark.webp +0 -0
  22. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_delete~light.webp +0 -0
  23. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_forward~dark.webp +0 -0
  24. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_forward~light.webp +0 -0
  25. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_reward~dark.webp +0 -0
  26. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_reward~light.webp +0 -0
  27. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_back~dark.webp +0 -0
  28. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_back~light.webp +0 -0
  29. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_blur~dark.webp +0 -0
  30. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_blur~light.webp +0 -0
  31. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_check_small~dark.webp +0 -0
  32. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_check_small~light.webp +0 -0
  33. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_check~dark.webp +0 -0
  34. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_check~light.webp +0 -0
  35. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_close~dark.webp +0 -0
  36. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_close~light.webp +0 -0
  37. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_crop~dark.webp +0 -0
  38. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_crop~light.webp +0 -0
  39. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_drawing~dark.webp +0 -0
  40. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_drawing~light.webp +0 -0
  41. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_effect~dark.webp +0 -0
  42. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_effect~light.webp +0 -0
  43. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_emoji~dark.webp +0 -0
  44. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_emoji~light.webp +0 -0
  45. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_eraser~dark.webp +0 -0
  46. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_eraser~light.webp +0 -0
  47. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_mosaic~dark.webp +0 -0
  48. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_mosaic~light.webp +0 -0
  49. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_text~dark.webp +0 -0
  50. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_text~light.webp +0 -0
  51. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_text_add~dark.webp +0 -0
  52. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_text_add~light.webp +0 -0
  53. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/_CodeSignature/CodeDirectory +0 -0
  54. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/_CodeSignature/CodeResources +195 -63
  55. package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/_CodeSignature/CodeSignature +0 -0
  56. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Assets.car +0 -0
  57. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Headers/RNAttachmentKit-Swift.h +102 -10
  58. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Info.plist +0 -0
  59. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.abi.json +51876 -18835
  60. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +310 -5
  61. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  62. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface +310 -5
  63. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.abi.json +51876 -18835
  64. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +310 -5
  65. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  66. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +310 -5
  67. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/RNAttachmentKit +0 -0
  68. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_crop_reflect~dark.webp +0 -0
  69. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_crop_reflect~light.webp +0 -0
  70. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_crop_rotate~dark.webp +0 -0
  71. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_crop_rotate~light.webp +0 -0
  72. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_delete~dark.webp +0 -0
  73. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_delete~light.webp +0 -0
  74. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_forward~dark.webp +0 -0
  75. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_forward~light.webp +0 -0
  76. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_reward~dark.webp +0 -0
  77. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_reward~light.webp +0 -0
  78. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_back~dark.webp +0 -0
  79. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_back~light.webp +0 -0
  80. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_blur~dark.webp +0 -0
  81. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_blur~light.webp +0 -0
  82. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_check_small~dark.webp +0 -0
  83. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_check_small~light.webp +0 -0
  84. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_check~dark.webp +0 -0
  85. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_check~light.webp +0 -0
  86. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_close~dark.webp +0 -0
  87. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_close~light.webp +0 -0
  88. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_crop~dark.webp +0 -0
  89. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_crop~light.webp +0 -0
  90. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_drawing~dark.webp +0 -0
  91. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_drawing~light.webp +0 -0
  92. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_effect~dark.webp +0 -0
  93. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_effect~light.webp +0 -0
  94. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_emoji~dark.webp +0 -0
  95. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_emoji~light.webp +0 -0
  96. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_eraser~dark.webp +0 -0
  97. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_eraser~light.webp +0 -0
  98. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_mosaic~dark.webp +0 -0
  99. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_mosaic~light.webp +0 -0
  100. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_text~dark.webp +0 -0
  101. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_text~light.webp +0 -0
  102. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_text_add~dark.webp +0 -0
  103. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_text_add~light.webp +0 -0
  104. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/_CodeSignature/CodeDirectory +0 -0
  105. package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/_CodeSignature/CodeResources +205 -73
  106. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_crop_reflect~dark.webp +0 -0
  107. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_crop_reflect~light.webp +0 -0
  108. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_crop_rotate~dark.webp +0 -0
  109. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_crop_rotate~light.webp +0 -0
  110. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_drawing_delete~dark.webp +0 -0
  111. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_drawing_delete~light.webp +0 -0
  112. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_drawing_forward~dark.webp +0 -0
  113. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_drawing_forward~light.webp +0 -0
  114. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_drawing_reward~dark.webp +0 -0
  115. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_drawing_reward~light.webp +0 -0
  116. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_back~dark.webp +0 -0
  117. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_back~light.webp +0 -0
  118. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_blur~dark.webp +0 -0
  119. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_blur~light.webp +0 -0
  120. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_check_small~dark.webp +0 -0
  121. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_check_small~light.webp +0 -0
  122. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_check~dark.webp +0 -0
  123. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_check~light.webp +0 -0
  124. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_close~dark.webp +0 -0
  125. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_close~light.webp +0 -0
  126. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_crop~dark.webp +0 -0
  127. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_crop~light.webp +0 -0
  128. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_drawing~dark.webp +0 -0
  129. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_drawing~light.webp +0 -0
  130. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_effect~dark.webp +0 -0
  131. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_effect~light.webp +0 -0
  132. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_emoji~dark.webp +0 -0
  133. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_emoji~light.webp +0 -0
  134. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_eraser~dark.webp +0 -0
  135. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_eraser~light.webp +0 -0
  136. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_mosaic~dark.webp +0 -0
  137. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_mosaic~light.webp +0 -0
  138. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_text~dark.webp +0 -0
  139. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_text~light.webp +0 -0
  140. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_text_add~dark.webp +0 -0
  141. package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_text_add~light.webp +0 -0
  142. package/package.json +1 -1
  143. package/react-native-attachment-kit.podspec +1 -1
  144. package/src/NativeAttachmentKit.ts +15 -0
  145. package/src/index.d.ts +5 -56
  146. package/src/index.ts +301 -37
  147. package/src/types.ts +380 -11
package/README.md CHANGED
@@ -1,16 +1,24 @@
1
1
  # React Native Attachment Kit 🚀
2
2
 
3
- A highly customizable, high-performance, and purely native image picker and editor for React Native.
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
- - **Pure Native UI**: Built with native iOS and Android for the fastest and smoothest performance.
7
- - **Built-in Editor & Viewer**: Includes an integrated image editor (cropping, filtering) and a highly optimized image viewer.
8
- - **Smooth Animations**: Carefully crafted interactions for a premium user experience (e.g., iOS-like smooth thumbnail centering).
9
- - **Sticky Image Viewer**: The viewer "sticks" to the source image thumbnail with a border-radius animation, creating a seamless shared-element transition on both iOS and Android.
10
- - **Native Mask View**: A native mask view hides the source thumbnail while the viewer is open, preventing visual duplication — no extra JS-side opacity manipulation required.
11
- - **Android Overlay Viewer**: Option to render the viewer directly inside the host Activity's Window (no separate Activity or DialogFragment), eliminating AppState blips and enabling real-time background layout updates.
12
- - **Customizable**: Easily configure theme colors, maximum selection limits, and more.
13
- - **New Architecture Ready**: Fully supports React Native's New Architecture (Turbo Modules).
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
- ### iOS Permissions (Info.plist)
50
- You must add the following permissions to your `ios/YourAppName/Info.plist` file, otherwise your app will crash when trying to access the camera or photo library.
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 and videos.</string>
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 edited photos to your library.</string>
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
- ## 🛠 Usage Example
64
-
65
- ```javascript
66
- import AttachmentKit from 'react-native-attachment-kit';
67
-
68
- // Step 1: Initialize the module (usually in App.tsx or index.js)
69
- // Pass your license key (if you have one to remove the watermark) and default configurations
70
- AttachmentKit.init("", {
71
- languageCode: 'en',
72
- themeColor: '#ff0000',
73
- appearance: 'system', // 'system' (default, follows the device) | 'light' | 'dark'
74
- }).catch(console.error);
75
-
76
- // Step 2: Open Image Picker
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
- ### Image Viewer with Sticky Transition (Recommended)
93
-
94
- ```javascript
95
- import AttachmentKit from 'react-native-attachment-kit';
96
-
97
- // Get the on-screen position of the thumbnail to enable the sticky viewer transition
98
- const imageRef = useRef(null);
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 Reference
133
+ ## 📖 API reference
131
134
 
132
- ### `AttachmentKit.init(licenseKey, options?): Promise<boolean>`
135
+ ### Photo picker
133
136
 
134
- Initializes the module with a license key and global defaults.
137
+ #### `openGallery(options?: GalleryOptions): Promise<ImageResult[]>`
135
138
 
136
- | Option | Type | Description |
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
- | `licenseKey` | `string` | Your license key. Pass `""` for evaluation (watermark shown). |
139
- | `options.themeColor` | `string` | Global default theme color (hex). |
140
- | `options.languageCode` | `string` | Global default language code. |
141
- | `options.appearance` | `'system' \| 'light' \| 'dark'` | Global default screen mode. `'system'` (default) follows the device setting; `'light'` / `'dark'` force it. Every `open*` call also accepts `appearance` to override per call. |
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
- ### `AttachmentKit.setDefaults(options)` / `AttachmentKit.getDefaults()`
182
+ #### Sending progress (events)
144
183
 
145
- Changes the global defaults (`languageCode`, `themeColor`, `appearance`) at any time after `init` — e.g. right after the user changes the app's theme or language. Only the values you pass are changed.
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
- AttachmentKit.setDefaults({ appearance: 'dark' })
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
- ### `AttachmentKit.openGallery(options?): Promise<ImageResult[]>`
204
+ ### Attach menu (Location / File)
154
205
 
155
- Opens the native image gallery for selecting photos and videos.
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
- | `maxSelection` | `number` | `1` | Maximum number of images a user can select. |
160
- | `maxWidth` / `maxHeight` | `number` | — | Maximum dimensions to scale the output image. |
161
- | `mediaType` | `"photo" \| "video" \| "all"` | `"photo"` | Type of media to show. |
162
- | `quality` | `number` | `0.8` | Compression quality (0 to 1). |
163
- | `enableEditor` | `boolean` | `false` | Opens the image editor after a single image selection. |
164
- | `profileMode` | `boolean` | `false` | Enables 1:1 circular crop mode for profile pictures. |
165
- | `themeColor` | `string` | — | Primary hex color for the UI (e.g., `"#FFEB3B"`). |
166
- | `autoCloseOnSelect` | `boolean` | `false` | Close picker automatically on single selection. |
167
- | `asyncProcessing` | `boolean` | `false` | When `true`, image processing runs in the background and results are delivered via `onImageProcessed`. |
168
- | `onSelectionChange` | `(event) => void` | — | Callback fired each time the selection count changes. |
169
- | `onImageProcessed` | `(event) => void` | — | Callback fired per image when `asyncProcessing` is `true`. |
170
- | `openDuration` / `closeDuration` | `number` | — | Override the open/close animation duration (ms). |
171
- | `languageCode` | `string` | — | Language for the UI. Supported: `en`, `ko`, `ja`, `zh`, `fr`, `de`, `es`, `pt`, `ru`, `it`, `nl`, `pl`, `tr`, `vi`, `th`, `id`, `ms`, `hi`, `da`. |
172
- | `doneButtonText` / `allItemsText` / `selectedItemsText` / `recentsAlbumText` | `string` | — | Text overrides for specific UI labels. |
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
- ### `AttachmentKit.openEditor(options): Promise<ImageResult>`
256
+ ### Image editor
177
257
 
178
- Directly opens the built-in image editor for a specific image.
258
+ #### `openEditor(options: EditorOptions): Promise<ImageResult>`
179
259
 
180
260
  | Option | Type | Description |
181
261
  |---|---|---|
182
- | `uri` | `string` | The URI of the image to edit (`originalUri` from a picker result). |
183
- | `editedFileUri` | `string` | (Optional) Path to a previously edited version to resume editing from. |
184
- | `themeColor` | `string` | UI theme color. |
185
- | `maxWidth` / `maxHeight` | `number` | Maximum bounds for the edited image output. |
186
- | `openDuration` / `closeDuration` | `number` | Override the open/close animation duration (ms). |
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
- ### `AttachmentKit.openViewer(options): Promise<void>`
272
+ ### Image viewer
191
273
 
192
- Opens a high-performance, full-screen image viewer with smooth swiping, thumbnail navigation, and pinch-to-zoom capabilities.
274
+ #### `openViewer(options: ViewerOptions): Promise<void>`
193
275
 
194
- #### Basic Options
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** | Array of image URIs to display. |
199
- | `placeholderImages` | `string[]` | — | Low-resolution placeholder URIs shown while the full image loads. |
200
- | `initialIndex` | `number` | `0` | The index of the image to show first. |
201
- | `themeColor` | `string` | — | UI theme color (counter badge, thumbnails). |
202
- | `title` | `string` | — | Title text shown in the viewer toolbar. |
203
- | `openDuration` / `closeDuration` | `number` | — | Override the open/close animation duration (ms). |
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
- #### Sticky Viewer / Shared-Element Transition Options
307
+ **Sticky transition**
206
308
 
207
- These options enable a smooth "shared-element"-style transition where the viewer zooms out from (and back into) the source thumbnail.
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
- | `sourceRect` | `SourceRect` | Screen coordinates (`x`, `y`, `width`, `height`) of the source thumbnail. Obtain via `ref.measure(...)`. Enables the zoom-from-thumbnail open animation. |
212
- | `sourceBorderRadius` | `number` | Border radius of the source thumbnail. The viewer animates from this radius to 0 (and back on close), creating a seamless shape transition. |
213
- | `sourceBorderCorners` | `('topLeft' \| 'topRight' \| 'bottomLeft' \| 'bottomRight')[]` | Specifies which corners of the source thumbnail have the border radius. Useful for images with a radius only on specific corners (e.g., chat bubbles). |
214
- | `sourceBackgroundColor` | `string` | Background color behind the source thumbnail (used to match the background during the open animation). |
215
- | `hideSourceImage` | `boolean` | When `true`, the native mask view hides the source thumbnail while the viewer is open, preventing visual duplication. The mask is automatically removed when the viewer closes. |
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
- #### Callbacks / Event Options
327
+ **Events**
218
328
 
219
- | Option | Type | Platform | Description |
220
- |---|---|---|---|
221
- | `onPageSelected` | `(index: number) => void` | Both | Called whenever the user swipes to a different image. |
222
- | `onViewerWillClose` | `() => void` | Both | Called just before the viewer begins its close animation. |
223
- | `onViewerOpened` | `() => void` | Android | Called once when the open animation finishes. Ideal for triggering background scroll corrections, since the viewer fully covers the screen at this point — no race conditions. |
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
- #### Android Viewer Mode Options
335
+ **Android viewer modes**
226
336
 
227
- On Android, the viewer can be opened in three different modes. Choose based on your app's requirements:
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
- | Option | Type | Default | Description |
230
- |---|---|---|---|
231
- | `useDialogViewer` | `boolean` | `false` | Opens the viewer as a **DialogFragment** (full-screen dialog) instead of a separate Activity. Prevents the RN host Activity from pausing (`onPause`), which otherwise causes `AppState` to briefly report `"background"` and temporarily freezes RN UI interaction. **Limitation:** The DialogFragment still uses a separate Android Window, so background RN view updates (e.g., list scroll corrections) may not render to screen until the viewer closes. |
232
- | `useOverlayViewer` | `boolean` | `false` | ⭐ **Recommended for Android.** Opens the viewer as a `View` added directly on top of the host Activity's existing Window — no separate Activity or Dialog Window. **Benefits:** AppState never flickers, background RN layout changes (e.g., scroll corrections for the sticky viewer) are reflected in real time (same render tree, same frame). **Note:** When both `useDialogViewer` and `useOverlayViewer` are `true`, `useOverlayViewer` takes priority. |
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
- > **Android Viewer Mode Summary:**
235
- > - Default (no flag): Separate Activity — `AppState` blips, but most stable.
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
- ### `AttachmentKit.updateSourceRect(rect): Promise<void>`
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
- Updates the source thumbnail coordinates while the viewer is open. Call this inside `onPageSelected` after measuring the newly selected thumbnail's position, so the close animation correctly animates back to the right thumbnail.
399
+ Result: `{ uri: 'file://…webp', path, width, height (px), cached }`.
244
400
 
245
- ```typescript
246
- onPageSelected: async (index) => {
247
- const rect = await measureThumbnail(index); // your measurement logic
248
- if (rect) {
249
- AttachmentKit.updateSourceRect(rect);
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
- ### `AttachmentKit.closeGallery(): Promise<boolean>`
423
+ #### `openMapViewer(options: MapViewerOptions): Promise<void>`
257
424
 
258
- Programmatically closes the gallery picker. Returns `true` if it was open and is now closed.
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
- ### `AttachmentKit.addSelectionChangeListener(listener): () => void`
447
+ ### Media gallery ("all photos")
263
448
 
264
- Subscribes to selection change events outside of `openGallery`. Returns an unsubscribe function.
449
+ #### `openMediaGallery(options: MediaGalleryOptions): Promise<{ galleryId }>`
265
450
 
266
- ```typescript
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
- // Later, when done:
272
- unsubscribe();
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
- ### `AttachmentKit.getDefaultAnimationConfig(): Promise<AnimationConfig>`
464
+ ### AI (generate & edit)
278
465
 
279
- Returns the native default animation durations (in ms) for all transitions. Useful for synchronizing JS animations.
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
- ```typescript
282
- const config = await AttachmentKit.getDefaultAnimationConfig();
283
- // { galleryOpen, galleryClose, editorOpen, editorClose, viewerOpen, viewerClose }
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
- ### `AttachmentKit.injectImageCache(url, localPath): Promise<boolean>`
495
+ #### Your backend (the `endpoint`)
289
496
 
290
- Pre-populates the native image cache (SDWebImage on iOS, Glide on Android) with a local file for a given remote URL. This lets the viewer display the image instantly from disk without a network request.
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
- ```typescript
293
- await AttachmentKit.injectImageCache(
294
- 'https://example.com/photo.jpg',
295
- '/path/to/cached/photo.jpg'
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
- ### `ImageResult` (Returned Object)
538
+ ### Misc
302
539
 
303
- When an image is selected or edited, the promise resolves to an array of `ImageResult` objects:
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
- | Field | Type | Description |
306
- |---|---|---|
307
- | `originalUri` | `string` | URI of the original asset (`ph://` or `file://`). |
308
- | `originalFileUri` | `string` | File URI of the original-resolution image (`file://`). |
309
- | `originalWidth` / `originalHeight` | `number` | Dimensions of the original asset. |
310
- | `uri` | `string` | URI of the resized/edited output image (`file://`). |
311
- | `width` / `height` | `number` | Dimensions of the output image. |
312
- | `type` | `string` | MIME type (e.g., `"image/jpeg"`). |
313
- | `caption` | `string` | Caption text entered in the editor (if any). |
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
- ### `SourceRect`
559
+ ## 🗂 Caches & temp files
321
560
 
322
- ```typescript
323
- interface SourceRect {
324
- x: number; // Screen X coordinate of the thumbnail
325
- y: number; // Screen Y coordinate of the thumbnail
326
- width: number; // Thumbnail width
327
- height: number; // Thumbnail height
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
- This `react-native-attachment-kit` library is released under the **MIT License**.
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
- 📧 **Contact**: [contact@usomnia.co.kr](mailto:contact@usomnia.co.kr)
577
+ See `CHANGELOG.md` for version history.