-
Notifications
You must be signed in to change notification settings - Fork 1.4k
[GSOC POC bottom tabs navigation]feat: migrate from drawer to bottom tabs navigation with iOS 26 Liqui… #7070
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,275 @@ | ||
| import React from 'react'; | ||
| import { createNativeStackNavigator } from '@react-navigation/native-stack'; | ||
| import { createNativeBottomTabNavigator } from '@bottom-tabs/react-navigation'; | ||
|
|
||
| import { ThemeContext } from '../theme'; | ||
| import { defaultHeader, themedHeader } from '../lib/methods/helpers/navigation'; | ||
| // Chat screens | ||
| import RoomsListView from '../views/RoomsListView'; | ||
| import RoomView from '../views/RoomView'; | ||
| import RoomActionsView from '../views/RoomActionsView'; | ||
| import RoomInfoView from '../views/RoomInfoView'; | ||
| import ReportUserView from '../views/ReportUserView'; | ||
| import RoomInfoEditView from '../views/RoomInfoEditView'; | ||
| import RoomMembersView from '../views/RoomMembersView'; | ||
| import SearchMessagesView from '../views/SearchMessagesView'; | ||
| import SelectedUsersView from '../views/SelectedUsersView'; | ||
| import InviteUsersView from '../views/InviteUsersView'; | ||
| import InviteUsersEditView from '../views/InviteUsersEditView'; | ||
| import MessagesView from '../views/MessagesView'; | ||
| import AutoTranslateView from '../views/AutoTranslateView'; | ||
| import DirectoryView from '../views/DirectoryView'; | ||
| import NotificationPrefView from '../views/NotificationPreferencesView'; | ||
| import E2EEToggleRoomView from '../views/E2EEToggleRoomView'; | ||
| import ForwardLivechatView from '../views/ForwardLivechatView'; | ||
| import CloseLivechatView from '../views/CloseLivechatView'; | ||
| import LivechatEditView from '../views/LivechatEditView'; | ||
| import PickerView from '../views/PickerView'; | ||
| import ThreadMessagesView from '../views/ThreadMessagesView'; | ||
| import TeamChannelsView from '../views/TeamChannelsView'; | ||
| import ReadReceiptsView from '../views/ReadReceiptView'; | ||
| import CannedResponsesListView from '../views/CannedResponsesListView'; | ||
| import CannedResponseDetail from '../views/CannedResponseDetail'; | ||
| import DiscussionsView from '../views/DiscussionsView'; | ||
| import ChangeAvatarView from '../views/ChangeAvatarView'; | ||
| import AddChannelTeamView from '../views/AddChannelTeamView'; | ||
| import AddExistingChannelView from '../views/AddExistingChannelView'; | ||
| import SelectListView from '../views/SelectListView'; | ||
| import QueueListView from '../ee/omnichannel/views/QueueListView'; | ||
| import JitsiMeetView from '../views/JitsiMeetView'; | ||
| import CreateChannelView from '../views/CreateChannelView'; | ||
| import PushTroubleshootView from '../views/PushTroubleshootView'; | ||
| // Search | ||
| import SearchView from '../views/SearchView'; | ||
| // More tab screens | ||
| import MoreView from '../views/MoreView'; | ||
| import ProfileView from '../views/ProfileView'; | ||
| import UserPreferencesView from '../views/UserPreferencesView'; | ||
| import UserNotificationPrefView from '../views/UserNotificationPreferencesView'; | ||
| import ChangePasswordView from '../views/ChangePasswordView'; | ||
| import SettingsView from '../views/SettingsView'; | ||
| import SecurityPrivacyView from '../views/SecurityPrivacyView'; | ||
| import GetHelpView from '../views/GetHelpView'; | ||
| import E2EEncryptionSecurityView from '../views/E2EEncryptionSecurityView'; | ||
| import LanguageView from '../views/LanguageView'; | ||
| import ThemeView from '../views/ThemeView'; | ||
| import DefaultBrowserView from '../views/DefaultBrowserView'; | ||
| import ScreenLockConfigView from '../views/ScreenLockConfigView'; | ||
| import MediaAutoDownloadView from '../views/MediaAutoDownloadView'; | ||
| import AdminPanelView from '../views/AdminPanelView'; | ||
| import AccessibilityAndAppearanceView from '../views/AccessibilityAndAppearanceView'; | ||
| import DisplayPrefsView from '../views/DisplayPrefsView'; | ||
| import LegalView from '../views/LegalView'; | ||
| import { isIOS } from '../lib/methods/helpers'; | ||
| import { type TNavigation } from './stackType'; | ||
| import i18n from '../i18n'; | ||
| import { type BottomTabParamList, type ChatsStackParamList, type MoreStackParamList, type SearchStackParamList } from './types'; | ||
|
|
||
| const renderChatScreens = (Stack: any) => ( | ||
| <> | ||
| <Stack.Screen name='RoomView' component={RoomView} /> | ||
| <Stack.Screen name='RoomActionsView' component={RoomActionsView} options={RoomActionsView.navigationOptions} /> | ||
| {/* @ts-ignore */} | ||
| <Stack.Screen name='SelectListView' component={SelectListView} options={SelectListView.navigationOptions} /> | ||
| <Stack.Screen name='RoomInfoView' component={RoomInfoView} /> | ||
| <Stack.Screen name='ReportUserView' component={ReportUserView} /> | ||
| {/* @ts-ignore */} | ||
| <Stack.Screen name='RoomInfoEditView' component={RoomInfoEditView} options={RoomInfoEditView.navigationOptions} /> | ||
| <Stack.Screen name='ChangeAvatarView' component={ChangeAvatarView} /> | ||
| <Stack.Screen name='RoomMembersView' component={RoomMembersView} /> | ||
| {/* @ts-ignore */} | ||
| <Stack.Screen name='DiscussionsView' component={DiscussionsView} /> | ||
| <Stack.Screen | ||
| name='SearchMessagesView' | ||
| // @ts-ignore | ||
| component={SearchMessagesView} | ||
| options={SearchMessagesView.navigationOptions} | ||
| /> | ||
| <Stack.Screen name='SelectedUsersView' component={SelectedUsersView} /> | ||
| {/* @ts-ignore */} | ||
| <Stack.Screen name='InviteUsersView' component={InviteUsersView} /> | ||
| <Stack.Screen name='InviteUsersEditView' component={InviteUsersEditView} /> | ||
| <Stack.Screen name='MessagesView' component={MessagesView} /> | ||
| <Stack.Screen name='AutoTranslateView' component={AutoTranslateView} /> | ||
| {/* @ts-ignore */} | ||
| <Stack.Screen name='DirectoryView' component={DirectoryView} options={DirectoryView.navigationOptions} /> | ||
| <Stack.Screen name='NotificationPrefView' component={NotificationPrefView} /> | ||
| <Stack.Screen name='E2EEToggleRoomView' component={E2EEToggleRoomView} /> | ||
| <Stack.Screen name='PushTroubleshootView' component={PushTroubleshootView} /> | ||
| <Stack.Screen name='ForwardLivechatView' component={ForwardLivechatView} /> | ||
| {/* @ts-ignore */} | ||
| <Stack.Screen name='CloseLivechatView' component={CloseLivechatView} /> | ||
| {/* @ts-ignore */} | ||
| <Stack.Screen name='LivechatEditView' component={LivechatEditView} options={LivechatEditView.navigationOptions} /> | ||
| <Stack.Screen name='PickerView' component={PickerView} /> | ||
| {/* @ts-ignore */} | ||
| <Stack.Screen name='ThreadMessagesView' component={ThreadMessagesView} /> | ||
| <Stack.Screen name='TeamChannelsView' component={TeamChannelsView} /> | ||
| <Stack.Screen name='CreateChannelView' component={CreateChannelView} /> | ||
| <Stack.Screen name='AddChannelTeamView' component={AddChannelTeamView} /> | ||
| <Stack.Screen name='AddExistingChannelView' component={AddExistingChannelView} /> | ||
| {/* @ts-ignore */} | ||
| <Stack.Screen name='ReadReceiptsView' component={ReadReceiptsView} options={ReadReceiptsView.navigationOptions} /> | ||
| <Stack.Screen name='QueueListView' component={QueueListView} /> | ||
| <Stack.Screen name='CannedResponsesListView' component={CannedResponsesListView} /> | ||
| <Stack.Screen name='CannedResponseDetail' component={CannedResponseDetail} /> | ||
| <Stack.Screen | ||
| name='JitsiMeetView' | ||
| component={JitsiMeetView} | ||
| options={{ | ||
| headerShown: false, | ||
| animation: isIOS ? 'default' : 'none' | ||
| }} | ||
| /> | ||
| </> | ||
| ); | ||
|
|
||
| const HomeStack = createNativeStackNavigator<ChatsStackParamList & TNavigation>(); | ||
| const HomeStackScreen = () => { | ||
| 'use memo'; | ||
|
|
||
| const { theme } = React.useContext(ThemeContext); | ||
| return ( | ||
| <HomeStack.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme) }}> | ||
| <HomeStack.Screen name='RoomsListView' component={RoomsListView} initialParams={{ roomFilter: 'home' }} /> | ||
| {renderChatScreens(HomeStack)} | ||
| </HomeStack.Navigator> | ||
| ); | ||
| }; | ||
|
|
||
| const DiscussionsStack = createNativeStackNavigator<ChatsStackParamList & TNavigation>(); | ||
| const DiscussionsStackScreen = () => { | ||
| 'use memo'; | ||
|
|
||
| const { theme } = React.useContext(ThemeContext); | ||
| return ( | ||
| <DiscussionsStack.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme) }}> | ||
| <DiscussionsStack.Screen name='RoomsListView' component={RoomsListView} initialParams={{ roomFilter: 'discussions' }} /> | ||
| {renderChatScreens(DiscussionsStack)} | ||
| </DiscussionsStack.Navigator> | ||
| ); | ||
| }; | ||
|
|
||
| const DMsStack = createNativeStackNavigator<ChatsStackParamList & TNavigation>(); | ||
| const DMsStackScreen = () => { | ||
| 'use memo'; | ||
|
|
||
| const { theme } = React.useContext(ThemeContext); | ||
| return ( | ||
| <DMsStack.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme) }}> | ||
| <DMsStack.Screen name='RoomsListView' component={RoomsListView} initialParams={{ roomFilter: 'dms' }} /> | ||
| {renderChatScreens(DMsStack)} | ||
| </DMsStack.Navigator> | ||
| ); | ||
| }; | ||
|
|
||
| const SearchStackNav = createNativeStackNavigator<SearchStackParamList>(); | ||
| const SearchStackScreen = () => { | ||
| 'use memo'; | ||
|
|
||
| const { theme } = React.useContext(ThemeContext); | ||
| return ( | ||
| <SearchStackNav.Navigator | ||
| screenOptions={{ | ||
| ...themedHeader(theme), | ||
| headerLargeTitle: true | ||
| }}> | ||
| <SearchStackNav.Screen name='SearchView' component={SearchView} options={{ title: i18n.t('Search') }} /> | ||
| </SearchStackNav.Navigator> | ||
| ); | ||
| }; | ||
|
|
||
| const MoreStack = createNativeStackNavigator<MoreStackParamList & TNavigation>(); | ||
| const MoreStackScreen = () => { | ||
| 'use memo'; | ||
|
|
||
| const { theme } = React.useContext(ThemeContext); | ||
| return ( | ||
| <MoreStack.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme) }}> | ||
| <MoreStack.Screen name='MoreView' component={MoreView} /> | ||
| <MoreStack.Screen name='ProfileView' component={ProfileView} /> | ||
| <MoreStack.Screen name='ChangePasswordView' component={ChangePasswordView} /> | ||
| <MoreStack.Screen name='UserPreferencesView' component={UserPreferencesView} /> | ||
| <MoreStack.Screen name='ChangeAvatarView' component={ChangeAvatarView} /> | ||
| <MoreStack.Screen name='UserNotificationPrefView' component={UserNotificationPrefView} /> | ||
| <MoreStack.Screen name='PushTroubleshootView' component={PushTroubleshootView} /> | ||
| <MoreStack.Screen name='PickerView' component={PickerView} /> | ||
| <MoreStack.Screen name='SettingsView' component={SettingsView} /> | ||
| <MoreStack.Screen name='SecurityPrivacyView' component={SecurityPrivacyView} /> | ||
| <MoreStack.Screen name='E2EEncryptionSecurityView' component={E2EEncryptionSecurityView} /> | ||
| <MoreStack.Screen name='LanguageView' component={LanguageView} /> | ||
| <MoreStack.Screen name='DefaultBrowserView' component={DefaultBrowserView} /> | ||
| <MoreStack.Screen name='MediaAutoDownloadView' component={MediaAutoDownloadView} /> | ||
| <MoreStack.Screen name='GetHelpView' component={GetHelpView} /> | ||
| {/* @ts-ignore */} | ||
| <MoreStack.Screen name='LegalView' component={LegalView} /> | ||
| <MoreStack.Screen | ||
| name='ScreenLockConfigView' | ||
| // @ts-ignore | ||
| component={ScreenLockConfigView} | ||
| options={ScreenLockConfigView.navigationOptions} | ||
| /> | ||
| <MoreStack.Screen name='AccessibilityAndAppearanceView' component={AccessibilityAndAppearanceView} /> | ||
| <MoreStack.Screen name='DisplayPrefsView' component={DisplayPrefsView} /> | ||
| <MoreStack.Screen name='ThemeView' component={ThemeView} /> | ||
| <MoreStack.Screen name='AdminPanelView' component={AdminPanelView} /> | ||
| </MoreStack.Navigator> | ||
| ); | ||
| }; | ||
|
|
||
| const Tab = createNativeBottomTabNavigator<BottomTabParamList>(); | ||
| const BottomTabNavigator = () => { | ||
| 'use memo'; | ||
|
|
||
| return ( | ||
| <Tab.Navigator | ||
| sidebarAdaptable | ||
| ignoresTopSafeArea | ||
| // @ts-expect-error — headerShown is supported at runtime but missing from type defs | ||
| screenOptions={{ headerShown: false }}> | ||
| <Tab.Screen | ||
| name='HomeStack' | ||
| component={HomeStackScreen} | ||
| options={{ | ||
| title: i18n.t('Chats'), | ||
| tabBarIcon: () => ({ sfSymbol: 'house.fill' }) | ||
| }} | ||
| /> | ||
| <Tab.Screen | ||
| name='DiscussionsStack' | ||
| component={DiscussionsStackScreen} | ||
| options={{ | ||
| title: i18n.t('Discussions'), | ||
| tabBarIcon: () => ({ sfSymbol: 'bubble.left.and.bubble.right.fill' }) | ||
| }} | ||
| /> | ||
| <Tab.Screen | ||
| name='DMsStack' | ||
| component={DMsStackScreen} | ||
| options={{ | ||
| title: i18n.t('Direct_Messages'), | ||
| tabBarIcon: () => ({ sfSymbol: 'envelope.fill' }) | ||
| }} | ||
| /> | ||
| <Tab.Screen | ||
| name='SearchStack' | ||
| component={SearchStackScreen} | ||
| options={{ | ||
| title: i18n.t('Search'), | ||
| role: 'search', | ||
| tabBarIcon: () => ({ sfSymbol: 'magnifyingglass' }) | ||
| }} | ||
| /> | ||
| <Tab.Screen | ||
| name='MoreStack' | ||
| component={MoreStackScreen} | ||
| options={{ | ||
| title: i18n.t('More'), | ||
| tabBarIcon: () => ({ sfSymbol: 'ellipsis' }) | ||
| }} | ||
| /> | ||
| </Tab.Navigator> | ||
| ); | ||
| }; | ||
|
|
||
| export default BottomTabNavigator; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ import { createDrawerNavigator } from '@react-navigation/drawer'; | |
| import { ThemeContext } from '../theme'; | ||
| import { defaultHeader, themedHeader } from '../lib/methods/helpers/navigation'; | ||
| import Sidebar from '../views/SidebarView'; | ||
| import BottomTabNavigator from './BottomTabNavigator'; | ||
| // Chats Stack | ||
| import RoomView from '../views/RoomView'; | ||
| import RoomsListView from '../views/RoomsListView'; | ||
|
|
@@ -235,9 +236,8 @@ const AccessibilityStackNavigator = () => { | |
| ); | ||
| }; | ||
|
|
||
| // DrawerNavigator | ||
| const Drawer = createDrawerNavigator<DrawerParamList>(); | ||
| const DrawerNavigator = () => { | ||
| export const DrawerNavigator = () => { | ||
| 'use memo'; | ||
|
|
||
| const { colors } = React.useContext(ThemeContext); | ||
|
|
@@ -321,7 +321,7 @@ const InsideStackNavigator = () => { | |
|
|
||
| return ( | ||
| <InsideStack.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme), presentation: 'containedModal' }}> | ||
| <InsideStack.Screen name='DrawerNavigator' component={DrawerNavigator} options={{ headerShown: false }} /> | ||
| <InsideStack.Screen name='BottomTabNavigator' component={BottomTabNavigator} options={{ headerShown: false }} /> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "All references to DrawerNavigator route name:"
rg -n "['\"]DrawerNavigator['\"]" -C2
echo
echo "InsideStack registered screen names:"
rg -n "InsideStack\\.Screen name=" app/stacks/InsideStack.tsx -C1Repository: RocketChat/Rocket.Chat.ReactNative Length of output: 3870 🏁 Script executed: # Check the goRoom.ts function and its callers
cat -n app/lib/methods/helpers/goRoom.ts | head -60
echo "---"
echo "Check if goRoom function has isMasterDetail usage:"
rg -B5 -A10 "popTo\('DrawerNavigator'\)" app/lib/methods/helpers/goRoom.tsRepository: RocketChat/Rocket.Chat.ReactNative Length of output: 2730 🏁 Script executed: # Check what's exported from goRoom and who calls it
echo "=== Exports from goRoom.ts ==="
grep -n "export" app/lib/methods/helpers/goRoom.ts
echo ""
echo "=== Callers of goRoom ==="
rg "from.*goRoom|import.*goRoom" -A2
echo ""
echo "=== Direct calls to navigate from goRoom ==="
rg "popTo\('DrawerNavigator'\)" -B20 | grep -A5 "goRoom"Repository: RocketChat/Rocket.Chat.ReactNative Length of output: 7911 🏁 Script executed: # Check the full goRoom function
sed -n '85,132p' app/lib/methods/helpers/goRoom.tsRepository: RocketChat/Rocket.Chat.ReactNative Length of output: 1402 🏁 Script executed: # Check how goRoom is called in a few key callers
echo "=== RoomsListView calling goRoom ==="
rg -B3 -A3 "goRoom\(" app/views/RoomsListView/index.tsx | head -30
echo ""
echo "=== SearchView calling goRoom ==="
rg -B3 -A3 "goRoom\(" app/views/SearchView/index.tsx | head -30
echo ""
echo "=== Check for isMasterDetail usage in these views ==="
rg "isMasterDetail" app/views/RoomsListView/index.tsx | head -10
</parameter>Repository: RocketChat/Rocket.Chat.ReactNative Length of output: 773 🏁 Script executed: # Find where InsideStack and MasterDetailStack are used
echo "=== InsideStack usage ==="
rg "InsideStack" --type tsx --type ts | grep -v "import" | head -20
echo ""
echo "=== MasterDetailStack usage ==="
rg "MasterDetailStack" --type tsx --type ts | grep -v "import" | head -20Repository: RocketChat/Rocket.Chat.ReactNative Length of output: 209 🏁 Script executed: # Find where InsideStack and MasterDetailStack are used
echo "=== InsideStack usage ==="
rg "InsideStack" -t typescript -t javascript | grep -v "^.*import" | head -20
echo ""
echo "=== MasterDetailStack usage ==="
rg "MasterDetailStack" -t typescript -t javascript | grep -v "^.*import" | head -20
echo ""
echo "=== Check RootNavigator to see which stack is used based on isMasterDetail ==="
find app -name "*RootNavigator*" -o -name "*root*" -o -name "*Root*" | grep -i navRepository: RocketChat/Rocket.Chat.ReactNative Length of output: 303 🏁 Script executed: # Find where InsideStack and MasterDetailStack are referenced
echo "=== InsideStack usage ==="
rg "InsideStack" | grep -v "^.*:.*import" | head -20
echo ""
echo "=== MasterDetailStack usage ==="
rg "MasterDetailStack" | grep -v "^.*:.*import" | head -20
echo ""
echo "=== List app/stacks directory ==="
ls -la app/stacks/Repository: RocketChat/Rocket.Chat.ReactNative Length of output: 2950 🏁 Script executed: # Check AppContainer to understand when each stack is used
echo "=== AppContainer navigation setup ==="
cat -n app/AppContainer.tsx | grep -B10 -A10 "MasterDetailStack\|InsideStack"
echo ""
echo "=== Check if there's conditional rendering based on isMasterDetail ==="
rg -B5 -A5 "isMasterDetail.*?" app/AppContainer.tsx | head -40Repository: RocketChat/Rocket.Chat.ReactNative Length of output: 4271 Critical: Line 324 removes The 🤖 Prompt for AI Agents |
||
| <InsideStack.Screen name='NewMessageStackNavigator' component={NewMessageStackNavigator} options={{ headerShown: false }} /> | ||
| <InsideStack.Screen | ||
| name='E2ESaveYourPasswordStackNavigator' | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
@bottom-tabs/react-navigation Android icon support sfSymbol💡 Result:
No,
@react-navigation/bottom-tabsdoes not support SF Symbols (sfSymbol type) on Android. SF Symbols are iOS-only. Official React Navigation documentation explicitly states: - For Native Bottom Tabs Navigator (which uses native BottomNavigationView on Android): tabBarIcon with type: 'sfSymbol' is "Supported on iOS" only. On Android, use type: 'image' with source (images are always tinted). Example from docs: tabBarIcon: Platform.select({ ios: { type: 'sfSymbol', name: 'heart', }, android: { type: 'image', source: require('./path/to/icon.png'), }, }); - Icons docs: sfSymbol (iOS only), materialSymbol (Android only). - Customizing bottom tab bar (v8.x): "SF Symbols name - iOS", "For Android, you can use Material Symbols by setting type: 'materialSymbol'". A GitHub issue confirms users attempting sfSymbol on Android must use 'resource' or images instead. On Android (native impl), active/inactive icon switching with functions is not supported; inactive icon is used for both.Citations:
Add Android icon handling for all tab bar icons.
sfSymbolis iOS-only and will not display on Android. UsePlatform.select()to provide iOS icons withtype: 'sfSymbol'and Android icons withtype: 'image'(image resource) ortype: 'materialSymbol'(Material Symbols).This applies to all tab bar icons at lines 235-236, 243-244, 251-252, 260-261, and 268-269.
🤖 Prompt for AI Agents