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
5 changes: 5 additions & 0 deletions .changeset/orange-pandas-exercise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-native-bottom-tabs": patch
---

fix(android): handle tabBarIcon sources in release mode
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.rcttabview

import android.annotation.SuppressLint
import android.content.Context
import android.net.Uri
import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper
import java.util.Locale

data class ImageSource(
val context: Context,
val uri: String? = null,
) {
private fun isLocalResourceUri(uri: Uri?) = uri?.scheme?.startsWith("res") ?: false

fun getUri(context: Context): Uri? {
val uri = computeUri(context)

if (isLocalResourceUri(uri)) {
return Uri.parse(
uri!!.toString().replace("res:/", "android.resource://" + context.packageName + "/")
)
}

return uri
}

private fun computeUri(context: Context): Uri? {
val stringUri = uri ?: return null
return try {
val uri: Uri = Uri.parse(stringUri)
// Verify scheme is set, so that relative uri (used by static resources) are not handled.
if (uri.scheme == null) {
computeLocalUri(stringUri, context)
} else {
uri
}
} catch (e: Exception) {
computeLocalUri(stringUri, context)
}
}

private fun computeLocalUri(stringUri: String, context: Context): Uri? {
return ResourceIdHelper.getResourceUri(context, stringUri)
}
}

// Taken from https://github.com/expo/expo/blob/sdk-52/packages/expo-image/android/src/main/java/expo/modules/image/ResourceIdHelper.kt
object ResourceIdHelper {
private val idMap = mutableMapOf<String, Int>()

@SuppressLint("DiscouragedApi")
private fun getResourceRawId(context: Context, name: String): Int {
if (name.isEmpty()) {
return -1
}

val normalizedName = name.lowercase(Locale.ROOT).replace("-", "_")
synchronized(this) {
val id = idMap[normalizedName]
if (id != null) {
return id
}

return context
.resources
.getIdentifier(normalizedName, "raw", context.packageName)
.also {
idMap[normalizedName] = it
}
}
}

fun getResourceUri(context: Context, name: String): Uri? {
val drawableUri = ResourceDrawableIdHelper.instance.getResourceDrawableUri(context, name)
if (drawableUri != Uri.EMPTY) {
return drawableUri
}

val resId = getResourceRawId(context, name)
return if (resId > 0) {
Uri.Builder().scheme("res").path(resId.toString()).build()
} else {
null
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,18 @@ import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.WritableMap
import com.facebook.react.common.assets.ReactFontManager
import com.facebook.react.modules.core.ReactChoreographer
import com.facebook.react.views.imagehelper.ImageSource
import com.facebook.react.views.text.ReactTypefaceUtils
import com.google.android.material.bottomnavigation.BottomNavigationView
import coil3.request.ImageRequest
import coil3.svg.SvgDecoder


class ReactBottomNavigationView(context: Context) : BottomNavigationView(context) {
private val icons: MutableMap<Int, ImageSource> = mutableMapOf()
private val iconSources: MutableMap<Int, ImageSource> = mutableMapOf()
private var isLayoutEnqueued = false
var items: MutableList<TabInfo>? = null
var onTabSelectedListener: ((WritableMap) -> Unit)? = null
var onTabLongPressedListener: ((WritableMap) -> Unit)? = null
private var isAnimating = false
private var activeTintColor: Int? = null
private var inactiveTintColor: Int? = null
private val checkedStateSet = intArrayOf(android.R.attr.state_checked)
Expand Down Expand Up @@ -91,7 +89,7 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context

private fun onTabSelected(item: MenuItem) {
if (isLayoutEnqueued) {
return;
return
}
val selectedItem = items?.first { it.title == item.title }
selectedItem?.let {
Expand All @@ -108,8 +106,8 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
items.forEachIndexed { index, item ->
val menuItem = getOrCreateItem(index, item.title)
menuItem.isVisible = !item.hidden
if (icons.containsKey(index)) {
getDrawable(icons[index]!!) {
if (iconSources.containsKey(index)) {
getDrawable(iconSources[index]!!) {
menuItem.icon = it
}
}
Expand Down Expand Up @@ -150,12 +148,9 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
if (uri.isNullOrEmpty()) {
continue
}
val imageSource =
ImageSource(
context,
uri
)
this.icons[idx] = imageSource

val imageSource = ImageSource(context, uri)
this.iconSources[idx] = imageSource

// Update existing item if exists.
menu.findItem(idx)?.let { menuItem ->
Expand Down Expand Up @@ -183,7 +178,7 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
@SuppressLint("CheckResult")
private fun getDrawable(imageSource: ImageSource, onDrawableReady: (Drawable?) -> Unit) {
val request = ImageRequest.Builder(context)
.data(imageSource.uri)
.data(imageSource.getUri(context))
.target { drawable ->
post { onDrawableReady(drawable.asDrawable(context.resources)) }
}
Expand All @@ -197,11 +192,6 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
imageLoader.enqueue(request)
}

override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
isAnimating = false
}

fun setBarTintColor(color: Int?) {
// Set the color, either using the active background color or a default color.
val backgroundColor = color ?: getDefaultColorFor(android.R.attr.colorPrimary) ?: return
Expand Down Expand Up @@ -241,10 +231,10 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
updateTextAppearance()
}

fun setFontWeight(weight: String?) {
val fontWeight = ReactTypefaceUtils.parseFontWeight(weight)
this.fontWeight = fontWeight
updateTextAppearance()
fun setFontWeight(weight: String?) {
val fontWeight = ReactTypefaceUtils.parseFontWeight(weight)
this.fontWeight = fontWeight
updateTextAppearance()
}

private fun getTypefaceStyle(weight: Int?) = when (weight) {
Expand Down Expand Up @@ -289,7 +279,7 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
// First let's check current item color.
val currentItemTintColor = items?.find { it.title == item?.title }?.activeTintColor

// getDeaultColor will always return a valid color but to satisfy the compiler we need to check for null
// getDefaultColor will always return a valid color but to satisfy the compiler we need to check for null
val colorPrimary = currentItemTintColor ?: activeTintColor ?: getDefaultColorFor(android.R.attr.colorPrimary) ?: return
val colorSecondary =
inactiveTintColor ?: getDefaultColorFor(android.R.attr.textColorSecondary) ?: return
Expand All @@ -313,3 +303,6 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
return baseColor.defaultColor
}
}



Loading