Skip to content

Commit

Permalink
feat: implement custom image handling on Android
Browse files Browse the repository at this point in the history
  • Loading branch information
okwasniewski committed Oct 2, 2024
1 parent 3670255 commit b73c35f
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 18 deletions.
67 changes: 58 additions & 9 deletions android/src/main/java/com/rcttabview/RCTTabView.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
package com.rcttabview

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.net.Uri
import android.view.Choreographer
import android.view.MenuItem
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.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.WritableMap
import com.facebook.react.views.imagehelper.ImageSource
import com.facebook.react.views.imagehelper.ImageSource.Companion.getTransparentBitmapImageSource
import com.google.android.material.bottomnavigation.BottomNavigationView


class ReactBottomNavigationView(context: Context) : BottomNavigationView(context) {
private val ANIMATION_DURATION: Long = 300
private val icons: MutableList<ImageSource> = mutableListOf()

var items: MutableList<TabInfo>? = null
var onTabSelectedListener: ((WritableMap) -> Unit)? = null
Expand Down Expand Up @@ -66,14 +79,8 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
menu.clear()
items.forEachIndexed {index, item ->
val menuItem = menu.add(0, index, 0, item.title)
val iconResourceId = resources.getIdentifier(
item.icon, "drawable", context.packageName
)
if (iconResourceId != 0) {
menuItem.icon = AppCompatResources.getDrawable(context, iconResourceId)
} else {
menuItem.setIcon(android.R.drawable.btn_star) // fallback icon
}
val drawable = this.getDrawable(icons[index])
menuItem.setIcon(drawable)
if (item.badge.isNotEmpty()) {
val badge = this.getOrCreateBadge(index)
badge.isVisible = true
Expand All @@ -84,6 +91,48 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
}
}

fun setIcons(icons: ReadableArray?) {
if (icons == null || icons.size() == 0) {
return
}
val tmpSources = mutableListOf<ImageSource>()

for (idx in 0 until icons.size()) {
val source = icons.getMap(idx)
var imageSource =
ImageSource(
context,
source.getString("uri"),
source.getDouble("width"),
source.getDouble("height"))
if (Uri.EMPTY == imageSource.uri) {
imageSource = getTransparentBitmapImageSource(context)
}
tmpSources.add(imageSource)

}

if (this.icons == tmpSources) {
return
}

this.icons.clear()
this.icons.addAll(tmpSources)
}

@SuppressLint("UseCompatLoadingForDrawables")
private fun getDrawable(imageSource: ImageSource): Drawable {
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)
}

// Fixes issues with BottomNavigationView children layouting.
private fun measureAndLayout() {
measure(
Expand Down
7 changes: 5 additions & 2 deletions android/src/main/java/com/rcttabview/RCTTabViewViewManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import com.facebook.yoga.YogaNode

data class TabInfo(
val key: String,
val icon: String,
val title: String,
val badge: String
)
Expand All @@ -40,7 +39,6 @@ class RCTTabViewViewManager :
itemsArray.add(
TabInfo(
key = item.getString("key") ?: "",
icon = item.getString("icon") ?: "",
title = item.getString("title") ?: "",
badge = item.getString("badge") ?: ""
)
Expand All @@ -57,6 +55,11 @@ class RCTTabViewViewManager :
}
}

@ReactProp(name = "icons")
fun setIcons(view: ReactBottomNavigationView, icons: ReadableArray?) {
view.setIcons(icons)
}

public override fun createViewInstance(context: ThemedReactContext): ReactBottomNavigationView {
eventDispatcher = context.getNativeModule(UIManagerModule::class.java)!!.eventDispatcher
val view = ReactBottomNavigationView(context)
Expand Down
10 changes: 3 additions & 7 deletions example/src/Examples/MaterialBottomTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ function MaterialBottomTabs() {
component={Albums}
options={{
tabBarIcon: () => (

Check warning on line 32 in example/src/Examples/MaterialBottomTabs.tsx

View workflow job for this annotation

GitHub Actions / lint

Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (https://reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component “MaterialBottomTabs” and pass data as props. If you want to allow component creation in props, set allowAsProps option to true
<TabBarIcon
source={require('../../assets/icons/article_dark.png')}
/>
<TabBarIcon source={require('../../assets/icons/grid_dark.png')} />
),
}}
/>
Expand All @@ -42,7 +40,7 @@ function MaterialBottomTabs() {
options={{
tabBarIcon: () => (

Check warning on line 41 in example/src/Examples/MaterialBottomTabs.tsx

View workflow job for this annotation

GitHub Actions / lint

Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (https://reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component “MaterialBottomTabs” and pass data as props. If you want to allow component creation in props, set allowAsProps option to true
<TabBarIcon
source={require('../../assets/icons/article_dark.png')}
source={require('../../assets/icons/person_dark.png')}
/>
),
}}
Expand All @@ -52,9 +50,7 @@ function MaterialBottomTabs() {
component={Chat}
options={{
tabBarIcon: () => (

Check warning on line 52 in example/src/Examples/MaterialBottomTabs.tsx

View workflow job for this annotation

GitHub Actions / lint

Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (https://reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component “MaterialBottomTabs” and pass data as props. If you want to allow component creation in props, set allowAsProps option to true
<TabBarIcon
source={require('../../assets/icons/article_dark.png')}
/>
<TabBarIcon source={require('../../assets/icons/chat_dark.png')} />
),
}}
/>
Expand Down

0 comments on commit b73c35f

Please sign in to comment.