@granite-js/image 0.1.34 → 1.0.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 +4 -276
- package/GraniteImage.podspec +72 -0
- package/android/build.gradle +178 -0
- package/android/gradle.properties +5 -0
- package/android/src/coil/java/run/granite/image/providers/CoilImageProvider.kt +156 -0
- package/android/src/glide/java/run/granite/image/providers/GlideImageProvider.kt +168 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/run/granite/image/GraniteImage.kt +277 -0
- package/android/src/main/java/run/granite/image/GraniteImageEvents.kt +83 -0
- package/android/src/main/java/run/granite/image/GraniteImageManager.kt +100 -0
- package/android/src/main/java/run/granite/image/GraniteImageModule.kt +131 -0
- package/android/src/main/java/run/granite/image/GraniteImagePackage.kt +59 -0
- package/android/src/main/java/run/granite/image/GraniteImageProvider.kt +105 -0
- package/android/src/main/java/run/granite/image/GraniteImageRegistry.kt +29 -0
- package/android/src/okhttp/java/run/granite/image/providers/OkHttpImageProvider.kt +228 -0
- package/dist/module/GraniteImage.js +127 -0
- package/dist/module/GraniteImage.js.map +1 -0
- package/dist/module/GraniteImageNativeComponent.ts +56 -0
- package/dist/module/NativeGraniteImageModule.js +5 -0
- package/dist/module/NativeGraniteImageModule.js.map +1 -0
- package/dist/module/index.js +6 -0
- package/dist/module/index.js.map +1 -0
- package/dist/module/package.json +1 -0
- package/dist/typescript/GraniteImage.d.ts +35 -0
- package/dist/typescript/GraniteImageNativeComponent.d.ts +37 -0
- package/dist/typescript/NativeGraniteImageModule.d.ts +16 -0
- package/dist/typescript/index.d.ts +4 -0
- package/example/react-native.config.js +21 -0
- package/ios/GraniteImageComponentView.h +14 -0
- package/ios/GraniteImageComponentView.mm +388 -0
- package/ios/GraniteImageModule.swift +107 -0
- package/ios/GraniteImageModuleBridge.m +15 -0
- package/ios/GraniteImageProvider.swift +70 -0
- package/ios/GraniteImageRegistry.swift +30 -0
- package/ios/Providers/SDWebImageProvider.swift +175 -0
- package/package.json +71 -32
- package/src/GraniteImage.tsx +215 -0
- package/src/GraniteImageNativeComponent.ts +56 -0
- package/src/NativeGraniteImageModule.ts +16 -0
- package/src/index.ts +21 -0
- package/dist/index.d.mts +0 -70
- package/dist/index.d.ts +0 -70
- package/dist/index.js +0 -204
- package/dist/index.mjs +0 -180
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
package run.granite.image
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import com.facebook.react.bridge.Promise
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
7
|
+
import com.facebook.react.bridge.ReactMethod
|
|
8
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
9
|
+
import org.json.JSONArray
|
|
10
|
+
import org.json.JSONObject
|
|
11
|
+
import java.util.concurrent.Executors
|
|
12
|
+
import java.util.concurrent.atomic.AtomicInteger
|
|
13
|
+
|
|
14
|
+
@ReactModule(name = GraniteImageModule.NAME)
|
|
15
|
+
class GraniteImageModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
|
|
16
|
+
|
|
17
|
+
companion object {
|
|
18
|
+
const val NAME = "GraniteImageModule"
|
|
19
|
+
private const val TAG = "GraniteImageModule"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private val executor = Executors.newFixedThreadPool(4)
|
|
23
|
+
|
|
24
|
+
override fun getName(): String = NAME
|
|
25
|
+
|
|
26
|
+
@ReactMethod
|
|
27
|
+
fun preload(sourcesJson: String, promise: Promise) {
|
|
28
|
+
Log.d(TAG, "preload called with: $sourcesJson")
|
|
29
|
+
|
|
30
|
+
val provider = GraniteImageRegistry.provider
|
|
31
|
+
if (provider == null) {
|
|
32
|
+
Log.w(TAG, "No provider registered, cannot preload")
|
|
33
|
+
promise.reject("NO_PROVIDER", "No provider registered, cannot preload")
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
executor.execute {
|
|
38
|
+
try {
|
|
39
|
+
val sources = JSONArray(sourcesJson)
|
|
40
|
+
val totalCount = sources.length()
|
|
41
|
+
val completedCount = AtomicInteger(0)
|
|
42
|
+
val successCount = AtomicInteger(0)
|
|
43
|
+
val failCount = AtomicInteger(0)
|
|
44
|
+
|
|
45
|
+
if (totalCount == 0) {
|
|
46
|
+
promise.resolve(null)
|
|
47
|
+
return@execute
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (i in 0 until sources.length()) {
|
|
51
|
+
val source = sources.getJSONObject(i)
|
|
52
|
+
val uri = source.optString("uri", "")
|
|
53
|
+
if (uri.isEmpty()) {
|
|
54
|
+
if (completedCount.incrementAndGet() == totalCount) {
|
|
55
|
+
Log.d(TAG, "Preload completed: ${successCount.get()} succeeded, ${failCount.get()} failed")
|
|
56
|
+
promise.resolve(null)
|
|
57
|
+
}
|
|
58
|
+
continue
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
val headersObj = source.optJSONObject("headers")
|
|
62
|
+
val headers = mutableMapOf<String, String>()
|
|
63
|
+
headersObj?.let {
|
|
64
|
+
val keys = it.keys()
|
|
65
|
+
while (keys.hasNext()) {
|
|
66
|
+
val key = keys.next()
|
|
67
|
+
headers[key] = it.getString(key)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
val priorityStr = source.optString("priority", "normal")
|
|
72
|
+
val priority = when (priorityStr) {
|
|
73
|
+
"high" -> GraniteImagePriority.HIGH
|
|
74
|
+
"low" -> GraniteImagePriority.LOW
|
|
75
|
+
else -> GraniteImagePriority.NORMAL
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
val cacheStr = source.optString("cache", "")
|
|
79
|
+
val cachePolicy = when (cacheStr) {
|
|
80
|
+
"cacheOnly" -> GraniteImageCachePolicy.DISK
|
|
81
|
+
"web" -> GraniteImageCachePolicy.NONE
|
|
82
|
+
else -> GraniteImageCachePolicy.DISK
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
Log.d(TAG, "Preloading: $uri")
|
|
86
|
+
|
|
87
|
+
// Call provider's preload method (loadImage with null view for preload)
|
|
88
|
+
provider.loadImage(
|
|
89
|
+
url = uri,
|
|
90
|
+
imageView = null,
|
|
91
|
+
contentMode = "cover",
|
|
92
|
+
headers = headers.ifEmpty { null },
|
|
93
|
+
priority = priority,
|
|
94
|
+
cachePolicy = cachePolicy,
|
|
95
|
+
onProgress = null,
|
|
96
|
+
onCompletion = { success, width, height, error ->
|
|
97
|
+
if (success) {
|
|
98
|
+
Log.d(TAG, "Preloaded successfully: $uri (${width}x${height})")
|
|
99
|
+
successCount.incrementAndGet()
|
|
100
|
+
} else {
|
|
101
|
+
Log.d(TAG, "Preload failed for $uri: $error")
|
|
102
|
+
failCount.incrementAndGet()
|
|
103
|
+
}
|
|
104
|
+
if (completedCount.incrementAndGet() == totalCount) {
|
|
105
|
+
Log.d(TAG, "Preload completed: ${successCount.get()} succeeded, ${failCount.get()} failed")
|
|
106
|
+
promise.resolve(null)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
} catch (e: Exception) {
|
|
112
|
+
Log.e(TAG, "Failed to parse sources JSON: ${e.message}")
|
|
113
|
+
promise.reject("PARSE_ERROR", "Failed to parse sources JSON: ${e.message}")
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@ReactMethod
|
|
119
|
+
fun clearMemoryCache(promise: Promise) {
|
|
120
|
+
Log.d(TAG, "clearMemoryCache called")
|
|
121
|
+
// Memory cache clearing depends on provider implementation
|
|
122
|
+
promise.resolve(null)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@ReactMethod
|
|
126
|
+
fun clearDiskCache(promise: Promise) {
|
|
127
|
+
Log.d(TAG, "clearDiskCache called")
|
|
128
|
+
// Disk cache clearing depends on provider implementation
|
|
129
|
+
promise.resolve(null)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
package run.granite.image
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import com.facebook.react.ReactPackage
|
|
5
|
+
import com.facebook.react.bridge.NativeModule
|
|
6
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
7
|
+
import com.facebook.react.uimanager.ViewManager
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* React Native Package that registers the GraniteImage component and GraniteImageModule.
|
|
11
|
+
*/
|
|
12
|
+
class GraniteImagePackage : ReactPackage {
|
|
13
|
+
companion object {
|
|
14
|
+
private const val TAG = "GraniteImagePackage"
|
|
15
|
+
private var providerRegistered = false
|
|
16
|
+
|
|
17
|
+
init {
|
|
18
|
+
registerDefaultProvider()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private fun registerDefaultProvider() {
|
|
22
|
+
if (providerRegistered) return
|
|
23
|
+
|
|
24
|
+
// Try to auto-register a provider based on available implementations
|
|
25
|
+
val providerClasses = listOf(
|
|
26
|
+
"run.granite.image.providers.OkHttpImageProvider",
|
|
27
|
+
"run.granite.image.providers.GlideImageProvider",
|
|
28
|
+
"run.granite.image.providers.CoilImageProvider"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
for (className in providerClasses) {
|
|
32
|
+
try {
|
|
33
|
+
val clazz = Class.forName(className)
|
|
34
|
+
val provider = clazz.getDeclaredConstructor().newInstance() as GraniteImageProvider
|
|
35
|
+
GraniteImageRegistry.registerProvider(provider)
|
|
36
|
+
Log.d(TAG, "Auto-registered provider: $className")
|
|
37
|
+
providerRegistered = true
|
|
38
|
+
break
|
|
39
|
+
} catch (e: ClassNotFoundException) {
|
|
40
|
+
// Provider not available, try next
|
|
41
|
+
} catch (e: Exception) {
|
|
42
|
+
Log.e(TAG, "Failed to instantiate provider $className: ${e.message}")
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!providerRegistered) {
|
|
47
|
+
Log.w(TAG, "No image provider found. Make sure to include one of: okhttp, glide, or coil flavor.")
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
53
|
+
return listOf(GraniteImageModule(reactContext))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
57
|
+
return listOf(GraniteImageManager())
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
package run.granite.image
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Bitmap
|
|
5
|
+
import android.view.View
|
|
6
|
+
import android.widget.ImageView
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Priority levels for image loading
|
|
10
|
+
*/
|
|
11
|
+
enum class GraniteImagePriority {
|
|
12
|
+
LOW, NORMAL, HIGH
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Cache policy for image loading
|
|
17
|
+
*/
|
|
18
|
+
enum class GraniteImageCachePolicy {
|
|
19
|
+
MEMORY, DISK, NONE
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Progress callback
|
|
24
|
+
*/
|
|
25
|
+
typealias GraniteImageProgressCallback = (loaded: Long, total: Long) -> Unit
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Completion callback with image dimensions
|
|
29
|
+
*/
|
|
30
|
+
typealias GraniteImageCompletionCallback = (bitmap: Bitmap?, error: Exception?, width: Int, height: Int) -> Unit
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Interface that defines the contract for image loading providers.
|
|
34
|
+
* Implementations can use any image loading library (OkHttp, Glide, Coil, etc.)
|
|
35
|
+
*/
|
|
36
|
+
interface GraniteImageProvider {
|
|
37
|
+
/**
|
|
38
|
+
* Creates and returns a new View that will be used to display the image.
|
|
39
|
+
* The returned view should be capable of displaying images (typically ImageView).
|
|
40
|
+
*/
|
|
41
|
+
fun createImageView(context: Context): View
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Loads an image from the given URL into the provided view.
|
|
45
|
+
* @param url The URL string of the image to load
|
|
46
|
+
* @param view The view returned from createImageView where the image should be displayed
|
|
47
|
+
* @param scaleType The scale type to use when displaying the image
|
|
48
|
+
*/
|
|
49
|
+
fun loadImage(url: String, into: View, scaleType: ImageView.ScaleType)
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Cancels any ongoing image load for the given view.
|
|
53
|
+
* @param view The view whose image load should be cancelled
|
|
54
|
+
*/
|
|
55
|
+
fun cancelLoad(view: View)
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Loads an image with full options support including headers, priority, cache policy, and callbacks.
|
|
59
|
+
* Default implementation delegates to simple loadImage.
|
|
60
|
+
*/
|
|
61
|
+
fun loadImage(
|
|
62
|
+
url: String,
|
|
63
|
+
into: View?,
|
|
64
|
+
scaleType: ImageView.ScaleType,
|
|
65
|
+
headers: Map<String, String>?,
|
|
66
|
+
priority: GraniteImagePriority,
|
|
67
|
+
cachePolicy: GraniteImageCachePolicy,
|
|
68
|
+
defaultSource: String?,
|
|
69
|
+
progressCallback: GraniteImageProgressCallback?,
|
|
70
|
+
completionCallback: GraniteImageCompletionCallback?
|
|
71
|
+
) {
|
|
72
|
+
if (into != null) {
|
|
73
|
+
// Default implementation - just use simple load
|
|
74
|
+
loadImage(url, into, scaleType)
|
|
75
|
+
}
|
|
76
|
+
// Notify completion with unknown dimensions
|
|
77
|
+
completionCallback?.invoke(null, null, 0, 0)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Preloads an image without displaying it.
|
|
82
|
+
* Uses the extended loadImage with null view.
|
|
83
|
+
*/
|
|
84
|
+
fun loadImage(
|
|
85
|
+
url: String,
|
|
86
|
+
imageView: Nothing?,
|
|
87
|
+
contentMode: String,
|
|
88
|
+
headers: Map<String, String>?,
|
|
89
|
+
priority: GraniteImagePriority,
|
|
90
|
+
cachePolicy: GraniteImageCachePolicy,
|
|
91
|
+
onProgress: GraniteImageProgressCallback?,
|
|
92
|
+
onCompletion: ((success: Boolean, width: Int, height: Int, error: String?) -> Unit)?
|
|
93
|
+
) {
|
|
94
|
+
// Default implementation does nothing for preload
|
|
95
|
+
onCompletion?.invoke(false, 0, 0, "Provider does not support preloading")
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Applies a tint color to the image view.
|
|
100
|
+
* Default implementation does nothing.
|
|
101
|
+
*/
|
|
102
|
+
fun applyTintColor(color: Int, view: View) {
|
|
103
|
+
// Default implementation - do nothing
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
package run.granite.image
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Singleton registry for managing the current GraniteImageProvider.
|
|
5
|
+
* Applications should register their provider implementation at app startup.
|
|
6
|
+
*/
|
|
7
|
+
object GraniteImageRegistry {
|
|
8
|
+
/**
|
|
9
|
+
* The currently registered image provider.
|
|
10
|
+
* If null, GraniteImage will display an error state.
|
|
11
|
+
*/
|
|
12
|
+
var provider: GraniteImageProvider? = null
|
|
13
|
+
private set
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Registers an image provider to be used by all GraniteImage instances.
|
|
17
|
+
* @param provider The provider implementation to register
|
|
18
|
+
*/
|
|
19
|
+
fun registerProvider(provider: GraniteImageProvider) {
|
|
20
|
+
this.provider = provider
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Clears the currently registered provider.
|
|
25
|
+
*/
|
|
26
|
+
fun clearProvider() {
|
|
27
|
+
this.provider = null
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
package run.granite.image.providers
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Bitmap
|
|
5
|
+
import android.graphics.BitmapFactory
|
|
6
|
+
import android.graphics.PorterDuff
|
|
7
|
+
import android.graphics.PorterDuffColorFilter
|
|
8
|
+
import android.os.Handler
|
|
9
|
+
import android.os.Looper
|
|
10
|
+
import android.util.Log
|
|
11
|
+
import android.view.View
|
|
12
|
+
import android.widget.ImageView
|
|
13
|
+
import run.granite.image.GraniteImageProvider
|
|
14
|
+
import run.granite.image.GraniteImagePriority
|
|
15
|
+
import run.granite.image.GraniteImageCachePolicy
|
|
16
|
+
import run.granite.image.GraniteImageProgressCallback
|
|
17
|
+
import run.granite.image.GraniteImageCompletionCallback
|
|
18
|
+
import okhttp3.Call
|
|
19
|
+
import okhttp3.Callback
|
|
20
|
+
import okhttp3.CacheControl
|
|
21
|
+
import okhttp3.OkHttpClient
|
|
22
|
+
import okhttp3.Request
|
|
23
|
+
import okhttp3.Response
|
|
24
|
+
import java.io.IOException
|
|
25
|
+
import java.util.WeakHashMap
|
|
26
|
+
import java.util.concurrent.TimeUnit
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* GraniteImageProvider implementation using OkHttp.
|
|
30
|
+
* This is analogous to iOS's URLSessionImageProvider.
|
|
31
|
+
*/
|
|
32
|
+
class OkHttpImageProvider : GraniteImageProvider {
|
|
33
|
+
companion object {
|
|
34
|
+
private const val TAG = "OkHttpImageProvider"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private val client = OkHttpClient.Builder()
|
|
38
|
+
.connectTimeout(30, TimeUnit.SECONDS)
|
|
39
|
+
.readTimeout(30, TimeUnit.SECONDS)
|
|
40
|
+
.build()
|
|
41
|
+
private val activeCalls = WeakHashMap<View, Call>()
|
|
42
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
43
|
+
|
|
44
|
+
override fun createImageView(context: Context): View {
|
|
45
|
+
return ImageView(context).apply {
|
|
46
|
+
setBackgroundColor(android.graphics.Color.LTGRAY)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
override fun loadImage(url: String, into: View, scaleType: ImageView.ScaleType) {
|
|
51
|
+
loadImage(url, into, scaleType, null, GraniteImagePriority.NORMAL, GraniteImageCachePolicy.DISK, null, null, null)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
override fun loadImage(
|
|
55
|
+
url: String,
|
|
56
|
+
into: View?,
|
|
57
|
+
scaleType: ImageView.ScaleType,
|
|
58
|
+
headers: Map<String, String>?,
|
|
59
|
+
priority: GraniteImagePriority,
|
|
60
|
+
cachePolicy: GraniteImageCachePolicy,
|
|
61
|
+
defaultSource: String?,
|
|
62
|
+
progressCallback: GraniteImageProgressCallback?,
|
|
63
|
+
completionCallback: GraniteImageCompletionCallback?
|
|
64
|
+
) {
|
|
65
|
+
// Allow null view for preloading
|
|
66
|
+
val imageView: ImageView? = if (into != null) {
|
|
67
|
+
if (into !is ImageView) {
|
|
68
|
+
Log.e(TAG, "View is not an ImageView")
|
|
69
|
+
completionCallback?.invoke(null, Exception("View is not an ImageView"), 0, 0)
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
into.scaleType = scaleType
|
|
73
|
+
// Cancel any existing request for this view
|
|
74
|
+
cancelLoad(into)
|
|
75
|
+
into
|
|
76
|
+
} else {
|
|
77
|
+
null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Apply default source (placeholder) before loading
|
|
81
|
+
if (imageView != null && !defaultSource.isNullOrEmpty()) {
|
|
82
|
+
val resourceId = imageView.context.resources.getIdentifier(defaultSource, "drawable", imageView.context.packageName)
|
|
83
|
+
if (resourceId != 0) {
|
|
84
|
+
imageView.setImageResource(resourceId)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
val requestBuilder = Request.Builder().url(url)
|
|
89
|
+
|
|
90
|
+
// Add headers
|
|
91
|
+
headers?.forEach { (key, value) ->
|
|
92
|
+
requestBuilder.addHeader(key, value)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Apply cache policy
|
|
96
|
+
when (cachePolicy) {
|
|
97
|
+
GraniteImageCachePolicy.NONE -> {
|
|
98
|
+
requestBuilder.cacheControl(CacheControl.FORCE_NETWORK)
|
|
99
|
+
}
|
|
100
|
+
GraniteImageCachePolicy.MEMORY, GraniteImageCachePolicy.DISK -> {
|
|
101
|
+
// Use default caching
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
val request = requestBuilder.build()
|
|
106
|
+
val call = client.newCall(request)
|
|
107
|
+
if (into != null) {
|
|
108
|
+
activeCalls[into] = call
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
call.enqueue(object : Callback {
|
|
112
|
+
override fun onFailure(call: Call, e: IOException) {
|
|
113
|
+
Log.e(TAG, "Error loading image: ${e.message}")
|
|
114
|
+
if (into != null) {
|
|
115
|
+
activeCalls.remove(into)
|
|
116
|
+
}
|
|
117
|
+
mainHandler.post {
|
|
118
|
+
completionCallback?.invoke(null, e, 0, 0)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
override fun onResponse(call: Call, response: Response) {
|
|
123
|
+
if (into != null) {
|
|
124
|
+
activeCalls.remove(into)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!response.isSuccessful) {
|
|
128
|
+
val error = Exception("HTTP error: ${response.code}")
|
|
129
|
+
Log.e(TAG, "HTTP error: ${response.code}")
|
|
130
|
+
mainHandler.post {
|
|
131
|
+
completionCallback?.invoke(null, error, 0, 0)
|
|
132
|
+
}
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
val body = response.body
|
|
137
|
+
if (body == null) {
|
|
138
|
+
val error = Exception("No data received")
|
|
139
|
+
Log.e(TAG, "No data received")
|
|
140
|
+
mainHandler.post {
|
|
141
|
+
completionCallback?.invoke(null, error, 0, 0)
|
|
142
|
+
}
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Get content length for progress
|
|
147
|
+
val contentLength = body.contentLength()
|
|
148
|
+
|
|
149
|
+
// Read bytes with progress reporting
|
|
150
|
+
val inputStream = body.byteStream()
|
|
151
|
+
val bytes = ByteArray(contentLength.toInt().coerceAtLeast(1024))
|
|
152
|
+
var totalBytesRead = 0L
|
|
153
|
+
val buffer = ByteArray(8192)
|
|
154
|
+
var bytesRead: Int
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
val outputStream = java.io.ByteArrayOutputStream()
|
|
158
|
+
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
|
|
159
|
+
outputStream.write(buffer, 0, bytesRead)
|
|
160
|
+
totalBytesRead += bytesRead
|
|
161
|
+
if (contentLength > 0) {
|
|
162
|
+
progressCallback?.invoke(totalBytesRead, contentLength)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
val imageBytes = outputStream.toByteArray()
|
|
166
|
+
|
|
167
|
+
val bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
|
|
168
|
+
if (bitmap == null) {
|
|
169
|
+
val error = Exception("Failed to decode image data")
|
|
170
|
+
Log.e(TAG, "Failed to decode image data")
|
|
171
|
+
mainHandler.post {
|
|
172
|
+
completionCallback?.invoke(null, error, 0, 0)
|
|
173
|
+
}
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
mainHandler.post {
|
|
178
|
+
imageView?.setImageBitmap(bitmap)
|
|
179
|
+
Log.d(TAG, "Loaded with OkHttp: $url")
|
|
180
|
+
completionCallback?.invoke(bitmap, null, bitmap.width, bitmap.height)
|
|
181
|
+
}
|
|
182
|
+
} catch (e: Exception) {
|
|
183
|
+
Log.e(TAG, "Error reading image data: ${e.message}")
|
|
184
|
+
mainHandler.post {
|
|
185
|
+
completionCallback?.invoke(null, e, 0, 0)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
override fun cancelLoad(view: View) {
|
|
193
|
+
activeCalls[view]?.cancel()
|
|
194
|
+
activeCalls.remove(view)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
override fun applyTintColor(color: Int, view: View) {
|
|
198
|
+
if (view is ImageView) {
|
|
199
|
+
view.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
override fun loadImage(
|
|
204
|
+
url: String,
|
|
205
|
+
imageView: Nothing?,
|
|
206
|
+
contentMode: String,
|
|
207
|
+
headers: Map<String, String>?,
|
|
208
|
+
priority: GraniteImagePriority,
|
|
209
|
+
cachePolicy: GraniteImageCachePolicy,
|
|
210
|
+
onProgress: GraniteImageProgressCallback?,
|
|
211
|
+
onCompletion: ((success: Boolean, width: Int, height: Int, error: String?) -> Unit)?
|
|
212
|
+
) {
|
|
213
|
+
// Preload implementation - load without displaying
|
|
214
|
+
loadImage(
|
|
215
|
+
url = url,
|
|
216
|
+
into = null,
|
|
217
|
+
scaleType = ImageView.ScaleType.CENTER_CROP,
|
|
218
|
+
headers = headers,
|
|
219
|
+
priority = priority,
|
|
220
|
+
cachePolicy = cachePolicy,
|
|
221
|
+
defaultSource = null,
|
|
222
|
+
progressCallback = onProgress,
|
|
223
|
+
completionCallback = { bitmap, error, width, height ->
|
|
224
|
+
onCompletion?.invoke(bitmap != null, width, height, error?.message)
|
|
225
|
+
}
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React, { useCallback } from 'react';
|
|
4
|
+
import { NativeModules } from 'react-native';
|
|
5
|
+
import GraniteImageNativeComponent from './GraniteImageNativeComponent';
|
|
6
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
7
|
+
const {
|
|
8
|
+
GraniteImageModule
|
|
9
|
+
} = NativeModules;
|
|
10
|
+
|
|
11
|
+
// Source types matching GraniteImage
|
|
12
|
+
|
|
13
|
+
// Map resizeMode to contentMode for native
|
|
14
|
+
const resizeModeToContentMode = resizeMode => {
|
|
15
|
+
return resizeMode || 'cover';
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Map GraniteImage cache to native cachePolicy
|
|
19
|
+
const mapCachePolicy = cache => {
|
|
20
|
+
switch (cache) {
|
|
21
|
+
case 'cacheOnly':
|
|
22
|
+
return 'disk';
|
|
23
|
+
case 'web':
|
|
24
|
+
return 'none';
|
|
25
|
+
case 'immutable':
|
|
26
|
+
default:
|
|
27
|
+
return 'disk';
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Type declarations for static methods
|
|
32
|
+
|
|
33
|
+
// Static methods for cache management
|
|
34
|
+
const clearMemoryCache = () => {
|
|
35
|
+
if (GraniteImageModule?.clearMemoryCache) {
|
|
36
|
+
return GraniteImageModule.clearMemoryCache();
|
|
37
|
+
}
|
|
38
|
+
console.warn('GraniteImage.clearMemoryCache: Native module not available');
|
|
39
|
+
return Promise.resolve();
|
|
40
|
+
};
|
|
41
|
+
const clearDiskCache = () => {
|
|
42
|
+
if (GraniteImageModule?.clearDiskCache) {
|
|
43
|
+
return GraniteImageModule.clearDiskCache();
|
|
44
|
+
}
|
|
45
|
+
console.warn('GraniteImage.clearDiskCache: Native module not available');
|
|
46
|
+
return Promise.resolve();
|
|
47
|
+
};
|
|
48
|
+
const preload = sources => {
|
|
49
|
+
if (GraniteImageModule?.preload) {
|
|
50
|
+
const sourcesJson = JSON.stringify(sources);
|
|
51
|
+
return GraniteImageModule.preload(sourcesJson);
|
|
52
|
+
} else {
|
|
53
|
+
console.warn('GraniteImage.preload: Native module not available');
|
|
54
|
+
return Promise.resolve();
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const GraniteImageBase = ({
|
|
58
|
+
source,
|
|
59
|
+
resizeMode = 'cover',
|
|
60
|
+
tintColor,
|
|
61
|
+
style,
|
|
62
|
+
defaultSource,
|
|
63
|
+
fallbackSource,
|
|
64
|
+
priority = 'normal',
|
|
65
|
+
cachePolicy = 'disk',
|
|
66
|
+
onLoadStart,
|
|
67
|
+
onProgress,
|
|
68
|
+
onLoad,
|
|
69
|
+
onError,
|
|
70
|
+
onLoadEnd,
|
|
71
|
+
...viewProps
|
|
72
|
+
}) => {
|
|
73
|
+
// Parse source
|
|
74
|
+
const uri = typeof source === 'string' ? source : (source && typeof source === 'object' ? source.uri : '') ?? '';
|
|
75
|
+
const headers = typeof source === 'object' && source.headers ? JSON.stringify(source.headers) : undefined;
|
|
76
|
+
const sourcePriority = typeof source === 'object' ? source.priority : undefined;
|
|
77
|
+
const sourceCache = typeof source === 'object' ? source.cache : undefined;
|
|
78
|
+
|
|
79
|
+
// Handle defaultSource
|
|
80
|
+
const defaultSourceUri = typeof defaultSource === 'string' ? defaultSource : typeof defaultSource === 'number' ? undefined // Local require() - need native handling
|
|
81
|
+
: undefined;
|
|
82
|
+
|
|
83
|
+
// Handle fallbackSource
|
|
84
|
+
const fallbackSourceUri = typeof fallbackSource === 'string' ? fallbackSource : typeof fallbackSource === 'number' ? undefined // Local require() - need native handling
|
|
85
|
+
: undefined;
|
|
86
|
+
|
|
87
|
+
// Event handlers
|
|
88
|
+
const handleLoadStart = useCallback(event => {
|
|
89
|
+
onLoadStart?.(event);
|
|
90
|
+
}, [onLoadStart]);
|
|
91
|
+
const handleProgress = useCallback(event => {
|
|
92
|
+
onProgress?.(event);
|
|
93
|
+
}, [onProgress]);
|
|
94
|
+
const handleLoad = useCallback(event => {
|
|
95
|
+
onLoad?.(event);
|
|
96
|
+
}, [onLoad]);
|
|
97
|
+
const handleError = useCallback(event => {
|
|
98
|
+
onError?.(event);
|
|
99
|
+
}, [onError]);
|
|
100
|
+
const handleLoadEnd = useCallback(event => {
|
|
101
|
+
onLoadEnd?.(event);
|
|
102
|
+
}, [onLoadEnd]);
|
|
103
|
+
return /*#__PURE__*/_jsx(GraniteImageNativeComponent, {
|
|
104
|
+
...viewProps,
|
|
105
|
+
uri: uri,
|
|
106
|
+
headers: headers,
|
|
107
|
+
contentMode: resizeModeToContentMode(resizeMode),
|
|
108
|
+
tintColor: tintColor,
|
|
109
|
+
defaultSource: defaultSourceUri,
|
|
110
|
+
fallbackSource: fallbackSourceUri,
|
|
111
|
+
priority: sourcePriority || priority,
|
|
112
|
+
cachePolicy: sourceCache ? mapCachePolicy(sourceCache) : cachePolicy,
|
|
113
|
+
style: style,
|
|
114
|
+
onGraniteLoadStart: onLoadStart ? handleLoadStart : undefined,
|
|
115
|
+
onGraniteProgress: onProgress ? handleProgress : undefined,
|
|
116
|
+
onGraniteLoad: onLoad ? handleLoad : undefined,
|
|
117
|
+
onGraniteError: onError ? handleError : undefined,
|
|
118
|
+
onGraniteLoadEnd: onLoadEnd ? handleLoadEnd : undefined
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
export const GraniteImage = Object.assign(GraniteImageBase, {
|
|
122
|
+
clearMemoryCache,
|
|
123
|
+
clearDiskCache,
|
|
124
|
+
preload
|
|
125
|
+
});
|
|
126
|
+
export default GraniteImage;
|
|
127
|
+
//# sourceMappingURL=GraniteImage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["React","useCallback","NativeModules","GraniteImageNativeComponent","jsx","_jsx","GraniteImageModule","resizeModeToContentMode","resizeMode","mapCachePolicy","cache","clearMemoryCache","console","warn","Promise","resolve","clearDiskCache","preload","sources","sourcesJson","JSON","stringify","GraniteImageBase","source","tintColor","style","defaultSource","fallbackSource","priority","cachePolicy","onLoadStart","onProgress","onLoad","onError","onLoadEnd","viewProps","uri","headers","undefined","sourcePriority","sourceCache","defaultSourceUri","fallbackSourceUri","handleLoadStart","event","handleProgress","handleLoad","handleError","handleLoadEnd","contentMode","onGraniteLoadStart","onGraniteProgress","onGraniteLoad","onGraniteError","onGraniteLoadEnd","GraniteImage","Object","assign"],"sourceRoot":"../../src","sources":["GraniteImage.tsx"],"mappings":";;AAAA,OAAOA,KAAK,IAAIC,WAAW,QAAQ,OAAO;AAC1C,SAKEC,aAAa,QAGR,cAAc;AACrB,OAAOC,2BAA2B,MAM3B,+BAA+B;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAEvC,MAAM;EAAEC;AAAmB,CAAC,GAAGJ,aAAa;;AAE5C;;AAuCA;AACA,MAAMK,uBAAuB,GAAIC,UAAuB,IAAiD;EACvG,OAAOA,UAAU,IAAI,OAAO;AAC9B,CAAC;;AAED;AACA,MAAMC,cAAc,GAAIC,KAAyC,IAAkB;EACjF,QAAQA,KAAK;IACX,KAAK,WAAW;MACd,OAAO,MAAM;IACf,KAAK,KAAK;MACR,OAAO,MAAM;IACf,KAAK,WAAW;IAChB;MACE,OAAO,MAAM;EACjB;AACF,CAAC;;AAED;;AASA;AACA,MAAMC,gBAAgB,GAAGA,CAAA,KAAqB;EAC5C,IAAIL,kBAAkB,EAAEK,gBAAgB,EAAE;IACxC,OAAOL,kBAAkB,CAACK,gBAAgB,CAAC,CAAC;EAC9C;EACAC,OAAO,CAACC,IAAI,CAAC,4DAA4D,CAAC;EAC1E,OAAOC,OAAO,CAACC,OAAO,CAAC,CAAC;AAC1B,CAAC;AAED,MAAMC,cAAc,GAAGA,CAAA,KAAqB;EAC1C,IAAIV,kBAAkB,EAAEU,cAAc,EAAE;IACtC,OAAOV,kBAAkB,CAACU,cAAc,CAAC,CAAC;EAC5C;EACAJ,OAAO,CAACC,IAAI,CAAC,0DAA0D,CAAC;EACxE,OAAOC,OAAO,CAACC,OAAO,CAAC,CAAC;AAC1B,CAAC;AAED,MAAME,OAAO,GAAIC,OAA6B,IAAoB;EAChE,IAAIZ,kBAAkB,EAAEW,OAAO,EAAE;IAC/B,MAAME,WAAW,GAAGC,IAAI,CAACC,SAAS,CAACH,OAAO,CAAC;IAC3C,OAAOZ,kBAAkB,CAACW,OAAO,CAACE,WAAW,CAAC;EAChD,CAAC,MAAM;IACLP,OAAO,CAACC,IAAI,CAAC,mDAAmD,CAAC;IACjE,OAAOC,OAAO,CAACC,OAAO,CAAC,CAAC;EAC1B;AACF,CAAC;AAED,MAAMO,gBAA6C,GAAGA,CAAC;EACrDC,MAAM;EACNf,UAAU,GAAG,OAAO;EACpBgB,SAAS;EACTC,KAAK;EACLC,aAAa;EACbC,cAAc;EACdC,QAAQ,GAAG,QAAQ;EACnBC,WAAW,GAAG,MAAM;EACpBC,WAAW;EACXC,UAAU;EACVC,MAAM;EACNC,OAAO;EACPC,SAAS;EACT,GAAGC;AACL,CAAC,KAAK;EACJ;EACA,MAAMC,GAAG,GAAG,OAAOb,MAAM,KAAK,QAAQ,GAAGA,MAAM,GAAI,CAACA,MAAM,IAAI,OAAOA,MAAM,KAAK,QAAQ,GAAGA,MAAM,CAACa,GAAG,GAAG,EAAE,KAAK,EAAG;EAClH,MAAMC,OAAO,GAAG,OAAOd,MAAM,KAAK,QAAQ,IAAIA,MAAM,CAACc,OAAO,GAAGjB,IAAI,CAACC,SAAS,CAACE,MAAM,CAACc,OAAO,CAAC,GAAGC,SAAS;EACzG,MAAMC,cAAc,GAAG,OAAOhB,MAAM,KAAK,QAAQ,GAAGA,MAAM,CAACK,QAAQ,GAAGU,SAAS;EAC/E,MAAME,WAAW,GAAG,OAAOjB,MAAM,KAAK,QAAQ,GAAGA,MAAM,CAACb,KAAK,GAAG4B,SAAS;;EAEzE;EACA,MAAMG,gBAAgB,GACpB,OAAOf,aAAa,KAAK,QAAQ,GAC7BA,aAAa,GACb,OAAOA,aAAa,KAAK,QAAQ,GAC/BY,SAAS,CAAC;EAAA,EACVA,SAAS;;EAEjB;EACA,MAAMI,iBAAiB,GACrB,OAAOf,cAAc,KAAK,QAAQ,GAC9BA,cAAc,GACd,OAAOA,cAAc,KAAK,QAAQ,GAChCW,SAAS,CAAC;EAAA,EACVA,SAAS;;EAEjB;EACA,MAAMK,eAAe,GAAG1C,WAAW,CAChC2C,KAA6C,IAAK;IACjDd,WAAW,GAAGc,KAAK,CAAC;EACtB,CAAC,EACD,CAACd,WAAW,CACd,CAAC;EAED,MAAMe,cAAc,GAAG5C,WAAW,CAC/B2C,KAA4C,IAAK;IAChDb,UAAU,GAAGa,KAAK,CAAC;EACrB,CAAC,EACD,CAACb,UAAU,CACb,CAAC;EAED,MAAMe,UAAU,GAAG7C,WAAW,CAC3B2C,KAAwC,IAAK;IAC5CZ,MAAM,GAAGY,KAAK,CAAC;EACjB,CAAC,EACD,CAACZ,MAAM,CACT,CAAC;EAED,MAAMe,WAAW,GAAG9C,WAAW,CAC5B2C,KAAyC,IAAK;IAC7CX,OAAO,GAAGW,KAAK,CAAC;EAClB,CAAC,EACD,CAACX,OAAO,CACV,CAAC;EAED,MAAMe,aAAa,GAAG/C,WAAW,CAC9B2C,KAA2C,IAAK;IAC/CV,SAAS,GAAGU,KAAK,CAAC;EACpB,CAAC,EACD,CAACV,SAAS,CACZ,CAAC;EAED,oBACE7B,IAAA,CAACF,2BAA2B;IAAA,GACtBgC,SAAS;IACbC,GAAG,EAAEA,GAAI;IACTC,OAAO,EAAEA,OAAQ;IACjBY,WAAW,EAAE1C,uBAAuB,CAACC,UAAU,CAAE;IACjDgB,SAAS,EAAEA,SAAU;IACrBE,aAAa,EAAEe,gBAAiB;IAChCd,cAAc,EAAEe,iBAAkB;IAClCd,QAAQ,EAAEW,cAAc,IAAIX,QAAS;IACrCC,WAAW,EAAEW,WAAW,GAAG/B,cAAc,CAAC+B,WAAW,CAAC,GAAGX,WAAY;IACrEJ,KAAK,EAAEA,KAAM;IACbyB,kBAAkB,EAAEpB,WAAW,GAAGa,eAAe,GAAGL,SAAU;IAC9Da,iBAAiB,EAAEpB,UAAU,GAAGc,cAAc,GAAGP,SAAU;IAC3Dc,aAAa,EAAEpB,MAAM,GAAGc,UAAU,GAAGR,SAAU;IAC/Ce,cAAc,EAAEpB,OAAO,GAAGc,WAAW,GAAGT,SAAU;IAClDgB,gBAAgB,EAAEpB,SAAS,GAAGc,aAAa,GAAGV;EAAU,CACzD,CAAC;AAEN,CAAC;AAED,OAAO,MAAMiB,YAAmC,GAAGC,MAAM,CAACC,MAAM,CAACnC,gBAAgB,EAAE;EACjFX,gBAAgB;EAChBK,cAAc;EACdC;AACF,CAAC,CAAC;AAEF,eAAesC,YAAY","ignoreList":[]}
|