@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,652 @@
1
+ package expo.modules.image
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.app.Activity
5
+ import android.content.Context
6
+ import android.graphics.drawable.Animatable
7
+ import android.graphics.drawable.Drawable
8
+ import android.os.Build
9
+ import android.os.Handler
10
+ import android.view.View
11
+ import android.widget.FrameLayout
12
+ import androidx.core.view.AccessibilityDelegateCompat
13
+ import androidx.core.view.ViewCompat
14
+ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
15
+ import androidx.core.view.isVisible
16
+ import com.bumptech.glide.Glide
17
+ import com.bumptech.glide.RequestManager
18
+ import com.bumptech.glide.load.engine.DiskCacheStrategy
19
+ import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy
20
+ import com.bumptech.glide.request.RequestOptions
21
+ import com.bumptech.glide.request.target.Target.SIZE_ORIGINAL
22
+ import com.facebook.yoga.YogaConstants
23
+ import expo.modules.image.enums.ContentFit
24
+ import expo.modules.image.enums.Priority
25
+ import expo.modules.image.events.GlideRequestListener
26
+ import expo.modules.image.events.OkHttpProgressListener
27
+ import expo.modules.image.okhttp.GlideUrlWrapper
28
+ import expo.modules.image.records.CachePolicy
29
+ import expo.modules.image.records.ContentPosition
30
+ import expo.modules.image.records.ImageErrorEvent
31
+ import expo.modules.image.records.ImageLoadEvent
32
+ import expo.modules.image.records.ImageProgressEvent
33
+ import expo.modules.image.records.ImageTransition
34
+ import expo.modules.image.records.SourceMap
35
+ import expo.modules.kotlin.AppContext
36
+ import expo.modules.kotlin.viewevent.EventDispatcher
37
+ import expo.modules.kotlin.views.ExpoView
38
+ import jp.wasabeef.glide.transformations.BlurTransformation
39
+ import java.lang.Float.max
40
+ import java.lang.ref.WeakReference
41
+ import kotlin.math.abs
42
+ import kotlin.math.min
43
+
44
+ @SuppressLint("ViewConstructor")
45
+ class ExpoImageViewWrapper(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
46
+ private val activity: Activity
47
+ get() = appContext.currentActivity ?: throw MissingActivity()
48
+
49
+ internal val requestManager = getOrCreateRequestManager(appContext, activity)
50
+ private val progressListener = OkHttpProgressListener(WeakReference(this))
51
+
52
+ private val firstView = ExpoImageView(activity)
53
+ private val secondView = ExpoImageView(activity)
54
+
55
+ private val mainHandler = Handler(context.mainLooper)
56
+
57
+ /**
58
+ * @returns the view which is currently active or will be used when both views are empty
59
+ */
60
+ private val activeView: ExpoImageView
61
+ get() {
62
+ if (secondView.drawable != null) {
63
+ return secondView
64
+ }
65
+ return firstView
66
+ }
67
+
68
+ private var firstTarget = ImageViewWrapperTarget(WeakReference(this))
69
+ private var secondTarget = ImageViewWrapperTarget(WeakReference(this))
70
+
71
+ internal val onLoadStart by EventDispatcher<Unit>()
72
+ internal val onProgress by EventDispatcher<ImageProgressEvent>()
73
+ internal val onError by EventDispatcher<ImageErrorEvent>()
74
+ internal val onLoad by EventDispatcher<ImageLoadEvent>()
75
+
76
+ internal var sources: List<SourceMap> = emptyList()
77
+ private val bestSource: SourceMap?
78
+ get() = getBestSource(sources)
79
+
80
+ internal var placeholders: List<SourceMap> = emptyList()
81
+ private val bestPlaceholder: SourceMap?
82
+ get() = getBestSource(placeholders)
83
+
84
+ internal var blurRadius: Int? = null
85
+ set(value) {
86
+ if (field != value) {
87
+ shouldRerender = true
88
+ }
89
+ field = value
90
+ }
91
+
92
+ internal var transition: ImageTransition? = null
93
+
94
+ internal var contentFit: ContentFit = ContentFit.Cover
95
+ set(value) {
96
+ field = value
97
+ activeView.contentFit = value
98
+ transformationMatrixChanged = true
99
+ }
100
+
101
+ internal var placeholderContentFit: ContentFit = ContentFit.ScaleDown
102
+ set(value) {
103
+ field = value
104
+ activeView.placeholderContentFit = value
105
+ transformationMatrixChanged = true
106
+ }
107
+
108
+ internal var contentPosition: ContentPosition = ContentPosition.center
109
+ set(value) {
110
+ field = value
111
+ activeView.contentPosition = value
112
+ transformationMatrixChanged = true
113
+ }
114
+
115
+ internal var borderStyle: String? = null
116
+ set(value) {
117
+ field = value
118
+ activeView.setBorderStyle(value)
119
+ }
120
+
121
+ internal var backgroundColor: Int? = null
122
+ set(value) {
123
+ field = value
124
+ activeView.setBackgroundColor(value)
125
+ }
126
+
127
+ internal var tintColor: Int? = null
128
+ set(value) {
129
+ field = value
130
+ activeView.setTintColor(value)
131
+ }
132
+
133
+ internal var isFocusableProp: Boolean = false
134
+ set(value) {
135
+ field = value
136
+ activeView.isFocusable = value
137
+ }
138
+
139
+ internal var accessible: Boolean = false
140
+ set(value) {
141
+ field = value
142
+ setIsScreenReaderFocusable(activeView, value)
143
+ }
144
+
145
+ internal var accessibilityLabel: String? = null
146
+ set(value) {
147
+ field = value
148
+ activeView.contentDescription = accessibilityLabel
149
+ }
150
+
151
+ var recyclingKey: String? = null
152
+ set(value) {
153
+ clearViewBeforeChangingSource = field != null && value != null && value != field
154
+ field = value
155
+ }
156
+
157
+ internal var allowDownscaling: Boolean = true
158
+ set(value) {
159
+ field = value
160
+ shouldRerender = true
161
+ }
162
+
163
+ internal var priority: Priority = Priority.NORMAL
164
+ internal var cachePolicy: CachePolicy = CachePolicy.DISK
165
+
166
+ private var borderRadius = FloatArray(9) { YogaConstants.UNDEFINED }
167
+ private var borderWidth = FloatArray(9) { YogaConstants.UNDEFINED }
168
+ private var borderColor = Array(9) { YogaConstants.UNDEFINED to YogaConstants.UNDEFINED }
169
+
170
+ fun setBorderRadius(index: Int, radius: Float) {
171
+ borderRadius[index] = radius
172
+ activeView.setBorderRadius(index, radius)
173
+ }
174
+
175
+ fun setBorderWidth(index: Int, width: Float) {
176
+ borderWidth[index] = width
177
+ activeView.setBorderWidth(index, width)
178
+ }
179
+
180
+ fun setBorderColor(index: Int, rgb: Float, alpha: Float) {
181
+ borderColor[index] = rgb to alpha
182
+ activeView.setBorderColor(index, rgb, alpha)
183
+ }
184
+
185
+ /**
186
+ * Whether the image should be loaded again
187
+ */
188
+ private var shouldRerender = false
189
+
190
+ /**
191
+ * Currently loaded source
192
+ */
193
+ private var loadedSource: GlideModel? = null
194
+
195
+ /**
196
+ * Whether the transformation matrix should be reapplied
197
+ */
198
+ private var transformationMatrixChanged = false
199
+
200
+ /**
201
+ * Whether the view content should be cleared to blank when the source was changed.
202
+ */
203
+ private var clearViewBeforeChangingSource = false
204
+
205
+ /**
206
+ * Copies saved props to the provided view.
207
+ * It ensures that the view state is up to date.
208
+ */
209
+ private fun copyProps(view: ExpoImageView) {
210
+ view.contentFit = contentFit
211
+ view.contentPosition = contentPosition
212
+ view.setBorderStyle(borderStyle)
213
+ view.setBackgroundColor(backgroundColor)
214
+ view.setTintColor(tintColor)
215
+ view.isFocusable = isFocusableProp
216
+ view.contentDescription = accessibilityLabel
217
+ borderColor.forEachIndexed { index, (rgb, alpha) ->
218
+ view.setBorderColor(index, rgb, alpha)
219
+ }
220
+ borderRadius.forEachIndexed { index, value ->
221
+ view.setBorderRadius(index, value)
222
+ }
223
+ borderWidth.forEachIndexed { index, value ->
224
+ view.setBorderWidth(index, value)
225
+ }
226
+ setIsScreenReaderFocusable(view, accessible)
227
+ }
228
+
229
+ /**
230
+ * Allows `isScreenReaderFocusable` to be set on apis below level 28
231
+ */
232
+ private fun setIsScreenReaderFocusable(view: View, value: Boolean) {
233
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
234
+ view.isScreenReaderFocusable = value
235
+ } else {
236
+ ViewCompat.setAccessibilityDelegate(
237
+ this,
238
+ object : AccessibilityDelegateCompat() {
239
+ override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
240
+ info.isScreenReaderFocusable = value
241
+ super.onInitializeAccessibilityNodeInfo(host, info)
242
+ }
243
+ }
244
+ )
245
+ }
246
+ }
247
+
248
+ /**
249
+ * When a new resource is available, this method tries to handle it.
250
+ * It decides where provided bitmap should be displayed and clears the previous target/image.
251
+ */
252
+ fun onResourceReady(
253
+ target: ImageViewWrapperTarget,
254
+ resource: Drawable,
255
+ isPlaceholder: Boolean = false
256
+ ) =
257
+ // The "onResourceReady" function will be triggered when the new resource is available by the Glide.
258
+ // According to the Glide documentation (https://bumptech.github.io/glide/doc/debugging.html#you-cant-start-or-clear-loads-in-requestlistener-or-target-callbacks),
259
+ // it's not advisable to clear the Glide target within the stack frame.
260
+ // To avoid this, a new runnable is posted to the front of the main queue, which can then clean or create targets.
261
+ // This ensures that the "onResourceReady" frame of the Glide code will be discarded, and the internal state can be altered once again.
262
+ // Normally, using "postAtFrontOfQueue" can lead to issues such as message queue starvation, ordering problems, and other unexpected consequences.
263
+ // However, in this case, it is safe to use as long as nothing else is added to the queue.
264
+ // The intention is simply to wait for the Glide code to finish before the content of the underlying views is changed during the same rendering tick.
265
+ mainHandler.postAtFrontOfQueue {
266
+ val transitionDuration = (transition?.duration ?: 0).toLong()
267
+
268
+ // If provided resource is a placeholder, but the target doesn't have a source, we treat it as a normal image.
269
+ if (!isPlaceholder || !target.hasSource) {
270
+ val (newView, previousView) = if (firstView.drawable == null) {
271
+ firstView to secondView
272
+ } else {
273
+ secondView to firstView
274
+ }
275
+
276
+ val clearPreviousView = {
277
+ previousView
278
+ .recycleView()
279
+ ?.apply {
280
+ // When the placeholder is loaded, one target is displayed in both views.
281
+ // So we just have to move the reference to a new view instead of clearing the target.
282
+ if (this != target) {
283
+ clear(requestManager)
284
+ }
285
+ }
286
+ }
287
+
288
+ configureView(newView, target, resource, isPlaceholder)
289
+ if (transitionDuration <= 0) {
290
+ clearPreviousView()
291
+ newView.alpha = 1f
292
+ newView.bringToFront()
293
+ } else {
294
+ newView.bringToFront()
295
+ previousView.alpha = 1f
296
+ newView.alpha = 0f
297
+ previousView.animate().apply {
298
+ duration = transitionDuration
299
+ alpha(0f)
300
+ withEndAction {
301
+ clearPreviousView()
302
+ }
303
+ }
304
+ newView.animate().apply {
305
+ duration = transitionDuration
306
+ alpha(1f)
307
+ }
308
+ }
309
+ } else {
310
+ // We don't want to show the placeholder if something is currently displayed.
311
+ // There is one exception - when we're displaying a different placeholder.
312
+ if ((firstView.drawable != null && !firstView.isPlaceholder) || secondView.drawable != null) {
313
+ return@postAtFrontOfQueue
314
+ }
315
+
316
+ firstView
317
+ .recycleView()
318
+ ?.apply {
319
+ // The current target is already bound to the view. We don't want to cancel it in that case.
320
+ if (this != target) {
321
+ clear(requestManager)
322
+ }
323
+ }
324
+
325
+ configureView(firstView, target, resource, isPlaceholder)
326
+ if (transitionDuration > 0) {
327
+ firstView.bringToFront()
328
+ firstView.alpha = 0f
329
+ secondView.isVisible = false
330
+ firstView.animate().apply {
331
+ duration = transitionDuration
332
+ alpha(1f)
333
+ }
334
+ }
335
+ }
336
+ }
337
+
338
+ private fun configureView(
339
+ view: ExpoImageView,
340
+ target: ImageViewWrapperTarget,
341
+ resource: Drawable,
342
+ isPlaceholder: Boolean
343
+ ) {
344
+ view.let {
345
+ it.setImageDrawable(resource)
346
+
347
+ it.isPlaceholder = isPlaceholder
348
+ it.placeholderContentFit = target.placeholderContentFit ?: ContentFit.ScaleDown
349
+ copyProps(it)
350
+
351
+ it.isVisible = true
352
+
353
+ it.currentTarget = target
354
+
355
+ // The view isn't layout when it's invisible.
356
+ // Therefore, we have to set the correct size manually.
357
+ it.layout(0, 0, width, height)
358
+
359
+ it.applyTransformationMatrix()
360
+ }
361
+ target.isUsed = true
362
+
363
+ if (resource is Animatable) {
364
+ resource.start()
365
+ }
366
+ }
367
+
368
+ private fun getBestSource(sources: List<SourceMap>): SourceMap? {
369
+ if (sources.isEmpty()) {
370
+ return null
371
+ }
372
+
373
+ if (sources.size == 1) {
374
+ return sources.first()
375
+ }
376
+
377
+ val targetPixelCount = width * height
378
+ if (targetPixelCount == 0) {
379
+ return null
380
+ }
381
+
382
+ var bestSource: SourceMap? = null
383
+ var bestFit = Double.MAX_VALUE
384
+
385
+ sources.forEach {
386
+ val fit = abs(1 - (it.pixelCount / targetPixelCount))
387
+ if (fit < bestFit) {
388
+ bestFit = fit
389
+ bestSource = it
390
+ }
391
+ }
392
+
393
+ return bestSource
394
+ }
395
+
396
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
397
+ super.onSizeChanged(w, h, oldw, oldh)
398
+ rerenderIfNeeded(
399
+ shouldRerenderBecauseOfResize = allowDownscaling &&
400
+ contentFit != ContentFit.Fill &&
401
+ contentFit != ContentFit.None
402
+ )
403
+ }
404
+
405
+ private fun createPropOptions(): RequestOptions {
406
+ return RequestOptions()
407
+ .apply {
408
+ priority(this@ExpoImageViewWrapper.priority.toGlidePriority())
409
+
410
+ if (cachePolicy != CachePolicy.MEMORY_AND_DISK && cachePolicy != CachePolicy.MEMORY) {
411
+ skipMemoryCache(true)
412
+ }
413
+ if (cachePolicy == CachePolicy.NONE || cachePolicy == CachePolicy.MEMORY) {
414
+ diskCacheStrategy(DiskCacheStrategy.NONE)
415
+ }
416
+
417
+ blurRadius?.let {
418
+ transform(BlurTransformation(min(it, 25), 4))
419
+ }
420
+ }
421
+ }
422
+
423
+ fun onViewDestroys() {
424
+ firstView.setImageDrawable(null)
425
+ secondView.setImageDrawable(null)
426
+
427
+ requestManager.clear(firstTarget)
428
+ requestManager.clear(secondTarget)
429
+ }
430
+
431
+ internal fun rerenderIfNeeded(shouldRerenderBecauseOfResize: Boolean = false) {
432
+ val bestSource = bestSource
433
+ val bestPlaceholder = bestPlaceholder
434
+
435
+ val sourceToLoad = bestSource?.createGlideModel(context)
436
+ val placeholder = bestPlaceholder?.createGlideModel(context)
437
+ // We only clean the image when the source is set to null and we don't have a placeholder or the view is empty.
438
+ if (width == 0 || height == 0 || (bestSource == null || sourceToLoad == null) && placeholder == null) {
439
+ firstView.recycleView()
440
+ secondView.recycleView()
441
+
442
+ requestManager.clear(firstTarget)
443
+ requestManager.clear(secondTarget)
444
+
445
+ shouldRerender = false
446
+ loadedSource = null
447
+ transformationMatrixChanged = false
448
+ clearViewBeforeChangingSource = false
449
+ return
450
+ }
451
+
452
+ val shouldRerender = sourceToLoad != loadedSource || shouldRerender || (sourceToLoad == null && placeholder != null)
453
+ if (shouldRerender || shouldRerenderBecauseOfResize) {
454
+ if (clearViewBeforeChangingSource) {
455
+ val activeView = if (firstView.drawable != null) {
456
+ firstView
457
+ } else {
458
+ secondView
459
+ }
460
+
461
+ activeView
462
+ .recycleView()
463
+ ?.apply {
464
+ clear(requestManager)
465
+ }
466
+ }
467
+
468
+ this.shouldRerender = false
469
+ loadedSource = sourceToLoad
470
+ val options = bestSource?.createOptions(context)
471
+ val propOptions = createPropOptions()
472
+
473
+ val model = sourceToLoad?.glideData
474
+ if (model is GlideUrlWrapper) {
475
+ model.progressListener = progressListener
476
+ }
477
+
478
+ onLoadStart.invoke(Unit)
479
+ val newTarget = if (secondTarget.isUsed) {
480
+ firstTarget
481
+ } else {
482
+ secondTarget
483
+ }
484
+ newTarget.hasSource = sourceToLoad != null
485
+
486
+ val downsampleStrategy = if (allowDownscaling) {
487
+ object : DownsampleStrategy() {
488
+ var wasTriggered = false
489
+ override fun getScaleFactor(
490
+ sourceWidth: Int,
491
+ sourceHeight: Int,
492
+ requestedWidth: Int,
493
+ requestedHeight: Int
494
+ ): Float {
495
+ // The method is invoked twice per asset, but we only need to preserve the original dimensions for the first call.
496
+ // As Glide uses Android downsampling, it can only adjust dimensions by a factor of two,
497
+ // and hence two distinct scaling factors are computed to achieve greater accuracy.
498
+ if (!wasTriggered) {
499
+ newTarget.sourceWidth = sourceWidth
500
+ newTarget.sourceHeight = sourceHeight
501
+ wasTriggered = true
502
+ }
503
+
504
+ // The size of the container is unknown, we don't know what to do, so we just run the default scale.
505
+ if (requestedWidth == SIZE_ORIGINAL || requestedHeight == SIZE_ORIGINAL) {
506
+ return 1f
507
+ }
508
+
509
+ val aspectRation = calculateScaleFactor(
510
+ sourceWidth.toFloat(),
511
+ sourceHeight.toFloat(),
512
+ requestedWidth.toFloat(),
513
+ requestedHeight.toFloat()
514
+ )
515
+
516
+ // We don't want to upscale the image
517
+ return min(1f, aspectRation)
518
+ }
519
+
520
+ private fun calculateScaleFactor(
521
+ sourceWidth: Float,
522
+ sourceHeight: Float,
523
+ requestedWidth: Float,
524
+ requestedHeight: Float
525
+ ): Float = when (contentFit) {
526
+ ContentFit.Contain -> min(
527
+ requestedWidth / sourceWidth,
528
+ requestedHeight / sourceHeight
529
+ )
530
+ ContentFit.Cover -> max(
531
+ requestedWidth / sourceWidth,
532
+ requestedHeight / sourceHeight
533
+ )
534
+ ContentFit.Fill, ContentFit.None -> 1f
535
+ ContentFit.ScaleDown -> if (requestedWidth < sourceWidth || requestedHeight < sourceHeight) {
536
+ // The container is smaller than the image — scale it down and behave like `contain`
537
+ min(
538
+ requestedWidth / sourceWidth,
539
+ requestedHeight / sourceHeight
540
+ )
541
+ } else {
542
+ // The container is bigger than the image — don't scale it and behave like `none`
543
+ 1f
544
+ }
545
+ }
546
+
547
+ override fun getSampleSizeRounding(
548
+ sourceWidth: Int,
549
+ sourceHeight: Int,
550
+ requestedWidth: Int,
551
+ requestedHeight: Int
552
+ ) = SampleSizeRounding.QUALITY
553
+ }
554
+ } else {
555
+ DownsampleStrategy.NONE
556
+ }
557
+
558
+ val request = requestManager
559
+ .asDrawable()
560
+ .load(model)
561
+ .apply {
562
+ if (placeholder != null) {
563
+ thumbnail(requestManager.load(placeholder.glideData))
564
+ val newPlaceholderContentFit = if (bestPlaceholder.isBlurhash() || bestPlaceholder.isThumbhash()) {
565
+ contentFit
566
+ } else {
567
+ placeholderContentFit
568
+ }
569
+ newTarget.placeholderContentFit = newPlaceholderContentFit
570
+ }
571
+ }
572
+ .apply {
573
+ options?.let {
574
+ apply(it)
575
+ }
576
+ }
577
+ .downsample(downsampleStrategy)
578
+ .addListener(GlideRequestListener(WeakReference(this)))
579
+ .encodeQuality(100)
580
+ .apply(propOptions)
581
+
582
+ request.into(newTarget)
583
+ } else {
584
+ // In the case where the source didn't change, but the transformation matrix has to be
585
+ // recalculated, we can apply the new transformation right away.
586
+ // When the source and the matrix is different, we don't want to do anything.
587
+ // We don't want to changed the transformation of the currently displayed image.
588
+ // The new matrix will be applied when new resource is loaded.
589
+ if (transformationMatrixChanged) {
590
+ activeView.applyTransformationMatrix()
591
+ }
592
+ }
593
+ transformationMatrixChanged = false
594
+ clearViewBeforeChangingSource = false
595
+ }
596
+
597
+ init {
598
+ val matchParent = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
599
+
600
+ layoutParams = matchParent
601
+
602
+ firstView.isVisible = true
603
+ secondView.isVisible = true
604
+
605
+ // We need to add a `FrameLayout` to allow views to overflow.
606
+ // With the `LinearLayout` is impossible to render two views on each other.
607
+ val layout = FrameLayout(context).apply {
608
+ layoutParams = matchParent
609
+ addView(
610
+ firstView,
611
+ matchParent
612
+ )
613
+ addView(
614
+ secondView,
615
+ matchParent
616
+ )
617
+ }
618
+
619
+ addView(layout, matchParent)
620
+ }
621
+
622
+ companion object {
623
+ private var requestManager: RequestManager? = null
624
+ private var appContextRef: WeakReference<AppContext?> = WeakReference(null)
625
+ private var activityRef: WeakReference<Activity?> = WeakReference(null)
626
+
627
+ fun getOrCreateRequestManager(
628
+ appContext: AppContext,
629
+ activity: Activity
630
+ ): RequestManager = synchronized(Companion) {
631
+ val cachedRequestManager = requestManager
632
+ ?: return createNewRequestManager(activity).also {
633
+ requestManager = it
634
+ appContextRef = WeakReference(appContext)
635
+ activityRef = WeakReference(activity)
636
+ }
637
+
638
+ // Request manager was created using different activity or app context
639
+ if (appContextRef.get() != appContext || activityRef.get() != activity) {
640
+ return createNewRequestManager(activity).also {
641
+ requestManager = it
642
+ appContextRef = WeakReference(appContext)
643
+ activityRef = WeakReference(activity)
644
+ }
645
+ }
646
+
647
+ return cachedRequestManager
648
+ }
649
+
650
+ private fun createNewRequestManager(activity: Activity): RequestManager = Glide.with(activity)
651
+ }
652
+ }