expo-image 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -10,6 +10,15 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 1.2.1 — 2023-04-17
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - [Android] Fix `url` property returned by the `onLoad` event. ([#22161](https://github.com/expo/expo/pull/22161) by [@lukmccall](https://github.com/lukmccall))
18
+ - [Android] Fix images not loading after the app was foregrounded. ([#22159](https://github.com/expo/expo/pull/22159) by [@lukmccall](https://github.com/lukmccall))
19
+ - [Android] Fixed image was loaded event if the view dimensions were 0. ([#22157](https://github.com/expo/expo/pull/22157) by [@lukmccall](https://github.com/lukmccall))
20
+ - Fix generating the image from ThumbHash that starts with a slash character. ([#22160](https://github.com/expo/expo/pull/22160) by [@tsapeta](https://github.com/tsapeta))
21
+
13
22
  ## 1.2.0 — 2023-04-14
14
23
 
15
24
  ### 🎉 New features
@@ -22,6 +31,7 @@
22
31
  ### 🐛 Bug fixes
23
32
 
24
33
  - [Web] Prevent breaking in static rendering environments. ([#21883](https://github.com/expo/expo/pull/21883) by [@EvanBacon](https://github.com/EvanBacon))
34
+ - [Android] Fixed image disappearing before navigation animation is complete. ([#22066](https://github.com/expo/expo/pull/22066) by [@sallen450](https://github.com/sallen450))
25
35
  - [Web] Fixed monorepo asset resolution in production for Metro web. ([#22094](https://github.com/expo/expo/pull/22094) by [@EvanBacon](https://github.com/EvanBacon))
26
36
 
27
37
  ## 1.1.0 — 2023-03-25
@@ -49,7 +49,7 @@ android {
49
49
  minSdkVersion safeExtGet("minSdkVersion", 21)
50
50
  targetSdkVersion safeExtGet("targetSdkVersion", 33)
51
51
  versionCode 1
52
- versionName "1.2.0"
52
+ versionName "1.2.1"
53
53
  }
54
54
  lintOptions {
55
55
  abortOnError false
@@ -1,6 +1,7 @@
1
1
  package expo.modules.image
2
2
 
3
3
  import android.view.View
4
+ import androidx.core.view.doOnDetach
4
5
  import com.bumptech.glide.Glide
5
6
  import com.bumptech.glide.load.model.GlideUrl
6
7
  import com.facebook.react.uimanager.PixelUtil
@@ -171,7 +172,9 @@ class ExpoImageModule : Module() {
171
172
  }
172
173
 
173
174
  OnViewDestroys { view: ExpoImageViewWrapper ->
174
- view.onViewDestroys()
175
+ view.doOnDetach {
176
+ view.onViewDestroys()
177
+ }
175
178
  }
176
179
  }
177
180
  }
@@ -3,7 +3,6 @@ package expo.modules.image
3
3
  import android.annotation.SuppressLint
4
4
  import android.app.Activity
5
5
  import android.content.Context
6
- import android.graphics.Rect
7
6
  import android.graphics.drawable.Animatable
8
7
  import android.graphics.drawable.Drawable
9
8
  import android.os.Build
@@ -371,13 +370,11 @@ class ExpoImageViewWrapper(context: Context, appContext: AppContext) : ExpoView(
371
370
  return sources.first()
372
371
  }
373
372
 
374
- val selfRect = Rect(0, 0, width, height)
375
- if (selfRect.isEmpty) {
373
+ val targetPixelCount = width * height
374
+ if (targetPixelCount == 0) {
376
375
  return null
377
376
  }
378
377
 
379
- val targetPixelCount = selfRect.width() * selfRect.height()
380
-
381
378
  var bestSource: SourceMap? = null
382
379
  var bestFit = Double.MAX_VALUE
383
380
 
@@ -433,8 +430,8 @@ class ExpoImageViewWrapper(context: Context, appContext: AppContext) : ExpoView(
433
430
 
434
431
  val sourceToLoad = bestSource?.createGlideModel(context)
435
432
  val placeholder = bestPlaceholder?.createGlideModel(context)
436
- // We only clean the image when the source is set to null and we don't have a placeholder.
437
- if ((bestSource == null || sourceToLoad == null) && placeholder == null) {
433
+ // We only clean the image when the source is set to null and we don't have a placeholder or the view is empty.
434
+ if (width == 0 || height == 0 || (bestSource == null || sourceToLoad == null) && placeholder == null) {
438
435
  firstView.recycleView()
439
436
  secondView.recycleView()
440
437
 
@@ -621,6 +618,7 @@ class ExpoImageViewWrapper(context: Context, appContext: AppContext) : ExpoView(
621
618
  companion object {
622
619
  private var requestManager: RequestManager? = null
623
620
  private var appContextRef: WeakReference<AppContext?> = WeakReference(null)
621
+ private var activityRef: WeakReference<Activity?> = WeakReference(null)
624
622
 
625
623
  fun getOrCreateRequestManager(
626
624
  appContext: AppContext,
@@ -630,13 +628,15 @@ class ExpoImageViewWrapper(context: Context, appContext: AppContext) : ExpoView(
630
628
  ?: return createNewRequestManager(activity).also {
631
629
  requestManager = it
632
630
  appContextRef = WeakReference(appContext)
631
+ activityRef = WeakReference(activity)
633
632
  }
634
633
 
635
- // Request manager was created using different activity
636
- if (appContextRef.get() != appContext) {
634
+ // Request manager was created using different activity or app context
635
+ if (appContextRef.get() != appContext || activityRef.get() != activity) {
637
636
  return createNewRequestManager(activity).also {
638
637
  requestManager = it
639
638
  appContextRef = WeakReference(appContext)
639
+ activityRef = WeakReference(activity)
640
640
  }
641
641
  }
642
642
 
@@ -62,6 +62,10 @@ class GlideUrlWithCustomCacheKey(
62
62
  */
63
63
  data class GlideUrlWrapper(val glideUrl: GlideUrl) {
64
64
  var progressListener: OkHttpProgressListener? = null
65
+
66
+ override fun toString(): String {
67
+ return glideUrl.toString()
68
+ }
65
69
  }
66
70
 
67
71
  @GlideModule
@@ -13,8 +13,11 @@ class ThumbhashModelLoader : ModelLoader<GlideThumbhashModel, Bitmap> {
13
13
  override fun handles(model: GlideThumbhashModel): Boolean = true
14
14
 
15
15
  override fun buildLoadData(model: GlideThumbhashModel, width: Int, height: Int, options: Options): ModelLoader.LoadData<Bitmap> {
16
- // Thumbhash might contain '/' characters
17
- val thumbhash = model.uri.pathSegments.joinToString(separator = "/")
16
+ // The URI looks like this: thumbhash:/3OcRJYB4d3h\iIeHeEh3eIhw+j2w
17
+ // ThumbHash may include slashes which could break the structure of the URL, so we replace them
18
+ // with backslashes on the JS side and revert them back to slashes here, before generating the image.
19
+ val thumbhash = model.uri.pathSegments.joinToString(separator = "/").replace("\\", "/")
20
+
18
21
  return ModelLoader.LoadData(
19
22
  ObjectKey(model),
20
23
  ThumbhashFetcher(thumbhash)
@@ -1 +1 @@
1
- {"version":3,"file":"resolveHashString.d.ts","sourceRoot":"","sources":["../../src/utils/resolveHashString.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAS7C;;;;KAIK;AACL,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAO9D;AAED;;;;KAIK;AACL,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAK/D"}
1
+ {"version":3,"file":"resolveHashString.d.ts","sourceRoot":"","sources":["../../src/utils/resolveHashString.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAS7C;;;;KAIK;AACL,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAO9D;AAED;;;;KAIK;AACL,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAO/D"}
@@ -21,7 +21,9 @@ export function resolveBlurhashString(str) {
21
21
  * @return An ImageSource representing the provided thumbhash.
22
22
  * */
23
23
  export function resolveThumbhashString(str) {
24
- const thumbhash = str.replace(/^thumbhash:\//, '');
24
+ // ThumbHash may contain slashes that could break the url when the slash is at the beginning.
25
+ // We replace slashes with backslashes to make sure we don't break the url's path.
26
+ const thumbhash = str.replace(/^thumbhash:\//, '').replace(/\//g, '\\');
25
27
  return {
26
28
  uri: hashToUri('thumbhash', thumbhash),
27
29
  };
@@ -1 +1 @@
1
- {"version":3,"file":"resolveHashString.js","sourceRoot":"","sources":["../../src/utils/resolveHashString.tsx"],"names":[],"mappings":"AAIA,SAAS,SAAS,CAAC,IAAmB,EAAE,IAAY;IAClD,MAAM,eAAe,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACnF,OAAO,GAAG,IAAI,KAAK,eAAe,EAAE,CAAC;AACvC,CAAC;AAED;;;;KAIK;AACL,MAAM,UAAU,qBAAqB,CAAC,GAAW;IAC/C,MAAM,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7E,OAAO;QACL,GAAG,EAAE,SAAS,CAAC,UAAU,EAAE,QAAQ,CAAC;QACpC,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,EAAE;QAChC,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,EAAE;KACnC,CAAC;AACJ,CAAC;AAED;;;;KAIK;AACL,MAAM,UAAU,sBAAsB,CAAC,GAAW;IAChD,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC;IACnD,OAAO;QACL,GAAG,EAAE,SAAS,CAAC,WAAW,EAAE,SAAS,CAAC;KACvC,CAAC;AACJ,CAAC","sourcesContent":["import { ImageSource } from '../Image.types';\n\ntype ImageHashType = 'blurhash' | 'thumbhash';\n\nfunction hashToUri(type: ImageHashType, hash: string): string {\n const encodedBlurhash = encodeURI(hash).replace(/#/g, '%23').replace(/\\?/g, '%3F');\n return `${type}:/${encodedBlurhash}`;\n}\n\n/**\n * Converts a blurhash string (`blurhash:/<hash>/<width>/<height>` or <hash>/<width>/<height>) into an `ImageSource`.\n *\n * @return An ImageSource representing the provided blurhash.\n * */\nexport function resolveBlurhashString(str: string): ImageSource {\n const [blurhash, width, height] = str.replace(/^blurhash:\\//, '').split('/');\n return {\n uri: hashToUri('blurhash', blurhash),\n width: parseInt(width, 10) || 16,\n height: parseInt(height, 10) || 16,\n };\n}\n\n/**\n * Converts a thumbhash string (`thumbhash:/<hash>` or `<hash>`) into an `ImageSource`.\n *\n * @return An ImageSource representing the provided thumbhash.\n * */\nexport function resolveThumbhashString(str: string): ImageSource {\n const thumbhash = str.replace(/^thumbhash:\\//, '');\n return {\n uri: hashToUri('thumbhash', thumbhash),\n };\n}\n"]}
1
+ {"version":3,"file":"resolveHashString.js","sourceRoot":"","sources":["../../src/utils/resolveHashString.tsx"],"names":[],"mappings":"AAIA,SAAS,SAAS,CAAC,IAAmB,EAAE,IAAY;IAClD,MAAM,eAAe,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACnF,OAAO,GAAG,IAAI,KAAK,eAAe,EAAE,CAAC;AACvC,CAAC;AAED;;;;KAIK;AACL,MAAM,UAAU,qBAAqB,CAAC,GAAW;IAC/C,MAAM,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7E,OAAO;QACL,GAAG,EAAE,SAAS,CAAC,UAAU,EAAE,QAAQ,CAAC;QACpC,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,EAAE;QAChC,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,EAAE;KACnC,CAAC;AACJ,CAAC;AAED;;;;KAIK;AACL,MAAM,UAAU,sBAAsB,CAAC,GAAW;IAChD,6FAA6F;IAC7F,kFAAkF;IAClF,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACxE,OAAO;QACL,GAAG,EAAE,SAAS,CAAC,WAAW,EAAE,SAAS,CAAC;KACvC,CAAC;AACJ,CAAC","sourcesContent":["import { ImageSource } from '../Image.types';\n\ntype ImageHashType = 'blurhash' | 'thumbhash';\n\nfunction hashToUri(type: ImageHashType, hash: string): string {\n const encodedBlurhash = encodeURI(hash).replace(/#/g, '%23').replace(/\\?/g, '%3F');\n return `${type}:/${encodedBlurhash}`;\n}\n\n/**\n * Converts a blurhash string (`blurhash:/<hash>/<width>/<height>` or <hash>/<width>/<height>) into an `ImageSource`.\n *\n * @return An ImageSource representing the provided blurhash.\n * */\nexport function resolveBlurhashString(str: string): ImageSource {\n const [blurhash, width, height] = str.replace(/^blurhash:\\//, '').split('/');\n return {\n uri: hashToUri('blurhash', blurhash),\n width: parseInt(width, 10) || 16,\n height: parseInt(height, 10) || 16,\n };\n}\n\n/**\n * Converts a thumbhash string (`thumbhash:/<hash>` or `<hash>`) into an `ImageSource`.\n *\n * @return An ImageSource representing the provided thumbhash.\n * */\nexport function resolveThumbhashString(str: string): ImageSource {\n // ThumbHash may contain slashes that could break the url when the slash is at the beginning.\n // We replace slashes with backslashes to make sure we don't break the url's path.\n const thumbhash = str.replace(/^thumbhash:\\//, '').replace(/\\//g, '\\\\');\n return {\n uri: hashToUri('thumbhash', thumbhash),\n };\n}\n"]}
@@ -21,9 +21,10 @@ class ThumbhashLoader: NSObject, SDImageLoader {
21
21
  return nil
22
22
  }
23
23
 
24
- // The URI looks like this: thumbhash:/3OcRJYB4d3h/iIeHeEh3eIhw+j2w
25
- // the "thumbhash:/" part has to be skipped, but the hash can contain other '/' characters, which need to be included
26
- var thumbhash = url.pathComponents[1..<url.pathComponents.count].joined(separator: "/")
24
+ // The URI looks like this: thumbhash:/3OcRJYB4d3h\iIeHeEh3eIhw+j2w
25
+ // ThumbHash may include slashes which could break the structure of the URL, so we replace them
26
+ // with backslashes on the JS side and revert them back to slashes here, before generating the image.
27
+ var thumbhash = (url.pathComponents[1] ?? "").replacingOccurrences(of: "\\", with: "/")
27
28
 
28
29
  // Thumbhashes with transparency cause the conversion to data to fail, padding the thumbhash string to correct length fixes that
29
30
  let remainder = thumbhash.count % 4
@@ -31,7 +32,7 @@ class ThumbhashLoader: NSObject, SDImageLoader {
31
32
  thumbhash = thumbhash.padding(toLength: thumbhash.count + 4 - remainder, withPad: "=", startingAt: 0)
32
33
  }
33
34
 
34
- guard let thumbhashData = Data(base64Encoded: thumbhash, options: .ignoreUnknownCharacters) else {
35
+ guard !thumbhash.isEmpty, let thumbhashData = Data(base64Encoded: thumbhash, options: .ignoreUnknownCharacters) else {
35
36
  let error = makeNSError(description: "URL provided to ThumbhashLoader is invalid")
36
37
  completedBlock?(nil, nil, error, false)
37
38
  return nil
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "expo-image",
3
3
  "title": "Expo Image",
4
- "version": "1.2.0",
4
+ "version": "1.2.1",
5
5
  "description": "A cross-platform, performant image component for React Native and Expo with Web support",
6
6
  "main": "build/index.js",
7
7
  "types": "build/index.d.ts",
@@ -33,5 +33,5 @@
33
33
  "peerDependencies": {
34
34
  "expo": "*"
35
35
  },
36
- "gitHead": "cbd598a92d6b14a61b429cf0f900a44ee73f0766"
36
+ "gitHead": "836bea54c7c2ba747df428d8217d85703a9701fb"
37
37
  }
@@ -27,7 +27,9 @@ export function resolveBlurhashString(str: string): ImageSource {
27
27
  * @return An ImageSource representing the provided thumbhash.
28
28
  * */
29
29
  export function resolveThumbhashString(str: string): ImageSource {
30
- const thumbhash = str.replace(/^thumbhash:\//, '');
30
+ // ThumbHash may contain slashes that could break the url when the slash is at the beginning.
31
+ // We replace slashes with backslashes to make sure we don't break the url's path.
32
+ const thumbhash = str.replace(/^thumbhash:\//, '').replace(/\//g, '\\');
31
33
  return {
32
34
  uri: hashToUri('thumbhash', thumbhash),
33
35
  };