@onekeyfe/react-native-image 3.0.129 → 3.0.131

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.
@@ -11,6 +11,7 @@ import android.graphics.Paint
11
11
  import android.graphics.drawable.Animatable
12
12
  import android.graphics.drawable.Drawable
13
13
  import android.os.Build
14
+ import android.os.Looper
14
15
  import android.os.SystemClock
15
16
  import android.provider.Settings
16
17
  import android.view.View
@@ -175,6 +176,8 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
175
176
  private var pendingDisplayGeneration: Long? = null
176
177
  private var fallbackRunnable: Runnable? = null
177
178
  private var requestStartedAtMs: Long? = null
179
+ private var dropCleanupPending = false
180
+ private var dropCleanupFinished = false
178
181
 
179
182
  /**
180
183
  * Native reusable containers own their request cleanup explicitly, so their
@@ -268,10 +271,10 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
268
271
 
269
272
  init {
270
273
  OneKeyImageGlideRegistry.ensureRegistered(context)
271
- hostView.onReadyForRequest = { scheduleLoad() }
274
+ hostView.onReadyForRequest = { loadForCurrentLayout() }
272
275
  hostView.onAttachmentChanged = { attached ->
273
276
  if (attached) {
274
- scheduleLoad()
277
+ loadForCurrentLayout()
275
278
  schedulePendingDisplayIfNeeded()
276
279
  } else {
277
280
  displayRunnable?.let(hostView::removeCallbacks)
@@ -284,12 +287,12 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
284
287
 
285
288
  override fun afterUpdate() {
286
289
  super.afterUpdate()
287
- scheduleLoad()
290
+ loadForCurrentLayout()
288
291
  }
289
292
 
290
293
  override fun reload() {
291
294
  lastSignature = null
292
- scheduleLoad(force = true)
295
+ loadForCurrentLayout(force = true)
293
296
  }
294
297
 
295
298
  override fun cancel() {
@@ -302,21 +305,27 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
302
305
  }
303
306
 
304
307
  override fun onDropView() {
305
- resetForReuse()
306
- disposed = true
307
- hostView.onReadyForRequest = null
308
- hostView.onAttachmentChanged = null
308
+ beginDrop()
309
309
  super.onDropView()
310
310
  }
311
311
 
312
312
  override fun dispose() {
313
- disposed = true
314
- cancelCurrent(invalidateGeneration = true)
315
- hostView.onReadyForRequest = null
316
- hostView.onAttachmentChanged = null
313
+ beginDrop()
317
314
  super.dispose()
318
315
  }
319
316
 
317
+ internal fun isDisplaying(
318
+ sourceUri: String?,
319
+ sourceHeadersJson: String?,
320
+ recyclingKey: String,
321
+ ): Boolean =
322
+ !disposed &&
323
+ displayState == DisplayState.IMAGE &&
324
+ hostView.drawable != null &&
325
+ this.sourceUri == sourceUri &&
326
+ this.sourceHeadersJson == sourceHeadersJson &&
327
+ this.recyclingKey == recyclingKey
328
+
320
329
  private fun requestIdentityChanged() {
321
330
  if (suppressPropEffects || disposed) return
322
331
  lastSignature = null
@@ -361,11 +370,29 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
361
370
  private fun scheduleLoad(force: Boolean = false) {
362
371
  if (disposed) return
363
372
  loadRunnable?.let(hostView::removeCallbacks)
364
- val runnable = Runnable { startLoad(force) }
373
+ val runnable = Runnable {
374
+ loadRunnable = null
375
+ startLoad(force)
376
+ }
365
377
  loadRunnable = runnable
366
378
  hostView.post(runnable)
367
379
  }
368
380
 
381
+ private fun loadForCurrentLayout(force: Boolean = false) {
382
+ if (disposed) return
383
+ loadRunnable?.let(hostView::removeCallbacks)
384
+ loadRunnable = null
385
+ if (
386
+ Looper.myLooper() == Looper.getMainLooper() &&
387
+ hostView.width > 0 &&
388
+ hostView.height > 0
389
+ ) {
390
+ startLoad(force)
391
+ } else {
392
+ scheduleLoad(force)
393
+ }
394
+ }
395
+
369
396
  private fun startLoad(force: Boolean) {
370
397
  if (disposed) return
371
398
  if (hostView.width <= 0 || hostView.height <= 0) {
@@ -662,6 +689,60 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
662
689
  hostView.invalidate()
663
690
  }
664
691
 
692
+ private fun beginDrop() {
693
+ if (dropCleanupPending || dropCleanupFinished) return
694
+ disposed = true
695
+ hostView.onReadyForRequest = null
696
+ cancelPendingCallbacksForDrop()
697
+ clearEventCallbacks()
698
+ hostView.autoplayEnabled = false
699
+ hostView.skeletonRequested = false
700
+ hostView.syncPlayback()
701
+ if (
702
+ !usesApplicationRequestManager &&
703
+ hostView.isAttachedToWindow &&
704
+ hostView.drawable != null
705
+ ) {
706
+ dropCleanupPending = true
707
+ hostView.onAttachmentChanged = { attached ->
708
+ if (!attached) finishDrop()
709
+ }
710
+ } else {
711
+ finishDrop()
712
+ }
713
+ }
714
+
715
+ private fun finishDrop() {
716
+ if (dropCleanupFinished) return
717
+ dropCleanupPending = false
718
+ dropCleanupFinished = true
719
+ resetForReuse()
720
+ hostView.onReadyForRequest = null
721
+ hostView.onAttachmentChanged = null
722
+ }
723
+
724
+ private fun cancelPendingCallbacksForDrop() {
725
+ loadRunnable?.let(hostView::removeCallbacks)
726
+ loadRunnable = null
727
+ displayRunnable?.let(hostView::removeCallbacks)
728
+ displayRunnable = null
729
+ pendingDisplayGeneration = null
730
+ fallbackRunnable?.let(hostView::removeCallbacks)
731
+ fallbackRunnable = null
732
+ requestActive = false
733
+ hostView.skeletonRequested = false
734
+ requestStartedAtMs = null
735
+ resetImageTransition()
736
+ }
737
+
738
+ private fun clearEventCallbacks() {
739
+ onLoadStart = null
740
+ onLoad = null
741
+ onDisplay = null
742
+ onError = null
743
+ onLoadEnd = null
744
+ }
745
+
665
746
  private fun applyVariant() {
666
747
  when (displayState) {
667
748
  DisplayState.IMAGE -> Unit
@@ -145,7 +145,8 @@ internal data class OneKeyImageDecodeDimensions(
145
145
  /**
146
146
  * Downsamples static bitmaps for the target while independently enforcing the
147
147
  * 16 MiB area cap. `override()` alone cannot enforce an area cap for very wide
148
- * or tall sources because Glide's default strategy follows target edges.
148
+ * or tall sources because Glide's default strategy follows target edges. Quality
149
+ * rounding keeps Glide's power-of-two sample from decoding below the target.
149
150
  */
150
151
  internal object OneKeyImageSafeDownsampleStrategy : DownsampleStrategy() {
151
152
  override fun getScaleFactor(
@@ -165,5 +166,5 @@ internal object OneKeyImageSafeDownsampleStrategy : DownsampleStrategy() {
165
166
  sourceHeight: Int,
166
167
  requestedWidth: Int,
167
168
  requestedHeight: Int,
168
- ): SampleSizeRounding = SampleSizeRounding.MEMORY
169
+ ): SampleSizeRounding = SampleSizeRounding.QUALITY
169
170
  }
@@ -68,6 +68,12 @@ class OneKeyImageReusableView(context: ThemedReactContext) : FrameLayout(context
68
68
  image.afterUpdate()
69
69
  }
70
70
 
71
+ fun isDisplaying(
72
+ sourceUri: String?,
73
+ sourceHeadersJson: String?,
74
+ recyclingKey: String,
75
+ ): Boolean = image.isDisplaying(sourceUri, sourceHeadersJson, recyclingKey)
76
+
71
77
  fun prepareForReuse() {
72
78
  image.prepareForRecycle()
73
79
  }
@@ -1,5 +1,6 @@
1
1
  package com.margelo.nitro.onekeyimage
2
2
 
3
+ import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy.SampleSizeRounding
3
4
  import org.junit.Assert.assertEquals
4
5
  import org.junit.Assert.assertTrue
5
6
  import org.junit.Test
@@ -69,6 +70,14 @@ class OneKeyImageDecodeDimensionsTest {
69
70
  assertTrue(decodedBytes <= OneKeyImageDecodeDimensions.MAX_DECODE_BYTES)
70
71
  }
71
72
 
73
+ @Test
74
+ fun safeDownsampleStrategyPrefersQualityOverUndersizedDecodes() {
75
+ assertEquals(
76
+ SampleSizeRounding.QUALITY,
77
+ OneKeyImageSafeDownsampleStrategy.getSampleSizeRounding(160, 160, 55, 55),
78
+ )
79
+ }
80
+
72
81
  @Test
73
82
  fun safeDownsampleStrategyIsSingletonForStableMemoryCacheIdentity() {
74
83
  val strategyClass = Class.forName(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-image",
3
- "version": "3.0.129",
3
+ "version": "3.0.131",
4
4
  "description": "High-performance native image view for OneKey",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -81,7 +81,7 @@
81
81
  "typescript": "^5.9.2"
82
82
  },
83
83
  "peerDependencies": {
84
- "@onekeyfe/react-native-skeleton": "3.0.129",
84
+ "@onekeyfe/react-native-skeleton": "3.0.131",
85
85
  "react": "*",
86
86
  "react-native": "*",
87
87
  "react-native-nitro-modules": "0.37.0"