Merge pull request #190 from Chroma-Case/front/typesafe-navigator
Front: Navigation: Use actual routes to build a typed navigator
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import { NativeStackScreenProps, createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import { NavigationProp, useNavigation as navigationHook } from "@react-navigation/native";
|
||||
import React from 'react';
|
||||
import { DarkTheme, DefaultTheme, NavigationContainer } from '@react-navigation/native';
|
||||
import { RootState, useSelector } from './state/Store';
|
||||
@@ -17,21 +18,48 @@ import LoadingComponent from './components/Loading';
|
||||
import ProfileView from './views/ProfileView';
|
||||
import useColorScheme from './hooks/colorScheme';
|
||||
|
||||
const Stack = createNativeStackNavigator();
|
||||
const protectedRoutes = () => ({
|
||||
Home: { component: HomeView, options: { title: translate('welcome') } },
|
||||
Settings: { component: SetttingsNavigator, options: { title: 'Settings' } },
|
||||
Song: { component: SongLobbyView, options: { title: translate('play') } },
|
||||
Play: { component: PlayView, options: { title: translate('play') } },
|
||||
Score: { component: ScoreView, options: { title: translate('score') } },
|
||||
Search: { component: SearchView, options: { title: translate('search') } },
|
||||
User: { component: ProfileView, options: { title: translate('user') } },
|
||||
}) as const;
|
||||
|
||||
export const protectedRoutes = <>
|
||||
<Stack.Screen name="Home" component={HomeView} options={{ title: translate('welcome') }} />
|
||||
<Stack.Screen name="Settings" component={SetttingsNavigator} options={{ title: 'Settings' }} />
|
||||
<Stack.Screen name="Song" component={SongLobbyView} options={{ title: translate('play') }} />
|
||||
<Stack.Screen name="Play" component={PlayView} options={{ title: translate('play') }} />
|
||||
<Stack.Screen name="Score" component={ScoreView} options={{ title: translate('score') }} />
|
||||
<Stack.Screen name="Search" component={SearchView} options={{ title: translate('search') }} />
|
||||
<Stack.Screen name="User" component={ProfileView} options={{ title: translate('user') }} />
|
||||
</>;
|
||||
const publicRoutes = () => ({
|
||||
Login: { component: AuthenticationView, options: { title: translate('signInBtn') } },
|
||||
}) as const;
|
||||
|
||||
export const publicRoutes = <React.Fragment>
|
||||
<Stack.Screen name="Login" component={AuthenticationView} options={{ title: translate('signInBtn')}} />
|
||||
</React.Fragment>;
|
||||
type Route<Args extends any[] = any[]> = {
|
||||
component: (...args: Args) => JSX.Element,
|
||||
options: any
|
||||
}
|
||||
|
||||
type RouteParams<Routes extends Record<string, Route>> = {
|
||||
[RouteName in keyof Routes]: Parameters<Routes[RouteName]['component']>[0];
|
||||
}
|
||||
|
||||
type PrivateRoutesParams = RouteParams<ReturnType<typeof protectedRoutes>>;
|
||||
type PublicRoutesParams = RouteParams<ReturnType<typeof publicRoutes>>;
|
||||
type AppRouteParams = PrivateRoutesParams & PublicRoutesParams;
|
||||
|
||||
const Stack = createNativeStackNavigator<AppRouteParams & { Loading: never }>();
|
||||
|
||||
const RouteToScreen = (component: Route['component']) => (props: NativeStackScreenProps<AppRouteParams>) =>
|
||||
<>
|
||||
{component(props.route.params)}
|
||||
</>
|
||||
|
||||
const routesToScreens = (routes: Record<keyof AppRouteParams, Route>) => Object.entries(routes)
|
||||
.map(([name, route]) => (
|
||||
<Stack.Screen
|
||||
name={name as keyof AppRouteParams}
|
||||
options={route.options}
|
||||
component={RouteToScreen(route.component)}
|
||||
/>
|
||||
))
|
||||
|
||||
export const Router = () => {
|
||||
const accessToken = useSelector((state: RootState) => state.user.accessToken);
|
||||
@@ -53,11 +81,13 @@ export const Router = () => {
|
||||
<LoadingComponent/>
|
||||
</Center>
|
||||
}/>
|
||||
: userProfile.isSuccess && accessToken
|
||||
? protectedRoutes
|
||||
: publicRoutes
|
||||
: routesToScreens(userProfile.isSuccess && accessToken
|
||||
? protectedRoutes()
|
||||
: publicRoutes())
|
||||
}
|
||||
</Stack.Navigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export const useNavigation = () => navigationHook<NavigationProp<AppRouteParams>>();
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useNavigation } from "@react-navigation/core";
|
||||
import { useNavigation } from "../Navigation";
|
||||
import { HStack, VStack, Text, Progress } from "native-base";
|
||||
import { translate } from "../i18n/i18n";
|
||||
import Card from './Card';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { translate } from "../i18n/i18n";
|
||||
import { Box, useBreakpointValue, Text, VStack, Progress, Stack, AspectRatio } from 'native-base';
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { useNavigation } from "../Navigation";
|
||||
import { Pressable, Image } from "native-base";
|
||||
import Card from "../components/Card";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import Card, { CardBorderRadius } from './Card';
|
||||
import { VStack, Text, Image, Pressable } from 'native-base';
|
||||
import { useNavigation } from "@react-navigation/core";
|
||||
import { useNavigation } from "../Navigation";
|
||||
type SongCardProps = {
|
||||
albumCover: string;
|
||||
songTitle: string;
|
||||
|
||||
@@ -3,7 +3,8 @@ import { useQueries, useQuery } from "react-query";
|
||||
import API from "../API";
|
||||
import LoadingComponent from "../components/Loading";
|
||||
import { Center, Box, ScrollView, Flex, useBreakpointValue, Stack, Heading, Container, VStack, HStack } from 'native-base';
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
|
||||
import { useNavigation } from "../Navigation";
|
||||
import SongCardGrid from '../components/SongCardGrid';
|
||||
import CompetenciesTable from '../components/CompetenciesTable'
|
||||
import ProgressBar from "../components/ProgressBar";
|
||||
|
||||
@@ -4,7 +4,8 @@ import * as ScreenOrientation from 'expo-screen-orientation';
|
||||
import { Box, Center, Column, Progress, Text, Row, View, useToast, Icon } from 'native-base';
|
||||
import IconButton from '../components/IconButton';
|
||||
import { Ionicons, MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
|
||||
import { useNavigation } from "../Navigation";
|
||||
import { useQuery, useQueryClient } from 'react-query';
|
||||
import API from '../API';
|
||||
import LoadingComponent from '../components/Loading';
|
||||
@@ -32,8 +33,7 @@ if (process.env.NODE_ENV != 'development' && Platform.OS === 'web') {
|
||||
}
|
||||
}
|
||||
|
||||
const PlayView = () => {
|
||||
const songId = 1;
|
||||
const PlayView = ({ songId }: PlayViewProps) => {
|
||||
const navigation = useNavigation();
|
||||
const queryClient = useQueryClient();
|
||||
const song = useQuery(['song', songId], () => API.getSong(songId));
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { Dimensions, View } from 'react-native';
|
||||
import { Box, Image, Heading, HStack, Card, Button, Spacer, Text } from 'native-base';
|
||||
import Translate from '../components/Translate';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { useNavigation } from "../Navigation";
|
||||
import TextButton from '../components/TextButton';
|
||||
|
||||
const UserMedals = () => {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Card, Column, Image, Row, Text, useTheme, ScrollView, Center, VStack } from "native-base"
|
||||
import Translate from "../components/Translate";
|
||||
import SongCardGrid from "../components/SongCardGrid";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { useNavigation } from "../Navigation";
|
||||
import { CardBorderRadius } from "../components/Card";
|
||||
import TextButton from "../components/TextButton";
|
||||
import API from '../API';
|
||||
import { useQuery } from "react-query";
|
||||
import LoadingComponent from "../components/Loading";
|
||||
|
||||
const ScoreView = (/*{ songId }, { songId: number }*/) => {
|
||||
const ScoreView = ({ songId }: { songId: number }) => {
|
||||
const theme = useTheme();
|
||||
const navigation = useNavigation();
|
||||
// const songQuery = useQuery(['song', props.songId], () => API.getSong(props.songId));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from "react";
|
||||
import { Box } from "native-base";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { useNavigation } from "../Navigation";
|
||||
import SearchBarSuggestions from "../components/SearchBarSuggestions";
|
||||
import { useQueries, useQuery } from "react-query";
|
||||
import { SuggestionType } from "../components/SearchBar";
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useNavigation, useRoute } from "@react-navigation/native";
|
||||
import { Button, Divider, Box, Center, Image, Text, VStack, PresenceTransition, Icon } from "native-base";
|
||||
import { useQuery } from 'react-query';
|
||||
import LoadingComponent from "../components/Loading";
|
||||
@@ -8,16 +7,15 @@ import formatDuration from "format-duration";
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import API from "../API";
|
||||
import TextButton from "../components/TextButton";
|
||||
import { useNavigation } from "../Navigation";
|
||||
|
||||
interface SongLobbyProps {
|
||||
// The unique identifier to find a song
|
||||
songId: number;
|
||||
}
|
||||
|
||||
const SongLobbyView = () => {
|
||||
const route = useRoute();
|
||||
const SongLobbyView = (props: SongLobbyProps) => {
|
||||
const navigation = useNavigation();
|
||||
const props: SongLobbyProps = route.params as any;
|
||||
const songQuery = useQuery(['song', props.songId], () => API.getSong(props.songId));
|
||||
const chaptersQuery = useQuery(['song', props.songId, 'chapters'], () => API.getSongChapters(props.songId));
|
||||
const scoresQuery = useQuery(['song', props.songId, 'scores'], () => API.getSongHistory(props.songId));
|
||||
@@ -47,7 +45,7 @@ const SongLobbyView = () => {
|
||||
/>
|
||||
</Text>
|
||||
<TextButton translate={{ translationKey: 'playBtn' }} width='auto'
|
||||
onPress={() => navigation.navigate('Play', { songId: songQuery.data?.id })}
|
||||
onPress={() => navigation.navigate('Play', { songId: songQuery.data!.id })}
|
||||
rightIcon={<Icon as={Ionicons} name="play-outline"/>}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user