From b8e7272d2cb1ec54e189c1830ab045b74278bd3c Mon Sep 17 00:00:00 2001 From: Arnaud Vergnet Date: Tue, 22 Sep 2020 21:49:39 +0200 Subject: [PATCH] Update navigation components to use TypeScript --- src/components/Tabbar/CustomTabBar.tsx | 45 ++--- .../{MainNavigator.js => MainNavigator.tsx} | 170 ++++++++++-------- .../{TabNavigator.js => TabNavigator.tsx} | 132 +++++++------- src/screens/About/AboutDependenciesScreen.tsx | 4 +- src/screens/Amicale/AmicaleContactScreen.tsx | 4 +- src/utils/CollapsibleUtils.tsx | 6 +- 6 files changed, 182 insertions(+), 179 deletions(-) rename src/navigation/{MainNavigator.js => MainNavigator.tsx} (63%) rename src/navigation/{TabNavigator.js => TabNavigator.tsx} (74%) diff --git a/src/components/Tabbar/CustomTabBar.tsx b/src/components/Tabbar/CustomTabBar.tsx index 797257e..cd18b82 100644 --- a/src/components/Tabbar/CustomTabBar.tsx +++ b/src/components/Tabbar/CustomTabBar.tsx @@ -21,36 +21,22 @@ import * as React from 'react'; import {Animated} from 'react-native'; import {withTheme} from 'react-native-paper'; import {Collapsible} from 'react-navigation-collapsible'; -import {StackNavigationProp} from '@react-navigation/stack'; import TabIcon from './TabIcon'; import TabHomeIcon from './TabHomeIcon'; +import {BottomTabBarProps} from '@react-navigation/bottom-tabs'; +import {NavigationState} from '@react-navigation/native'; +import { + PartialState, + Route, +} from '@react-navigation/routers/lib/typescript/src/types'; -type RouteType = { - name: string; - key: string; - params: {collapsible: Collapsible}; - state: { - index: number; - routes: Array; - }; +type RouteType = Route & { + state?: NavigationState | PartialState; }; -type PropsType = { - state: { - index: number; - routes: Array; - }; - descriptors: { - [key: string]: { - options: { - tabBarLabel: string; - title: string; - }; - }; - }; - navigation: StackNavigationProp; +interface PropsType extends BottomTabBarProps { theme: ReactNativePaper.Theme; -}; +} type StateType = { translateY: any; @@ -164,7 +150,7 @@ class CustomTabBar extends React.Component { onLongPress={onLongPress} icon={this.getTabBarIcon(route, isFocused)} color={color} - label={label} + label={label as string} focused={isFocused} extraData={state.index > index} key={route.key} @@ -193,9 +179,12 @@ class CustomTabBar extends React.Component { if (isFocused) { const stackState = route.state; const stackRoute = - stackState != null ? stackState.routes[stackState.index] : null; - const params: {collapsible: Collapsible} | null = - stackRoute != null ? stackRoute.params : null; + stackState && stackState.index + ? stackState.routes[stackState.index] + : null; + const params: {collapsible: Collapsible} | null | undefined = stackRoute + ? (stackRoute.params as {collapsible: Collapsible}) + : null; const collapsible = params != null ? params.collapsible : null; if (collapsible != null) { this.setState({ diff --git a/src/navigation/MainNavigator.js b/src/navigation/MainNavigator.tsx similarity index 63% rename from src/navigation/MainNavigator.js rename to src/navigation/MainNavigator.tsx index 5df203f..223af6d 100644 --- a/src/navigation/MainNavigator.js +++ b/src/navigation/MainNavigator.tsx @@ -17,8 +17,6 @@ * along with Campus INSAT. If not, see . */ -// @flow - import * as React from 'react'; import {createStackNavigator, TransitionPresets} from '@react-navigation/stack'; import i18n from 'i18n-js'; @@ -40,18 +38,63 @@ import ClubListScreen from '../screens/Amicale/Clubs/ClubListScreen'; import ClubAboutScreen from '../screens/Amicale/Clubs/ClubAboutScreen'; import ClubDisplayScreen from '../screens/Amicale/Clubs/ClubDisplayScreen'; import { - createScreenCollapsibleStack, + CreateScreenCollapsibleStack, getWebsiteStack, } from '../utils/CollapsibleUtils'; import BugReportScreen from '../screens/Other/FeedbackScreen'; import WebsiteScreen from '../screens/Services/WebsiteScreen'; -import EquipmentScreen from '../screens/Amicale/Equipment/EquipmentListScreen'; +import EquipmentScreen, { + DeviceType, +} from '../screens/Amicale/Equipment/EquipmentListScreen'; import EquipmentLendScreen from '../screens/Amicale/Equipment/EquipmentRentScreen'; import EquipmentConfirmScreen from '../screens/Amicale/Equipment/EquipmentConfirmScreen'; import DashboardEditScreen from '../screens/Other/Settings/DashboardEditScreen'; import GameStartScreen from '../screens/Game/screens/GameStartScreen'; import ImageGalleryScreen from '../screens/Media/ImageGalleryScreen'; +export enum MainRoutes { + Main = 'main', + Gallery = 'gallery', + Settings = 'settings', + DashboardEdit = 'dashboard-edit', + About = 'about', + Dependencies = 'dependencies', + Debug = 'debug', + GameStart = 'game-start', + GameMain = 'game-main', + Login = 'login', + SelfMenu = 'self-menu', + Proximo = 'proximo', + ProximoList = 'proximo-list', + ProximoAbout = 'proximo-about', + Profile = 'profile', + ClubList = 'club-list', + ClubInformation = 'club-information', + ClubAbout = 'club-about', + EquipmentList = 'equipment-list', + EquipmentRent = 'equipment-rent', + EquipmentConfirm = 'equipment-confirm', + Vote = 'vote', + Feedback = 'feedback', +} + +type DefaultParams = {[key in MainRoutes]: object | undefined}; + +export interface FullParamsList extends DefaultParams { + login: {nextScreen: string}; + 'equipment-confirm': { + item?: DeviceType; + dates: [string, string]; + }; + 'equipment-rent': {item?: DeviceType}; + gallery: {images: Array<{url: string}>}; +} + +// Don't know why but TS is complaining without this +// See: https://stackoverflow.com/questions/63652687/interface-does-not-satisfy-the-constraint-recordstring-object-undefined +export type MainStackParamsList = FullParamsList & + Record; + const modalTransition = Platform.OS === 'ios' ? TransitionPresets.ModalPresentationIOS @@ -63,19 +106,17 @@ const defaultScreenOptions = { ...TransitionPresets.SlideFromRightIOS, }; -const MainStack = createStackNavigator(); +const MainStack = createStackNavigator(); -function MainStackComponent(props: { - createTabNavigator: () => React.Node, -}): React.Node { +function MainStackComponent(props: {createTabNavigator: () => JSX.Element}) { const {createTabNavigator} = props; return ( - {createScreenCollapsibleStack( - 'settings', + {CreateScreenCollapsibleStack( + MainRoutes.Settings, MainStack, SettingsScreen, i18n.t('screens.settings.title'), )} - {createScreenCollapsibleStack( - 'dashboard-edit', + {CreateScreenCollapsibleStack( + MainRoutes.DashboardEdit, MainStack, DashboardEditScreen, i18n.t('screens.settings.dashboardEdit.title'), )} - {createScreenCollapsibleStack( - 'about', + {CreateScreenCollapsibleStack( + MainRoutes.About, MainStack, AboutScreen, i18n.t('screens.about.title'), )} - {createScreenCollapsibleStack( - 'dependencies', + {CreateScreenCollapsibleStack( + MainRoutes.Dependencies, MainStack, AboutDependenciesScreen, i18n.t('screens.about.libs'), )} - {createScreenCollapsibleStack( - 'debug', + {CreateScreenCollapsibleStack( + MainRoutes.Debug, MainStack, DebugScreen, i18n.t('screens.about.debug'), )} - {createScreenCollapsibleStack( - 'game-start', + {CreateScreenCollapsibleStack( + MainRoutes.GameStart, MainStack, GameStartScreen, i18n.t('screens.game.title'), true, - null, + undefined, 'transparent', )} - {createScreenCollapsibleStack( - 'login', + {CreateScreenCollapsibleStack( + MainRoutes.Login, MainStack, LoginScreen, i18n.t('screens.login.title'), @@ -148,26 +189,26 @@ function MainStackComponent(props: { )} {getWebsiteStack('website', MainStack, WebsiteScreen, '')} - {createScreenCollapsibleStack( - 'self-menu', + {CreateScreenCollapsibleStack( + MainRoutes.SelfMenu, MainStack, SelfMenuScreen, i18n.t('screens.menu.title'), )} - {createScreenCollapsibleStack( - 'proximo', + {CreateScreenCollapsibleStack( + MainRoutes.Proximo, MainStack, ProximoMainScreen, i18n.t('screens.proximo.title'), )} - {createScreenCollapsibleStack( - 'proximo-list', + {CreateScreenCollapsibleStack( + MainRoutes.ProximoList, MainStack, ProximoListScreen, i18n.t('screens.proximo.articleList'), )} - {createScreenCollapsibleStack( - 'proximo-about', + {CreateScreenCollapsibleStack( + MainRoutes.ProximoAbout, MainStack, ProximoAboutScreen, i18n.t('screens.proximo.title'), @@ -175,60 +216,60 @@ function MainStackComponent(props: { {...modalTransition}, )} - {createScreenCollapsibleStack( - 'profile', + {CreateScreenCollapsibleStack( + MainRoutes.Profile, MainStack, ProfileScreen, i18n.t('screens.profile.title'), )} - {createScreenCollapsibleStack( - 'club-list', + {CreateScreenCollapsibleStack( + MainRoutes.ClubList, MainStack, ClubListScreen, i18n.t('screens.clubs.title'), )} - {createScreenCollapsibleStack( - 'club-information', + {CreateScreenCollapsibleStack( + MainRoutes.ClubInformation, MainStack, ClubDisplayScreen, i18n.t('screens.clubs.details'), true, {...modalTransition}, )} - {createScreenCollapsibleStack( - 'club-about', + {CreateScreenCollapsibleStack( + MainRoutes.ClubAbout, MainStack, ClubAboutScreen, i18n.t('screens.clubs.title'), true, {...modalTransition}, )} - {createScreenCollapsibleStack( - 'equipment-list', + {CreateScreenCollapsibleStack( + MainRoutes.EquipmentList, MainStack, EquipmentScreen, i18n.t('screens.equipment.title'), )} - {createScreenCollapsibleStack( - 'equipment-rent', + {CreateScreenCollapsibleStack( + MainRoutes.EquipmentRent, MainStack, EquipmentLendScreen, i18n.t('screens.equipment.book'), )} - {createScreenCollapsibleStack( - 'equipment-confirm', + {CreateScreenCollapsibleStack( + MainRoutes.EquipmentConfirm, MainStack, EquipmentConfirmScreen, i18n.t('screens.equipment.confirm'), )} - {createScreenCollapsibleStack( - 'vote', + {CreateScreenCollapsibleStack( + MainRoutes.Vote, MainStack, VoteScreen, i18n.t('screens.vote.title'), )} - {createScreenCollapsibleStack( - 'feedback', + {CreateScreenCollapsibleStack( + MainRoutes.Feedback, MainStack, BugReportScreen, i18n.t('screens.feedback.title'), @@ -238,25 +279,10 @@ function MainStackComponent(props: { } type PropsType = { - defaultHomeRoute: string | null, - // eslint-disable-next-line flowtype/no-weak-types - defaultHomeData: {[key: string]: string}, + defaultHomeRoute: string | null; + defaultHomeData: {[key: string]: string}; }; -export default class MainNavigator extends React.Component { - createTabNavigator: () => React.Node; - - constructor(props: PropsType) { - super(props); - this.createTabNavigator = (): React.Node => ( - - ); - } - - render(): React.Node { - return ; - } +export default function MainNavigator(props: PropsType) { + return TabNavigator(props)} />; } diff --git a/src/navigation/TabNavigator.js b/src/navigation/TabNavigator.tsx similarity index 74% rename from src/navigation/TabNavigator.js rename to src/navigation/TabNavigator.tsx index f3c0606..4f62730 100644 --- a/src/navigation/TabNavigator.js +++ b/src/navigation/TabNavigator.tsx @@ -17,8 +17,6 @@ * along with Campus INSAT. If not, see . */ -// @flow - import * as React from 'react'; import {createStackNavigator, TransitionPresets} from '@react-navigation/stack'; import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'; @@ -44,7 +42,7 @@ import WebsitesHomeScreen from '../screens/Services/ServicesScreen'; import ServicesSectionScreen from '../screens/Services/ServicesSectionScreen'; import AmicaleContactScreen from '../screens/Amicale/AmicaleContactScreen'; import { - createScreenCollapsibleStack, + CreateScreenCollapsibleStack, getWebsiteStack, } from '../utils/CollapsibleUtils'; import Mascot, {MASCOT_STYLE} from '../components/Mascot/Mascot'; @@ -62,25 +60,25 @@ const defaultScreenOptions = { const ServicesStack = createStackNavigator(); -function ServicesStackComponent(): React.Node { +function ServicesStackComponent() { return ( - {createScreenCollapsibleStack( + {CreateScreenCollapsibleStack( 'index', ServicesStack, WebsitesHomeScreen, i18n.t('screens.services.title'), )} - {createScreenCollapsibleStack( + {CreateScreenCollapsibleStack( 'services-section', ServicesStack, ServicesSectionScreen, 'SECTION', )} - {createScreenCollapsibleStack( + {CreateScreenCollapsibleStack( 'amicale-contact', ServicesStack, AmicaleContactScreen, @@ -92,19 +90,19 @@ function ServicesStackComponent(): React.Node { const ProxiwashStack = createStackNavigator(); -function ProxiwashStackComponent(): React.Node { +function ProxiwashStackComponent() { return ( - {createScreenCollapsibleStack( + {CreateScreenCollapsibleStack( 'index', ProxiwashStack, ProxiwashScreen, i18n.t('screens.proxiwash.title'), )} - {createScreenCollapsibleStack( + {CreateScreenCollapsibleStack( 'proxiwash-about', ProxiwashStack, ProxiwashAboutScreen, @@ -116,7 +114,7 @@ function ProxiwashStackComponent(): React.Node { const PlanningStack = createStackNavigator(); -function PlanningStackComponent(): React.Node { +function PlanningStackComponent() { return ( - {createScreenCollapsibleStack( + {CreateScreenCollapsibleStack( 'planning-information', PlanningStack, PlanningDisplayScreen, @@ -142,10 +140,11 @@ const HomeStack = createStackNavigator(); function HomeStackComponent( initialRoute: string | null, defaultData: {[key: string]: string}, -): React.Node { +) { let params; - if (initialRoute != null) + if (initialRoute != null) { params = {data: defaultData, nextScreen: initialRoute, shouldOpen: true}; + } const {colors} = useTheme(); return ( ( + headerTitle: () => ( - {createScreenCollapsibleStack( + {CreateScreenCollapsibleStack( 'club-information', HomeStack, ClubDisplayScreen, i18n.t('screens.clubs.details'), )} - {createScreenCollapsibleStack( + {CreateScreenCollapsibleStack( 'feed-information', HomeStack, FeedItemScreen, i18n.t('screens.home.feed'), )} - {createScreenCollapsibleStack( + {CreateScreenCollapsibleStack( 'planning-information', HomeStack, PlanningDisplayScreen, @@ -227,7 +226,7 @@ function HomeStackComponent( const PlanexStack = createStackNavigator(); -function PlanexStackComponent(): React.Node { +function PlanexStackComponent() { return ( { - createHomeStackComponent: () => React.Node; - - defaultRoute: string; - - constructor(props: PropsType) { - super(props); - if (props.defaultHomeRoute != null) this.defaultRoute = 'home'; - else - this.defaultRoute = AsyncStorageManager.getString( - AsyncStorageManager.PREFERENCES.defaultStartScreen.key, - ).toLowerCase(); - this.createHomeStackComponent = (): React.Node => - HomeStackComponent(props.defaultHomeRoute, props.defaultHomeData); +export default function TabNavigator(props: PropsType) { + let defaultRoute = 'home'; + if (!props.defaultHomeRoute) { + defaultRoute = AsyncStorageManager.getString( + AsyncStorageManager.PREFERENCES.defaultStartScreen.key, + ).toLowerCase(); } + const createHomeStackComponent = () => + HomeStackComponent(props.defaultHomeRoute, props.defaultHomeData); - render(): React.Node { - return ( - }> - - - - - - - - ); - } + return ( + }> + + + + + + + ); } diff --git a/src/screens/About/AboutDependenciesScreen.tsx b/src/screens/About/AboutDependenciesScreen.tsx index 9a283b0..82dc982 100644 --- a/src/screens/About/AboutDependenciesScreen.tsx +++ b/src/screens/About/AboutDependenciesScreen.tsx @@ -50,11 +50,11 @@ const LIST_ITEM_HEIGHT = 64; /** * Class defining a screen showing the list of libraries used by the app, taken from package.json */ -export default class AboutDependenciesScreen extends React.Component { +export default class AboutDependenciesScreen extends React.Component<{}> { data: Array; constructor() { - super(null); + super({}); this.data = generateListFromObject(packageJson.dependencies); } diff --git a/src/screens/Amicale/AmicaleContactScreen.tsx b/src/screens/Amicale/AmicaleContactScreen.tsx index b7f0ae4..de99509 100644 --- a/src/screens/Amicale/AmicaleContactScreen.tsx +++ b/src/screens/Amicale/AmicaleContactScreen.tsx @@ -34,12 +34,12 @@ type DatasetItemType = { /** * Class defining a planning event information page. */ -class AmicaleContactScreen extends React.Component { +class AmicaleContactScreen extends React.Component<{}> { // Dataset containing information about contacts CONTACT_DATASET: Array; constructor() { - super(null); + super({}); this.CONTACT_DATASET = [ { name: i18n.t('screens.amicaleAbout.roles.interSchools'), diff --git a/src/utils/CollapsibleUtils.tsx b/src/utils/CollapsibleUtils.tsx index 921cb12..5438752 100644 --- a/src/utils/CollapsibleUtils.tsx +++ b/src/utils/CollapsibleUtils.tsx @@ -21,7 +21,7 @@ import * as React from 'react'; import {useTheme} from 'react-native-paper'; import {createCollapsibleStack} from 'react-navigation-collapsible'; import StackNavigator, {StackNavigationOptions} from '@react-navigation/stack'; -import {StackNavigationState} from '@react-navigation/native'; +import {StackNavigationState, TypedNavigator} from '@react-navigation/native'; import {StackNavigationEventMap} from '@react-navigation/stack/lib/typescript/src/types'; type StackNavigatorType = import('@react-navigation/native').TypedNavigator< @@ -50,7 +50,7 @@ type StackNavigatorType = import('@react-navigation/native').TypedNavigator< */ export function CreateScreenCollapsibleStack( name: string, - Stack: StackNavigatorType, + Stack: TypedNavigator, component: React.ComponentType, title: string, useNativeDriver: boolean = true, @@ -91,7 +91,7 @@ export function CreateScreenCollapsibleStack( */ export function getWebsiteStack( name: string, - Stack: StackNavigatorType, + Stack: TypedNavigator, component: React.ComponentType, title: string, ) {