Skip to content

Commit f51b193

Browse files
committed
feat: implement custom image handling on Android
1 parent 3670255 commit f51b193

File tree

4 files changed

+80
-23
lines changed

4 files changed

+80
-23
lines changed

Diff for: android/src/main/java/com/rcttabview/RCTTabView.kt

+59-11
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,28 @@
11
package com.rcttabview
22

3+
import android.annotation.SuppressLint
34
import android.content.Context
5+
import android.graphics.drawable.BitmapDrawable
6+
import android.graphics.drawable.Drawable
7+
import android.net.Uri
48
import android.view.Choreographer
59
import android.view.MenuItem
6-
import androidx.appcompat.content.res.AppCompatResources
10+
import com.facebook.common.references.CloseableReference
11+
import com.facebook.datasource.DataSources
12+
import com.facebook.drawee.backends.pipeline.Fresco
13+
import com.facebook.imagepipeline.image.CloseableBitmap
14+
import com.facebook.imagepipeline.request.ImageRequestBuilder
715
import com.facebook.react.bridge.Arguments
16+
import com.facebook.react.bridge.ReadableArray
817
import com.facebook.react.bridge.WritableMap
18+
import com.facebook.react.views.imagehelper.ImageSource
19+
import com.facebook.react.views.imagehelper.ImageSource.Companion.getTransparentBitmapImageSource
920
import com.google.android.material.bottomnavigation.BottomNavigationView
1021

22+
1123
class ReactBottomNavigationView(context: Context) : BottomNavigationView(context) {
1224
private val ANIMATION_DURATION: Long = 300
25+
private val icons: MutableMap<Int, ImageSource> = mutableMapOf()
1326

1427
var items: MutableList<TabInfo>? = null
1528
var onTabSelectedListener: ((WritableMap) -> Unit)? = null
@@ -62,17 +75,10 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
6275

6376
fun updateItems(items: MutableList<TabInfo>) {
6477
this.items = items
65-
// TODO: This doesn't work with hot reload. It clears all menu items
66-
menu.clear()
6778
items.forEachIndexed {index, item ->
68-
val menuItem = menu.add(0, index, 0, item.title)
69-
val iconResourceId = resources.getIdentifier(
70-
item.icon, "drawable", context.packageName
71-
)
72-
if (iconResourceId != 0) {
73-
menuItem.icon = AppCompatResources.getDrawable(context, iconResourceId)
74-
} else {
75-
menuItem.setIcon(android.R.drawable.btn_star) // fallback icon
79+
val menuItem = getOrCreateItem(index, item.title)
80+
if (icons.containsKey(index)) {
81+
menuItem.icon = getDrawable(icons[index]!!)
7682
}
7783
if (item.badge.isNotEmpty()) {
7884
val badge = this.getOrCreateBadge(index)
@@ -84,6 +90,48 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
8490
}
8591
}
8692

93+
private fun getOrCreateItem(index: Int, title: String): MenuItem {
94+
return menu.findItem(index) ?: menu.add(0, index, 0, title)
95+
}
96+
97+
fun setIcons(icons: ReadableArray?) {
98+
if (icons == null || icons.size() == 0) {
99+
return
100+
}
101+
102+
for (idx in 0 until icons.size()) {
103+
val source = icons.getMap(idx)
104+
var imageSource =
105+
ImageSource(
106+
context,
107+
source.getString("uri"),
108+
source.getDouble("width"),
109+
source.getDouble("height"))
110+
if (Uri.EMPTY == imageSource.uri) {
111+
imageSource = getTransparentBitmapImageSource(context)
112+
}
113+
this.icons[idx] = imageSource
114+
115+
// Update existing item if exists.
116+
menu.findItem(idx)?.let { menuItem ->
117+
menuItem.icon = getDrawable(imageSource)
118+
}
119+
}
120+
}
121+
122+
@SuppressLint("UseCompatLoadingForDrawables")
123+
private fun getDrawable(imageSource: ImageSource): Drawable {
124+
val imageRequest = ImageRequestBuilder.newBuilderWithSource(imageSource.uri).build()
125+
val dataSource = Fresco.getImagePipeline().fetchDecodedImage(imageRequest, context)
126+
val result = DataSources.waitForFinalResult(dataSource) as CloseableReference<CloseableBitmap>
127+
val bitmap = result.get().underlyingBitmap
128+
129+
CloseableReference.closeSafely(result)
130+
dataSource.close()
131+
132+
return BitmapDrawable(resources, bitmap)
133+
}
134+
87135
// Fixes issues with BottomNavigationView children layouting.
88136
private fun measureAndLayout() {
89137
measure(

Diff for: android/src/main/java/com/rcttabview/RCTTabViewViewManager.kt

+5-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import com.facebook.yoga.YogaNode
1818

1919
data class TabInfo(
2020
val key: String,
21-
val icon: String,
2221
val title: String,
2322
val badge: String
2423
)
@@ -40,7 +39,6 @@ class RCTTabViewViewManager :
4039
itemsArray.add(
4140
TabInfo(
4241
key = item.getString("key") ?: "",
43-
icon = item.getString("icon") ?: "",
4442
title = item.getString("title") ?: "",
4543
badge = item.getString("badge") ?: ""
4644
)
@@ -57,6 +55,11 @@ class RCTTabViewViewManager :
5755
}
5856
}
5957

58+
@ReactProp(name = "icons")
59+
fun setIcons(view: ReactBottomNavigationView, icons: ReadableArray?) {
60+
view.setIcons(icons)
61+
}
62+
6063
public override fun createViewInstance(context: ThemedReactContext): ReactBottomNavigationView {
6164
eventDispatcher = context.getNativeModule(UIManagerModule::class.java)!!.eventDispatcher
6265
val view = ReactBottomNavigationView(context)

Diff for: example/src/Examples/MaterialBottomTabs.tsx

+3-7
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,7 @@ function MaterialBottomTabs() {
3030
component={Albums}
3131
options={{
3232
tabBarIcon: () => (
33-
<TabBarIcon
34-
source={require('../../assets/icons/article_dark.png')}
35-
/>
33+
<TabBarIcon source={require('../../assets/icons/grid_dark.png')} />
3634
),
3735
}}
3836
/>
@@ -42,7 +40,7 @@ function MaterialBottomTabs() {
4240
options={{
4341
tabBarIcon: () => (
4442
<TabBarIcon
45-
source={require('../../assets/icons/article_dark.png')}
43+
source={require('../../assets/icons/person_dark.png')}
4644
/>
4745
),
4846
}}
@@ -52,9 +50,7 @@ function MaterialBottomTabs() {
5250
component={Chat}
5351
options={{
5452
tabBarIcon: () => (
55-
<TabBarIcon
56-
source={require('../../assets/icons/article_dark.png')}
57-
/>
53+
<TabBarIcon source={require('../../assets/icons/chat_dark.png')} />
5854
),
5955
}}
6056
/>

Diff for: example/src/Examples/ThreeTabs.tsx

+13-3
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,24 @@ import { Contacts } from '../Screens/Contacts';
77
export default function ThreeTabs() {
88
const [index, setIndex] = useState(0);
99
const [routes] = useState([
10-
{ key: 'article', title: 'Article', icon: 'document.fill', badge: '!' },
10+
{
11+
key: 'article',
12+
title: 'Article',
13+
focusedIcon: require('../../assets/icons/article.png'),
14+
unfocusedIcon: require('../../assets/icons/chat.png'),
15+
badge: '!',
16+
},
1117
{
1218
key: 'albums',
1319
title: 'Albums',
14-
icon: 'square.grid.2x2.fill',
20+
focusedIcon: require('../../assets/icons/grid.png'),
1521
badge: '5',
1622
},
17-
{ key: 'contacts', title: 'Contacts', icon: 'person.fill' },
23+
{
24+
key: 'contacts',
25+
focusedIcon: require('../../assets/icons/person.png'),
26+
title: 'Contacts',
27+
},
1828
]);
1929

2030
const renderScene = SceneMap({

0 commit comments

Comments
 (0)