@onekeyfe/react-native-image 3.0.156 → 3.0.157-alpha.249
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/README.md +3 -0
- package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImage.kt +293 -20
- package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageCache.kt +34 -4
- package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageMemoryVariants.kt +119 -0
- package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageMemoryVariantsTest.kt +149 -0
- package/ios/OneKeyImage.swift +268 -38
- package/ios/OneKeyImageCache.swift +40 -3
- package/ios/OneKeyImageMemoryVariants.swift +137 -0
- package/ios/OneKeyImageRequestContext.swift +55 -0
- package/ios/tests/OneKeyImageInfrastructureTests.swift +218 -0
- package/lib/module/index.js +24 -3
- package/lib/nitrogen/generated/android/c++/JHybridOneKeyImageSpec.cpp +9 -0
- package/lib/nitrogen/generated/android/c++/JHybridOneKeyImageSpec.hpp +2 -0
- package/lib/nitrogen/generated/android/c++/views/JHybridOneKeyImageStateUpdater.cpp +5 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyImageSpec.kt +6 -0
- package/lib/nitrogen/generated/ios/c++/HybridOneKeyImageSpecSwift.hpp +7 -0
- package/lib/nitrogen/generated/ios/c++/views/HybridOneKeyImageComponent.mm +6 -0
- package/lib/nitrogen/generated/ios/swift/HybridOneKeyImageSpec.swift +1 -0
- package/lib/nitrogen/generated/ios/swift/HybridOneKeyImageSpec_cxx.swift +24 -0
- package/lib/nitrogen/generated/shared/c++/HybridOneKeyImageSpec.cpp +2 -0
- package/lib/nitrogen/generated/shared/c++/HybridOneKeyImageSpec.hpp +2 -0
- package/lib/nitrogen/generated/shared/c++/views/HybridOneKeyImageComponent.cpp +2 -0
- package/lib/nitrogen/generated/shared/c++/views/HybridOneKeyImageComponent.hpp +3 -0
- package/lib/nitrogen/generated/shared/json/OneKeyImageConfig.json +1 -0
- package/lib/typescript/src/OneKeyImage.nitro.d.ts +6 -0
- package/lib/typescript/src/index.d.ts +1 -1
- package/nitrogen/generated/android/c++/JHybridOneKeyImageSpec.cpp +9 -0
- package/nitrogen/generated/android/c++/JHybridOneKeyImageSpec.hpp +2 -0
- package/nitrogen/generated/android/c++/views/JHybridOneKeyImageStateUpdater.cpp +5 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyImageSpec.kt +6 -0
- package/nitrogen/generated/ios/c++/HybridOneKeyImageSpecSwift.hpp +7 -0
- package/nitrogen/generated/ios/c++/views/HybridOneKeyImageComponent.mm +6 -0
- package/nitrogen/generated/ios/swift/HybridOneKeyImageSpec.swift +1 -0
- package/nitrogen/generated/ios/swift/HybridOneKeyImageSpec_cxx.swift +24 -0
- package/nitrogen/generated/shared/c++/HybridOneKeyImageSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridOneKeyImageSpec.hpp +2 -0
- package/nitrogen/generated/shared/c++/views/HybridOneKeyImageComponent.cpp +2 -0
- package/nitrogen/generated/shared/c++/views/HybridOneKeyImageComponent.hpp +3 -0
- package/nitrogen/generated/shared/json/OneKeyImageConfig.json +1 -0
- package/package.json +2 -2
- package/src/OneKeyImage.nitro.ts +6 -0
- package/src/index.tsx +23 -1
package/README.md
CHANGED
|
@@ -5,6 +5,9 @@ native loading placeholders, opt-in shared-renderer skeleton, fallback states,
|
|
|
5
5
|
cache controls,
|
|
6
6
|
and conservative OneKey TOS URL resizing without depending on Expo Image.
|
|
7
7
|
|
|
8
|
+
The authoritative lifecycle, cache identity, cross-size memory-preview, safety,
|
|
9
|
+
and platform contract is defined in [docs/SPEC.md](docs/SPEC.md).
|
|
10
|
+
|
|
8
11
|
```tsx
|
|
9
12
|
<OneKeyImage
|
|
10
13
|
source={{ uri: 'https://example.com/avatar.png' }}
|
|
@@ -26,6 +26,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy
|
|
|
26
26
|
import com.bumptech.glide.load.engine.GlideException
|
|
27
27
|
import com.bumptech.glide.request.RequestListener
|
|
28
28
|
import com.bumptech.glide.request.RequestOptions
|
|
29
|
+
import com.bumptech.glide.request.target.CustomTarget
|
|
29
30
|
import com.bumptech.glide.request.target.CustomViewTarget
|
|
30
31
|
import com.bumptech.glide.request.target.Target
|
|
31
32
|
import com.bumptech.glide.request.transition.Transition
|
|
@@ -46,6 +47,34 @@ private fun Context.findActivity(): Activity? {
|
|
|
46
47
|
return currentContext as? Activity
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
internal fun shouldScaleCenterMemoryPreview(
|
|
51
|
+
contentFit: OneKeyImageContentFit,
|
|
52
|
+
candidate: OneKeyImageMemoryVariant,
|
|
53
|
+
target: OneKeyImageMemoryVariant,
|
|
54
|
+
): Boolean =
|
|
55
|
+
contentFit == OneKeyImageContentFit.CENTER &&
|
|
56
|
+
(candidate.width != target.width || candidate.height != target.height)
|
|
57
|
+
|
|
58
|
+
internal fun shouldProbeMemoryPreview(
|
|
59
|
+
hasTarget: Boolean,
|
|
60
|
+
sameFamily: Boolean,
|
|
61
|
+
hasDrawable: Boolean,
|
|
62
|
+
): Boolean = !hasTarget || !sameFamily || !hasDrawable
|
|
63
|
+
|
|
64
|
+
internal fun shouldAnimateLoadedImageTransition(
|
|
65
|
+
cacheType: OneKeyImageCacheType,
|
|
66
|
+
fadeDelayElapsed: Boolean,
|
|
67
|
+
animationsEnabled: Boolean,
|
|
68
|
+
replacingMemoryPreview: Boolean,
|
|
69
|
+
): Boolean =
|
|
70
|
+
!replacingMemoryPreview && cacheType != OneKeyImageCacheType.MEMORY &&
|
|
71
|
+
fadeDelayElapsed && animationsEnabled
|
|
72
|
+
|
|
73
|
+
internal fun memoryProbeRequestOptions(base: RequestOptions): RequestOptions =
|
|
74
|
+
base
|
|
75
|
+
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
76
|
+
.onlyRetrieveFromCache(true)
|
|
77
|
+
|
|
49
78
|
private class OneKeyImageHostView(context: ThemedReactContext) : ImageView(context) {
|
|
50
79
|
private val roundClipPath = Path()
|
|
51
80
|
private val roundOutlineProvider = object : ViewOutlineProvider() {
|
|
@@ -180,6 +209,7 @@ internal fun oneKeyImageRequestSignature(
|
|
|
180
209
|
height: Int,
|
|
181
210
|
density: Float,
|
|
182
211
|
resizeWidth: Double? = null,
|
|
212
|
+
resizeHeight: Double? = null,
|
|
183
213
|
): String = listOf(
|
|
184
214
|
rawUrl,
|
|
185
215
|
sourceHeadersJson.orEmpty(),
|
|
@@ -190,6 +220,7 @@ internal fun oneKeyImageRequestSignature(
|
|
|
190
220
|
overscan.toString(),
|
|
191
221
|
round.toString(),
|
|
192
222
|
resizeWidth.toString(),
|
|
223
|
+
resizeHeight.toString(),
|
|
193
224
|
width.toString(),
|
|
194
225
|
height.toString(),
|
|
195
226
|
density.toString(),
|
|
@@ -213,12 +244,16 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
|
|
|
213
244
|
}
|
|
214
245
|
private var loadRunnable: Runnable? = null
|
|
215
246
|
private var currentTarget: CustomViewTarget<OneKeyImageHostView, Drawable>? = null
|
|
247
|
+
private var memoryPreviewTarget: CustomTarget<Drawable>? = null
|
|
248
|
+
private var memoryPreviewFamily: OneKeyImageMemoryFamilyKey? = null
|
|
249
|
+
private var scalesCenterMemoryPreview = false
|
|
216
250
|
private var generation = 0L
|
|
217
251
|
private var lastSignature: String? = null
|
|
218
252
|
private var disposed = false
|
|
219
253
|
private var suppressPropEffects = false
|
|
220
254
|
private var requestActive = false
|
|
221
255
|
private var displayState = DisplayState.LOADING
|
|
256
|
+
private var isPreservingDisplayedImage = false
|
|
222
257
|
private var displayRunnable: Runnable? = null
|
|
223
258
|
private var pendingDisplayGeneration: Long? = null
|
|
224
259
|
private var fallbackRunnable: Runnable? = null
|
|
@@ -304,6 +339,14 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
|
|
|
304
339
|
field = value
|
|
305
340
|
requestIdentityChanged()
|
|
306
341
|
}
|
|
342
|
+
// Layout hint pair with resizeWidth; the pre-layout memory probe uses both
|
|
343
|
+
// values to reproduce a preload's physical decode size.
|
|
344
|
+
override var resizeHeight: Double? = null
|
|
345
|
+
set(value) {
|
|
346
|
+
if (field == value) return
|
|
347
|
+
field = value
|
|
348
|
+
requestIdentityChanged()
|
|
349
|
+
}
|
|
307
350
|
override var loadingStrategy: OneKeyImageLoadingStrategy? = OneKeyImageLoadingStrategy.STATIC
|
|
308
351
|
set(value) {
|
|
309
352
|
field = value
|
|
@@ -385,7 +428,11 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
|
|
|
385
428
|
if (suppressPropEffects || disposed) return
|
|
386
429
|
lastSignature = null
|
|
387
430
|
cancelCurrent(invalidateGeneration = true)
|
|
388
|
-
if (sourceUri.isNullOrBlank())
|
|
431
|
+
if (sourceUri.isNullOrBlank()) {
|
|
432
|
+
showFallback()
|
|
433
|
+
} else if (!showMemoryCachedPreviewBeforeLayout()) {
|
|
434
|
+
showLoading(requestIsActive = false)
|
|
435
|
+
}
|
|
389
436
|
scheduleLoad()
|
|
390
437
|
}
|
|
391
438
|
|
|
@@ -403,6 +450,7 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
|
|
|
403
450
|
optimizeTos = true
|
|
404
451
|
round = false
|
|
405
452
|
resizeWidth = null
|
|
453
|
+
resizeHeight = null
|
|
406
454
|
overscan = 1.1
|
|
407
455
|
loadingStrategy = OneKeyImageLoadingStrategy.STATIC
|
|
408
456
|
placeholderColor = null
|
|
@@ -414,6 +462,7 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
|
|
|
414
462
|
suppressPropEffects = false
|
|
415
463
|
requestActive = false
|
|
416
464
|
displayState = DisplayState.LOADING
|
|
465
|
+
isPreservingDisplayedImage = false
|
|
417
466
|
hostView.setImageDrawable(null)
|
|
418
467
|
hostView.drawStateSymbol = false
|
|
419
468
|
hostView.skeletonRequested = false
|
|
@@ -449,12 +498,147 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
|
|
|
449
498
|
}
|
|
450
499
|
}
|
|
451
500
|
|
|
501
|
+
private fun showMemoryCachedPreviewBeforeLayout(): Boolean {
|
|
502
|
+
if (Looper.myLooper() != Looper.getMainLooper()) return false
|
|
503
|
+
val rawUrl = sourceUri?.takeIf { it.isNotBlank() } ?: return false
|
|
504
|
+
val policy = cachePolicy ?: OneKeyImageCachePolicy.MEMORY_DISK
|
|
505
|
+
if (
|
|
506
|
+
policy != OneKeyImageCachePolicy.MEMORY &&
|
|
507
|
+
policy != OneKeyImageCachePolicy.MEMORY_DISK
|
|
508
|
+
) {
|
|
509
|
+
return false
|
|
510
|
+
}
|
|
511
|
+
val displayWidth = resizeWidth?.takeIf { it.isFinite() && it > 0.0 }
|
|
512
|
+
val displayHeight = resizeHeight?.takeIf { it.isFinite() && it > 0.0 }
|
|
513
|
+
if (displayWidth == null && displayHeight == null) return false
|
|
514
|
+
val density = hostView.resources.displayMetrics.density.coerceAtLeast(1f)
|
|
515
|
+
val displaySize = when {
|
|
516
|
+
displayWidth != null && displayHeight != null -> maxOf(displayWidth, displayHeight)
|
|
517
|
+
displayWidth != null -> displayWidth
|
|
518
|
+
else -> requireNotNull(displayHeight)
|
|
519
|
+
}
|
|
520
|
+
val customIdentity = OneKeyImageModel.headers(sourceHeadersJson).isNotEmpty()
|
|
521
|
+
val requestUrl = if (optimizeTos != false) {
|
|
522
|
+
OneKeyTosUrl.optimized(
|
|
523
|
+
rawUrl,
|
|
524
|
+
ceil(displaySize).toInt(),
|
|
525
|
+
density,
|
|
526
|
+
overscan ?: 1.1,
|
|
527
|
+
customIdentity,
|
|
528
|
+
)
|
|
529
|
+
} else rawUrl
|
|
530
|
+
val dimensions = OneKeyImageDecodeDimensions.forPreload(
|
|
531
|
+
displayWidth,
|
|
532
|
+
displayHeight,
|
|
533
|
+
density.toDouble(),
|
|
534
|
+
)
|
|
535
|
+
return showMemoryCachedPreview(
|
|
536
|
+
family = OneKeyImageMemoryFamilyKey.from(rawUrl, sourceHeadersJson),
|
|
537
|
+
exactVariant = OneKeyImageMemoryVariant(
|
|
538
|
+
requestUrl = requestUrl,
|
|
539
|
+
width = dimensions.width,
|
|
540
|
+
height = dimensions.height,
|
|
541
|
+
round = round == true,
|
|
542
|
+
),
|
|
543
|
+
includeExact = true,
|
|
544
|
+
policy = policy,
|
|
545
|
+
requestGeneration = generation,
|
|
546
|
+
)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
private fun showMemoryCachedPreview(
|
|
550
|
+
family: OneKeyImageMemoryFamilyKey,
|
|
551
|
+
exactVariant: OneKeyImageMemoryVariant,
|
|
552
|
+
includeExact: Boolean,
|
|
553
|
+
policy: OneKeyImageCachePolicy,
|
|
554
|
+
requestGeneration: Long,
|
|
555
|
+
): Boolean {
|
|
556
|
+
if (
|
|
557
|
+
Looper.myLooper() != Looper.getMainLooper() ||
|
|
558
|
+
disposed ||
|
|
559
|
+
requestGeneration != generation ||
|
|
560
|
+
(policy != OneKeyImageCachePolicy.MEMORY &&
|
|
561
|
+
policy != OneKeyImageCachePolicy.MEMORY_DISK)
|
|
562
|
+
) {
|
|
563
|
+
return false
|
|
564
|
+
}
|
|
565
|
+
val candidates = buildList {
|
|
566
|
+
if (includeExact) {
|
|
567
|
+
add(exactVariant)
|
|
568
|
+
if (exactVariant.requestUrl != family.rawUrl) {
|
|
569
|
+
add(exactVariant.copy(requestUrl = family.rawUrl))
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
addAll(OneKeyImageMemoryVariantRegistry.candidates(family, exactVariant))
|
|
573
|
+
}.distinct()
|
|
574
|
+
|
|
575
|
+
for (candidate in candidates) {
|
|
576
|
+
var synchronousResource: Drawable? = null
|
|
577
|
+
val target = object : CustomTarget<Drawable>(candidate.width, candidate.height) {
|
|
578
|
+
override fun onResourceReady(
|
|
579
|
+
resource: Drawable,
|
|
580
|
+
transition: Transition<in Drawable>?,
|
|
581
|
+
) {
|
|
582
|
+
if (
|
|
583
|
+
requestGeneration == generation &&
|
|
584
|
+
!disposed &&
|
|
585
|
+
resource !is Animatable
|
|
586
|
+
) {
|
|
587
|
+
synchronousResource = resource
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
override fun onLoadCleared(placeholder: Drawable?) = Unit
|
|
592
|
+
}
|
|
593
|
+
requestManager()
|
|
594
|
+
.asDrawable()
|
|
595
|
+
.load(OneKeyImageModel.build(candidate.requestUrl, sourceHeadersJson))
|
|
596
|
+
.apply(
|
|
597
|
+
memoryProbeRequestOptions(
|
|
598
|
+
requestOptions(policy, candidate.requestUrl, candidate.round),
|
|
599
|
+
),
|
|
600
|
+
)
|
|
601
|
+
.override(candidate.width, candidate.height)
|
|
602
|
+
.into(target)
|
|
603
|
+
|
|
604
|
+
val resource = synchronousResource
|
|
605
|
+
if (resource == null) {
|
|
606
|
+
requestManager().clear(target)
|
|
607
|
+
OneKeyImageMemoryVariantRegistry.remove(family, candidate)
|
|
608
|
+
continue
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
clearMemoryPreviewTarget()
|
|
612
|
+
memoryPreviewTarget = target
|
|
613
|
+
memoryPreviewFamily = family
|
|
614
|
+
scalesCenterMemoryPreview = shouldScaleCenterMemoryPreview(
|
|
615
|
+
contentFit = contentFit ?: OneKeyImageContentFit.COVER,
|
|
616
|
+
candidate = candidate,
|
|
617
|
+
target = exactVariant,
|
|
618
|
+
)
|
|
619
|
+
applyContentFit()
|
|
620
|
+
displayState = DisplayState.IMAGE
|
|
621
|
+
requestActive = false
|
|
622
|
+
isPreservingDisplayedImage = false
|
|
623
|
+
hostView.skeletonRequested = false
|
|
624
|
+
hostView.drawStateSymbol = false
|
|
625
|
+
hostView.setBackgroundColor(Color.TRANSPARENT)
|
|
626
|
+
hostView.setImageDrawable(resource)
|
|
627
|
+
applyAutoplay()
|
|
628
|
+
OneKeyImageMemoryVariantRegistry.record(family, candidate)
|
|
629
|
+
return true
|
|
630
|
+
}
|
|
631
|
+
return false
|
|
632
|
+
}
|
|
633
|
+
|
|
452
634
|
private fun startLoad(force: Boolean) {
|
|
453
635
|
if (disposed) return
|
|
454
636
|
if (hostView.width <= 0 || hostView.height <= 0) {
|
|
455
|
-
cancelCurrent(invalidateGeneration = true)
|
|
637
|
+
cancelCurrent(invalidateGeneration = true, clearMemoryPreview = false)
|
|
456
638
|
lastSignature = null
|
|
457
|
-
|
|
639
|
+
if (memoryPreviewTarget == null || hostView.drawable == null) {
|
|
640
|
+
showLoading(requestIsActive = false)
|
|
641
|
+
}
|
|
458
642
|
return
|
|
459
643
|
}
|
|
460
644
|
val rawUrl = sourceUri?.takeIf { it.isNotBlank() }
|
|
@@ -477,11 +661,12 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
|
|
|
477
661
|
height = hostView.height,
|
|
478
662
|
density = hostView.resources.displayMetrics.density,
|
|
479
663
|
resizeWidth = resizeWidth,
|
|
664
|
+
resizeHeight = resizeHeight,
|
|
480
665
|
)
|
|
481
666
|
if (!force && signature == lastSignature) return
|
|
482
667
|
lastSignature = signature
|
|
483
668
|
|
|
484
|
-
cancelCurrent(invalidateGeneration = true)
|
|
669
|
+
cancelCurrent(invalidateGeneration = true, clearMemoryPreview = false)
|
|
485
670
|
val requestGeneration = generation
|
|
486
671
|
requestStartedAtMs = SystemClock.uptimeMillis()
|
|
487
672
|
onLoadStart?.invoke()
|
|
@@ -489,8 +674,14 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
|
|
|
489
674
|
val customIdentity = OneKeyImageModel.headers(sourceHeadersJson).isNotEmpty()
|
|
490
675
|
val optimizedUrl = if (optimizeTos != false) {
|
|
491
676
|
val density = hostView.resources.displayMetrics.density.coerceAtLeast(1f)
|
|
492
|
-
val
|
|
493
|
-
|
|
677
|
+
val displayWidth = resizeWidth?.takeIf { it.isFinite() && it > 0.0 }
|
|
678
|
+
val displayHeight = resizeHeight?.takeIf { it.isFinite() && it > 0.0 }
|
|
679
|
+
val displaySize = when {
|
|
680
|
+
displayWidth != null && displayHeight != null -> maxOf(displayWidth, displayHeight)
|
|
681
|
+
displayWidth != null -> displayWidth
|
|
682
|
+
displayHeight != null -> displayHeight
|
|
683
|
+
else -> maxOf(hostView.width, hostView.height) / density.toDouble()
|
|
684
|
+
}
|
|
494
685
|
OneKeyTosUrl.optimized(
|
|
495
686
|
rawUrl,
|
|
496
687
|
ceil(displaySize).toInt(),
|
|
@@ -500,6 +691,28 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
|
|
|
500
691
|
)
|
|
501
692
|
} else rawUrl
|
|
502
693
|
val decodeDimensions = OneKeyImageDecodeDimensions.forRender(hostView.width, hostView.height)
|
|
694
|
+
val memoryFamily = OneKeyImageMemoryFamilyKey.from(rawUrl, sourceHeadersJson)
|
|
695
|
+
val exactVariant = OneKeyImageMemoryVariant(
|
|
696
|
+
requestUrl = optimizedUrl,
|
|
697
|
+
width = decodeDimensions.width,
|
|
698
|
+
height = decodeDimensions.height,
|
|
699
|
+
round = round == true,
|
|
700
|
+
)
|
|
701
|
+
if (
|
|
702
|
+
shouldProbeMemoryPreview(
|
|
703
|
+
hasTarget = memoryPreviewTarget != null,
|
|
704
|
+
sameFamily = memoryPreviewFamily == memoryFamily,
|
|
705
|
+
hasDrawable = hostView.drawable != null,
|
|
706
|
+
)
|
|
707
|
+
) {
|
|
708
|
+
showMemoryCachedPreview(
|
|
709
|
+
family = memoryFamily,
|
|
710
|
+
exactVariant = exactVariant,
|
|
711
|
+
includeExact = false,
|
|
712
|
+
policy = cachePolicy ?: OneKeyImageCachePolicy.MEMORY_DISK,
|
|
713
|
+
requestGeneration = requestGeneration,
|
|
714
|
+
)
|
|
715
|
+
}
|
|
503
716
|
performRequest(
|
|
504
717
|
requestUrl = optimizedUrl,
|
|
505
718
|
rawUrl = rawUrl,
|
|
@@ -508,6 +721,8 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
|
|
|
508
721
|
headersJson = sourceHeadersJson,
|
|
509
722
|
policy = cachePolicy ?: OneKeyImageCachePolicy.MEMORY_DISK,
|
|
510
723
|
decodeDimensions = decodeDimensions,
|
|
724
|
+
memoryFamily = memoryFamily,
|
|
725
|
+
memoryVariant = exactVariant,
|
|
511
726
|
)
|
|
512
727
|
}
|
|
513
728
|
|
|
@@ -519,6 +734,8 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
|
|
|
519
734
|
headersJson: String?,
|
|
520
735
|
policy: OneKeyImageCachePolicy,
|
|
521
736
|
decodeDimensions: OneKeyImageDecodeDimensions,
|
|
737
|
+
memoryFamily: OneKeyImageMemoryFamilyKey,
|
|
738
|
+
memoryVariant: OneKeyImageMemoryVariant,
|
|
522
739
|
) {
|
|
523
740
|
if (requestGeneration != generation || disposed) return
|
|
524
741
|
clearCurrentTarget()
|
|
@@ -528,18 +745,34 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
|
|
|
528
745
|
val target = object : CustomViewTarget<OneKeyImageHostView, Drawable>(hostView) {
|
|
529
746
|
override fun onResourceLoading(placeholder: Drawable?) {
|
|
530
747
|
if (requestGeneration != generation || disposed) return
|
|
531
|
-
showLoading(
|
|
748
|
+
showLoading(
|
|
749
|
+
requestIsActive = true,
|
|
750
|
+
preserveDisplayedImage = memoryPreviewTarget != null && hostView.drawable != null,
|
|
751
|
+
)
|
|
532
752
|
}
|
|
533
753
|
|
|
534
754
|
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
|
535
755
|
if (requestGeneration != generation || disposed) return
|
|
756
|
+
val replacesMemoryPreview = memoryPreviewTarget != null && hostView.drawable != null
|
|
536
757
|
requestActive = false
|
|
537
758
|
displayState = DisplayState.IMAGE
|
|
538
759
|
hostView.skeletonRequested = false
|
|
539
760
|
hostView.drawStateSymbol = false
|
|
540
761
|
hostView.setBackgroundColor(Color.TRANSPARENT)
|
|
541
762
|
hostView.setImageDrawable(resource)
|
|
542
|
-
|
|
763
|
+
isPreservingDisplayedImage = false
|
|
764
|
+
if (
|
|
765
|
+
resource !is Animatable &&
|
|
766
|
+
(policy == OneKeyImageCachePolicy.MEMORY ||
|
|
767
|
+
policy == OneKeyImageCachePolicy.MEMORY_DISK)
|
|
768
|
+
) {
|
|
769
|
+
OneKeyImageMemoryVariantRegistry.record(memoryFamily, memoryVariant)
|
|
770
|
+
}
|
|
771
|
+
clearMemoryPreviewTarget()
|
|
772
|
+
applyLoadedImageTransition(
|
|
773
|
+
cacheType = resolvedCacheType,
|
|
774
|
+
replacingMemoryPreview = replacesMemoryPreview,
|
|
775
|
+
)
|
|
543
776
|
applyAutoplay()
|
|
544
777
|
val loadCallback = onLoad
|
|
545
778
|
val loadEndCallback = onLoadEnd
|
|
@@ -591,6 +824,8 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
|
|
|
591
824
|
headersJson = headersJson,
|
|
592
825
|
policy = policy,
|
|
593
826
|
decodeDimensions = decodeDimensions,
|
|
827
|
+
memoryFamily = memoryFamily,
|
|
828
|
+
memoryVariant = memoryVariant.copy(requestUrl = rawUrl),
|
|
594
829
|
)
|
|
595
830
|
}
|
|
596
831
|
}
|
|
@@ -606,7 +841,7 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
|
|
|
606
841
|
requestManager()
|
|
607
842
|
.asDrawable()
|
|
608
843
|
.load(OneKeyImageModel.build(requestUrl, headersJson))
|
|
609
|
-
.apply(requestOptions(policy, requestUrl, round
|
|
844
|
+
.apply(requestOptions(policy, requestUrl, memoryVariant.round))
|
|
610
845
|
.override(decodeDimensions.width, decodeDimensions.height)
|
|
611
846
|
.listener(object : RequestListener<Drawable> {
|
|
612
847
|
override fun onLoadFailed(
|
|
@@ -666,7 +901,10 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
|
|
|
666
901
|
)
|
|
667
902
|
}
|
|
668
903
|
|
|
669
|
-
private fun cancelCurrent(
|
|
904
|
+
private fun cancelCurrent(
|
|
905
|
+
invalidateGeneration: Boolean,
|
|
906
|
+
clearMemoryPreview: Boolean = true,
|
|
907
|
+
) {
|
|
670
908
|
loadRunnable?.let(hostView::removeCallbacks)
|
|
671
909
|
loadRunnable = null
|
|
672
910
|
displayRunnable?.let(hostView::removeCallbacks)
|
|
@@ -675,7 +913,9 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
|
|
|
675
913
|
fallbackRunnable?.let(hostView::removeCallbacks)
|
|
676
914
|
fallbackRunnable = null
|
|
677
915
|
clearCurrentTarget()
|
|
916
|
+
if (clearMemoryPreview) clearMemoryPreviewTarget()
|
|
678
917
|
requestActive = false
|
|
918
|
+
isPreservingDisplayedImage = false
|
|
679
919
|
hostView.skeletonRequested = false
|
|
680
920
|
requestStartedAtMs = null
|
|
681
921
|
resetImageTransition()
|
|
@@ -690,6 +930,15 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
|
|
|
690
930
|
requestManager().clear(target)
|
|
691
931
|
}
|
|
692
932
|
|
|
933
|
+
private fun clearMemoryPreviewTarget() {
|
|
934
|
+
val target = memoryPreviewTarget
|
|
935
|
+
memoryPreviewTarget = null
|
|
936
|
+
memoryPreviewFamily = null
|
|
937
|
+
scalesCenterMemoryPreview = false
|
|
938
|
+
applyContentFit()
|
|
939
|
+
if (target != null) requestManager().clear(target)
|
|
940
|
+
}
|
|
941
|
+
|
|
693
942
|
private fun scheduleOnDisplay(requestGeneration: Long) {
|
|
694
943
|
pendingDisplayGeneration = requestGeneration
|
|
695
944
|
schedulePendingDisplayIfNeeded()
|
|
@@ -718,10 +967,22 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
|
|
|
718
967
|
hostView.postOnAnimation(runnable)
|
|
719
968
|
}
|
|
720
969
|
|
|
721
|
-
private fun showLoading(
|
|
970
|
+
private fun showLoading(
|
|
971
|
+
requestIsActive: Boolean,
|
|
972
|
+
preserveDisplayedImage: Boolean = false,
|
|
973
|
+
) {
|
|
722
974
|
displayState = DisplayState.LOADING
|
|
723
975
|
requestActive = requestIsActive
|
|
724
976
|
resetImageTransition()
|
|
977
|
+
if (preserveDisplayedImage && hostView.drawable != null) {
|
|
978
|
+
isPreservingDisplayedImage = true
|
|
979
|
+
hostView.skeletonRequested = false
|
|
980
|
+
hostView.drawStateSymbol = false
|
|
981
|
+
hostView.setBackgroundColor(Color.TRANSPARENT)
|
|
982
|
+
hostView.invalidate()
|
|
983
|
+
return
|
|
984
|
+
}
|
|
985
|
+
isPreservingDisplayedImage = false
|
|
725
986
|
hostView.setImageDrawable(null)
|
|
726
987
|
hostView.drawStateSymbol = false
|
|
727
988
|
applyLoadingAppearance()
|
|
@@ -738,7 +999,9 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
|
|
|
738
999
|
private fun showTerminalState(state: DisplayState) {
|
|
739
1000
|
displayState = state
|
|
740
1001
|
requestActive = false
|
|
1002
|
+
isPreservingDisplayedImage = false
|
|
741
1003
|
resetImageTransition()
|
|
1004
|
+
clearMemoryPreviewTarget()
|
|
742
1005
|
hostView.setImageDrawable(null)
|
|
743
1006
|
hostView.skeletonRequested = false
|
|
744
1007
|
val hidesTerminalState = loadingStrategy == OneKeyImageLoadingStrategy.NONE
|
|
@@ -807,7 +1070,7 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
|
|
|
807
1070
|
private fun applyVariant() {
|
|
808
1071
|
when (displayState) {
|
|
809
1072
|
DisplayState.IMAGE -> Unit
|
|
810
|
-
DisplayState.LOADING -> applyLoadingAppearance()
|
|
1073
|
+
DisplayState.LOADING -> if (!isPreservingDisplayedImage) applyLoadingAppearance()
|
|
811
1074
|
DisplayState.ERROR, DisplayState.FALLBACK -> {
|
|
812
1075
|
val hidesTerminalState = loadingStrategy == OneKeyImageLoadingStrategy.NONE
|
|
813
1076
|
hostView.setBackgroundColor(
|
|
@@ -839,16 +1102,19 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
|
|
|
839
1102
|
OneKeyImageVariant.AVATAR -> "●"
|
|
840
1103
|
}
|
|
841
1104
|
|
|
842
|
-
private fun applyLoadedImageTransition(
|
|
1105
|
+
private fun applyLoadedImageTransition(
|
|
1106
|
+
cacheType: OneKeyImageCacheType,
|
|
1107
|
+
replacingMemoryPreview: Boolean,
|
|
1108
|
+
) {
|
|
843
1109
|
resetImageTransition()
|
|
844
1110
|
val startedAt = requestStartedAtMs ?: return
|
|
845
|
-
|
|
846
|
-
cacheType
|
|
847
|
-
SystemClock.uptimeMillis() - startedAt
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
1111
|
+
val shouldAnimate = shouldAnimateLoadedImageTransition(
|
|
1112
|
+
cacheType = cacheType,
|
|
1113
|
+
fadeDelayElapsed = SystemClock.uptimeMillis() - startedAt >= FADE_DELAY_THRESHOLD_MS,
|
|
1114
|
+
animationsEnabled = animationsEnabled(),
|
|
1115
|
+
replacingMemoryPreview = replacingMemoryPreview,
|
|
1116
|
+
)
|
|
1117
|
+
if (!shouldAnimate) return
|
|
852
1118
|
hostView.alpha = 0f
|
|
853
1119
|
hostView.animate()
|
|
854
1120
|
.alpha(1f)
|
|
@@ -902,6 +1168,13 @@ class HybridOneKeyImage(private val context: ThemedReactContext) :
|
|
|
902
1168
|
}
|
|
903
1169
|
|
|
904
1170
|
private fun applyContentFit() {
|
|
1171
|
+
if (
|
|
1172
|
+
scalesCenterMemoryPreview &&
|
|
1173
|
+
(contentFit ?: OneKeyImageContentFit.COVER) == OneKeyImageContentFit.CENTER
|
|
1174
|
+
) {
|
|
1175
|
+
hostView.scaleType = ImageView.ScaleType.FIT_CENTER
|
|
1176
|
+
return
|
|
1177
|
+
}
|
|
905
1178
|
hostView.scaleType = when (contentFit ?: OneKeyImageContentFit.COVER) {
|
|
906
1179
|
OneKeyImageContentFit.COVER -> ImageView.ScaleType.CENTER_CROP
|
|
907
1180
|
OneKeyImageContentFit.CONTAIN -> ImageView.ScaleType.FIT_CENTER
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
package com.margelo.nitro.onekeyimage
|
|
2
2
|
|
|
3
|
+
import android.graphics.drawable.Animatable
|
|
4
|
+
import android.graphics.drawable.Drawable
|
|
3
5
|
import com.bumptech.glide.Glide
|
|
4
6
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
|
5
7
|
import com.bumptech.glide.request.RequestOptions
|
|
@@ -65,7 +67,8 @@ class HybridOneKeyImageCache : HybridOneKeyImageCacheSpec() {
|
|
|
65
67
|
} else source.uri
|
|
66
68
|
|
|
67
69
|
val succeeded = try {
|
|
68
|
-
load(context, requestUrl, source, options, decodeDimensions)
|
|
70
|
+
val resource = load(context, requestUrl, source, options, decodeDimensions)
|
|
71
|
+
recordMemoryVariant(source, requestUrl, decodeDimensions, resource)
|
|
69
72
|
true
|
|
70
73
|
} catch (optimizedError: Exception) {
|
|
71
74
|
if (
|
|
@@ -75,7 +78,8 @@ class HybridOneKeyImageCache : HybridOneKeyImageCacheSpec() {
|
|
|
75
78
|
false
|
|
76
79
|
} else {
|
|
77
80
|
try {
|
|
78
|
-
load(context, source.uri, source, options, decodeDimensions)
|
|
81
|
+
val resource = load(context, source.uri, source, options, decodeDimensions)
|
|
82
|
+
recordMemoryVariant(source, source.uri, decodeDimensions, resource)
|
|
79
83
|
true
|
|
80
84
|
} catch (_: Exception) {
|
|
81
85
|
false
|
|
@@ -92,6 +96,7 @@ class HybridOneKeyImageCache : HybridOneKeyImageCacheSpec() {
|
|
|
92
96
|
?: throw IllegalStateException("React application context is unavailable")
|
|
93
97
|
withContext(Dispatchers.Main) {
|
|
94
98
|
Glide.get(context).clearMemory()
|
|
99
|
+
OneKeyImageMemoryVariantRegistry.clear()
|
|
95
100
|
}
|
|
96
101
|
}
|
|
97
102
|
|
|
@@ -106,6 +111,7 @@ class HybridOneKeyImageCache : HybridOneKeyImageCacheSpec() {
|
|
|
106
111
|
?: throw IllegalStateException("React application context is unavailable")
|
|
107
112
|
withContext(Dispatchers.Main) {
|
|
108
113
|
Glide.get(context).clearMemory()
|
|
114
|
+
OneKeyImageMemoryVariantRegistry.clear()
|
|
109
115
|
}
|
|
110
116
|
Glide.get(context).clearDiskCache()
|
|
111
117
|
}
|
|
@@ -116,7 +122,7 @@ class HybridOneKeyImageCache : HybridOneKeyImageCacheSpec() {
|
|
|
116
122
|
source: OneKeyImagePreloadSource,
|
|
117
123
|
options: RequestOptions,
|
|
118
124
|
decodeDimensions: OneKeyImageDecodeDimensions,
|
|
119
|
-
) {
|
|
125
|
+
): Drawable {
|
|
120
126
|
val request = Glide.with(context)
|
|
121
127
|
.asDrawable()
|
|
122
128
|
.load(OneKeyImageModel.build(url, source.headersJson))
|
|
@@ -124,9 +130,33 @@ class HybridOneKeyImageCache : HybridOneKeyImageCacheSpec() {
|
|
|
124
130
|
.override(decodeDimensions.width, decodeDimensions.height)
|
|
125
131
|
val future = request.submit()
|
|
126
132
|
try {
|
|
127
|
-
future.get()
|
|
133
|
+
return future.get()
|
|
128
134
|
} finally {
|
|
129
135
|
Glide.with(context).clear(future)
|
|
130
136
|
}
|
|
131
137
|
}
|
|
138
|
+
|
|
139
|
+
private fun recordMemoryVariant(
|
|
140
|
+
source: OneKeyImagePreloadSource,
|
|
141
|
+
requestUrl: String,
|
|
142
|
+
decodeDimensions: OneKeyImageDecodeDimensions,
|
|
143
|
+
resource: Drawable,
|
|
144
|
+
) {
|
|
145
|
+
val policy = source.cachePolicy ?: OneKeyImageCachePolicy.MEMORY_DISK
|
|
146
|
+
if (
|
|
147
|
+
resource is Animatable ||
|
|
148
|
+
(policy != OneKeyImageCachePolicy.MEMORY && policy != OneKeyImageCachePolicy.MEMORY_DISK)
|
|
149
|
+
) {
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
OneKeyImageMemoryVariantRegistry.record(
|
|
153
|
+
family = OneKeyImageMemoryFamilyKey.from(source.uri, source.headersJson),
|
|
154
|
+
variant = OneKeyImageMemoryVariant(
|
|
155
|
+
requestUrl = requestUrl,
|
|
156
|
+
width = decodeDimensions.width,
|
|
157
|
+
height = decodeDimensions.height,
|
|
158
|
+
round = false,
|
|
159
|
+
),
|
|
160
|
+
)
|
|
161
|
+
}
|
|
132
162
|
}
|