diff --git a/native/chat/chat-tab-bar-button.react.js b/native/chat/chat-tab-bar-button.react.js
new file mode 100644
--- /dev/null
+++ b/native/chat/chat-tab-bar-button.react.js
@@ -0,0 +1,145 @@
+// @flow
+
+import invariant from 'invariant';
+import * as React from 'react';
+import { TouchableOpacity, View, Text } from 'react-native';
+
+import { threadSettingsNotificationsCopy } from 'lib/shared/thread-settings-notifications-utils.js';
+import { useSelector } from 'lib/utils/redux-utils.js';
+
+import {
+ nuxTip,
+ NUXTipsContext,
+} from '../components/nux-tips-context.react.js';
+import { useStyles } from '../themes/colors.js';
+import { LightTheme, DarkTheme } from '../themes/navigation.js';
+
+type Props = {
+ +title: string,
+ +tabBarIcon?:
+ | string
+ | (({ +color: string, +focused: boolean }) => React$Node),
+ +onPress?: () => void,
+ +onLongPress?: () => void,
+ +isFocused?: boolean,
+ +tabBarAccessibilityLabel?: string,
+ +tabBarTestID?: string,
+};
+
+const ButtonTitleToTip = Object.freeze({
+ [threadSettingsNotificationsCopy.MUTED]: nuxTip.MUTED,
+ [threadSettingsNotificationsCopy.HOME]: nuxTip.HOME,
+});
+
+const unboundStyles = {
+ button: {
+ flex: 1,
+ backgroundColor: 'tabBarBackground',
+ },
+ innerContainer: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ flexDirection: 'row',
+ },
+ tabBarIndicatorStyle: {
+ borderColor: 'tabBarAccent',
+ borderBottomWidth: 2,
+ },
+};
+
+export default function TabBarButton(props: Props): React.Node {
+ const {
+ title,
+ tabBarIcon,
+ onPress,
+ onLongPress,
+ isFocused,
+ tabBarAccessibilityLabel,
+ tabBarTestID,
+ } = props;
+
+ const styles = useStyles(unboundStyles);
+
+ const viewRef = React.useRef>();
+
+ const tipsContext = React.useContext(NUXTipsContext);
+ invariant(tipsContext, 'NUXTipsContext should be defined');
+
+ const onLayout = () => {
+ const button = viewRef.current;
+ if (!button) {
+ return;
+ }
+ const tipType = ButtonTitleToTip[title];
+ if (!tipType) {
+ return;
+ }
+ button.measure((x, y, width, height, pageX, pageY) => {
+ tipsContext.registerTipButton(tipType, {
+ x,
+ y,
+ width,
+ height,
+ pageX,
+ pageY,
+ });
+ });
+ };
+
+ const activeTheme = useSelector(state => state.globalThemeInfo.activeTheme);
+
+ const textColor = React.useMemo(() => {
+ const color =
+ activeTheme === 'dark' ? DarkTheme.colors.text : LightTheme.colors.text;
+
+ if (!isFocused) {
+ return 'gray';
+ }
+ return color;
+ }, [activeTheme, isFocused]);
+
+ const icon = React.useMemo(
+ () =>
+ typeof tabBarIcon === 'string' || !tabBarIcon
+ ? tabBarIcon
+ : tabBarIcon({ color: textColor, focused: isFocused ?? false }),
+ [isFocused, tabBarIcon, textColor],
+ );
+
+ const buttonStyle = React.useMemo(() => {
+ if (isFocused) {
+ return [styles.button, styles.tabBarIndicatorStyle];
+ }
+ return styles.button;
+ }, [isFocused, styles.button, styles.tabBarIndicatorStyle]);
+
+ const textStyle = React.useMemo(
+ () => ({
+ color: textColor,
+ textTransform: 'uppercase',
+ fontSize: 14,
+ margin: 4,
+ textAlign: 'center',
+ backgroundColor: 'transparent',
+ }),
+ [textColor],
+ );
+
+ return (
+
+
+ {icon}
+ {title}
+
+
+ );
+}
diff --git a/native/chat/chat-tab-bar.react.js b/native/chat/chat-tab-bar.react.js
new file mode 100644
--- /dev/null
+++ b/native/chat/chat-tab-bar.react.js
@@ -0,0 +1,73 @@
+// @flow
+
+import type { MaterialTopTabBarProps } from '@react-navigation/core';
+import invariant from 'invariant';
+import * as React from 'react';
+import { View } from 'react-native';
+
+import ChatTabBarButton from './chat-tab-bar-button.react.js';
+import { NUXTipsContext } from '../components/nux-tips-context.react.js';
+import { useStyles } from '../themes/colors.js';
+
+const unboundStyles = {
+ container: {
+ flexDirection: 'row',
+ backgroundColor: 'white',
+ height: 48,
+ },
+};
+
+export default function TabBar(props: MaterialTopTabBarProps): React.Node {
+ const styles = useStyles(unboundStyles);
+ const { state, descriptors, navigation } = props;
+
+ const tipsContext = React.useContext(NUXTipsContext);
+ invariant(tipsContext, 'NUXTipsContext should be defined');
+
+ const buttons = React.useMemo(
+ () =>
+ state.routes.map((route, index) => {
+ const { options } = descriptors[route.key];
+ const { title, tabBarIcon } = options;
+ invariant(
+ title && tabBarIcon,
+ 'title and tabBarIcon should be defined',
+ );
+
+ const isFocused = state.index === index;
+
+ const onPress = () => {
+ // $FlowFixMe
+ const event = navigation.emit({
+ type: 'tabPress',
+ target: route.key,
+ });
+
+ if (!isFocused && !event.defaultPrevented) {
+ navigation.navigate(route.name);
+ }
+ };
+
+ const onLongPress = () => {
+ // $FlowFixMe
+ navigation.emit({
+ type: 'tabLongPress',
+ target: route.key,
+ });
+ };
+
+ return (
+
+ );
+ }),
+ [descriptors, navigation, state.index, state.routes],
+ );
+ return {buttons};
+}
diff --git a/native/chat/chat.react.js b/native/chat/chat.react.js
--- a/native/chat/chat.react.js
+++ b/native/chat/chat.react.js
@@ -38,6 +38,7 @@
type ChatRouterNavigationHelpers,
type ChatRouterNavigationAction,
} from './chat-router.js';
+import TabBar from './chat-tab-bar.react.js';
import ComposeSubchannel from './compose-subchannel.react.js';
import ComposeThreadButton from './compose-thread-button.react.js';
import FullScreenThreadMediaGallery from './fullscreen-thread-media-gallery.react.js';
@@ -119,15 +120,21 @@
export type ChatTopTabsNavigationHelpers =
MaterialTopTabNavigationHelpers;
-const homeChatThreadListOptions = {
+const homeChatThreadListOptions: {
+ title: string,
+ tabBarIcon: ({ +color: string, ... }) => React$Node,
+} = {
title: threadSettingsNotificationsCopy.HOME,
- tabBarIcon: ({ color }: { +color: string, ... }) => (
+ tabBarIcon: ({ color }) => (
),
};
-const backgroundChatThreadListOptions = {
+const backgroundChatThreadListOptions: {
+ title: string,
+ tabBarIcon: ({ +color: string, ... }) => React$Node,
+} = {
title: threadSettingsNotificationsCopy.MUTED,
- tabBarIcon: ({ color }: { +color: string, ... }) => (
+ tabBarIcon: ({ color }) => (
),
};
@@ -156,8 +163,9 @@
}),
[tabBarAccent, tabBarBackground],
);
+
return (
-
+
);
}
+
+export { backgroundChatThreadListOptions, homeChatThreadListOptions };
diff --git a/native/components/nux-tips-context.react.js b/native/components/nux-tips-context.react.js
--- a/native/components/nux-tips-context.react.js
+++ b/native/components/nux-tips-context.react.js
@@ -9,6 +9,7 @@
const nuxTip = Object.freeze({
COMMUNITY_DRAWER: 'community_drawer',
MUTED: 'muted',
+ HOME: 'home',
});
export type NUXTip = $Values;