@@ -17,29 +17,43 @@ import com.facebook.react.common.assets.ReactFontManager
1717import com.facebook.react.views.text.ReactTypefaceUtils
1818import com.google.android.material.navigationrail.NavigationRailView
1919
20+ /* *
21+ * A React Native compatible NavigationRailView that provides Material 3
22+ * sidebar navigation for tablet devices.
23+ *
24+ * This view extends Material's NavigationRailView to support React Native's
25+ * requirements including image loading, theming, and event handling.
26+ */
2027class ReactNavigationRailView (context : Context ) : NavigationRailView(context) {
2128 override fun getMaxItemCount (): Int {
2229 return 100
2330 }
2431
32+ // Event listeners
2533 var onTabSelectedListener: ((key: String ) -> Unit )? = null
2634 var onTabLongPressedListener: ((key: String ) -> Unit )? = null
35+
36+ // Data and state
2737 var items: MutableList <TabInfo > = mutableListOf ()
28- private val iconSources: MutableMap <Int , ImageSource > = mutableMapOf ()
29- private val drawableCache: MutableMap <ImageSource , Drawable > = mutableMapOf ()
30- private var pendingRailSelection: String? = null
31-
3238 private var selectedItem: String? = null
39+
40+ // Visual appearance properties
3341 private var activeTintColor: Int? = null
3442 private var inactiveTintColor: Int? = null
35- private val checkedStateSet = intArrayOf(android.R .attr.state_checked)
36- private val uncheckedStateSet = intArrayOf(- android.R .attr.state_checked)
37- private var hapticFeedbackEnabled = false
3843 private var fontSize: Int? = null
3944 private var fontFamily: String? = null
4045 private var fontWeight: Int? = null
4146 private var labeled: Boolean? = null
4247 private var hasCustomAppearance = false
48+ private var hapticFeedbackEnabled = false
49+
50+ // Icon and image management
51+ private val iconSources: MutableMap <Int , ImageSource > = mutableMapOf ()
52+ private val drawableCache: MutableMap <ImageSource , Drawable > = mutableMapOf ()
53+
54+ // Material state constants
55+ private val checkedStateSet = intArrayOf(android.R .attr.state_checked)
56+ private val uncheckedStateSet = intArrayOf(- android.R .attr.state_checked)
4357
4458 private val imageLoader = ImageLoader .Builder (context)
4559 .components {
@@ -48,29 +62,45 @@ class ReactNavigationRailView(context: Context) : NavigationRailView(context) {
4862 .build()
4963
5064 init {
51- // Set up navigation rail listeners using Material3's built-in methods
65+ setupNavigationListeners()
66+ }
67+
68+ // MARK: - Initialization
69+
70+ private fun setupNavigationListeners () {
5271 setOnItemSelectedListener { menuItem ->
53- try {
54- val selectedTab = items.getOrNull(menuItem.itemId)
55- selectedTab?.let {
56- selectedItem = it.key
57- onTabSelectedListener?.invoke(it.key)
58- emitHapticFeedback(HapticFeedbackConstants .CONTEXT_CLICK )
59- }
60- } catch (e: Exception ) {
61- // Silently handle selection errors
62- }
63- true
72+ handleItemSelection(menuItem)
6473 }
6574
6675 setOnItemReselectedListener { menuItem ->
67- val reselectedTab = items.getOrNull(menuItem.itemId)
68- reselectedTab?.let {
69- // Handle reselection if needed
76+ handleItemReselection(menuItem)
77+ }
78+ }
79+
80+ private fun handleItemSelection (menuItem : MenuItem ): Boolean {
81+ return try {
82+ val selectedTab = items.getOrNull(menuItem.itemId)
83+ selectedTab?.let { tab ->
84+ selectedItem = tab.key
85+ onTabSelectedListener?.invoke(tab.key)
86+ emitHapticFeedback(HapticFeedbackConstants .CONTEXT_CLICK )
7087 }
88+ true
89+ } catch (e: Exception ) {
90+ // Silently handle selection errors
91+ false
92+ }
93+ }
94+
95+ private fun handleItemReselection (menuItem : MenuItem ) {
96+ val reselectedTab = items.getOrNull(menuItem.itemId)
97+ reselectedTab?.let {
98+ // Handle reselection if needed in the future
7199 }
72100 }
73101
102+ // MARK: - Image Loading
103+
74104 private fun getDrawable (imageSource : ImageSource , onDrawableReady : (Drawable ? ) -> Unit ) {
75105 drawableCache[imageSource]?.let {
76106 onDrawableReady(it)
@@ -95,6 +125,8 @@ class ReactNavigationRailView(context: Context) : NavigationRailView(context) {
95125 imageLoader.enqueue(request)
96126 }
97127
128+ // MARK: - Tab Management
129+
98130 fun updateItems (items : MutableList <TabInfo >) {
99131 // If an item got removed, let's re-add all items
100132 if (items.size < this .items.size) {
@@ -173,7 +205,11 @@ class ReactNavigationRailView(context: Context) : NavigationRailView(context) {
173205 }
174206 }
175207 }
176- } fun setLabeled (labeled : Boolean? ) {
208+ }
209+
210+ // MARK: - Configuration Methods
211+
212+ fun setLabeled (labeled : Boolean? ) {
177213 this .labeled = labeled
178214 labelVisibilityMode = when (labeled) {
179215 false -> com.google.android.material.navigation.NavigationBarView .LABEL_VISIBILITY_UNLABELED
@@ -240,17 +276,23 @@ class ReactNavigationRailView(context: Context) : NavigationRailView(context) {
240276 }
241277
242278 fun setRippleColor (color : Int? ) {
243- itemRippleColor = color?.let { android.content.res.ColorStateList .valueOf(it) }
279+ // NavigationRail doesn't have direct ripple color support like BottomNavigationView
280+ // The ripple effect is handled by the Material theme
281+ // This method exists for API compatibility but doesn't perform any action
244282 }
245283
246284 fun setActiveIndicatorColor (color : Int? ) {
247- activeTintColor = color
285+ // NavigationRail doesn't have an active indicator like BottomNavigationView
286+ // The active state is shown through different styling
287+ // This method exists for API compatibility but doesn't perform any action
248288 }
249289
250290 override fun setHapticFeedbackEnabled (hapticFeedbackEnabled : Boolean ) {
251291 this .hapticFeedbackEnabled = hapticFeedbackEnabled
252292 }
253293
294+ // MARK: - Appearance Updates
295+
254296 fun updateTextAppearance () {
255297 // Early return if there is no custom text appearance
256298 if (fontSize == null && fontFamily == null && fontWeight == null ) {
@@ -301,12 +343,16 @@ class ReactNavigationRailView(context: Context) : NavigationRailView(context) {
301343 }
302344 }
303345
346+ // MARK: - Utility Methods
347+
304348 private fun emitHapticFeedback (feedbackConstants : Int ) {
305349 if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .R && hapticFeedbackEnabled) {
306350 this .performHapticFeedback(feedbackConstants)
307351 }
308352 }
309353
354+ // MARK: - Lifecycle Methods
355+
310356 fun handleConfigurationChanged (newConfig : Configuration ? ) {
311357 if (hasCustomAppearance) {
312358 return
0 commit comments