Skip to content

Commit 9827f6a

Browse files
committed
wip: svg support
1 parent 28ce672 commit 9827f6a

File tree

15 files changed

+348
-99
lines changed

15 files changed

+348
-99
lines changed

android/build.gradle

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def isNewArchitectureEnabled() {
2525

2626
apply plugin: "com.android.library"
2727
apply plugin: "kotlin-android"
28+
apply plugin: "kotlin-kapt"
2829

2930
if (isNewArchitectureEnabled()) {
3031
apply plugin: "com.facebook.react"
@@ -110,12 +111,20 @@ repositories {
110111
def kotlin_version = getExtOrDefault("kotlinVersion")
111112

112113
dependencies {
114+
def GLIDE_VERSION = "4.16.0"
113115
// For < 0.71, this will be from the local maven repo
114116
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
115117
//noinspection GradleDynamicVersion
116118
implementation "com.facebook.react:react-native:+"
117119
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
118120
implementation 'com.google.android.material:material:1.13.0-alpha06'
121+
122+
api "com.github.bumptech.glide:glide:${GLIDE_VERSION}"
123+
kapt "com.github.bumptech.glide:compiler:${GLIDE_VERSION}"
124+
125+
api 'com.caverock:androidsvg-aar:1.4'
126+
// implementation 'com.github.bumptech.glide:glide:4.15.1'
127+
// implementation 'com.github.bumptech.glide:svg:4.15.1'
119128
}
120129

121130
if (isNewArchitectureEnabled()) {

android/src/main/java/com/rcttabview/RCTTabView.kt

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import android.graphics.drawable.BitmapDrawable
77
import android.graphics.drawable.ColorDrawable
88
import android.graphics.drawable.Drawable
99
import android.os.Build
10+
import android.util.Log
1011
import android.util.TypedValue
1112
import android.view.Choreographer
1213
import android.view.HapticFeedbackConstants
@@ -15,11 +16,12 @@ import android.view.View
1516
import android.view.ViewGroup
1617
import android.widget.TextView
1718
import androidx.appcompat.content.res.AppCompatResources
18-
import com.facebook.common.references.CloseableReference
19-
import com.facebook.datasource.DataSources
20-
import com.facebook.drawee.backends.pipeline.Fresco
21-
import com.facebook.imagepipeline.image.CloseableBitmap
22-
import com.facebook.imagepipeline.request.ImageRequestBuilder
19+
import androidx.core.graphics.drawable.DrawableCompat
20+
import com.bumptech.glide.load.DataSource
21+
import com.bumptech.glide.load.DecodeFormat
22+
import com.bumptech.glide.load.engine.GlideException
23+
import com.bumptech.glide.request.RequestListener
24+
import com.bumptech.glide.request.target.Target
2325
import com.facebook.react.bridge.Arguments
2426
import com.facebook.react.bridge.ReadableArray
2527
import com.facebook.react.bridge.WritableMap
@@ -28,6 +30,8 @@ import com.facebook.react.modules.core.ReactChoreographer
2830
import com.facebook.react.views.imagehelper.ImageSource
2931
import com.facebook.react.views.text.ReactTypefaceUtils
3032
import com.google.android.material.bottomnavigation.BottomNavigationView
33+
import kotlinx.coroutines.Dispatchers
34+
import kotlinx.coroutines.runBlocking
3135

3236

3337
class ReactBottomNavigationView(context: Context) : BottomNavigationView(context) {
@@ -170,21 +174,37 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
170174
}
171175

172176
private fun getDrawable(imageSource: ImageSource): Drawable? {
173-
try {
174-
val imageRequest = ImageRequestBuilder.newBuilderWithSource(imageSource.uri).build()
175-
val dataSource = Fresco.getImagePipeline().fetchDecodedImage(imageRequest, context)
176-
val result = DataSources.waitForFinalResult(dataSource) as CloseableReference<CloseableBitmap>
177-
val bitmap = result.get().underlyingBitmap
178-
179-
CloseableReference.closeSafely(result)
180-
dataSource.close()
181-
182-
return BitmapDrawable(resources, bitmap)
183-
} catch (_: Exception) {
184-
// Asset doesn't exist
177+
val uri = imageSource.uri.toString()
178+
val isSvg = uri.contains(".svg", ignoreCase = true)
179+
Log.d("ReactBottomNav", "Loading image: $uri, isSvg: $isSvg")
180+
181+
return try {
182+
runBlocking(Dispatchers.IO) {
183+
val drawable = GlideApp.with(context)
184+
.`as`(Drawable::class.java)
185+
.load(imageSource.uri)
186+
.apply {
187+
if (isSvg) {
188+
override(200, 200)
189+
}
190+
}
191+
.submit()
192+
.get()
193+
194+
// Make the drawable tintable
195+
if (isSvg && drawable != null) {
196+
DrawableCompat.wrap(drawable.mutate()).apply {
197+
DrawableCompat.setTintList(this, null) // Clear any existing tint
198+
alpha = 255
199+
}
200+
} else {
201+
drawable
202+
}
203+
}
204+
} catch (e: Exception) {
205+
Log.e("ReactBottomNav", "Error loading image: $uri", e)
206+
null
185207
}
186-
187-
return null
188208
}
189209

190210
override fun onDetachedFromWindow() {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.rcttabview
2+
3+
import com.bumptech.glide.load.Options
4+
import com.bumptech.glide.load.ResourceDecoder
5+
import com.bumptech.glide.load.engine.Resource
6+
import com.bumptech.glide.load.resource.SimpleResource
7+
import com.caverock.androidsvg.SVG
8+
import com.caverock.androidsvg.SVGParseException
9+
import java.io.IOException
10+
import java.io.InputStream
11+
12+
class SVGDecoder : ResourceDecoder<InputStream, SVG> {
13+
override fun handles(source: InputStream, options: Options) = true
14+
15+
@Throws(IOException::class)
16+
override fun decode(source: InputStream, width: Int, height: Int, options: Options): Resource<SVG>? {
17+
return try {
18+
val svg: SVG = SVG.getFromInputStream(source)
19+
// Use document width and height if view box is not set.
20+
// Later, we will override the document width and height with the dimensions of the native view.
21+
if (svg.documentViewBox == null) {
22+
val documentWidth = svg.documentWidth
23+
val documentHeight = svg.documentHeight
24+
if (documentWidth != -1f && documentHeight != -1f) {
25+
svg.setDocumentViewBox(0f, 0f, documentWidth, documentHeight)
26+
}
27+
}
28+
svg.documentWidth = width.toFloat()
29+
svg.documentHeight = height.toFloat()
30+
SimpleResource(svg)
31+
} catch (ex: SVGParseException) {
32+
throw IOException("Cannot load SVG from stream", ex)
33+
}
34+
}
35+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.rcttabview
2+
3+
import android.content.Context
4+
import android.graphics.Picture
5+
import android.graphics.drawable.Drawable
6+
import android.graphics.drawable.PictureDrawable
7+
import com.bumptech.glide.load.Options
8+
import com.bumptech.glide.load.engine.Resource
9+
import com.bumptech.glide.load.resource.SimpleResource
10+
import com.bumptech.glide.load.resource.transcode.ResourceTranscoder
11+
import com.caverock.androidsvg.SVG
12+
13+
/**
14+
* We have to use the intrinsicWidth/Height from the Picture to render the image at a high enough resolution, but at the same time we want to return the actual
15+
* preferred width and height of the SVG to JS. This class allows us to do that.
16+
*/
17+
class SVGPictureDrawable(picture: Picture, val svgIntrinsicWidth: Int, val svgIntrinsicHeight: Int) : PictureDrawable(picture)
18+
19+
20+
21+
/**
22+
* Convert the [SVG]'s internal representation to an Android-compatible one ([Picture]).
23+
*
24+
* Copied from https://github.com/bumptech/glide/blob/10acc31a16b4c1b5684f69e8de3117371dfa77a8/samples/svg/src/main/java/com/bumptech/glide/samples/svg/SvgDrawableTranscoder.java
25+
* and rewritten to Kotlin.
26+
*/
27+
class SVGDrawableTranscoder(val context: Context) : ResourceTranscoder<SVG?, Drawable> {
28+
override fun transcode(toTranscode: Resource<SVG?>, options: Options): Resource<Drawable> {
29+
val svgData = toTranscode.get()
30+
// If the svg doesn't have a viewBox, we can't determine its intrinsic width and height, so we default to 512x512.
31+
// Same dimensions are used in the AndroidSVG library when the viewBox is not set.
32+
val intrinsicWidth = svgData.documentViewBox?.width()?.toInt() ?: 512
33+
val intrinsicHeight = svgData.documentViewBox?.height()?.toInt() ?: 512
34+
35+
val picture = SVGPictureDrawable(
36+
svgData.renderToPicture(),
37+
intrinsicWidth,
38+
intrinsicHeight
39+
)
40+
return SimpleResource(
41+
picture
42+
)
43+
}
44+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.rcttabview
2+
3+
import android.content.Context
4+
import android.util.Log
5+
import com.bumptech.glide.GlideBuilder
6+
import com.bumptech.glide.annotation.GlideModule
7+
import com.bumptech.glide.module.AppGlideModule
8+
9+
@GlideModule
10+
class TabViewAppGlideModule : AppGlideModule() {
11+
override fun applyOptions(context: Context, builder: GlideBuilder) {
12+
super.applyOptions(context, builder)
13+
14+
builder.setLogLevel(
15+
Log.ERROR
16+
)
17+
}
18+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.rcttabview
2+
3+
import android.content.Context
4+
import android.graphics.drawable.Drawable
5+
import com.bumptech.glide.Glide
6+
import com.bumptech.glide.Registry
7+
import com.bumptech.glide.annotation.GlideModule
8+
import com.bumptech.glide.module.LibraryGlideModule
9+
import com.caverock.androidsvg.SVG
10+
import java.io.InputStream
11+
12+
13+
@GlideModule
14+
class SvgModule: LibraryGlideModule() {
15+
override fun registerComponents(
16+
context: Context, glide: Glide, registry: Registry
17+
) {
18+
registry
19+
.register(
20+
SVG::class.java,
21+
Drawable::class.java, SVGDrawableTranscoder(context)
22+
)
23+
.append(InputStream::class.java, SVG::class.java, SVGDecoder())
24+
}
25+
}

example/assets/setttings.svg

Lines changed: 1 addition & 0 deletions
Loading

example/ios/.xcode.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export NODE_BINARY='/opt/homebrew/bin/node'
1+
export NODE_BINARY='/Users/okwasniewski/.nvm/versions/node/v20.15.1/bin/node'

example/ios/Podfile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,8 @@ require "#{ws_dir}/node_modules/react-native-test-app/test_app.rb"
66

77
workspace 'ReactNativeBottomTabsExample.xcworkspace'
88

9-
use_test_app!
9+
use_frameworks! :linkage => :static
10+
11+
use_test_app! do |test_app|
12+
end
13+

0 commit comments

Comments
 (0)