Skip to content

Commit c450cd8

Browse files
authored
feat: Migrate to native bottom tab navigator using image assets for tab icons and update react-native-screens. (#415)
* feat: Migrate to native bottom tab navigator using image assets for tab icons and update `react-native-screens`. * fix: add contentInsetAdjustmentBehavior to ScrollViews for liquid glass tab bar - Add contentInsetAdjustmentBehavior='automatic' to ActScreen, BudgetScreen, CategorySelectionScreen, EmissionsScreen, SettingsScreen, and SubCategorySelectionScreen - This ensures content scrolls properly and is not hidden behind the translucent native bottom tab bar - Update related snapshots * refactor: Update MonthlyBudgetScreen to use a root ScrollView and adjust button container layout. * feat: Add `contentInsetAdjustmentBehavior='automatic'` to all scroll views and flat lists. * feat: Add safe area bottom padding to scrollable content on various screens.
1 parent a1b7e52 commit c450cd8

56 files changed

Lines changed: 572 additions & 407 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/components/NoEmission/NoEmission.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const NoEmission: React.FC = () => {
1515
const navigator = navigate(navigation);
1616

1717
return (
18-
<ScrollView style={styles.container}>
18+
<ScrollView style={styles.container} contentInsetAdjustmentBehavior="automatic">
1919
<StickersImage sticker="earth" />
2020
<View style={styles.textView}>
2121
<Text.H1 style={styles.header}>{t("NO_EMISSION_COMPONENT_TITLE")}</Text.H1>

app/components/NoEmission/__tests__/__snapshots__/NoEmissions.test.tsx.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
exports[`renders correctly NoEmission 1`] = `
44
<ScrollView
5+
contentInsetAdjustmentBehavior="automatic"
56
ref={null}
67
style={
78
{

app/constant/Layout.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ const SPACING = {
1111
const PADDING_HORIZONTAL = SPACING.DOUBLE;
1212

1313
const isSmallDevice = width < 375;
14-
1514
const screen = {
1615
width,
1716
height,
1817
};
1918

20-
export { screen, isSmallDevice, PADDING_HORIZONTAL, SPACING };
19+
// Standard Android Bottom Tab Bar height (typically 56dp) plus a small 4dp buffer. We must use a hardcoded value here because the dynamic useBottomTabBarHeight hook causes a crash when used outside the Tab Navigator context (which is the case with our Native Tabs setup)
20+
const ANDROID_TAB_BAR_HEIGHT = 60;
21+
22+
export { screen, isSmallDevice, PADDING_HORIZONTAL, SPACING, ANDROID_TAB_BAR_HEIGHT };
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { useSafeAreaInsets } from "react-native-safe-area-context";
2+
3+
import { platform } from "utils";
4+
import { Layout } from "constant";
5+
6+
export const useTabBarBottomPadding = (): number => {
7+
const insets = useSafeAreaInsets();
8+
return platform.isAndroid ? Layout.ANDROID_TAB_BAR_HEIGHT + insets.bottom : 0;
9+
};
Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import React from "react";
2-
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
3-
import { useSafeAreaInsets } from "react-native-safe-area-context";
2+
import { createNativeBottomTabNavigator, type NativeBottomTabNavigationOptions } from "@react-navigation/bottom-tabs/unstable";
43

5-
import { TabBarIcon } from "components";
64
import { t } from "utils";
75
import { Colors } from "style";
86

@@ -12,35 +10,34 @@ import EmissionsNavigator from "./BottomTab/EmissionsNavigator";
1210
import SettingsNavigator from "./BottomTab/SettingsNavigator";
1311
import AddEmissionNavigator from "./BottomTab/AddEmissionNavigator";
1412

15-
const BottomTab = createBottomTabNavigator();
13+
const BottomTab = createNativeBottomTabNavigator();
1614

17-
const BudgetOptions = {
15+
const BudgetOptions: NativeBottomTabNavigationOptions = {
1816
tabBarLabel: t("BUDGET_SCREEN_TAB_NAME"),
19-
tabBarIcon: ({ focused }) => <TabBarIcon focused={focused} name={"calculator"} />,
17+
tabBarIcon: { type: "image", source: require("../../../assets/images/tabs/budget.png") },
2018
};
2119

22-
const EmissionsOptions = {
20+
const EmissionsOptions: NativeBottomTabNavigationOptions = {
2321
tabBarLabel: t("EMISSIONS_SCREEN_TAB_NAME"),
24-
tabBarIcon: ({ focused }) => <TabBarIcon focused={focused} name={"stats-chart-sharp"} />,
22+
tabBarIcon: { type: "image", source: require("../../../assets/images/tabs/emissions.png") },
2523
};
2624

27-
const ActOptions = {
25+
const ActOptions: NativeBottomTabNavigationOptions = {
2826
tabBarLabel: t("ACT_SCREEN_TAB_NAME"),
29-
tabBarIcon: ({ focused }) => <TabBarIcon focused={focused} name={"hand-left-sharp"} />,
27+
tabBarIcon: { type: "image", source: require("../../../assets/images/tabs/act.png") },
3028
};
3129

32-
const SettingsOptions = {
30+
const SettingsOptions: NativeBottomTabNavigationOptions = {
3331
tabBarLabel: t("SETTINGS_SCREEN_TAB_NAME"),
34-
tabBarIcon: ({ focused }) => <TabBarIcon focused={focused} name={"options"} />,
32+
tabBarIcon: { type: "image", source: require("../../../assets/images/tabs/settings.png") },
3533
};
3634

37-
const AddEmissionOptions = {
35+
const AddEmissionOptions: NativeBottomTabNavigationOptions = {
3836
tabBarLabel: t("ADD_EMISSION_SCREEN_TAB_NAME"),
39-
tabBarIcon: ({ focused }) => <TabBarIcon focused={focused} name={"add-circle"} />,
37+
tabBarIcon: { type: "image", source: require("../../../assets/images/tabs/add.png") },
4038
};
4139

4240
const BottomTabNavigator = (): React.ReactElement => {
43-
const { bottom } = useSafeAreaInsets();
4441
return (
4542
<BottomTab.Navigator
4643
id="BottomTab"
@@ -49,12 +46,7 @@ const BottomTabNavigator = (): React.ReactElement => {
4946
headerShown: false,
5047
tabBarActiveTintColor: Colors.primary,
5148
tabBarInactiveTintColor: Colors.black50,
52-
tabBarBadgeStyle: {
53-
backgroundColor: Colors.white,
54-
borderTopWidth: 2,
55-
borderTopColor: Colors.primary10,
56-
paddingBottom: bottom / 2 + 6,
57-
},
49+
tabBarLabelVisibilityMode: "labeled",
5850
}}
5951
>
6052
<BottomTab.Screen
@@ -72,7 +64,11 @@ const BottomTabNavigator = (): React.ReactElement => {
7264
options={AddEmissionOptions}
7365
component={AddEmissionNavigator}
7466
/>
75-
<BottomTab.Screen name="Act" options={ActOptions} component={ActNavigator} />
67+
<BottomTab.Screen
68+
name="Act"
69+
options={ActOptions}
70+
component={ActNavigator}
71+
/>
7672
<BottomTab.Screen
7773
name="SettingsNavigator"
7874
options={SettingsOptions}
@@ -83,3 +79,4 @@ const BottomTabNavigator = (): React.ReactElement => {
8379
};
8480

8581
export default BottomTabNavigator;
82+

app/screens/About/AboutScreen.tsx

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,38 @@ import { ScrollView, View } from "react-native";
44
import { Text, StickersImage } from "components";
55
import { t } from "utils";
66
import { NavStatelessComponent } from "interfaces";
7+
import { useTabBarBottomPadding } from "hooks/useTabBarBottomPadding";
78

89
import styles from "./AboutScreen.styles";
910
import navigationOptions from "./AboutScreen.navigationOptions";
1011

11-
const AboutScreen: NavStatelessComponent = () => (
12-
<ScrollView style={styles.container}>
13-
<StickersImage sticker="earth" />
14-
<Text.Primary style={styles.header}>{t("ABOUT_SCREEN_INTRO")}</Text.Primary>
15-
<Text.H2 style={styles.header}>{t("ABOUT_SCREEN_CARE_HEADER")}</Text.H2>
16-
<Text.H3 style={styles.subHeader}>{t("ABOUT_SCREEN_PRIVACY")}</Text.H3>
17-
<Text.Primary>{t("ABOUT_SCREEN_PRIVACY_BODY")}</Text.Primary>
18-
<Text.H3 style={styles.subHeader}>{t("ABOUT_SCREEN_ETHIC")}</Text.H3>
19-
<Text.Primary>{t("ABOUT_SCREEN_ETHIC_BODY")}</Text.Primary>
20-
<Text.H3 style={styles.subHeader}>{t("ABOUT_SCREEN_OPEN_SOURCE")}</Text.H3>
21-
<Text.Primary>{t("ABOUT_SCREEN_OPEN_SOURCE_BODY")}</Text.Primary>
22-
<Text.H2 style={styles.header}>{t("ABOUT_SCREEN_LIBRARIES_AND_CONTRIBUTORS")}</Text.H2>
23-
<View style={styles.githubView}>
24-
<Text.Primary>{t("ABOUT_SCREEN_CAN_BE_FOUND")}</Text.Primary>
25-
<Text.Link url="https://github.com/NMF-earth/nmf-app">{t("ABOUT_SCREEN_GITHUB")}</Text.Link>
26-
</View>
27-
<View style={styles.separator} />
28-
</ScrollView>
29-
);
12+
const AboutScreen: NavStatelessComponent = () => {
13+
const bottomPadding = useTabBarBottomPadding();
14+
15+
return (
16+
<ScrollView
17+
style={styles.container}
18+
contentInsetAdjustmentBehavior="automatic"
19+
contentContainerStyle={{ paddingBottom: bottomPadding }}
20+
>
21+
<StickersImage sticker="earth" />
22+
<Text.Primary style={styles.header}>{t("ABOUT_SCREEN_INTRO")}</Text.Primary>
23+
<Text.H2 style={styles.header}>{t("ABOUT_SCREEN_CARE_HEADER")}</Text.H2>
24+
<Text.H3 style={styles.subHeader}>{t("ABOUT_SCREEN_PRIVACY")}</Text.H3>
25+
<Text.Primary>{t("ABOUT_SCREEN_PRIVACY_BODY")}</Text.Primary>
26+
<Text.H3 style={styles.subHeader}>{t("ABOUT_SCREEN_ETHIC")}</Text.H3>
27+
<Text.Primary>{t("ABOUT_SCREEN_ETHIC_BODY")}</Text.Primary>
28+
<Text.H3 style={styles.subHeader}>{t("ABOUT_SCREEN_OPEN_SOURCE")}</Text.H3>
29+
<Text.Primary>{t("ABOUT_SCREEN_OPEN_SOURCE_BODY")}</Text.Primary>
30+
<Text.H2 style={styles.header}>{t("ABOUT_SCREEN_LIBRARIES_AND_CONTRIBUTORS")}</Text.H2>
31+
<View style={styles.githubView}>
32+
<Text.Primary>{t("ABOUT_SCREEN_CAN_BE_FOUND")}</Text.Primary>
33+
<Text.Link url="https://github.com/NMF-earth/nmf-app">{t("ABOUT_SCREEN_GITHUB")}</Text.Link>
34+
</View>
35+
<View style={styles.separator} />
36+
</ScrollView>
37+
);
38+
};
3039

3140
AboutScreen.navigationOptions = navigationOptions();
3241

app/screens/About/__tests__/__snapshots__/AboutScreen.test.tsx.snap

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
exports[`AboutScreen renders correctly 1`] = `
44
<ScrollView
5+
contentContainerStyle={
6+
{
7+
"paddingBottom": 0,
8+
}
9+
}
10+
contentInsetAdjustmentBehavior="automatic"
511
ref={null}
612
style={
713
{

app/screens/Act/ActScreen.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const ActScreen: NavStatelessComponent = () => {
3232
return (
3333
<FlatList
3434
style={styles.container}
35+
contentInsetAdjustmentBehavior="automatic"
3536
data={data}
3637
renderItem={renderItem}
3738
keyExtractor={(item) => item.title}

app/screens/Act/__tests__/__snapshots__/ActScreen.test.tsx.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ exports[`ActScreen renders correctly 1`] = `
1717
"type": [Function],
1818
}
1919
}
20+
contentInsetAdjustmentBehavior="automatic"
2021
data={[]}
2122
keyExtractor={[Function]}
2223
ref={null}

app/screens/ActDetail/ActDetailScreen.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import HTML from "react-native-render-html";
66
import { HTMLImage } from "components";
77
import { ui } from "utils";
88
import { NavStatelessComponent } from "interfaces";
9+
import { useTabBarBottomPadding } from "hooks/useTabBarBottomPadding";
910

1011
import styles from "./ActDetailScreen.styles";
1112
import navigationOptions from "./ActDetailScreen.navigationOptions";
@@ -21,9 +22,14 @@ const ActDetailScreen: NavStatelessComponent = () => {
2122
// eslint-disable-next-line no-unsafe-optional-chaining
2223
const { body } = route?.params;
2324
const contentWidth = useWindowDimensions().width;
25+
const paddingBottom = useTabBarBottomPadding();
2426

2527
return (
26-
<ScrollView style={styles.container}>
28+
<ScrollView
29+
style={styles.container}
30+
contentInsetAdjustmentBehavior="automatic"
31+
contentContainerStyle={{ paddingBottom: paddingBottom }}
32+
>
2733
<HTML
2834
source={{ html: body }}
2935
contentWidth={contentWidth}

0 commit comments

Comments
 (0)