Skip to content

Commit

Permalink
fix(android): Handle tabBarIcon sources (#175)
Browse files Browse the repository at this point in the history
* fix: android tabBarIcon not visible when build to apk

* fix: handle all image sources

* Create orange-pandas-exercise.md

---------

Co-authored-by: Ethan <[email protected]>
  • Loading branch information
okwasniewski and Peek-A-Booo authored Dec 3, 2024
1 parent fc096de commit c31a00f
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 23 deletions.
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
}
}



0 comments on commit c31a00f

Please sign in to comment.