Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def isNewArchitectureEnabled() {

apply plugin: "com.android.library"
apply plugin: "kotlin-android"
apply plugin: "kotlin-kapt"

if (isNewArchitectureEnabled()) {
apply plugin: "com.facebook.react"
Expand Down Expand Up @@ -110,12 +111,18 @@ repositories {
def kotlin_version = getExtOrDefault("kotlinVersion")

dependencies {
def GLIDE_VERSION = "4.16.0"
// For < 0.71, this will be from the local maven repo
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'com.google.android.material:material:1.13.0-alpha06'

api "com.github.bumptech.glide:glide:${GLIDE_VERSION}"
kapt "com.github.bumptech.glide:compiler:${GLIDE_VERSION}"

api 'com.caverock:androidsvg-aar:1.4'
}

if (isNewArchitectureEnabled()) {
Expand Down
63 changes: 41 additions & 22 deletions android/src/main/java/com/rcttabview/RCTTabView.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.rcttabview

import android.annotation.SuppressLint
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Typeface
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.os.Build
import android.util.Log
import android.util.TypedValue
import android.view.Choreographer
import android.view.HapticFeedbackConstants
Expand All @@ -15,11 +17,10 @@ import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.content.res.AppCompatResources
import com.facebook.common.references.CloseableReference
import com.facebook.datasource.DataSources
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.image.CloseableBitmap
import com.facebook.imagepipeline.request.ImageRequestBuilder
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.WritableMap
Expand Down Expand Up @@ -69,6 +70,7 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
override fun requestLayout() {
super.requestLayout()
@Suppress("SENSELESS_COMPARISON") // layoutCallback can be null here since this method can be called in init

if (!isLayoutEnqueued && layoutCallback != null) {
isLayoutEnqueued = true
// we use NATIVE_ANIMATED_MODULE choreographer queue because it allows us to catch the current
Expand Down Expand Up @@ -102,7 +104,9 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
val menuItem = getOrCreateItem(index, item.title)
menuItem.isVisible = !item.hidden
if (icons.containsKey(index)) {
menuItem.icon = getDrawable(icons[index]!!)
getDrawable(icons[index]!!) {
menuItem.icon = it
}
}

if (item.badge.isNotEmpty()) {
Expand Down Expand Up @@ -150,7 +154,9 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context

// Update existing item if exists.
menu.findItem(idx)?.let { menuItem ->
menuItem.icon = getDrawable(imageSource)
getDrawable(imageSource) {
menuItem.icon = it
}
}
}
}
Expand All @@ -169,22 +175,35 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
itemRippleColor = color
}

private fun getDrawable(imageSource: ImageSource): Drawable? {
try {
val imageRequest = ImageRequestBuilder.newBuilderWithSource(imageSource.uri).build()
val dataSource = Fresco.getImagePipeline().fetchDecodedImage(imageRequest, context)
val result = DataSources.waitForFinalResult(dataSource) as CloseableReference<CloseableBitmap>
val bitmap = result.get().underlyingBitmap

CloseableReference.closeSafely(result)
dataSource.close()

return BitmapDrawable(resources, bitmap)
} catch (_: Exception) {
// Asset doesn't exist
}
@SuppressLint("CheckResult")
private fun getDrawable(imageSource: ImageSource, onDrawableReady: (Drawable?) -> Unit) {
GlideApp.with(context)
.`as`(Drawable::class.java)
.load(imageSource.uri)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>,
isFirstResource: Boolean
): Boolean {
Log.e("RCTTabView", "Error loading image: ${imageSource.uri}", e)
return false
}

return null
override fun onResourceReady(
resource: Drawable,
model: Any,
target: Target<Drawable>?,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
// Update images on the main queue.
post { onDrawableReady(resource) }
return true
}
})
.submit()
}

override fun onDetachedFromWindow() {
Expand Down
18 changes: 18 additions & 0 deletions android/src/main/java/com/rcttabview/TabViewAppGlideModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.rcttabview

import android.content.Context
import android.util.Log
import com.bumptech.glide.GlideBuilder
import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.module.AppGlideModule

@GlideModule
class TabViewAppGlideModule : AppGlideModule() {
override fun applyOptions(context: Context, builder: GlideBuilder) {
super.applyOptions(context, builder)

builder.setLogLevel(
Log.ERROR
)
}
}
40 changes: 40 additions & 0 deletions android/src/main/java/com/rcttabview/svg/SVGDecoder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.rcttabview.svg

import com.bumptech.glide.load.Options
import com.bumptech.glide.load.ResourceDecoder
import com.bumptech.glide.load.engine.Resource
import com.bumptech.glide.load.resource.SimpleResource
import com.caverock.androidsvg.SVG
import com.caverock.androidsvg.SVGParseException
import java.io.IOException
import java.io.InputStream


class SVGDecoder : ResourceDecoder<InputStream, SVG> {
override fun handles(source: InputStream, options: Options) = true

companion object {
const val DEFAULT_SIZE = 40f
}

@Throws(IOException::class)
override fun decode(source: InputStream, width: Int, height: Int, options: Options): Resource<SVG>? {
return try {
val svg: SVG = SVG.getFromInputStream(source)
// Taken from https://github.com/expo/expo/blob/215d8a13a7ef3f0b36b14eead41291e2d2d6cd0c/packages/expo-image/android/src/main/java/expo/modules/image/svg/SVGDecoder.kt#L28
if (svg.documentViewBox == null) {
val documentWidth = svg.documentWidth
val documentHeight = svg.documentHeight
if (documentWidth != -1f && documentHeight != -1f) {
svg.setDocumentViewBox(0f, 0f, documentWidth, documentHeight)
}
}

svg.documentWidth = DEFAULT_SIZE
svg.documentHeight = DEFAULT_SIZE
SimpleResource(svg)
} catch (ex: SVGParseException) {
throw IOException("Cannot load SVG from stream", ex)
}
}
}
32 changes: 32 additions & 0 deletions android/src/main/java/com/rcttabview/svg/SVGDrawableTranscoder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.rcttabview.svg

import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.PictureDrawable
import com.bumptech.glide.load.Options
import com.bumptech.glide.load.engine.Resource
import com.bumptech.glide.load.resource.SimpleResource
import com.bumptech.glide.load.resource.transcode.ResourceTranscoder
import com.caverock.androidsvg.SVG

class SVGDrawableTranscoder(val context: Context) : ResourceTranscoder<SVG?, Drawable> {
override fun transcode(toTranscode: Resource<SVG?>, options: Options): Resource<Drawable> {
val svg = toTranscode.get()
val picture = svg.renderToPicture()
val drawable = PictureDrawable(picture)

val returnedBitmap = Bitmap.createBitmap(
drawable.intrinsicWidth,
drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)

val canvas = Canvas(returnedBitmap)
canvas.drawPicture(drawable.picture)
val bitMapDrawable = BitmapDrawable(context.resources, returnedBitmap)
return SimpleResource(bitMapDrawable)
}
}
25 changes: 25 additions & 0 deletions android/src/main/java/com/rcttabview/svg/TabViewGlideModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.rcttabview.svg

import android.content.Context
import android.graphics.drawable.Drawable
import com.bumptech.glide.Glide
import com.bumptech.glide.Registry
import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.module.LibraryGlideModule
import com.caverock.androidsvg.SVG
import java.io.InputStream


@GlideModule
class SvgModule: LibraryGlideModule() {
override fun registerComponents(
context: Context, glide: Glide, registry: Registry
) {
registry
.register(
SVG::class.java,
Drawable::class.java, SVGDrawableTranscoder(context)
)
.append(InputStream::class.java, SVG::class.java, SVGDecoder())
}
}
30 changes: 29 additions & 1 deletion docs/docs/docs/getting-started/quick-start.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ import { PackageManagerTabs } from '@theme';

### Expo

Add the library plugin in your `app.json` config file and [create a new build](https://docs.expo.dev/develop/development-builds/create-a-build/):
Add the library plugin in your `app.json` config file and [create a new build](https://docs.expo.dev/develop/development-builds/create-a-build/).


```diff
"expo": {
Expand All @@ -50,6 +51,27 @@ Add the library plugin in your `app.json` config file and [create a new build](h
}
```

You also need to enable static linking for iOS by adding `"useFrameworks": "static"` in the `expo-build-properties` plugin.

```diff
{
"expo": {
"plugins": [
"react-native-bottom-tabs",
+ [
+ "expo-build-properties",
+ {
+ "ios": {
+ "useFrameworks": "static"
+ }
+ }
+ ]
+ ]
}
}
```


:::warning

This library is not supported in [Expo Go](https://expo.dev/go).
Expand All @@ -72,6 +94,12 @@ Edit `android/app/src/main/res/values/styles.xml` to inherit from provided theme

Here you can read more about [Android Native Styling](/docs/guides/android-native-styling).

To enable static linking for iOS, Open the `./ios/Podfile` file and add the following:

```ruby
use_frameworks!, :linkage => :static
```


## Example usage

Expand Down
4 changes: 4 additions & 0 deletions docs/docs/docs/guides/usage-with-react-navigation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,12 @@ Function that given `{ focused: boolean }` returns `ImageSource` or `AppleIcon`
component={Albums}
options={{
tabBarIcon: () => require('person.png'),
// SVG is also supported
tabBarIcon: () => require('person.svg'),
// or
tabBarIcon: () => ({ sfSymbol: 'person' }),
// You can also pass a URL
tabBarIcon: () => ({ uri: 'https://example.com/icon.png' }),
}}
/>
```
Expand Down
1 change: 1 addition & 0 deletions example/assets/icons/book-image.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions example/assets/icons/message-circle-code.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions example/assets/icons/newspaper.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions example/assets/icons/user-round-search.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions example/assets/icons/user-round.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 6 additions & 1 deletion example/ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,9 @@ require "#{ws_dir}/node_modules/react-native-test-app/test_app.rb"

workspace 'ReactNativeBottomTabsExample.xcworkspace'

use_test_app!
use_test_app! do |test_app|
# Workaround for not using use_frameworks! in the Podfile
pod 'SDWebImage', :modular_headers => true
pod 'SDWebImageSVGCoder', :modular_headers => true
end

Loading
Loading