From c812a97ce7ed1be3c517c6de7ef576a284ed130a Mon Sep 17 00:00:00 2001 From: shubhamguptadream11 Date: Fri, 18 Oct 2024 14:13:02 +0530 Subject: [PATCH 1/5] feat: added support for bar tint color on android ios --- .../main/java/com/rcttabview/RCTTabView.kt | 29 +++++++++++++++++++ .../com/rcttabview/RCTTabViewViewManager.kt | 5 ++++ .../guides/usage-with-react-navigation.md | 4 +++ example/src/App.tsx | 8 +++++ example/src/Examples/FourTabs.tsx | 4 +++ ios/RCTTabViewViewManager.mm | 1 + ios/TabViewImpl.swift | 18 ++++++++++++ ios/TabViewProvider.swift | 6 ++++ src/TabView.tsx | 16 +++++++++- src/TabViewNativeComponent.ts | 3 +- 10 files changed, 92 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/rcttabview/RCTTabView.kt b/android/src/main/java/com/rcttabview/RCTTabView.kt index e955b57..d5cf3c8 100644 --- a/android/src/main/java/com/rcttabview/RCTTabView.kt +++ b/android/src/main/java/com/rcttabview/RCTTabView.kt @@ -2,10 +2,13 @@ package com.rcttabview import android.content.Context import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable +import android.util.TypedValue 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 @@ -25,6 +28,7 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context var items: MutableList? = null var onTabSelectedListener: ((WritableMap) -> Unit)? = null private var isAnimating = false + private var barTintColor : Int? = null private val layoutCallback = Choreographer.FrameCallback { isLayoutEnqueued = false @@ -140,4 +144,29 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context super.onDetachedFromWindow() isAnimating = false } + + fun setBarTintColor(color: Int?) { + barTintColor = color + updateBackgroundColors() + } + + private fun updateBackgroundColors() { + // Set the color, either using the active background color or a default color. + val backgroundColor = barTintColor ?: getDefaultColorFor(android.R.attr.colorPrimary) ?: return + + // Apply the same color to both active and inactive states + val colorDrawable = ColorDrawable(backgroundColor) + + itemBackground = colorDrawable + } + + + private fun getDefaultColorFor(baseColorThemeAttr: Int): Int? { + val value = TypedValue() + if (!context.theme.resolveAttribute(baseColorThemeAttr, value, true)) { + return null + } + val baseColor = AppCompatResources.getColorStateList(context, value.resourceId) + return baseColor.defaultColor + } } diff --git a/android/src/main/java/com/rcttabview/RCTTabViewViewManager.kt b/android/src/main/java/com/rcttabview/RCTTabViewViewManager.kt index 54fcbd8..485a6a2 100644 --- a/android/src/main/java/com/rcttabview/RCTTabViewViewManager.kt +++ b/android/src/main/java/com/rcttabview/RCTTabViewViewManager.kt @@ -67,6 +67,11 @@ class RCTTabViewViewManager : view.setIcons(icons) } + @ReactProp(name = "barTintColor") + fun setBarTintColor(view: ReactBottomNavigationView, color: Int?) { + view.setBarTintColor(color) + } + public override fun createViewInstance(context: ThemedReactContext): ReactBottomNavigationView { eventDispatcher = context.getNativeModule(UIManagerModule::class.java)!!.eventDispatcher val view = ReactBottomNavigationView(context) diff --git a/docs/docs/docs/guides/usage-with-react-navigation.md b/docs/docs/docs/guides/usage-with-react-navigation.md index 25e9622..3f363e8 100644 --- a/docs/docs/docs/guides/usage-with-react-navigation.md +++ b/docs/docs/docs/guides/usage-with-react-navigation.md @@ -51,6 +51,10 @@ Default options to use for the screens in the navigator. Whether to show labels in tabs. Defaults to true. +#### `barTintColor` + +Background color of the tab bar. + #### `disablePageAnimations` Whether to disable page animations between tabs. (iOS only) diff --git a/example/src/App.tsx b/example/src/App.tsx index 07ffa0f..31f857d 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -35,6 +35,10 @@ const FourTabsTransparentScrollEdgeAppearance = () => { return ; }; +const FourTabsWithBarTintColor = () => { + return ; +}; + const examples = [ { component: ThreeTabs, name: 'Three Tabs' }, { component: FourTabs, name: 'Four Tabs' }, @@ -50,6 +54,10 @@ const examples = [ component: FourTabsTransparentScrollEdgeAppearance, name: 'Four Tabs - Transparent scroll edge appearance', }, + { + component: FourTabsWithBarTintColor, + name: 'Four Tabs - Custom Background Color of Tabs', + }, { component: NativeBottomTabs, name: 'Native Bottom Tabs' }, { component: JSBottomTabs, name: 'JS Bottom Tabs' }, { diff --git a/example/src/Examples/FourTabs.tsx b/example/src/Examples/FourTabs.tsx index 344cbde..65f3267 100644 --- a/example/src/Examples/FourTabs.tsx +++ b/example/src/Examples/FourTabs.tsx @@ -4,17 +4,20 @@ import { Article } from '../Screens/Article'; import { Albums } from '../Screens/Albums'; import { Contacts } from '../Screens/Contacts'; import { Chat } from '../Screens/Chat'; +import { ColorValue } from 'react-native'; interface Props { ignoresTopSafeArea?: boolean; disablePageAnimations?: boolean; scrollEdgeAppearance?: 'default' | 'opaque' | 'transparent'; + barTintColor?: ColorValue; } export default function FourTabs({ ignoresTopSafeArea = false, disablePageAnimations = false, scrollEdgeAppearance = 'default', + barTintColor, }: Props) { const [index, setIndex] = useState(0); const [routes] = useState([ @@ -59,6 +62,7 @@ export default function FourTabs({ navigationState={{ index, routes }} onIndexChange={setIndex} renderScene={renderScene} + barTintColor={barTintColor} /> ); } diff --git a/ios/RCTTabViewViewManager.mm b/ios/RCTTabViewViewManager.mm index e60c896..400f9ca 100644 --- a/ios/RCTTabViewViewManager.mm +++ b/ios/RCTTabViewViewManager.mm @@ -32,5 +32,6 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(ignoresTopSafeArea, BOOL) RCT_EXPORT_VIEW_PROPERTY(disablePageAnimations, BOOL) RCT_EXPORT_VIEW_PROPERTY(scrollEdgeAppearance, NSString) +RCT_EXPORT_VIEW_PROPERTY(barTintColor, NSNumber) @end diff --git a/ios/TabViewImpl.swift b/ios/TabViewImpl.swift index f0502a4..a81ee81 100644 --- a/ios/TabViewImpl.swift +++ b/ios/TabViewImpl.swift @@ -15,6 +15,7 @@ class TabViewProps: ObservableObject { @Published var ignoresTopSafeArea: Bool? @Published var disablePageAnimations: Bool = false @Published var scrollEdgeAppearance: String? + @Published var barTintColor: UIColor? } /** @@ -76,6 +77,9 @@ struct TabViewImpl: View { UITabBar.appearance().scrollEdgeAppearance = configureAppearance(for: newValue ?? "") } } + .onAppear { + updateTabBarAppearance(with: props.barTintColor) + } } } @@ -94,6 +98,20 @@ private func configureAppearance(for appearanceType: String) -> UITabBarAppearan return appearance } +// Helper function to update the tab bar appearance +private func updateTabBarAppearance(with barTintColor: UIColor?) { + + if #available(iOS 15.0, *) { + let appearance = UITabBarAppearance() + + appearance.backgroundColor = barTintColor + UITabBar.appearance().standardAppearance = appearance + UITabBar.appearance().scrollEdgeAppearance = appearance + } else { + UITabBar.appearance().barTintColor = barTintColor + } +} + struct TabItem: View { var title: String? var icon: UIImage? diff --git a/ios/TabViewProvider.swift b/ios/TabViewProvider.swift index ead7613..33d935e 100644 --- a/ios/TabViewProvider.swift +++ b/ios/TabViewProvider.swift @@ -70,6 +70,12 @@ struct TabData: Codable { props.items = parseTabData(from: items) } } + + @objc var barTintColor: NSNumber? { + didSet { + props.barTintColor = RCTConvert.uiColor(barTintColor) + } + } @objc public convenience init(eventDispatcher: RCTEventDispatcherProtocol, imageLoader: RCTImageLoader) { self.init() diff --git a/src/TabView.tsx b/src/TabView.tsx index 6c30e50..f392aad 100644 --- a/src/TabView.tsx +++ b/src/TabView.tsx @@ -1,5 +1,12 @@ import type { TabViewItems } from './TabViewNativeComponent'; -import { Image, Platform, StyleSheet, View } from 'react-native'; +import { + ColorValue, + Image, + Platform, + StyleSheet, + View, + processColor, +} from 'react-native'; //@ts-ignore import type { ImageSource } from 'react-native/Libraries/Image/ImageSource'; @@ -77,6 +84,11 @@ interface Props { route: Route; focused: boolean; }) => ImageSource | undefined; + + /** + * Background color of the tab bar. + */ + barTintColor?: ColorValue; } const ANDROID_MAX_TABS = 6; @@ -93,6 +105,7 @@ const TabView = ({ ? route.focusedIcon : route.unfocusedIcon : route.focusedIcon, + barTintColor, ...props }: Props) => { // @ts-ignore @@ -179,6 +192,7 @@ const TabView = ({ onPageSelected={({ nativeEvent: { key } }) => { jumpTo(key); }} + barTintColor={processColor(barTintColor)} {...props} > {trimmedRoutes.map((route) => { diff --git a/src/TabViewNativeComponent.ts b/src/TabViewNativeComponent.ts index e15681b..4f05403 100644 --- a/src/TabViewNativeComponent.ts +++ b/src/TabViewNativeComponent.ts @@ -1,5 +1,5 @@ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; -import type { ViewProps } from 'react-native'; +import type { ProcessedColorValue, ViewProps } from 'react-native'; import type { DirectEventHandler } from 'react-native/Libraries/Types/CodegenTypes'; //@ts-ignore import type { ImageSource } from 'react-native/Libraries/Image/ImageSource'; @@ -23,6 +23,7 @@ export interface TabViewProps extends ViewProps { labeled?: boolean; sidebarAdaptable?: boolean; scrollEdgeAppearance?: string; + barTintColor?: ProcessedColorValue | null; } export default codegenNativeComponent('RCTTabView'); From 5ddd00be71e7a225abfb4d50b9e695dd658820a2 Mon Sep 17 00:00:00 2001 From: shubhamguptadream11 Date: Fri, 18 Oct 2024 14:19:12 +0530 Subject: [PATCH 2/5] fix: added color in example --- example/src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/src/App.tsx b/example/src/App.tsx index 31f857d..515a3a4 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -36,7 +36,7 @@ const FourTabsTransparentScrollEdgeAppearance = () => { }; const FourTabsWithBarTintColor = () => { - return ; + return ; }; const examples = [ From be576fd6e6c2a41d0a8cc19e91f858f005c66172 Mon Sep 17 00:00:00 2001 From: shubhamguptadream11 Date: Fri, 18 Oct 2024 20:06:14 +0530 Subject: [PATCH 3/5] fix: pr comments resovled android --- android/src/main/java/com/rcttabview/RCTTabView.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/com/rcttabview/RCTTabView.kt b/android/src/main/java/com/rcttabview/RCTTabView.kt index d5cf3c8..46655b7 100644 --- a/android/src/main/java/com/rcttabview/RCTTabView.kt +++ b/android/src/main/java/com/rcttabview/RCTTabView.kt @@ -28,7 +28,6 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context var items: MutableList? = null var onTabSelectedListener: ((WritableMap) -> Unit)? = null private var isAnimating = false - private var barTintColor : Int? = null private val layoutCallback = Choreographer.FrameCallback { isLayoutEnqueued = false @@ -146,13 +145,12 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context } fun setBarTintColor(color: Int?) { - barTintColor = color - updateBackgroundColors() + updateBackgroundColors(color) } - private fun updateBackgroundColors() { + private fun updateBackgroundColors(color: Int?) { // Set the color, either using the active background color or a default color. - val backgroundColor = barTintColor ?: getDefaultColorFor(android.R.attr.colorPrimary) ?: return + val backgroundColor = color ?: getDefaultColorFor(android.R.attr.colorPrimary) ?: return // Apply the same color to both active and inactive states val colorDrawable = ColorDrawable(backgroundColor) From 21dfd79c6628566b53cd611696d48b3159b5e8be Mon Sep 17 00:00:00 2001 From: shubhamguptadream11 Date: Fri, 18 Oct 2024 21:43:02 +0530 Subject: [PATCH 4/5] fix: pr comments resovled android --- android/src/main/java/com/rcttabview/RCTTabView.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/android/src/main/java/com/rcttabview/RCTTabView.kt b/android/src/main/java/com/rcttabview/RCTTabView.kt index 46655b7..8af7bcb 100644 --- a/android/src/main/java/com/rcttabview/RCTTabView.kt +++ b/android/src/main/java/com/rcttabview/RCTTabView.kt @@ -145,10 +145,6 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context } fun setBarTintColor(color: Int?) { - updateBackgroundColors(color) - } - - private fun updateBackgroundColors(color: Int?) { // Set the color, either using the active background color or a default color. val backgroundColor = color ?: getDefaultColorFor(android.R.attr.colorPrimary) ?: return @@ -158,7 +154,6 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context itemBackground = colorDrawable } - private fun getDefaultColorFor(baseColorThemeAttr: Int): Int? { val value = TypedValue() if (!context.theme.resolveAttribute(baseColorThemeAttr, value, true)) { From 36b95c2d1fcb46fabe3504f2b04f5ca4194cbb83 Mon Sep 17 00:00:00 2001 From: shubhamguptadream11 Date: Sun, 20 Oct 2024 17:11:19 +0530 Subject: [PATCH 5/5] feat: refactored code --- ios/TabViewImpl.swift | 48 +++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/ios/TabViewImpl.swift b/ios/TabViewImpl.swift index 19e2db7..571fb40 100644 --- a/ios/TabViewImpl.swift +++ b/ios/TabViewImpl.swift @@ -64,7 +64,6 @@ struct TabViewImpl: View { } } .getSidebarAdaptable(enabled: props.sidebarAdaptable ?? false) - .tabBarTranslucent(props.translucent) .onChange(of: props.selectedPage ?? "") { newValue in if (props.disablePageAnimations) { UIView.setAnimationsEnabled(false) @@ -74,19 +73,22 @@ struct TabViewImpl: View { } onSelect(newValue) } + .onAppear() { + updateTabBarAppearance(props: props) + } + .onChange(of: props.barTintColor) { newValue in + updateTabBarAppearance(props: props) + } .onChange(of: props.scrollEdgeAppearance) { newValue in - if #available(iOS 15.0, *) { - UITabBar.appearance().scrollEdgeAppearance = configureAppearance(for: newValue ?? "") - } + updateTabBarAppearance(props: props) } - .onAppear { - updateTabBarAppearance(with: props.barTintColor) + .onChange(of: props.translucent) { newValue in + updateTabBarAppearance(props: props) } } } -private func configureAppearance(for appearanceType: String) -> UITabBarAppearance { - let appearance = UITabBarAppearance() +private func configureAppearance(for appearanceType: String, appearance: UITabBarAppearance) -> UITabBarAppearance { switch appearanceType { case "opaque": @@ -100,17 +102,24 @@ private func configureAppearance(for appearanceType: String) -> UITabBarAppearan return appearance } -// Helper function to update the tab bar appearance -private func updateTabBarAppearance(with barTintColor: UIColor?) { - +private func updateTabBarAppearance(props: TabViewProps) { if #available(iOS 15.0, *) { let appearance = UITabBarAppearance() - appearance.backgroundColor = barTintColor + UITabBar.appearance().scrollEdgeAppearance = configureAppearance(for: props.scrollEdgeAppearance ?? "", appearance: appearance) + + if props.translucent == false { + appearance.configureWithOpaqueBackground() + } + + if props.barTintColor != nil { + appearance.backgroundColor = props.barTintColor + } + UITabBar.appearance().standardAppearance = appearance - UITabBar.appearance().scrollEdgeAppearance = appearance } else { - UITabBar.appearance().barTintColor = barTintColor + UITabBar.appearance().barTintColor = props.barTintColor + UITabBar.appearance().isTranslucent = props.translucent } } @@ -177,15 +186,4 @@ extension View { .frame(idealWidth: frame.width, idealHeight: frame.height) } } - - @ViewBuilder - func tabBarTranslucent(_ translucent: Bool) -> some View { - self - .onAppear { - UITabBar.appearance().isTranslucent = translucent - } - .onChange(of: translucent) { newValue in - UITabBar.appearance().isTranslucent = newValue - } - } }