@amazon-devices/expo-image 2.0.1758683737

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 (289) hide show
  1. package/.eslintrc.js +2 -0
  2. package/CHANGELOG.md +285 -0
  3. package/CMakeLists.txt +91 -0
  4. package/LICENSE +7 -0
  5. package/README.kepler.md +123 -0
  6. package/README.md +54 -0
  7. package/THIRD_PARTY_NOTICES.txt +24 -0
  8. package/android/build.gradle +124 -0
  9. package/android/proguard-rules.pro +28 -0
  10. package/android/src/main/AndroidManifest.xml +15 -0
  11. package/android/src/main/java/expo/modules/image/Exceptions.kt +9 -0
  12. package/android/src/main/java/expo/modules/image/ExpoImageAppGlideModule.kt +19 -0
  13. package/android/src/main/java/expo/modules/image/ExpoImageModule.kt +192 -0
  14. package/android/src/main/java/expo/modules/image/ExpoImageView.kt +220 -0
  15. package/android/src/main/java/expo/modules/image/ExpoImageViewWrapper.kt +652 -0
  16. package/android/src/main/java/expo/modules/image/GlideModel.kt +73 -0
  17. package/android/src/main/java/expo/modules/image/ImageUtils.kt +50 -0
  18. package/android/src/main/java/expo/modules/image/ImageViewWrapperTarget.kt +313 -0
  19. package/android/src/main/java/expo/modules/image/ResourceIdHelper.kt +47 -0
  20. package/android/src/main/java/expo/modules/image/ThumbnailRequestCoordinatorExtension.kt +21 -0
  21. package/android/src/main/java/expo/modules/image/YogaUtils.kt +20 -0
  22. package/android/src/main/java/expo/modules/image/blurhash/BlurHashFetcher.kt +32 -0
  23. package/android/src/main/java/expo/modules/image/blurhash/BlurhashDecoder.kt +189 -0
  24. package/android/src/main/java/expo/modules/image/blurhash/BlurhashModelLoader.kt +30 -0
  25. package/android/src/main/java/expo/modules/image/blurhash/BlurhashModelLoaderFactory.kt +14 -0
  26. package/android/src/main/java/expo/modules/image/blurhash/BlurhashModule.kt +17 -0
  27. package/android/src/main/java/expo/modules/image/dataurls/Base64DataFetcher.kt +27 -0
  28. package/android/src/main/java/expo/modules/image/dataurls/Base64ModelLoader.kt +24 -0
  29. package/android/src/main/java/expo/modules/image/dataurls/Base64ModelLoaderFactory.kt +12 -0
  30. package/android/src/main/java/expo/modules/image/dataurls/Base64Module.kt +16 -0
  31. package/android/src/main/java/expo/modules/image/drawing/OutlineProvider.kt +201 -0
  32. package/android/src/main/java/expo/modules/image/enums/ContentFit.kt +89 -0
  33. package/android/src/main/java/expo/modules/image/enums/ImageCacheType.kt +14 -0
  34. package/android/src/main/java/expo/modules/image/enums/Priority.kt +15 -0
  35. package/android/src/main/java/expo/modules/image/events/GlideRequestListener.kt +64 -0
  36. package/android/src/main/java/expo/modules/image/events/OkHttpProgressListener.kt +26 -0
  37. package/android/src/main/java/expo/modules/image/okhttp/ExpoImageOkHttpClientGlideModule.kt +80 -0
  38. package/android/src/main/java/expo/modules/image/okhttp/GlideUrlWrapperLoader.kt +55 -0
  39. package/android/src/main/java/expo/modules/image/records/CachePolicy.kt +10 -0
  40. package/android/src/main/java/expo/modules/image/records/ContentPosition.kt +89 -0
  41. package/android/src/main/java/expo/modules/image/records/ImageTransition.kt +8 -0
  42. package/android/src/main/java/expo/modules/image/records/SourceMap.kt +160 -0
  43. package/android/src/main/java/expo/modules/image/records/events.kt +25 -0
  44. package/android/src/main/java/expo/modules/image/svg/SVGDecoder.kt +33 -0
  45. package/android/src/main/java/expo/modules/image/svg/SVGDrawableTranscoder.kt +37 -0
  46. package/android/src/main/java/expo/modules/image/svg/SVGModule.kt +26 -0
  47. package/android/src/main/java/expo/modules/image/thumbhash/ThumbhashDecoder.kt +365 -0
  48. package/android/src/main/java/expo/modules/image/thumbhash/ThumbhashFetcher.kt +31 -0
  49. package/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModelLoader.kt +26 -0
  50. package/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModelLoaderFactory.kt +14 -0
  51. package/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModule.kt +17 -0
  52. package/build/ExpoImage.d.ts +15 -0
  53. package/build/ExpoImage.d.ts.map +1 -0
  54. package/build/ExpoImage.js +75 -0
  55. package/build/ExpoImage.js.map +1 -0
  56. package/build/ExpoImage.kepler.d.ts +23 -0
  57. package/build/ExpoImage.kepler.d.ts.map +1 -0
  58. package/build/ExpoImage.kepler.js +71 -0
  59. package/build/ExpoImage.kepler.js.map +1 -0
  60. package/build/ExpoImage.web.d.ts +9 -0
  61. package/build/ExpoImage.web.d.ts.map +1 -0
  62. package/build/ExpoImage.web.js +90 -0
  63. package/build/ExpoImage.web.js.map +1 -0
  64. package/build/ExpoImageModules.d.ts +3 -0
  65. package/build/ExpoImageModules.d.ts.map +1 -0
  66. package/build/ExpoImageModules.js +4 -0
  67. package/build/ExpoImageModules.js.map +1 -0
  68. package/build/ExpoImageModules.kepler.d.ts +6 -0
  69. package/build/ExpoImageModules.kepler.d.ts.map +1 -0
  70. package/build/ExpoImageModules.kepler.js +5 -0
  71. package/build/ExpoImageModules.kepler.js.map +1 -0
  72. package/build/Image.d.ts +30 -0
  73. package/build/Image.d.ts.map +1 -0
  74. package/build/Image.js +49 -0
  75. package/build/Image.js.map +1 -0
  76. package/build/Image.types.d.ts +374 -0
  77. package/build/Image.types.d.ts.map +1 -0
  78. package/build/Image.types.js +2 -0
  79. package/build/Image.types.js.map +1 -0
  80. package/build/ImageBackground.d.ts +6 -0
  81. package/build/ImageBackground.d.ts.map +1 -0
  82. package/build/ImageBackground.js +13 -0
  83. package/build/ImageBackground.js.map +1 -0
  84. package/build/ImageViewNativeComponent.d.ts +20 -0
  85. package/build/ImageViewNativeComponent.d.ts.map +1 -0
  86. package/build/ImageViewNativeComponent.js +42 -0
  87. package/build/ImageViewNativeComponent.js.map +1 -0
  88. package/build/NativeImageLoaderKepler.d.ts +18 -0
  89. package/build/NativeImageLoaderKepler.d.ts.map +1 -0
  90. package/build/NativeImageLoaderKepler.js +3 -0
  91. package/build/NativeImageLoaderKepler.js.map +1 -0
  92. package/build/fabric/registerNativeComponent.d.ts +15 -0
  93. package/build/fabric/registerNativeComponent.d.ts.map +1 -0
  94. package/build/fabric/registerNativeComponent.js +47 -0
  95. package/build/fabric/registerNativeComponent.js.map +1 -0
  96. package/build/index.d.ts +4 -0
  97. package/build/index.d.ts.map +1 -0
  98. package/build/index.js +4 -0
  99. package/build/index.js.map +1 -0
  100. package/build/utils/AssetSourceResolver.web.d.ts +24 -0
  101. package/build/utils/AssetSourceResolver.web.d.ts.map +1 -0
  102. package/build/utils/AssetSourceResolver.web.js +67 -0
  103. package/build/utils/AssetSourceResolver.web.js.map +1 -0
  104. package/build/utils/blurhash/base83.d.ts +3 -0
  105. package/build/utils/blurhash/base83.d.ts.map +1 -0
  106. package/build/utils/blurhash/base83.js +103 -0
  107. package/build/utils/blurhash/base83.js.map +1 -0
  108. package/build/utils/blurhash/decode.d.ts +7 -0
  109. package/build/utils/blurhash/decode.d.ts.map +1 -0
  110. package/build/utils/blurhash/decode.js +92 -0
  111. package/build/utils/blurhash/decode.js.map +1 -0
  112. package/build/utils/blurhash/error.d.ts +4 -0
  113. package/build/utils/blurhash/error.d.ts.map +1 -0
  114. package/build/utils/blurhash/error.js +8 -0
  115. package/build/utils/blurhash/error.js.map +1 -0
  116. package/build/utils/blurhash/useBlurhash.d.ts +8 -0
  117. package/build/utils/blurhash/useBlurhash.d.ts.map +1 -0
  118. package/build/utils/blurhash/useBlurhash.js +64 -0
  119. package/build/utils/blurhash/useBlurhash.js.map +1 -0
  120. package/build/utils/blurhash/utils.d.ts +5 -0
  121. package/build/utils/blurhash/utils.d.ts.map +1 -0
  122. package/build/utils/blurhash/utils.js +21 -0
  123. package/build/utils/blurhash/utils.js.map +1 -0
  124. package/build/utils/resolveAssetSource.d.ts +3 -0
  125. package/build/utils/resolveAssetSource.d.ts.map +1 -0
  126. package/build/utils/resolveAssetSource.js +3 -0
  127. package/build/utils/resolveAssetSource.js.map +1 -0
  128. package/build/utils/resolveAssetSource.web.d.ts +9 -0
  129. package/build/utils/resolveAssetSource.web.d.ts.map +1 -0
  130. package/build/utils/resolveAssetSource.web.js +31 -0
  131. package/build/utils/resolveAssetSource.web.js.map +1 -0
  132. package/build/utils/resolveHashString.d.ts +14 -0
  133. package/build/utils/resolveHashString.d.ts.map +1 -0
  134. package/build/utils/resolveHashString.js +31 -0
  135. package/build/utils/resolveHashString.js.map +1 -0
  136. package/build/utils/resolveHashString.web.d.ts +20 -0
  137. package/build/utils/resolveHashString.web.d.ts.map +1 -0
  138. package/build/utils/resolveHashString.web.js +31 -0
  139. package/build/utils/resolveHashString.web.js.map +1 -0
  140. package/build/utils/resolveSources.d.ts +8 -0
  141. package/build/utils/resolveSources.d.ts.map +1 -0
  142. package/build/utils/resolveSources.js +45 -0
  143. package/build/utils/resolveSources.js.map +1 -0
  144. package/build/utils/thumbhash/thumbhash.d.ts +66 -0
  145. package/build/utils/thumbhash/thumbhash.d.ts.map +1 -0
  146. package/build/utils/thumbhash/thumbhash.js +338 -0
  147. package/build/utils/thumbhash/thumbhash.js.map +1 -0
  148. package/build/utils.d.ts +17 -0
  149. package/build/utils.d.ts.map +1 -0
  150. package/build/utils.js +85 -0
  151. package/build/utils.js.map +1 -0
  152. package/build/web/AnimationManager.d.ts +37 -0
  153. package/build/web/AnimationManager.d.ts.map +1 -0
  154. package/build/web/AnimationManager.js +155 -0
  155. package/build/web/AnimationManager.js.map +1 -0
  156. package/build/web/ColorTintFilter.d.ts +12 -0
  157. package/build/web/ColorTintFilter.d.ts.map +1 -0
  158. package/build/web/ColorTintFilter.js +30 -0
  159. package/build/web/ColorTintFilter.js.map +1 -0
  160. package/build/web/ImageWrapper.d.ts +5 -0
  161. package/build/web/ImageWrapper.d.ts.map +1 -0
  162. package/build/web/ImageWrapper.js +44 -0
  163. package/build/web/ImageWrapper.js.map +1 -0
  164. package/build/web/ImageWrapper.types.d.ts +29 -0
  165. package/build/web/ImageWrapper.types.d.ts.map +1 -0
  166. package/build/web/ImageWrapper.types.js +2 -0
  167. package/build/web/ImageWrapper.types.js.map +1 -0
  168. package/build/web/getImageWrapperEventHandler.d.ts +8 -0
  169. package/build/web/getImageWrapperEventHandler.d.ts.map +1 -0
  170. package/build/web/getImageWrapperEventHandler.js +25 -0
  171. package/build/web/getImageWrapperEventHandler.js.map +1 -0
  172. package/build/web/hooks.d.ts +13 -0
  173. package/build/web/hooks.d.ts.map +1 -0
  174. package/build/web/hooks.js +58 -0
  175. package/build/web/hooks.js.map +1 -0
  176. package/build/web/imageStyles.d.ts +2 -0
  177. package/build/web/imageStyles.d.ts.map +1 -0
  178. package/build/web/imageStyles.js +137 -0
  179. package/build/web/imageStyles.js.map +1 -0
  180. package/build/web/positioning.d.ts +11 -0
  181. package/build/web/positioning.d.ts.map +1 -0
  182. package/build/web/positioning.js +35 -0
  183. package/build/web/positioning.js.map +1 -0
  184. package/build/web/useSourceSelection.d.ts +14 -0
  185. package/build/web/useSourceSelection.d.ts.map +1 -0
  186. package/build/web/useSourceSelection.js +98 -0
  187. package/build/web/useSourceSelection.js.map +1 -0
  188. package/expo-module.config.json +9 -0
  189. package/ios/Blurhash.swift +326 -0
  190. package/ios/BlurhashLoader.swift +52 -0
  191. package/ios/ContentFit.swift +55 -0
  192. package/ios/ContentPosition.swift +108 -0
  193. package/ios/ExpoImage.podspec +30 -0
  194. package/ios/ImageCachePolicy.swift +24 -0
  195. package/ios/ImageCacheType.swift +21 -0
  196. package/ios/ImageModule.swift +127 -0
  197. package/ios/ImagePriority.swift +22 -0
  198. package/ios/ImageSource.swift +44 -0
  199. package/ios/ImageTransition.swift +67 -0
  200. package/ios/ImageUtils.swift +227 -0
  201. package/ios/ImageView.swift +469 -0
  202. package/ios/Loaders/PhotoLibraryAssetLoader.swift +146 -0
  203. package/ios/Thumbhash.swift +550 -0
  204. package/ios/ThumbhashLoader.swift +53 -0
  205. package/jest.config.js +3 -0
  206. package/kepler/ExpoImage.cpp +63 -0
  207. package/kepler/ExpoImage.h +29 -0
  208. package/kepler/ExpoImageCacheHandler.cpp +15 -0
  209. package/kepler/ExpoImageCacheHandler.h +28 -0
  210. package/kepler/ExpoImageComponent.cpp +579 -0
  211. package/kepler/ExpoImageComponent.h +199 -0
  212. package/kepler/ImageWrapper/BlurhashImageWrapper.cpp +37 -0
  213. package/kepler/ImageWrapper/BlurhashImageWrapper.h +23 -0
  214. package/kepler/ImageWrapper/CacheImageWrapper.cpp +80 -0
  215. package/kepler/ImageWrapper/CacheImageWrapper.h +41 -0
  216. package/kepler/ImageWrapper/HashImageWrapper.cpp +21 -0
  217. package/kepler/ImageWrapper/HashImageWrapper.h +22 -0
  218. package/kepler/ImageWrapper/ImageWrapper.cpp +24 -0
  219. package/kepler/ImageWrapper/ImageWrapper.h +33 -0
  220. package/kepler/ImageWrapper/LocalImageWrapper.cpp +61 -0
  221. package/kepler/ImageWrapper/LocalImageWrapper.h +30 -0
  222. package/kepler/ImageWrapper/ThumbhashImageWrapper.cpp +33 -0
  223. package/kepler/ImageWrapper/ThumbhashImageWrapper.h +20 -0
  224. package/kepler/KeplerImageViewComponent.h +64 -0
  225. package/kepler/KeplerInit.cpp +35 -0
  226. package/kepler/blurhash/common.h +32 -0
  227. package/kepler/blurhash/decode.cpp +187 -0
  228. package/kepler/blurhash/decode.h +58 -0
  229. package/kepler/contentposition/ImageContentPosition.cpp +86 -0
  230. package/kepler/contentposition/ImageContentPosition.h +48 -0
  231. package/kepler/enums/ImageContentFit.h +17 -0
  232. package/kepler/enums/ImagePriority.h +17 -0
  233. package/kepler/managers/ExpoImageManager.cpp +38 -0
  234. package/kepler/managers/ExpoImageManager.h +23 -0
  235. package/kepler/test/ExpoImage.cpp +73 -0
  236. package/kepler/test/ExpoImageComponent.cpp +290 -0
  237. package/kepler/test/blurhash/decode.cpp +39 -0
  238. package/kepler/test/contentposition/ImageContentPosition.cpp +100 -0
  239. package/kepler/test/managers/ExpoImageManager.cpp +39 -0
  240. package/kepler/test/thumbhash/decode.cpp +18 -0
  241. package/kepler/test/utils/LoaderDelegate.cpp +130 -0
  242. package/kepler/thumbhash/decode.cpp +166 -0
  243. package/kepler/thumbhash/decode.h +22 -0
  244. package/kepler/utils/PlaceholderImageLoaderDelegate.cpp +26 -0
  245. package/kepler/utils/PlaceholderImageLoaderDelegate.h +26 -0
  246. package/kepler/utils/PrefetchImageLoaderDelegate.cpp +55 -0
  247. package/kepler/utils/PrefetchImageLoaderDelegate.h +35 -0
  248. package/kepler/utils/SourceImageLoaderDelegate.cpp +47 -0
  249. package/kepler/utils/SourceImageLoaderDelegate.h +42 -0
  250. package/kepler/utils/SourceImageLoaderDelegateEmitter.cpp +15 -0
  251. package/kepler/utils/SourceImageLoaderDelegateEmitter.h +28 -0
  252. package/kepler-compatibility.json +8 -0
  253. package/package.json +1 -0
  254. package/react-native.config.js +22 -0
  255. package/src/ExpoImage.kepler.tsx +119 -0
  256. package/src/ExpoImage.tsx +119 -0
  257. package/src/ExpoImage.web.tsx +155 -0
  258. package/src/ExpoImageModules.kepler.ts +7 -0
  259. package/src/ExpoImageModules.ts +4 -0
  260. package/src/Image.tsx +82 -0
  261. package/src/Image.types.ts +450 -0
  262. package/src/ImageBackground.tsx +18 -0
  263. package/src/ImageViewNativeComponent.ts +59 -0
  264. package/src/NativeImageLoaderKepler.ts +22 -0
  265. package/src/fabric/registerNativeComponent.ts +70 -0
  266. package/src/index.ts +3 -0
  267. package/src/utils/AssetSourceResolver.web.ts +88 -0
  268. package/src/utils/blurhash/base83.ts +104 -0
  269. package/src/utils/blurhash/decode.ts +114 -0
  270. package/src/utils/blurhash/error.ts +7 -0
  271. package/src/utils/blurhash/useBlurhash.tsx +81 -0
  272. package/src/utils/blurhash/utils.ts +21 -0
  273. package/src/utils/resolveAssetSource.tsx +3 -0
  274. package/src/utils/resolveAssetSource.web.ts +40 -0
  275. package/src/utils/resolveHashString.tsx +36 -0
  276. package/src/utils/resolveHashString.web.tsx +33 -0
  277. package/src/utils/resolveSources.tsx +48 -0
  278. package/src/utils/thumbhash/thumbhash.ts +387 -0
  279. package/src/utils.ts +109 -0
  280. package/src/web/AnimationManager.tsx +224 -0
  281. package/src/web/ColorTintFilter.tsx +36 -0
  282. package/src/web/ImageWrapper.tsx +85 -0
  283. package/src/web/ImageWrapper.types.ts +36 -0
  284. package/src/web/getImageWrapperEventHandler.ts +29 -0
  285. package/src/web/hooks.ts +72 -0
  286. package/src/web/imageStyles.tsx +136 -0
  287. package/src/web/positioning.ts +48 -0
  288. package/src/web/useSourceSelection.ts +151 -0
  289. package/tsconfig.json +9 -0
@@ -0,0 +1,73 @@
1
+ package expo.modules.image
2
+
3
+ import android.net.Uri
4
+ import com.bumptech.glide.load.model.GlideUrl
5
+ import expo.modules.image.okhttp.GlideUrlWrapper
6
+
7
+ sealed class GlideModel {
8
+ abstract val glideData: Any
9
+
10
+ override fun equals(other: Any?): Boolean {
11
+ if (this === other) {
12
+ return true
13
+ }
14
+ return other is GlideModel && glideData == other.glideData
15
+ }
16
+
17
+ override fun hashCode(): Int = glideData.hashCode()
18
+ }
19
+
20
+ class GlideUrlModel(
21
+ glideUrl: GlideUrl
22
+ ) : GlideModel() {
23
+ override val glideData: GlideUrlWrapper = GlideUrlWrapper(glideUrl)
24
+ }
25
+
26
+ class GlideRawModel(
27
+ data: String
28
+ ) : GlideModel() {
29
+ override val glideData: String = data
30
+ }
31
+
32
+ class GlideUriModel(
33
+ uri: Uri
34
+ ) : GlideModel() {
35
+ override val glideData: Uri = uri
36
+ }
37
+
38
+ class GlideBlurhashModel(
39
+ val uri: Uri,
40
+ val width: Int,
41
+ val height: Int
42
+ ) : GlideModel() {
43
+ override val glideData: GlideBlurhashModel = this
44
+
45
+ override fun equals(other: Any?): Boolean {
46
+ if (this === other) {
47
+ return true
48
+ }
49
+
50
+ return other is GlideBlurhashModel && uri == other.uri && width == other.width && height == other.height
51
+ }
52
+
53
+ override fun hashCode(): Int {
54
+ var result = uri.hashCode()
55
+ result = 31 * result + width
56
+ result = 31 * result + height
57
+ return result
58
+ }
59
+ }
60
+
61
+ class GlideThumbhashModel(
62
+ var uri: Uri
63
+ ) : GlideModel() {
64
+ override val glideData: GlideThumbhashModel = this
65
+
66
+ override fun equals(other: Any?): Boolean {
67
+ return (this === other) || other is GlideThumbhashModel && uri == other.uri
68
+ }
69
+
70
+ override fun hashCode(): Int {
71
+ return uri.hashCode()
72
+ }
73
+ }
@@ -0,0 +1,50 @@
1
+ package expo.modules.image
2
+
3
+ import android.graphics.RectF
4
+ import com.bumptech.glide.request.FutureTarget
5
+ import kotlinx.coroutines.Dispatchers
6
+ import kotlinx.coroutines.runInterruptible
7
+
8
+ /**
9
+ * Converts blocking [java.util.concurrent.Future] result into non-blocking suspend function.
10
+ */
11
+ internal suspend fun <T> FutureTarget<T>.awaitGet(): T = runInterruptible(Dispatchers.IO) { get() }
12
+
13
+ fun calcXTranslation(
14
+ value: Float,
15
+ imageRect: RectF,
16
+ viewRect: RectF,
17
+ isPercentage: Boolean = false,
18
+ isReverse: Boolean = false
19
+ ): Float = calcTranslation(value, imageRect.width(), viewRect.width(), isPercentage, isReverse)
20
+
21
+ fun calcYTranslation(
22
+ value: Float,
23
+ imageRect: RectF,
24
+ viewRect: RectF,
25
+ isPercentage: Boolean = false,
26
+ isReverse: Boolean = false
27
+ ): Float = calcTranslation(value, imageRect.height(), viewRect.height(), isPercentage, isReverse)
28
+
29
+ fun calcTranslation(
30
+ value: Float,
31
+ imageRefValue: Float,
32
+ viewRefValue: Float,
33
+ isPercentage: Boolean = false,
34
+ isReverse: Boolean = false
35
+ ): Float {
36
+ if (isPercentage) {
37
+ val finalPercentage = if (isReverse) {
38
+ 100f - value
39
+ } else {
40
+ value
41
+ }
42
+ return (finalPercentage / 100f) * (viewRefValue - imageRefValue)
43
+ }
44
+
45
+ if (isReverse) {
46
+ return viewRefValue - imageRefValue - value
47
+ }
48
+
49
+ return value
50
+ }
@@ -0,0 +1,313 @@
1
+ package expo.modules.image
2
+
3
+ import android.content.Context
4
+ import android.graphics.Point
5
+ import android.graphics.drawable.Drawable
6
+ import android.util.Log
7
+ import android.view.ViewGroup
8
+ import android.view.ViewTreeObserver
9
+ import android.view.WindowManager
10
+ import androidx.annotation.VisibleForTesting
11
+ import com.bumptech.glide.RequestManager
12
+ import com.bumptech.glide.request.Request
13
+ import com.bumptech.glide.request.ThumbnailRequestCoordinator
14
+ import com.bumptech.glide.request.target.SizeReadyCallback
15
+ import com.bumptech.glide.request.target.Target
16
+ import com.bumptech.glide.request.transition.Transition
17
+ import com.bumptech.glide.util.Preconditions
18
+ import com.bumptech.glide.util.Synthetic
19
+ import expo.modules.core.utilities.ifNull
20
+ import expo.modules.image.enums.ContentFit
21
+ import java.lang.ref.WeakReference
22
+ import kotlin.math.max
23
+
24
+ /**
25
+ * A custom target to provide a smooth transition between multiple drawables.
26
+ * It delegates images to the [ExpoImageViewWrapper], where we handle the loaded [Drawable].
27
+ * When the target is cleared, we don't do anything. The [ExpoImageViewWrapper] is responsible for
28
+ * clearing bitmaps before freeing targets. That may be error-prone, but that is the only way
29
+ * of implementing the transition between bitmaps.
30
+ */
31
+ class ImageViewWrapperTarget(
32
+ private val imageViewHolder: WeakReference<ExpoImageViewWrapper>,
33
+ ) : Target<Drawable> {
34
+ /**
35
+ * Whether the target has a main, non-placeholder source
36
+ */
37
+ var hasSource = false
38
+
39
+ /**
40
+ * Whether the target is used - the asset loaded by it has been drawn in the image view
41
+ */
42
+ var isUsed = false
43
+
44
+ /**
45
+ * The main source height where -1 means unknown
46
+ */
47
+ var sourceHeight = -1
48
+
49
+ /**
50
+ * The main source width where -1 means unknown
51
+ */
52
+ var sourceWidth = -1
53
+
54
+ /**
55
+ * The content fit of the placeholder
56
+ */
57
+ var placeholderContentFit: ContentFit? = null
58
+
59
+ private var request: Request? = null
60
+ private var sizeDeterminer = SizeDeterminer(imageViewHolder)
61
+
62
+ override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
63
+ // The image view should always be valid. When the view is deallocated, all targets should be
64
+ // canceled. Therefore that code shouldn't be called in that case. Instead of crashing, we
65
+ // decided to ignore that.
66
+ val imageView = imageViewHolder.get().ifNull {
67
+ Log.w("ExpoImage", "The `ExpoImageViewWrapper` was deallocated, but the target wasn't canceled in time.")
68
+ return
69
+ }
70
+
71
+ // The thumbnail and full request are handled in the same way by Glide.
72
+ // Here we're checking if the provided resource is the final bitmap or a thumbnail.
73
+ val isPlaceholder = if (request is ThumbnailRequestCoordinator) {
74
+ (request as? ThumbnailRequestCoordinator)
75
+ ?.getPrivateFullRequest()
76
+ ?.isComplete == false
77
+ } else {
78
+ false
79
+ }
80
+
81
+ imageView.onResourceReady(this, resource, isPlaceholder)
82
+ }
83
+
84
+ override fun onStart() = Unit
85
+
86
+ override fun onStop() = Unit
87
+
88
+ override fun onDestroy() = Unit
89
+
90
+ override fun onLoadStarted(placeholder: Drawable?) = Unit
91
+
92
+ // When loading fails, it's handled by the global listener, therefore that method can be NOOP.
93
+ override fun onLoadFailed(errorDrawable: Drawable?) = Unit
94
+
95
+ override fun onLoadCleared(placeholder: Drawable?) = Unit
96
+
97
+ override fun getSize(cb: SizeReadyCallback) {
98
+ // If we can't resolve the image, we just return unknown size.
99
+ // It shouldn't happen in a production application, because it means that our view was deallocated.
100
+ if (imageViewHolder.get() == null) {
101
+ cb.onSizeReady(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
102
+ return
103
+ }
104
+
105
+ sizeDeterminer.getSize(cb)
106
+ }
107
+
108
+ override fun removeCallback(cb: SizeReadyCallback) {
109
+ sizeDeterminer.removeCallback(cb)
110
+ }
111
+
112
+ override fun setRequest(request: Request?) {
113
+ this.request = request
114
+ }
115
+
116
+ override fun getRequest() = request
117
+
118
+ fun clear(requestManager: RequestManager) {
119
+ sizeDeterminer.clearCallbacksAndListener()
120
+ requestManager.clear(this)
121
+ }
122
+ }
123
+
124
+ // Copied from the Glide codebase.
125
+ // We modified that to receive a weak ref to our view instead of strong one.
126
+ internal class SizeDeterminer(private val imageViewHolder: WeakReference<ExpoImageViewWrapper>) {
127
+ private val cbs: MutableList<SizeReadyCallback> = ArrayList()
128
+
129
+ @Synthetic
130
+ var waitForLayout = false
131
+ private var layoutListener: SizeDeterminerLayoutListener? = null
132
+ private fun notifyCbs(width: Int, height: Int) {
133
+ // One or more callbacks may trigger the removal of one or more additional callbacks, so we
134
+ // need a copy of the list to avoid a concurrent modification exception. One place this
135
+ // happens is when a full request completes from the in memory cache while its thumbnail is
136
+ // still being loaded asynchronously. See #2237.
137
+ for (cb in ArrayList(cbs)) {
138
+ cb.onSizeReady(width, height)
139
+ }
140
+ }
141
+
142
+ @Synthetic
143
+ fun checkCurrentDimens() {
144
+ if (cbs.isEmpty()) {
145
+ return
146
+ }
147
+ val currentWidth = targetWidth
148
+ val currentHeight = targetHeight
149
+ if (!isViewStateAndSizeValid(currentWidth, currentHeight)) {
150
+ return
151
+ }
152
+ notifyCbs(currentWidth, currentHeight)
153
+ clearCallbacksAndListener()
154
+ }
155
+
156
+ fun getSize(cb: SizeReadyCallback) {
157
+ val view = imageViewHolder.get() ?: return
158
+
159
+ val currentWidth = targetWidth
160
+ val currentHeight = targetHeight
161
+ if (isViewStateAndSizeValid(currentWidth, currentHeight)) {
162
+ cb.onSizeReady(currentWidth, currentHeight)
163
+ return
164
+ }
165
+
166
+ // We want to notify callbacks in the order they were added and we only expect one or two
167
+ // callbacks to be added a time, so a List is a reasonable choice.
168
+ if (!cbs.contains(cb)) {
169
+ cbs.add(cb)
170
+ }
171
+ if (layoutListener == null) {
172
+ val observer = view.viewTreeObserver
173
+ layoutListener = SizeDeterminerLayoutListener(this)
174
+ observer.addOnPreDrawListener(layoutListener)
175
+ }
176
+ }
177
+
178
+ /**
179
+ * The callback may be called anyway if it is removed by another [SizeReadyCallback] or
180
+ * otherwise removed while we're notifying the list of callbacks.
181
+ *
182
+ *
183
+ * See #2237.
184
+ */
185
+ fun removeCallback(cb: SizeReadyCallback) {
186
+ cbs.remove(cb)
187
+ }
188
+
189
+ fun clearCallbacksAndListener() {
190
+ // Keep a reference to the layout attachStateListener and remove it here
191
+ // rather than having the observer remove itself because the observer
192
+ // we add the attachStateListener to will be almost immediately merged into
193
+ // another observer and will therefore never be alive. If we instead
194
+ // keep a reference to the attachStateListener and remove it here, we get the
195
+ // current view tree observer and should succeed.
196
+ val observer = imageViewHolder.get()?.viewTreeObserver
197
+ if (observer?.isAlive == true) {
198
+ observer.removeOnPreDrawListener(layoutListener)
199
+ }
200
+ layoutListener = null
201
+ cbs.clear()
202
+ }
203
+
204
+ private fun isViewStateAndSizeValid(width: Int, height: Int): Boolean {
205
+ return isDimensionValid(width) && isDimensionValid(height)
206
+ }
207
+
208
+ private val targetHeight: Int
209
+ get() {
210
+ val view = imageViewHolder.get() ?: return Target.SIZE_ORIGINAL
211
+ val verticalPadding = view.paddingTop + view.paddingBottom
212
+ val layoutParams = view.layoutParams
213
+ val layoutParamSize = layoutParams?.height ?: PENDING_SIZE
214
+ return getTargetDimen(view.height, layoutParamSize, verticalPadding)
215
+ }
216
+ private val targetWidth: Int
217
+ get() {
218
+ val view = imageViewHolder.get() ?: return Target.SIZE_ORIGINAL
219
+ val horizontalPadding = view.paddingLeft + view.paddingRight
220
+ val layoutParams = view.layoutParams
221
+ val layoutParamSize = layoutParams?.width ?: PENDING_SIZE
222
+ return getTargetDimen(view.width, layoutParamSize, horizontalPadding)
223
+ }
224
+
225
+ private fun getTargetDimen(viewSize: Int, paramSize: Int, paddingSize: Int): Int {
226
+ val view = imageViewHolder.get() ?: return Target.SIZE_ORIGINAL
227
+
228
+ // We consider the View state as valid if the View has non-null layout params and a non-zero
229
+ // layout params width and height. This is imperfect. We're making an assumption that View
230
+ // parents will obey their child's layout parameters, which isn't always the case.
231
+ val adjustedParamSize = paramSize - paddingSize
232
+ if (adjustedParamSize > 0) {
233
+ return adjustedParamSize
234
+ }
235
+
236
+ // Since we always prefer layout parameters with fixed sizes, even if waitForLayout is true,
237
+ // we might as well ignore it and just return the layout parameters above if we have them.
238
+ // Otherwise we should wait for a layout pass before checking the View's dimensions.
239
+ if (waitForLayout && view.isLayoutRequested) {
240
+ return PENDING_SIZE
241
+ }
242
+
243
+ // We also consider the View state valid if the View has a non-zero width and height. This
244
+ // means that the View has gone through at least one layout pass. It does not mean the Views
245
+ // width and height are from the current layout pass. For example, if a View is re-used in
246
+ // RecyclerView or ListView, this width/height may be from an old position. In some cases
247
+ // the dimensions of the View at the old position may be different than the dimensions of the
248
+ // View in the new position because the LayoutManager/ViewParent can arbitrarily decide to
249
+ // change them. Nevertheless, in most cases this should be a reasonable choice.
250
+ val adjustedViewSize = viewSize - paddingSize
251
+ if (adjustedViewSize > 0) {
252
+ return adjustedViewSize
253
+ }
254
+
255
+ // Finally we consider the view valid if the layout parameter size is set to wrap_content.
256
+ // It's difficult for Glide to figure out what to do here. Although Target.SIZE_ORIGINAL is a
257
+ // coherent choice, it's extremely dangerous because original images may be much too large to
258
+ // fit in memory or so large that only a couple can fit in memory, causing OOMs. If users want
259
+ // the original image, they can always use .override(Target.SIZE_ORIGINAL). Since wrap_content
260
+ // may never resolve to a real size unless we load something, we aim for a square whose length
261
+ // is the largest screen size. That way we're loading something and that something has some
262
+ // hope of being downsampled to a size that the device can support. We also log a warning that
263
+ // tries to explain what Glide is doing and why some alternatives are preferable.
264
+ // Since WRAP_CONTENT is sometimes used as a default layout parameter, we always wait for
265
+ // layout to complete before using this fallback parameter (ConstraintLayout among others).
266
+ if (!view.isLayoutRequested && paramSize == ViewGroup.LayoutParams.WRAP_CONTENT) {
267
+ return getMaxDisplayLength(view.context)
268
+ }
269
+
270
+ // If the layout parameters are < padding, the view size is < padding, or the layout
271
+ // parameters are set to match_parent or wrap_content and no layout has occurred, we should
272
+ // wait for layout and repeat.
273
+ return PENDING_SIZE
274
+ }
275
+
276
+ private fun isDimensionValid(size: Int): Boolean {
277
+ return size > 0 || size == Target.SIZE_ORIGINAL
278
+ }
279
+
280
+ private class SizeDeterminerLayoutListener(sizeDeterminer: SizeDeterminer) : ViewTreeObserver.OnPreDrawListener {
281
+ private val sizeDeterminerRef: WeakReference<SizeDeterminer>
282
+
283
+ init {
284
+ sizeDeterminerRef = WeakReference(sizeDeterminer)
285
+ }
286
+
287
+ override fun onPreDraw(): Boolean {
288
+ val sizeDeterminer = sizeDeterminerRef.get()
289
+ sizeDeterminer?.checkCurrentDimens()
290
+ return true
291
+ }
292
+ }
293
+
294
+ companion object {
295
+ // Some negative sizes (Target.SIZE_ORIGINAL) are valid, 0 is never valid.
296
+ private const val PENDING_SIZE = 0
297
+
298
+ @VisibleForTesting
299
+ var maxDisplayLength: Int? = null
300
+
301
+ // Use the maximum to avoid depending on the device's current orientation.
302
+ private fun getMaxDisplayLength(context: Context): Int {
303
+ if (maxDisplayLength == null) {
304
+ val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
305
+ val display = Preconditions.checkNotNull(windowManager).defaultDisplay
306
+ val displayDimensions = Point()
307
+ display.getSize(displayDimensions)
308
+ maxDisplayLength = max(displayDimensions.x, displayDimensions.y)
309
+ }
310
+ return maxDisplayLength!!
311
+ }
312
+ }
313
+ }
@@ -0,0 +1,47 @@
1
+ package expo.modules.image
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.content.Context
5
+ import android.net.Uri
6
+ import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper
7
+ import java.util.*
8
+
9
+ object ResourceIdHelper {
10
+ private val idMap = mutableMapOf<String, Int>()
11
+
12
+ @SuppressLint("DiscouragedApi")
13
+ private fun getResourceRawId(context: Context, name: String): Int {
14
+ if (name.isEmpty()) {
15
+ return -1
16
+ }
17
+
18
+ val normalizedName = name.lowercase(Locale.ROOT).replace("-", "_")
19
+ synchronized(this) {
20
+ val id = idMap[normalizedName]
21
+ if (id != null) {
22
+ return id
23
+ }
24
+
25
+ return context
26
+ .resources
27
+ .getIdentifier(normalizedName, "raw", context.packageName)
28
+ .also {
29
+ idMap[normalizedName] = it
30
+ }
31
+ }
32
+ }
33
+
34
+ fun getResourceUri(context: Context, name: String): Uri? {
35
+ val drawableUri = ResourceDrawableIdHelper.getInstance().getResourceDrawableUri(context, name)
36
+ if (drawableUri != null && drawableUri != Uri.EMPTY) {
37
+ return drawableUri
38
+ }
39
+
40
+ val resId = getResourceRawId(context, name)
41
+ return if (resId > 0) {
42
+ Uri.Builder().scheme("res").path(resId.toString()).build()
43
+ } else {
44
+ null
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,21 @@
1
+ package expo.modules.image
2
+
3
+ import android.util.Log
4
+ import com.bumptech.glide.request.Request
5
+ import com.bumptech.glide.request.ThumbnailRequestCoordinator
6
+
7
+ fun ThumbnailRequestCoordinator.getPrivateFullRequest(): Request? {
8
+ return getPrivateField("full")
9
+ }
10
+
11
+ private fun <T> ThumbnailRequestCoordinator.getPrivateField(name: String): T? {
12
+ return try {
13
+ val field = this.javaClass.getDeclaredField(name)
14
+ field.isAccessible = true
15
+ @Suppress("UNCHECKED_CAST")
16
+ field.get(this) as T
17
+ } catch (e: Throwable) {
18
+ Log.e("ExpoImage", "Couldn't receive the `$name` field", e)
19
+ null
20
+ }
21
+ }
@@ -0,0 +1,20 @@
1
+ package expo.modules.image
2
+
3
+ import com.facebook.yoga.YogaConstants
4
+
5
+ fun Float.ifYogaUndefinedUse(value: Float) =
6
+ if (YogaConstants.isUndefined(this)) {
7
+ value
8
+ } else {
9
+ this
10
+ }
11
+
12
+ inline fun Float.ifYogaDefinedUse(transformFun: (current: Float) -> Float) =
13
+ if (YogaConstants.isUndefined(this)) {
14
+ this
15
+ } else {
16
+ transformFun(this)
17
+ }
18
+
19
+ fun makeYogaUndefinedIfNegative(value: Float) =
20
+ if (!YogaConstants.isUndefined(value) && value < 0) YogaConstants.UNDEFINED else value
@@ -0,0 +1,32 @@
1
+ package expo.modules.image.blurhash
2
+
3
+ import android.graphics.Bitmap
4
+ import com.bumptech.glide.Priority
5
+ import com.bumptech.glide.load.DataSource
6
+ import com.bumptech.glide.load.data.DataFetcher
7
+ import expo.modules.kotlin.exception.CodedException
8
+
9
+ class BlurhashDecodingFailure(blurHash: String?) : CodedException(
10
+ message = "Cannot decode provided blurhash '$blurHash'"
11
+ )
12
+
13
+ class BlurHashFetcher(
14
+ private val blurHash: String?,
15
+ private val width: Int,
16
+ private val height: Int,
17
+ private val punch: Float
18
+ ) : DataFetcher<Bitmap> {
19
+ override fun cleanup() = Unit
20
+ override fun cancel() = Unit
21
+ override fun getDataClass(): Class<Bitmap> = Bitmap::class.java
22
+ override fun getDataSource(): DataSource = DataSource.LOCAL
23
+
24
+ override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in Bitmap>) {
25
+ val bitmap = BlurhashDecoder.decode(blurHash, width, height, punch)
26
+ if (bitmap == null) {
27
+ callback.onLoadFailed(BlurhashDecodingFailure(blurHash))
28
+ return
29
+ }
30
+ callback.onDataReady(bitmap)
31
+ }
32
+ }