@onekeyfe/react-native-image 3.0.98
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/LICENSE +21 -0
- package/OneKeyImage.podspec +32 -0
- package/README.md +88 -0
- package/android/CMakeLists.txt +12 -0
- package/android/build.gradle +85 -0
- package/android/consumer-rules.pro +10 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/cpp/cpp-adapter.cpp +6 -0
- package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImage.kt +616 -0
- package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageCache.kt +131 -0
- package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageDecodeDimensions.kt +169 -0
- package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageGlideRegistry.kt +285 -0
- package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageModel.kt +68 -0
- package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImagePackage.kt +28 -0
- package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageSafety.kt +1120 -0
- package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyTosUrl.kt +93 -0
- package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyAnimationTimelineInspectorTest.kt +210 -0
- package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageCacheTypeTest.kt +16 -0
- package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageDecodeDimensionsTest.kt +70 -0
- package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageModelTest.kt +65 -0
- package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageRequestSignatureTest.kt +33 -0
- package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageSafetyTest.kt +346 -0
- package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyResourceCleanupTest.kt +43 -0
- package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyTerminalEventPairTest.kt +38 -0
- package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyTosUrlTest.kt +82 -0
- package/ios/OneKeyImage.swift +636 -0
- package/ios/OneKeyImageCache.swift +215 -0
- package/ios/OneKeyImageRequestContext.swift +781 -0
- package/ios/OneKeyTosURL.swift +69 -0
- package/ios/tests/OneKeyImageInfrastructureTests.swift +555 -0
- package/ios/tests/OneKeyTosURLTests.swift +142 -0
- package/lib/module/OneKeyImage.nitro.js +30 -0
- package/lib/module/index.js +223 -0
- package/lib/module/package.json +1 -0
- package/lib/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
- package/lib/nitrogen/generated/android/c++/JFunc_void_double_double_OneKeyImageCacheType.hpp +77 -0
- package/lib/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
- package/lib/nitrogen/generated/android/c++/JHybridOneKeyImageCacheSpec.cpp +130 -0
- package/lib/nitrogen/generated/android/c++/JHybridOneKeyImageCacheSpec.hpp +66 -0
- package/lib/nitrogen/generated/android/c++/JHybridOneKeyImageSpec.cpp +255 -0
- package/lib/nitrogen/generated/android/c++/JHybridOneKeyImageSpec.hpp +93 -0
- package/lib/nitrogen/generated/android/c++/JOneKeyImageCachePolicy.hpp +64 -0
- package/lib/nitrogen/generated/android/c++/JOneKeyImageCacheType.hpp +61 -0
- package/lib/nitrogen/generated/android/c++/JOneKeyImageContentFit.hpp +64 -0
- package/lib/nitrogen/generated/android/c++/JOneKeyImageLoadingStrategy.hpp +61 -0
- package/lib/nitrogen/generated/android/c++/JOneKeyImagePreloadSource.hpp +88 -0
- package/lib/nitrogen/generated/android/c++/JOneKeyImageVariant.hpp +64 -0
- package/lib/nitrogen/generated/android/c++/views/JHybridOneKeyImageStateUpdater.cpp +144 -0
- package/lib/nitrogen/generated/android/c++/views/JHybridOneKeyImageStateUpdater.hpp +54 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void.kt +78 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void_double_double_OneKeyImageCacheType.kt +78 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void_std__string.kt +78 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyImageCacheSpec.kt +69 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyImageSpec.kt +189 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageCachePolicy.kt +25 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageCacheType.kt +24 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageContentFit.kt +25 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageLoadingStrategy.kt +24 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImagePreloadSource.kt +86 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageVariant.kt +25 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/onekeyimageOnLoad.kt +35 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyImageManager.kt +96 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyImageStateUpdater.kt +23 -0
- package/lib/nitrogen/generated/android/onekeyimage+autolinking.cmake +85 -0
- package/lib/nitrogen/generated/android/onekeyimage+autolinking.gradle +27 -0
- package/lib/nitrogen/generated/android/onekeyimageOnLoad.cpp +78 -0
- package/lib/nitrogen/generated/android/onekeyimageOnLoad.hpp +34 -0
- package/lib/nitrogen/generated/ios/OneKeyImage+autolinking.rb +62 -0
- package/lib/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.cpp +90 -0
- package/lib/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.hpp +405 -0
- package/lib/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Umbrella.hpp +71 -0
- package/lib/nitrogen/generated/ios/OneKeyImageAutolinking.mm +41 -0
- package/lib/nitrogen/generated/ios/OneKeyImageAutolinking.swift +38 -0
- package/lib/nitrogen/generated/ios/c++/HybridOneKeyImageCacheSpecSwift.cpp +11 -0
- package/lib/nitrogen/generated/ios/c++/HybridOneKeyImageCacheSpecSwift.hpp +114 -0
- package/lib/nitrogen/generated/ios/c++/HybridOneKeyImageSpecSwift.cpp +11 -0
- package/lib/nitrogen/generated/ios/c++/HybridOneKeyImageSpecSwift.hpp +206 -0
- package/lib/nitrogen/generated/ios/c++/views/HybridOneKeyImageComponent.mm +228 -0
- package/lib/nitrogen/generated/ios/swift/Func_void.swift +46 -0
- package/lib/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
- package/lib/nitrogen/generated/ios/swift/Func_void_double_double_OneKeyImageCacheType.swift +46 -0
- package/lib/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
- package/lib/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
- package/lib/nitrogen/generated/ios/swift/HybridOneKeyImageCacheSpec.swift +58 -0
- package/lib/nitrogen/generated/ios/swift/HybridOneKeyImageCacheSpec_cxx.swift +202 -0
- package/lib/nitrogen/generated/ios/swift/HybridOneKeyImageSpec.swift +70 -0
- package/lib/nitrogen/generated/ios/swift/HybridOneKeyImageSpec_cxx.swift +539 -0
- package/lib/nitrogen/generated/ios/swift/OneKeyImageCachePolicy.swift +48 -0
- package/lib/nitrogen/generated/ios/swift/OneKeyImageCacheType.swift +44 -0
- package/lib/nitrogen/generated/ios/swift/OneKeyImageContentFit.swift +48 -0
- package/lib/nitrogen/generated/ios/swift/OneKeyImageLoadingStrategy.swift +44 -0
- package/lib/nitrogen/generated/ios/swift/OneKeyImagePreloadSource.swift +148 -0
- package/lib/nitrogen/generated/ios/swift/OneKeyImageVariant.swift +48 -0
- package/lib/nitrogen/generated/shared/c++/HybridOneKeyImageCacheSpec.cpp +24 -0
- package/lib/nitrogen/generated/shared/c++/HybridOneKeyImageCacheSpec.hpp +68 -0
- package/lib/nitrogen/generated/shared/c++/HybridOneKeyImageSpec.cpp +52 -0
- package/lib/nitrogen/generated/shared/c++/HybridOneKeyImageSpec.hpp +108 -0
- package/lib/nitrogen/generated/shared/c++/OneKeyImageCachePolicy.hpp +84 -0
- package/lib/nitrogen/generated/shared/c++/OneKeyImageCacheType.hpp +80 -0
- package/lib/nitrogen/generated/shared/c++/OneKeyImageContentFit.hpp +84 -0
- package/lib/nitrogen/generated/shared/c++/OneKeyImageLoadingStrategy.hpp +80 -0
- package/lib/nitrogen/generated/shared/c++/OneKeyImagePreloadSource.hpp +114 -0
- package/lib/nitrogen/generated/shared/c++/OneKeyImageVariant.hpp +84 -0
- package/lib/nitrogen/generated/shared/c++/views/HybridOneKeyImageComponent.cpp +62 -0
- package/lib/nitrogen/generated/shared/c++/views/HybridOneKeyImageComponent.hpp +132 -0
- package/lib/nitrogen/generated/shared/json/OneKeyImageConfig.json +24 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/OneKeyImage.nitro.d.ts +67 -0
- package/lib/typescript/src/index.d.ts +55 -0
- package/nitro.json +31 -0
- package/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
- package/nitrogen/generated/android/c++/JFunc_void_double_double_OneKeyImageCacheType.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
- package/nitrogen/generated/android/c++/JHybridOneKeyImageCacheSpec.cpp +130 -0
- package/nitrogen/generated/android/c++/JHybridOneKeyImageCacheSpec.hpp +66 -0
- package/nitrogen/generated/android/c++/JHybridOneKeyImageSpec.cpp +255 -0
- package/nitrogen/generated/android/c++/JHybridOneKeyImageSpec.hpp +93 -0
- package/nitrogen/generated/android/c++/JOneKeyImageCachePolicy.hpp +64 -0
- package/nitrogen/generated/android/c++/JOneKeyImageCacheType.hpp +61 -0
- package/nitrogen/generated/android/c++/JOneKeyImageContentFit.hpp +64 -0
- package/nitrogen/generated/android/c++/JOneKeyImageLoadingStrategy.hpp +61 -0
- package/nitrogen/generated/android/c++/JOneKeyImagePreloadSource.hpp +88 -0
- package/nitrogen/generated/android/c++/JOneKeyImageVariant.hpp +64 -0
- package/nitrogen/generated/android/c++/views/JHybridOneKeyImageStateUpdater.cpp +144 -0
- package/nitrogen/generated/android/c++/views/JHybridOneKeyImageStateUpdater.hpp +54 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void.kt +78 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void_double_double_OneKeyImageCacheType.kt +78 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void_std__string.kt +78 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyImageCacheSpec.kt +69 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyImageSpec.kt +189 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageCachePolicy.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageCacheType.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageContentFit.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageLoadingStrategy.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImagePreloadSource.kt +86 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageVariant.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/onekeyimageOnLoad.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyImageManager.kt +96 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyImageStateUpdater.kt +23 -0
- package/nitrogen/generated/android/onekeyimage+autolinking.cmake +85 -0
- package/nitrogen/generated/android/onekeyimage+autolinking.gradle +27 -0
- package/nitrogen/generated/android/onekeyimageOnLoad.cpp +78 -0
- package/nitrogen/generated/android/onekeyimageOnLoad.hpp +34 -0
- package/nitrogen/generated/ios/OneKeyImage+autolinking.rb +62 -0
- package/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.cpp +90 -0
- package/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.hpp +405 -0
- package/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Umbrella.hpp +71 -0
- package/nitrogen/generated/ios/OneKeyImageAutolinking.mm +41 -0
- package/nitrogen/generated/ios/OneKeyImageAutolinking.swift +38 -0
- package/nitrogen/generated/ios/c++/HybridOneKeyImageCacheSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridOneKeyImageCacheSpecSwift.hpp +114 -0
- package/nitrogen/generated/ios/c++/HybridOneKeyImageSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridOneKeyImageSpecSwift.hpp +206 -0
- package/nitrogen/generated/ios/c++/views/HybridOneKeyImageComponent.mm +228 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_double_double_OneKeyImageCacheType.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridOneKeyImageCacheSpec.swift +58 -0
- package/nitrogen/generated/ios/swift/HybridOneKeyImageCacheSpec_cxx.swift +202 -0
- package/nitrogen/generated/ios/swift/HybridOneKeyImageSpec.swift +70 -0
- package/nitrogen/generated/ios/swift/HybridOneKeyImageSpec_cxx.swift +539 -0
- package/nitrogen/generated/ios/swift/OneKeyImageCachePolicy.swift +48 -0
- package/nitrogen/generated/ios/swift/OneKeyImageCacheType.swift +44 -0
- package/nitrogen/generated/ios/swift/OneKeyImageContentFit.swift +48 -0
- package/nitrogen/generated/ios/swift/OneKeyImageLoadingStrategy.swift +44 -0
- package/nitrogen/generated/ios/swift/OneKeyImagePreloadSource.swift +148 -0
- package/nitrogen/generated/ios/swift/OneKeyImageVariant.swift +48 -0
- package/nitrogen/generated/shared/c++/HybridOneKeyImageCacheSpec.cpp +24 -0
- package/nitrogen/generated/shared/c++/HybridOneKeyImageCacheSpec.hpp +68 -0
- package/nitrogen/generated/shared/c++/HybridOneKeyImageSpec.cpp +52 -0
- package/nitrogen/generated/shared/c++/HybridOneKeyImageSpec.hpp +108 -0
- package/nitrogen/generated/shared/c++/OneKeyImageCachePolicy.hpp +84 -0
- package/nitrogen/generated/shared/c++/OneKeyImageCacheType.hpp +80 -0
- package/nitrogen/generated/shared/c++/OneKeyImageContentFit.hpp +84 -0
- package/nitrogen/generated/shared/c++/OneKeyImageLoadingStrategy.hpp +80 -0
- package/nitrogen/generated/shared/c++/OneKeyImagePreloadSource.hpp +114 -0
- package/nitrogen/generated/shared/c++/OneKeyImageVariant.hpp +84 -0
- package/nitrogen/generated/shared/c++/views/HybridOneKeyImageComponent.cpp +62 -0
- package/nitrogen/generated/shared/c++/views/HybridOneKeyImageComponent.hpp +132 -0
- package/nitrogen/generated/shared/json/OneKeyImageConfig.json +24 -0
- package/package.json +128 -0
- package/src/OneKeyImage.nitro.ts +86 -0
- package/src/index.tsx +376 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
package com.margelo.nitro.onekeyimage
|
|
2
|
+
|
|
3
|
+
import com.bumptech.glide.Glide
|
|
4
|
+
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
|
5
|
+
import com.bumptech.glide.request.RequestOptions
|
|
6
|
+
import com.margelo.nitro.NitroModules
|
|
7
|
+
import com.margelo.nitro.core.Promise
|
|
8
|
+
import kotlinx.coroutines.Dispatchers
|
|
9
|
+
import kotlinx.coroutines.withContext
|
|
10
|
+
import kotlin.math.ceil
|
|
11
|
+
import kotlin.math.max
|
|
12
|
+
|
|
13
|
+
class HybridOneKeyImageCache : HybridOneKeyImageCacheSpec() {
|
|
14
|
+
override fun preload(sources: Array<OneKeyImagePreloadSource>): Promise<Boolean> = Promise.async {
|
|
15
|
+
val context = NitroModules.applicationContext
|
|
16
|
+
?: throw IllegalStateException("React application context is unavailable")
|
|
17
|
+
OneKeyImageGlideRegistry.ensureRegistered(context)
|
|
18
|
+
var allSucceeded = true
|
|
19
|
+
sources.forEach { source ->
|
|
20
|
+
if (source.uri.isBlank()) {
|
|
21
|
+
allSucceeded = false
|
|
22
|
+
return@forEach
|
|
23
|
+
}
|
|
24
|
+
val baseOptions = RequestOptions()
|
|
25
|
+
.dontTransform()
|
|
26
|
+
.downsample(OneKeyImageSafeDownsampleStrategy())
|
|
27
|
+
val options = when (source.cachePolicy ?: OneKeyImageCachePolicy.MEMORY_DISK) {
|
|
28
|
+
OneKeyImageCachePolicy.MEMORY_DISK -> baseOptions
|
|
29
|
+
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
|
|
30
|
+
OneKeyImageCachePolicy.MEMORY -> baseOptions
|
|
31
|
+
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
32
|
+
OneKeyImageCachePolicy.DISK -> baseOptions
|
|
33
|
+
.diskCacheStrategy(DiskCacheStrategy.DATA)
|
|
34
|
+
.skipMemoryCache(true)
|
|
35
|
+
OneKeyImageCachePolicy.NONE -> baseOptions
|
|
36
|
+
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
37
|
+
.skipMemoryCache(true)
|
|
38
|
+
}
|
|
39
|
+
val displayWidth = source.resizeWidth?.takeIf { it.isFinite() && it > 0.0 }
|
|
40
|
+
val displayHeight = source.resizeHeight?.takeIf { it.isFinite() && it > 0.0 }
|
|
41
|
+
val displaySize = when {
|
|
42
|
+
displayWidth != null && displayHeight != null -> max(displayWidth, displayHeight)
|
|
43
|
+
displayWidth != null -> displayWidth
|
|
44
|
+
else -> displayHeight
|
|
45
|
+
}
|
|
46
|
+
val density = (source.pixelRatio ?: context.resources.displayMetrics.density.toDouble())
|
|
47
|
+
.takeIf { it.isFinite() }
|
|
48
|
+
?.coerceAtLeast(1.0)
|
|
49
|
+
?: 1.0
|
|
50
|
+
val decodeDimensions = OneKeyImageDecodeDimensions.forPreload(
|
|
51
|
+
displayWidth,
|
|
52
|
+
displayHeight,
|
|
53
|
+
density,
|
|
54
|
+
)
|
|
55
|
+
val hasCustomIdentity = OneKeyImageModel.headers(source.headersJson).isNotEmpty()
|
|
56
|
+
val requestUrl = if (source.optimizeTos != false && displaySize != null) {
|
|
57
|
+
OneKeyTosUrl.optimized(
|
|
58
|
+
source.uri,
|
|
59
|
+
ceil(displaySize).toInt(),
|
|
60
|
+
density.toFloat(),
|
|
61
|
+
source.overscan ?: 1.1,
|
|
62
|
+
hasCustomIdentity,
|
|
63
|
+
)
|
|
64
|
+
} else source.uri
|
|
65
|
+
|
|
66
|
+
val succeeded = try {
|
|
67
|
+
load(context, requestUrl, source, options, decodeDimensions)
|
|
68
|
+
true
|
|
69
|
+
} catch (optimizedError: Exception) {
|
|
70
|
+
if (
|
|
71
|
+
requestUrl == source.uri ||
|
|
72
|
+
OneKeyImageSafety.isSafetyFailure(optimizedError)
|
|
73
|
+
) {
|
|
74
|
+
false
|
|
75
|
+
} else {
|
|
76
|
+
try {
|
|
77
|
+
load(context, source.uri, source, options, decodeDimensions)
|
|
78
|
+
true
|
|
79
|
+
} catch (_: Exception) {
|
|
80
|
+
false
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
allSucceeded = allSucceeded && succeeded
|
|
85
|
+
}
|
|
86
|
+
allSucceeded
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
override fun clearMemory(): Promise<Unit> = Promise.async {
|
|
90
|
+
val context = NitroModules.applicationContext
|
|
91
|
+
?: throw IllegalStateException("React application context is unavailable")
|
|
92
|
+
withContext(Dispatchers.Main) {
|
|
93
|
+
Glide.get(context).clearMemory()
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
override fun clearDisk(): Promise<Unit> = Promise.async {
|
|
98
|
+
val context = NitroModules.applicationContext
|
|
99
|
+
?: throw IllegalStateException("React application context is unavailable")
|
|
100
|
+
Glide.get(context).clearDiskCache()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
override fun clearAll(): Promise<Unit> = Promise.async {
|
|
104
|
+
val context = NitroModules.applicationContext
|
|
105
|
+
?: throw IllegalStateException("React application context is unavailable")
|
|
106
|
+
withContext(Dispatchers.Main) {
|
|
107
|
+
Glide.get(context).clearMemory()
|
|
108
|
+
}
|
|
109
|
+
Glide.get(context).clearDiskCache()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private fun load(
|
|
113
|
+
context: android.content.Context,
|
|
114
|
+
url: String,
|
|
115
|
+
source: OneKeyImagePreloadSource,
|
|
116
|
+
options: RequestOptions,
|
|
117
|
+
decodeDimensions: OneKeyImageDecodeDimensions,
|
|
118
|
+
) {
|
|
119
|
+
val request = Glide.with(context)
|
|
120
|
+
.asDrawable()
|
|
121
|
+
.load(OneKeyImageModel.build(url, source.headersJson))
|
|
122
|
+
.apply(options)
|
|
123
|
+
.override(decodeDimensions.width, decodeDimensions.height)
|
|
124
|
+
val future = request.submit()
|
|
125
|
+
try {
|
|
126
|
+
future.get()
|
|
127
|
+
} finally {
|
|
128
|
+
Glide.with(context).clear(future)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
package com.margelo.nitro.onekeyimage
|
|
2
|
+
|
|
3
|
+
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy
|
|
4
|
+
import kotlin.math.ceil
|
|
5
|
+
import kotlin.math.floor
|
|
6
|
+
import kotlin.math.max
|
|
7
|
+
import kotlin.math.min
|
|
8
|
+
import kotlin.math.sqrt
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Physical decode dimensions constrained to one 16 MiB ARGB_8888 bitmap.
|
|
12
|
+
*
|
|
13
|
+
* The same calculation is shared by render and preload so Glide's size-dependent
|
|
14
|
+
* cache identity cannot drift between the two paths.
|
|
15
|
+
*/
|
|
16
|
+
internal data class OneKeyImageDecodeDimensions(
|
|
17
|
+
val width: Int,
|
|
18
|
+
val height: Int,
|
|
19
|
+
) {
|
|
20
|
+
val estimatedBytes: Long
|
|
21
|
+
get() = width.toLong() * height.toLong() * BYTES_PER_PIXEL
|
|
22
|
+
|
|
23
|
+
companion object {
|
|
24
|
+
internal const val MAX_DECODE_BYTES = 16L * 1024L * 1024L
|
|
25
|
+
internal const val MAX_SQUARE_SIDE = 2048
|
|
26
|
+
private const val BYTES_PER_PIXEL = 4L
|
|
27
|
+
private const val MAX_PIXELS = MAX_DECODE_BYTES / BYTES_PER_PIXEL
|
|
28
|
+
|
|
29
|
+
fun forRender(width: Int, height: Int): OneKeyImageDecodeDimensions =
|
|
30
|
+
constrained(width.coerceAtLeast(1), height.coerceAtLeast(1))
|
|
31
|
+
|
|
32
|
+
fun forPreload(
|
|
33
|
+
logicalWidth: Double?,
|
|
34
|
+
logicalHeight: Double?,
|
|
35
|
+
pixelRatio: Double,
|
|
36
|
+
): OneKeyImageDecodeDimensions {
|
|
37
|
+
val width = logicalWidth?.takeIf { it.isFinite() && it > 0.0 }
|
|
38
|
+
val height = logicalHeight?.takeIf { it.isFinite() && it > 0.0 }
|
|
39
|
+
val density = pixelRatio.takeIf { it.isFinite() }?.coerceAtLeast(1.0) ?: 1.0
|
|
40
|
+
val physicalWidth = width?.let { physicalPixels(it, density) }
|
|
41
|
+
val physicalHeight = height?.let { physicalPixels(it, density) }
|
|
42
|
+
|
|
43
|
+
return when {
|
|
44
|
+
physicalWidth != null && physicalHeight != null ->
|
|
45
|
+
constrained(physicalWidth, physicalHeight)
|
|
46
|
+
physicalWidth != null -> constrained(physicalWidth, physicalWidth)
|
|
47
|
+
physicalHeight != null -> constrained(physicalHeight, physicalHeight)
|
|
48
|
+
else -> OneKeyImageDecodeDimensions(MAX_SQUARE_SIDE, MAX_SQUARE_SIDE)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Desired size passed to APNG4Android's uniform power-of-two sampler.
|
|
54
|
+
* Returns null only when the source aspect ratio is so extreme that the
|
|
55
|
+
* decoder cannot satisfy the cap without reducing one dimension below 1px.
|
|
56
|
+
*/
|
|
57
|
+
fun forAnimatedDecoder(
|
|
58
|
+
sourceWidth: Int,
|
|
59
|
+
sourceHeight: Int,
|
|
60
|
+
targetWidth: Int,
|
|
61
|
+
targetHeight: Int,
|
|
62
|
+
): OneKeyImageDecodeDimensions? {
|
|
63
|
+
if (sourceWidth <= 0 || sourceHeight <= 0) return null
|
|
64
|
+
val safeTarget = forRender(targetWidth, targetHeight)
|
|
65
|
+
val widthRatio = sourceWidth / safeTarget.width
|
|
66
|
+
val heightRatio = sourceHeight / safeTarget.height
|
|
67
|
+
var sampleSize = highestPowerOfTwoAtMost(min(widthRatio, heightRatio).coerceAtLeast(1))
|
|
68
|
+
|
|
69
|
+
while (
|
|
70
|
+
ceil(sourceWidth / sampleSize.toDouble()) *
|
|
71
|
+
ceil(sourceHeight / sampleSize.toDouble()) > MAX_PIXELS
|
|
72
|
+
) {
|
|
73
|
+
if (sampleSize > Int.MAX_VALUE / 2) return null
|
|
74
|
+
sampleSize *= 2
|
|
75
|
+
}
|
|
76
|
+
if (sampleSize > min(sourceWidth, sourceHeight)) return null
|
|
77
|
+
return OneKeyImageDecodeDimensions(
|
|
78
|
+
(sourceWidth / sampleSize).coerceAtLeast(1),
|
|
79
|
+
(sourceHeight / sampleSize).coerceAtLeast(1),
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fun safeStaticScaleFactor(
|
|
84
|
+
sourceWidth: Int,
|
|
85
|
+
sourceHeight: Int,
|
|
86
|
+
requestedWidth: Int,
|
|
87
|
+
requestedHeight: Int,
|
|
88
|
+
): Float {
|
|
89
|
+
if (sourceWidth <= 0 || sourceHeight <= 0) return 1f
|
|
90
|
+
OneKeyImageSafety.requireStaticDimensions(sourceWidth, sourceHeight)
|
|
91
|
+
val targetScale = max(
|
|
92
|
+
requestedWidth.toDouble() / sourceWidth,
|
|
93
|
+
requestedHeight.toDouble() / sourceHeight,
|
|
94
|
+
).coerceAtMost(1.0)
|
|
95
|
+
val sourcePixels = sourceWidth.toDouble() * sourceHeight.toDouble()
|
|
96
|
+
val safetyScale = sqrt(MAX_PIXELS.toDouble() / sourcePixels).coerceAtMost(1.0)
|
|
97
|
+
val resolvedScale = min(targetScale, safetyScale)
|
|
98
|
+
return if (resolvedScale < 1.0) {
|
|
99
|
+
// A Double -> Float conversion may round upward and cross the hard
|
|
100
|
+
// byte boundary by a few pixels.
|
|
101
|
+
java.lang.Math.nextDown(resolvedScale.toFloat())
|
|
102
|
+
} else {
|
|
103
|
+
1f
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private fun highestPowerOfTwoAtMost(value: Int): Int {
|
|
108
|
+
var result = 1
|
|
109
|
+
while (result <= value / 2) result *= 2
|
|
110
|
+
return result
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private fun physicalPixels(logicalSize: Double, pixelRatio: Double): Int {
|
|
114
|
+
val pixels = ceil(logicalSize * pixelRatio)
|
|
115
|
+
return when {
|
|
116
|
+
!pixels.isFinite() || pixels >= Int.MAX_VALUE.toDouble() -> Int.MAX_VALUE
|
|
117
|
+
else -> pixels.toInt().coerceAtLeast(1)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private fun constrained(width: Int, height: Int): OneKeyImageDecodeDimensions {
|
|
122
|
+
val area = width.toDouble() * height.toDouble()
|
|
123
|
+
if (area <= MAX_PIXELS.toDouble()) {
|
|
124
|
+
return OneKeyImageDecodeDimensions(width, height)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
val scale = sqrt(MAX_PIXELS.toDouble() / area)
|
|
128
|
+
var constrainedWidth = floor(width * scale).toInt().coerceAtLeast(1)
|
|
129
|
+
var constrainedHeight = floor(height * scale).toInt().coerceAtLeast(1)
|
|
130
|
+
|
|
131
|
+
// Floating-point rounding should already stay below the limit. Keep this
|
|
132
|
+
// final guard so the hard cap remains true for every Int input.
|
|
133
|
+
if (constrainedWidth.toLong() * constrainedHeight.toLong() > MAX_PIXELS) {
|
|
134
|
+
if (constrainedWidth >= constrainedHeight) {
|
|
135
|
+
constrainedWidth = (MAX_PIXELS / constrainedHeight).toInt().coerceAtLeast(1)
|
|
136
|
+
} else {
|
|
137
|
+
constrainedHeight = (MAX_PIXELS / constrainedWidth).toInt().coerceAtLeast(1)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return OneKeyImageDecodeDimensions(constrainedWidth, constrainedHeight)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Downsamples static bitmaps for the target while independently enforcing the
|
|
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.
|
|
149
|
+
*/
|
|
150
|
+
internal class OneKeyImageSafeDownsampleStrategy : DownsampleStrategy() {
|
|
151
|
+
override fun getScaleFactor(
|
|
152
|
+
sourceWidth: Int,
|
|
153
|
+
sourceHeight: Int,
|
|
154
|
+
requestedWidth: Int,
|
|
155
|
+
requestedHeight: Int,
|
|
156
|
+
): Float = OneKeyImageDecodeDimensions.safeStaticScaleFactor(
|
|
157
|
+
sourceWidth,
|
|
158
|
+
sourceHeight,
|
|
159
|
+
requestedWidth,
|
|
160
|
+
requestedHeight,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
override fun getSampleSizeRounding(
|
|
164
|
+
sourceWidth: Int,
|
|
165
|
+
sourceHeight: Int,
|
|
166
|
+
requestedWidth: Int,
|
|
167
|
+
requestedHeight: Int,
|
|
168
|
+
): SampleSizeRounding = SampleSizeRounding.MEMORY
|
|
169
|
+
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
package com.margelo.nitro.onekeyimage
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.drawable.Drawable
|
|
5
|
+
import android.graphics.drawable.PictureDrawable
|
|
6
|
+
import android.util.Base64
|
|
7
|
+
import com.bumptech.glide.Glide
|
|
8
|
+
import com.bumptech.glide.Priority
|
|
9
|
+
import com.bumptech.glide.integration.avif.AvifGlideModule
|
|
10
|
+
import com.bumptech.glide.load.DataSource
|
|
11
|
+
import com.bumptech.glide.load.Options
|
|
12
|
+
import com.bumptech.glide.load.ResourceDecoder
|
|
13
|
+
import com.bumptech.glide.load.data.DataFetcher
|
|
14
|
+
import com.bumptech.glide.load.engine.Resource
|
|
15
|
+
import com.bumptech.glide.load.model.ModelLoader
|
|
16
|
+
import com.bumptech.glide.load.model.ModelLoaderFactory
|
|
17
|
+
import com.bumptech.glide.load.model.MultiModelLoaderFactory
|
|
18
|
+
import com.bumptech.glide.load.resource.SimpleResource
|
|
19
|
+
import com.bumptech.glide.load.resource.transcode.ResourceTranscoder
|
|
20
|
+
import com.bumptech.glide.signature.ObjectKey
|
|
21
|
+
import com.caverock.androidsvg.SVG
|
|
22
|
+
import com.caverock.androidsvg.SVGParseException
|
|
23
|
+
import com.facebook.react.modules.network.OkHttpClientProvider
|
|
24
|
+
import com.github.penfeizhou.animation.decode.FrameSeqDecoder
|
|
25
|
+
import com.github.penfeizhou.animation.glide.ByteBufferAnimationDecoder
|
|
26
|
+
import com.github.penfeizhou.animation.glide.GlideAnimationModule
|
|
27
|
+
import com.github.penfeizhou.animation.glide.StreamAnimationDecoder
|
|
28
|
+
import java.io.ByteArrayOutputStream
|
|
29
|
+
import java.io.ByteArrayInputStream
|
|
30
|
+
import java.io.IOException
|
|
31
|
+
import java.io.InputStream
|
|
32
|
+
import java.nio.ByteBuffer
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Registers the small set of Glide integrations required by OneKeyImage.
|
|
36
|
+
*
|
|
37
|
+
* Registration is explicit instead of declaring another AppGlideModule. Android
|
|
38
|
+
* applications may only contain one AppGlideModule, and expo-image owns it while
|
|
39
|
+
* the two image modules coexist during migration.
|
|
40
|
+
*/
|
|
41
|
+
internal object OneKeyImageGlideRegistry {
|
|
42
|
+
@Volatile
|
|
43
|
+
private var registered = false
|
|
44
|
+
|
|
45
|
+
fun ensureRegistered(context: Context) {
|
|
46
|
+
if (registered) return
|
|
47
|
+
synchronized(this) {
|
|
48
|
+
if (registered) return
|
|
49
|
+
val appContext = context.applicationContext
|
|
50
|
+
val glide = Glide.get(appContext)
|
|
51
|
+
val registry = glide.registry
|
|
52
|
+
|
|
53
|
+
registry.prepend(
|
|
54
|
+
OneKeyImageDataUriModel::class.java,
|
|
55
|
+
ByteBuffer::class.java,
|
|
56
|
+
OneKeyDataUriModelLoaderFactory(),
|
|
57
|
+
)
|
|
58
|
+
registry.prepend(
|
|
59
|
+
OneKeyImageLocalModel::class.java,
|
|
60
|
+
InputStream::class.java,
|
|
61
|
+
OneKeyImageLocalModelLoaderFactory(appContext),
|
|
62
|
+
)
|
|
63
|
+
val imageHttpClient = OkHttpClientProvider.createClient()
|
|
64
|
+
.newBuilder()
|
|
65
|
+
.addInterceptor(OneKeyImageEncodedDataInterceptor())
|
|
66
|
+
.build()
|
|
67
|
+
registry.prepend(
|
|
68
|
+
OneKeyImageRemoteModel::class.java,
|
|
69
|
+
InputStream::class.java,
|
|
70
|
+
OneKeyImageRemoteModelLoaderFactory(imageHttpClient),
|
|
71
|
+
)
|
|
72
|
+
registry.append(
|
|
73
|
+
InputStream::class.java,
|
|
74
|
+
SVG::class.java,
|
|
75
|
+
OneKeySvgDecoder(),
|
|
76
|
+
)
|
|
77
|
+
registry.register(
|
|
78
|
+
SVG::class.java,
|
|
79
|
+
Drawable::class.java,
|
|
80
|
+
OneKeySvgDrawableTranscoder(),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
// These dependencies also ship LibraryGlideModules. Calling them directly
|
|
84
|
+
// keeps the feature available after expo-image (and its AppGlideModule) is
|
|
85
|
+
// removed, while remaining harmless when Expo registered them already.
|
|
86
|
+
AvifGlideModule().registerComponents(appContext, glide, registry)
|
|
87
|
+
GlideAnimationModule().registerComponents(appContext, glide, registry)
|
|
88
|
+
val animationDecoder = OneKeySafeAnimationDecoder()
|
|
89
|
+
registry.prepend(
|
|
90
|
+
InputStream::class.java,
|
|
91
|
+
FrameSeqDecoder::class.java,
|
|
92
|
+
OneKeySafeStreamAnimationDecoder(animationDecoder),
|
|
93
|
+
)
|
|
94
|
+
registry.prepend(
|
|
95
|
+
ByteBuffer::class.java,
|
|
96
|
+
FrameSeqDecoder::class.java,
|
|
97
|
+
animationDecoder,
|
|
98
|
+
)
|
|
99
|
+
registered = true
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private class OneKeySafeAnimationDecoder : ResourceDecoder<ByteBuffer, FrameSeqDecoder<*, *>> {
|
|
105
|
+
private val delegate = ByteBufferAnimationDecoder()
|
|
106
|
+
|
|
107
|
+
override fun handles(source: ByteBuffer, options: Options): Boolean =
|
|
108
|
+
delegate.handles(source, options)
|
|
109
|
+
|
|
110
|
+
override fun decode(
|
|
111
|
+
source: ByteBuffer,
|
|
112
|
+
width: Int,
|
|
113
|
+
height: Int,
|
|
114
|
+
options: Options,
|
|
115
|
+
): Resource<FrameSeqDecoder<*, *>>? {
|
|
116
|
+
val resource = delegate.decode(source, width, height, options) ?: return null
|
|
117
|
+
return OneKeyResourceCleanup.recycleOnException(resource::recycle) {
|
|
118
|
+
val decoder = resource.get()
|
|
119
|
+
val bounds = decoder.bounds
|
|
120
|
+
val desired = OneKeyImageDecodeDimensions.forAnimatedDecoder(
|
|
121
|
+
bounds.width(),
|
|
122
|
+
bounds.height(),
|
|
123
|
+
width,
|
|
124
|
+
height,
|
|
125
|
+
) ?: throw OneKeyImageSafetyException(
|
|
126
|
+
"Animated image cannot fit the 16 MiB target decode limit",
|
|
127
|
+
)
|
|
128
|
+
decoder.setDesiredSize(desired.width, desired.height)
|
|
129
|
+
@Suppress("UNCHECKED_CAST")
|
|
130
|
+
resource as Resource<FrameSeqDecoder<*, *>>
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
internal object OneKeyResourceCleanup {
|
|
136
|
+
fun <T> recycleOnException(recycle: () -> Unit, block: () -> T): T = try {
|
|
137
|
+
block()
|
|
138
|
+
} catch (error: Exception) {
|
|
139
|
+
try {
|
|
140
|
+
recycle()
|
|
141
|
+
} catch (cleanupError: Exception) {
|
|
142
|
+
error.addSuppressed(cleanupError)
|
|
143
|
+
}
|
|
144
|
+
throw error
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Penfeizhou's stream decoder swallows IOExceptions while buffering the input. */
|
|
149
|
+
private class OneKeySafeStreamAnimationDecoder(
|
|
150
|
+
private val byteBufferDecoder: ResourceDecoder<ByteBuffer, FrameSeqDecoder<*, *>>,
|
|
151
|
+
) : ResourceDecoder<InputStream, FrameSeqDecoder<*, *>> {
|
|
152
|
+
private val handlesDelegate = StreamAnimationDecoder(byteBufferDecoder)
|
|
153
|
+
|
|
154
|
+
override fun handles(source: InputStream, options: Options): Boolean =
|
|
155
|
+
handlesDelegate.handles(source, options)
|
|
156
|
+
|
|
157
|
+
override fun decode(
|
|
158
|
+
source: InputStream,
|
|
159
|
+
width: Int,
|
|
160
|
+
height: Int,
|
|
161
|
+
options: Options,
|
|
162
|
+
): Resource<FrameSeqDecoder<*, *>>? {
|
|
163
|
+
val output = ByteArrayOutputStream(16 * 1024)
|
|
164
|
+
val buffer = ByteArray(16 * 1024)
|
|
165
|
+
while (true) {
|
|
166
|
+
val count = source.read(buffer)
|
|
167
|
+
if (count < 0) break
|
|
168
|
+
if (count == 0) continue
|
|
169
|
+
OneKeyImageSafety.requireEncodedLength(
|
|
170
|
+
output.size().toLong() + count.toLong(),
|
|
171
|
+
OneKeyImageSafety.MAX_ANIMATED_ENCODED_BYTES,
|
|
172
|
+
)
|
|
173
|
+
output.write(buffer, 0, count)
|
|
174
|
+
}
|
|
175
|
+
return byteBufferDecoder.decode(ByteBuffer.wrap(output.toByteArray()), width, height, options)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private class OneKeyDataUriModelLoader : ModelLoader<OneKeyImageDataUriModel, ByteBuffer> {
|
|
180
|
+
override fun handles(model: OneKeyImageDataUriModel): Boolean = true
|
|
181
|
+
|
|
182
|
+
override fun buildLoadData(
|
|
183
|
+
model: OneKeyImageDataUriModel,
|
|
184
|
+
width: Int,
|
|
185
|
+
height: Int,
|
|
186
|
+
options: Options,
|
|
187
|
+
): ModelLoader.LoadData<ByteBuffer> =
|
|
188
|
+
ModelLoader.LoadData(
|
|
189
|
+
OneKeyImageSafetyVersionedKey(ObjectKey(model)),
|
|
190
|
+
OneKeyDataUriFetcher(model.dataUri),
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private class OneKeyDataUriModelLoaderFactory :
|
|
195
|
+
ModelLoaderFactory<OneKeyImageDataUriModel, ByteBuffer> {
|
|
196
|
+
override fun build(
|
|
197
|
+
multiFactory: MultiModelLoaderFactory,
|
|
198
|
+
): ModelLoader<OneKeyImageDataUriModel, ByteBuffer> =
|
|
199
|
+
OneKeyDataUriModelLoader()
|
|
200
|
+
|
|
201
|
+
override fun teardown() = Unit
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private class OneKeyDataUriFetcher(private val dataUri: String) : DataFetcher<ByteBuffer> {
|
|
205
|
+
override fun cleanup() = Unit
|
|
206
|
+
override fun cancel() = Unit
|
|
207
|
+
override fun getDataClass(): Class<ByteBuffer> = ByteBuffer::class.java
|
|
208
|
+
override fun getDataSource(): DataSource = DataSource.LOCAL
|
|
209
|
+
|
|
210
|
+
override fun loadData(
|
|
211
|
+
priority: Priority,
|
|
212
|
+
callback: DataFetcher.DataCallback<in ByteBuffer>,
|
|
213
|
+
) {
|
|
214
|
+
try {
|
|
215
|
+
val comma = dataUri.indexOf(',')
|
|
216
|
+
require(comma >= 0 && dataUri.substring(0, comma).contains(";base64")) {
|
|
217
|
+
"Only base64 image data URIs are supported"
|
|
218
|
+
}
|
|
219
|
+
val payloadStart = comma + 1
|
|
220
|
+
val decodedByteCount = OneKeyImageSafety.dataUriDecodedByteCount(dataUri, payloadStart)
|
|
221
|
+
OneKeyImageSafety.requireEncodedLength(
|
|
222
|
+
decodedByteCount,
|
|
223
|
+
OneKeyImageSafety.MAX_DATA_URI_DECODED_BYTES,
|
|
224
|
+
)
|
|
225
|
+
val decoded = Base64.decode(dataUri.substring(payloadStart), Base64.DEFAULT)
|
|
226
|
+
OneKeyImageSafety.requireEncodedLength(
|
|
227
|
+
decoded.size.toLong(),
|
|
228
|
+
OneKeyImageSafety.MAX_DATA_URI_DECODED_BYTES,
|
|
229
|
+
)
|
|
230
|
+
val metadata = OneKeyEncodedImageInspector.inspect(ByteArrayInputStream(decoded))
|
|
231
|
+
if (metadata.animated) {
|
|
232
|
+
OneKeyImageSafety.requireEncodedLength(
|
|
233
|
+
decoded.size.toLong(),
|
|
234
|
+
OneKeyImageSafety.MAX_ANIMATED_ENCODED_BYTES,
|
|
235
|
+
)
|
|
236
|
+
val format = metadata.animatedFormat
|
|
237
|
+
?: throw OneKeyImageSafetyException("Animated image format is unavailable")
|
|
238
|
+
OneKeyAnimationTimelineInspector.validate(format, decoded)
|
|
239
|
+
}
|
|
240
|
+
callback.onDataReady(ByteBuffer.wrap(decoded))
|
|
241
|
+
} catch (error: Exception) {
|
|
242
|
+
callback.onLoadFailed(error)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private class OneKeySvgDecoder : ResourceDecoder<InputStream, SVG> {
|
|
248
|
+
override fun handles(source: InputStream, options: Options): Boolean = true
|
|
249
|
+
|
|
250
|
+
@Throws(IOException::class)
|
|
251
|
+
override fun decode(
|
|
252
|
+
source: InputStream,
|
|
253
|
+
width: Int,
|
|
254
|
+
height: Int,
|
|
255
|
+
options: Options,
|
|
256
|
+
): Resource<SVG> {
|
|
257
|
+
val svg = try {
|
|
258
|
+
SVG.getFromInputStream(source)
|
|
259
|
+
} catch (error: SVGParseException) {
|
|
260
|
+
throw IOException("Cannot decode SVG", error)
|
|
261
|
+
}
|
|
262
|
+
if (svg.documentViewBox == null) {
|
|
263
|
+
val documentWidth = svg.documentWidth
|
|
264
|
+
val documentHeight = svg.documentHeight
|
|
265
|
+
if (documentWidth > 0f && documentHeight > 0f) {
|
|
266
|
+
svg.setDocumentViewBox(0f, 0f, documentWidth, documentHeight)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
val viewBox = svg.documentViewBox
|
|
271
|
+
if (viewBox != null && viewBox.width() > 0f && viewBox.height() > 0f) {
|
|
272
|
+
val scale = minOf(width.toFloat() / viewBox.width(), height.toFloat() / viewBox.height())
|
|
273
|
+
if (scale.isFinite() && scale > 0f) {
|
|
274
|
+
svg.documentWidth = viewBox.width() * scale
|
|
275
|
+
svg.documentHeight = viewBox.height() * scale
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return SimpleResource(svg)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private class OneKeySvgDrawableTranscoder : ResourceTranscoder<SVG, Drawable> {
|
|
283
|
+
override fun transcode(toTranscode: Resource<SVG>, options: Options): Resource<Drawable> =
|
|
284
|
+
SimpleResource(PictureDrawable(toTranscode.get().renderToPicture()))
|
|
285
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
package com.margelo.nitro.onekeyimage
|
|
2
|
+
|
|
3
|
+
import android.net.Uri
|
|
4
|
+
import com.bumptech.glide.load.model.GlideUrl
|
|
5
|
+
import com.bumptech.glide.load.model.LazyHeaders
|
|
6
|
+
import org.json.JSONObject
|
|
7
|
+
import java.nio.ByteBuffer
|
|
8
|
+
import java.security.MessageDigest
|
|
9
|
+
import java.util.Locale
|
|
10
|
+
|
|
11
|
+
internal object OneKeyImageModel {
|
|
12
|
+
fun headers(json: String?): Map<String, String> {
|
|
13
|
+
if (json.isNullOrBlank()) return emptyMap()
|
|
14
|
+
return try {
|
|
15
|
+
val objectValue = JSONObject(json)
|
|
16
|
+
buildMap {
|
|
17
|
+
objectValue.keys().forEach { key ->
|
|
18
|
+
val value = objectValue.opt(key)
|
|
19
|
+
if (value is String) put(key, value)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
} catch (_: Exception) {
|
|
23
|
+
emptyMap()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
fun build(uri: String, headersJson: String?): Any {
|
|
28
|
+
if (uri.startsWith("data:")) return OneKeyImageDataUriModel(uri)
|
|
29
|
+
if (!uri.startsWith("http://") && !uri.startsWith("https://")) {
|
|
30
|
+
return OneKeyImageLocalModel(Uri.parse(uri))
|
|
31
|
+
}
|
|
32
|
+
val parsedHeaders = headers(headersJson)
|
|
33
|
+
val headerBuilder = LazyHeaders.Builder()
|
|
34
|
+
parsedHeaders.forEach { (name, value) -> headerBuilder.addHeader(name, value) }
|
|
35
|
+
val headers = headerBuilder.build()
|
|
36
|
+
return OneKeyImageRemoteModel(
|
|
37
|
+
glideUrl = GlideUrl(uri, headers),
|
|
38
|
+
headersDigest = remoteHeadersDigest(parsedHeaders),
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
internal fun remoteHeadersDigest(headers: Map<String, String>): String? {
|
|
43
|
+
if (headers.isEmpty()) return null
|
|
44
|
+
val digest = MessageDigest.getInstance("SHA-256")
|
|
45
|
+
headers.entries
|
|
46
|
+
.map { it.key.lowercase(Locale.ROOT) to it.value }
|
|
47
|
+
.sortedWith(compareBy<Pair<String, String>>({ it.first }, { it.second }))
|
|
48
|
+
.forEach { (name, value) ->
|
|
49
|
+
updateLengthPrefixed(digest, name.toByteArray(Charsets.UTF_8))
|
|
50
|
+
updateLengthPrefixed(digest, value.toByteArray(Charsets.UTF_8))
|
|
51
|
+
}
|
|
52
|
+
return digest.digest().joinToString("") { "%02x".format(Locale.ROOT, it.toInt() and 0xff) }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private fun updateLengthPrefixed(digest: MessageDigest, value: ByteArray) {
|
|
56
|
+
digest.update(ByteBuffer.allocate(Int.SIZE_BYTES).putInt(value.size).array())
|
|
57
|
+
digest.update(value)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
internal data class OneKeyImageRemoteModel(
|
|
62
|
+
val glideUrl: GlideUrl,
|
|
63
|
+
val headersDigest: String?,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
internal data class OneKeyImageLocalModel(val uri: Uri)
|
|
67
|
+
|
|
68
|
+
internal data class OneKeyImageDataUriModel(val dataUri: String)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
package com.margelo.nitro.onekeyimage
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
7
|
+
import com.facebook.react.uimanager.ViewManager
|
|
8
|
+
import com.margelo.nitro.onekeyimage.views.HybridOneKeyImageManager
|
|
9
|
+
|
|
10
|
+
class OneKeyImagePackage : BaseReactPackage() {
|
|
11
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = null
|
|
12
|
+
|
|
13
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider =
|
|
14
|
+
ReactModuleInfoProvider { HashMap() }
|
|
15
|
+
|
|
16
|
+
override fun createViewManagers(
|
|
17
|
+
reactContext: ReactApplicationContext,
|
|
18
|
+
): List<ViewManager<*, *>> {
|
|
19
|
+
OneKeyImageGlideRegistry.ensureRegistered(reactContext)
|
|
20
|
+
return listOf(HybridOneKeyImageManager())
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
companion object {
|
|
24
|
+
init {
|
|
25
|
+
System.loadLibrary("onekeyimage")
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|