Improved flow typing and moved tab related options in component file

This commit is contained in:
Arnaud Vergnet 2020-04-19 16:59:40 +02:00
parent 759c369c93
commit 8c7ceb84fc
5 changed files with 195 additions and 197 deletions

111
App.js
View file

@ -11,7 +11,7 @@ import {NavigationContainer} from '@react-navigation/native';
import {createStackNavigator} from '@react-navigation/stack';
import DrawerNavigator from './src/navigation/DrawerNavigator';
import {initExpoToken} from "./src/utils/Notifications";
import {Provider as PaperProvider} from 'react-native-paper';
import {Provider as PaperProvider, Theme} from 'react-native-paper';
import AprilFoolsManager from "./src/managers/AprilFoolsManager";
import Update from "./src/constants/Update";
import ConnectionManager from "./src/managers/ConnectionManager";
@ -29,7 +29,7 @@ type State = {
showIntro: boolean,
showUpdate: boolean,
showAprilFools: boolean,
currentTheme: ?Object,
currentTheme: Theme | null,
};
const Stack = createStackNavigator();
@ -44,42 +44,58 @@ export default class App extends React.Component<Props, State> {
currentTheme: null,
};
navigatorRef: Object;
navigatorRef: { current: null | NavigationContainer };
defaultRoute: string | null;
defaultData: Object;
defaultHomeRoute: string | null;
defaultHomeData: { [key: string]: any }
createDrawerNavigator: Function;
createDrawerNavigator: () => React.Node;
urlHandler: URLHandler;
storageManager: AsyncStorageManager;
constructor() {
super();
LocaleManager.initTranslations();
SplashScreen.preventAutoHide();
this.navigatorRef = React.createRef();
this.defaultRoute = null;
this.defaultData = {};
this.defaultHomeRoute = null;
this.defaultHomeData = {};
this.storageManager = AsyncStorageManager.getInstance();
this.urlHandler = new URLHandler(this.onInitialURLParsed, this.onDetectURL);
this.urlHandler.listen();
setSafeBounceHeight(Platform.OS === 'ios' ? 100 : 20);
}
onInitialURLParsed = ({route, data}: Object) => {
this.defaultRoute = route;
this.defaultData = data;
};
onDetectURL = ({route, data}: Object) => {
// Navigate to nested navigator and pass data to the index screen
this.navigatorRef.current.navigate('home', {
screen: 'index',
params: {nextScreen: route, data: data}
});
/**
* THe app has been started by an url, and it has been parsed.
* Set a new default start route based on the data parsed.
*
* @param parsedData The data parsed from the url
*/
onInitialURLParsed = (parsedData: { route: string, data: { [key: string]: any } }) => {
this.defaultHomeRoute = parsedData.route;
this.defaultHomeData = parsedData.data;
};
/**
* Updates the theme
* An url has been opened and parsed while the app was active.
* Redirect the user to the screen according to parsed data.
*
* @param parsedData The data parsed from the url
*/
onDetectURL = (parsedData: { route: string, data: { [key: string]: any } }) => {
// Navigate to nested navigator and pass data to the index screen
if (this.navigatorRef.current != null) {
this.navigatorRef.current.navigate('home', {
screen: 'index',
params: {nextScreen: parsedData.route, data: parsedData.data}
});
}
};
/**
* Updates the current theme
*/
onUpdateTheme = () => {
this.setState({
@ -88,6 +104,10 @@ export default class App extends React.Component<Props, State> {
this.setupStatusBar();
};
/**
* Updates status bar content color if on iOS only,
* as the android status bar is always set to black.
*/
setupStatusBar() {
if (Platform.OS === 'ios') {
if (ThemeManager.getNightMode()) {
@ -96,12 +116,10 @@ export default class App extends React.Component<Props, State> {
StatusBar.setBarStyle('dark-content', true);
}
}
// StatusBar.setTranslucent(false);
// StatusBar.setBackgroundColor(ThemeManager.getCurrentTheme().colors.surface);
}
/**
* Callback when user ends the intro. Save in preferences to avaoid showing back the introSlides
* Callback when user ends the intro. Save in preferences to avoid showing back the introSlides
*/
onIntroDone = () => {
this.setState({
@ -109,45 +127,52 @@ export default class App extends React.Component<Props, State> {
showUpdate: false,
showAprilFools: false,
});
AsyncStorageManager.getInstance().savePref(AsyncStorageManager.getInstance().preferences.showIntro.key, '0');
AsyncStorageManager.getInstance().savePref(AsyncStorageManager.getInstance().preferences.updateNumber.key, Update.number.toString());
AsyncStorageManager.getInstance().savePref(AsyncStorageManager.getInstance().preferences.showAprilFoolsStart.key, '0');
this.storageManager.savePref(this.storageManager.preferences.showIntro.key, '0');
this.storageManager.savePref(this.storageManager.preferences.updateNumber.key, Update.number.toString());
this.storageManager.savePref(this.storageManager.preferences.showAprilFoolsStart.key, '0');
};
async componentDidMount() {
await this.loadAssetsAsync();
componentDidMount() {
this.loadAssetsAsync().then(() => {
this.onLoadFinished();
});
}
/**
* Loads every async data
*
* @returns {Promise<void>}
*/
async loadAssetsAsync() {
// Wait for custom fonts to be loaded before showing the app
await AsyncStorageManager.getInstance().loadPreferences();
ThemeManager.getInstance().setUpdateThemeCallback(this.onUpdateTheme);
await this.storageManager.loadPreferences();
await initExpoToken();
try {
await ConnectionManager.getInstance().recoverLogin();
} catch (e) {
}
this.createDrawerNavigator = () => <DrawerNavigator defaultRoute={this.defaultRoute}
defaultData={this.defaultData}/>;
this.onLoadFinished();
}
/**
* Async loading is done, finish processing startup data
*/
onLoadFinished() {
// console.log("finished");
// Only show intro if this is the first time starting the app
this.setState({
isLoading: false,
currentTheme: ThemeManager.getCurrentTheme(),
showIntro: AsyncStorageManager.getInstance().preferences.showIntro.current === '1',
showUpdate: AsyncStorageManager.getInstance().preferences.updateNumber.current !== Update.number.toString(),
showAprilFools: AprilFoolsManager.getInstance().isAprilFoolsEnabled() && AsyncStorageManager.getInstance().preferences.showAprilFoolsStart.current === '1',
});
this.createDrawerNavigator = () => <DrawerNavigator
defaultHomeRoute={this.defaultHomeRoute}
defaultHomeData={this.defaultHomeData}/>;
ThemeManager.getInstance().setUpdateThemeCallback(this.onUpdateTheme);
// Status bar goes dark if set too fast on ios
if (Platform.OS === 'ios')
setTimeout(this.setupStatusBar, 1000);
else
this.setupStatusBar();
this.setState({
isLoading: false,
currentTheme: ThemeManager.getCurrentTheme(),
showIntro: this.storageManager.preferences.showIntro.current === '1',
showUpdate: this.storageManager.preferences.updateNumber.current !== Update.number.toString(),
showAprilFools: AprilFoolsManager.getInstance().isAprilFoolsEnabled() && this.storageManager.preferences.showAprilFoolsStart.current === '1',
});
SplashScreen.hide();
}

View file

@ -12,8 +12,7 @@ const deviceWidth = Dimensions.get("window").width;
type Props = {
navigation: Object,
state: Object,
theme: Object,
theme?: Object,
};
type State = {

View file

@ -2,7 +2,6 @@ import * as React from 'react';
import {withTheme} from 'react-native-paper';
import TabIcon from "./TabIcon";
import TabHomeIcon from "./TabHomeIcon";
import {AnimatedValue} from "react-native-reanimated";
import {Animated} from 'react-native';
type Props = {
@ -17,6 +16,13 @@ type State = {
translateY: AnimatedValue,
}
const TAB_ICONS = {
planning: 'calendar-range',
proxiwash: 'tshirt-crew',
proximo: 'cart',
planex: 'clock',
};
/**
* Abstraction layer for Agenda component, using custom configuration
*/
@ -25,6 +31,8 @@ class CustomTabBar extends React.Component<Props, State> {
static TAB_BAR_HEIGHT = 48;
barSynced: boolean; // Is the bar synced with the header for animations?
activeColor: string;
inactiveColor: string;
state = {
translateY: new Animated.Value(0),
@ -37,10 +45,12 @@ class CustomTabBar extends React.Component<Props, State> {
tabRef: Object;
constructor() {
super();
constructor(props) {
super(props);
this.tabRef = React.createRef();
this.barSynced = false;
this.activeColor = props.theme.colors.primary;
this.inactiveColor = props.theme.colors.tabIcon;
}
onItemPress(route: Object, currentIndex: number, destIndex: number) {
@ -58,14 +68,73 @@ class CustomTabBar extends React.Component<Props, State> {
}
}
tabBarIcon = (route, focused) => {
let icon = TAB_ICONS[route.name];
icon = focused ? icon : icon + ('-outline');
if (route.name !== "home")
return icon;
else
return null;
};
onRouteChange = () => {
this.barSynced = false;
}
render() {
renderIcon = (route, index) => {
const state = this.props.state;
const descriptors = this.props.descriptors;
const navigation = this.props.navigation;
const {options} = this.props.descriptors[route.key];
const label =
options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: route.name;
const isFocused = state.index === index;
const onPress = () => this.onItemPress(route, state.index, index);
const onLongPress = () => {
this.props.navigation.emit({
type: 'tabLongPress',
target: route.key,
});
};
if (isFocused) {
const stackState = route.state;
const stackRoute = route.state ? stackState.routes[stackState.index] : undefined;
const params = stackRoute ? stackRoute.params : undefined;
const collapsible = params ? params.collapsible : undefined;
if (collapsible && !this.barSynced) {
this.barSynced = true;
this.setState({translateY: Animated.multiply(-1.5, collapsible.translateY)});
}
}
const color = isFocused ? this.activeColor : this.inactiveColor;
if (route.name !== "home") {
return <TabIcon
onPress={onPress}
onLongPress={onLongPress}
icon={this.tabBarIcon(route, isFocused)}
color={color}
label={label}
focused={isFocused}
extraData={state.index > index}
key={route.key}
/>
} else
return <TabHomeIcon
onPress={onPress}
onLongPress={onLongPress}
focused={isFocused}
key={route.key}
/>
};
render() {
this.props.navigation.addListener('state', this.onRouteChange);
return (
<Animated.View
@ -84,57 +153,7 @@ class CustomTabBar extends React.Component<Props, State> {
transform: [{translateY: this.state.translateY}]
}}
>
{state.routes.map((route, index) => {
const {options} = descriptors[route.key];
const label =
options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: route.name;
const isFocused = state.index === index;
const onPress = () => this.onItemPress(route, state.index, index);
const onLongPress = () => {
navigation.emit({
type: 'tabLongPress',
target: route.key,
});
};
if (isFocused) {
const stackState = route.state;
const stackRoute = route.state ? stackState.routes[stackState.index] : undefined;
const params = stackRoute ? stackRoute.params : undefined;
const collapsible = params ? params.collapsible : undefined;
if (collapsible && !this.barSynced) {
this.barSynced = true;
this.setState({translateY: Animated.multiply(-1.5, collapsible.translateY)});
}
}
const color = isFocused ? options.activeColor : options.inactiveColor;
const iconData = {focused: isFocused, color: color};
if (route.name !== "home") {
return <TabIcon
onPress={onPress}
onLongPress={onLongPress}
icon={options.tabBarIcon(iconData)}
color={color}
label={label}
focused={isFocused}
extraData={state.index > index}
key={route.key}
/>
} else
return <TabHomeIcon
onPress={onPress}
onLongPress={onLongPress}
focused={isFocused}
key={route.key}
/>
})}
{this.props.state.routes.map(this.renderIcon)}
</Animated.View>
);
}

View file

@ -1,7 +1,7 @@
// @flow
import * as React from 'react';
import {createDrawerNavigator} from '@react-navigation/drawer';
import {createDrawerNavigator, DrawerNavigationProp} from '@react-navigation/drawer';
import TabNavigator from './MainTabNavigator';
import SettingsScreen from '../screens/Other/SettingsScreen';
import AboutScreen from '../screens/About/AboutScreen';
@ -35,7 +35,7 @@ const defaultScreenOptions = {
...TransitionPresets.SlideFromRightIOS,
};
function getDrawerButton(navigation: Object) {
function getDrawerButton(navigation: DrawerNavigationProp) {
return (
<MaterialHeaderButtons left={true}>
<Item title="menu" iconName="menu" onPress={navigation.openDrawer}/>
@ -415,11 +415,9 @@ function ProfileStackComponent() {
<ClubStack.Screen
name="club-information"
component={ClubDisplayScreen}
options={({navigation}) => {
return {
title: i18n.t('screens.clubDisplayScreen'),
...TransitionPresets.ModalSlideFromBottomIOS,
};
options={{
title: i18n.t('screens.clubDisplayScreen'),
...TransitionPresets.ModalSlideFromBottomIOS,
}}
/>
</ProfileStack.Navigator>
@ -509,21 +507,17 @@ function ClubStackComponent() {
<ClubStack.Screen
name="club-information"
component={ClubDisplayScreen}
options={({navigation}) => {
return {
title: i18n.t('screens.clubDisplayScreen'),
...TransitionPresets.ModalSlideFromBottomIOS,
};
options={{
title: i18n.t('screens.clubDisplayScreen'),
...TransitionPresets.ModalSlideFromBottomIOS,
}}
/>
<ClubStack.Screen
name="club-about"
component={ClubAboutScreen}
options={({navigation}) => {
return {
title: i18n.t('screens.clubsAbout'),
...TransitionPresets.ModalSlideFromBottomIOS,
};
options={{
title: i18n.t('screens.clubsAbout'),
...TransitionPresets.ModalSlideFromBottomIOS,
}}
/>
</ClubStack.Navigator>
@ -533,27 +527,22 @@ function ClubStackComponent() {
const Drawer = createDrawerNavigator();
function getDrawerContent(props) {
return <Sidebar {...props}/>
}
type Props = {
defaultRoute: string | null,
defaultData: Object
defaultHomeRoute: string | null,
defaultHomeData: { [key: string]: any }
}
export default class DrawerNavigator extends React.Component<Props> {
createTabNavigator: Object;
createTabNavigator: () => React.Element<TabNavigator>;
constructor(props: Object) {
constructor(props: Props) {
super(props);
this.createTabNavigator = () => <TabNavigator defaultRoute={props.defaultRoute}
defaultData={props.defaultData}/>
this.createTabNavigator = () => <TabNavigator {...props}/>
}
getDrawerContent = (props: { navigation: DrawerNavigationProp }) => <Sidebar {...props}/>
render() {
return (
<Drawer.Navigator
@ -561,7 +550,7 @@ export default class DrawerNavigator extends React.Component<Props> {
headerMode={'float'}
backBehavior={'initialRoute'}
drawerType={'front'}
drawerContent={(props) => getDrawerContent(props)}
drawerContent={this.getDrawerContent}
screenOptions={defaultScreenOptions}
>
<Drawer.Screen

View file

@ -12,7 +12,7 @@ import ProximoListScreen from "../screens/Proximo/ProximoListScreen";
import ProximoAboutScreen from "../screens/Proximo/ProximoAboutScreen";
import PlanexScreen from '../screens/Planex/PlanexScreen';
import AsyncStorageManager from "../managers/AsyncStorageManager";
import {useTheme, withTheme} from 'react-native-paper';
import {useTheme} from 'react-native-paper';
import i18n from "i18n-js";
import ClubDisplayScreen from "../screens/Amicale/Clubs/ClubDisplayScreen";
import ScannerScreen from "../screens/Home/ScannerScreen";
@ -21,14 +21,7 @@ import FeedItemScreen from "../screens/Home/FeedItemScreen";
import {createCollapsibleStack} from "react-navigation-collapsible";
import GroupSelectionScreen from "../screens/Planex/GroupSelectionScreen";
import CustomTabBar from "../components/Tabbar/CustomTabBar";
const TAB_ICONS = {
home: 'triangle',
planning: 'calendar-range',
proxiwash: 'tshirt-crew',
proximo: 'cart',
planex: 'clock',
};
import {DrawerNavigationProp} from "@react-navigation/drawer";
const defaultScreenOptions = {
gestureEnabled: true,
@ -36,7 +29,7 @@ const defaultScreenOptions = {
...TransitionPresets.SlideFromRightIOS,
};
function getDrawerButton(navigation: Object) {
function getDrawerButton(navigation: DrawerNavigationProp) {
return (
<MaterialHeaderButtons left={true}>
<Item title="menu" iconName="menu" onPress={navigation.openDrawer}/>
@ -147,7 +140,6 @@ function ProxiwashStackComponent() {
const PlanningStack = createStackNavigator();
function PlanningStackComponent() {
const {colors} = useTheme();
return (
<PlanningStack.Navigator
initialRouteName="index"
@ -179,10 +171,10 @@ function PlanningStackComponent() {
const HomeStack = createStackNavigator();
function HomeStackComponent(initialRoute: string | null, defaultData: Object) {
let data;
if (initialRoute !== null)
data = {data: defaultData, nextScreen: initialRoute, shouldOpen: true};
function HomeStackComponent(initialRoute: string | null, defaultData: { [key: string]: any }) {
let params = undefined;
if (initialRoute != null)
params = {data: defaultData, nextScreen: initialRoute, shouldOpen: true};
const {colors} = useTheme();
return (
<HomeStack.Navigator
@ -204,7 +196,7 @@ function HomeStackComponent(initialRoute: string | null, defaultData: Object) {
},
};
}}
initialParams={data}
initialParams={params}
/>,
{
collapsedColor: 'transparent',
@ -222,31 +214,25 @@ function HomeStackComponent(initialRoute: string | null, defaultData: Object) {
<HomeStack.Screen
name="home-club-information"
component={ClubDisplayScreen}
options={({navigation}) => {
return {
title: i18n.t('screens.clubDisplayScreen'),
...TransitionPresets.ModalSlideFromBottomIOS,
};
options={{
title: i18n.t('screens.clubDisplayScreen'),
...TransitionPresets.ModalSlideFromBottomIOS,
}}
/>
<HomeStack.Screen
name="feed-information"
component={FeedItemScreen}
options={({navigation}) => {
return {
title: i18n.t('screens.feedDisplayScreen'),
...TransitionPresets.ModalSlideFromBottomIOS,
};
options={{
title: i18n.t('screens.feedDisplayScreen'),
...TransitionPresets.ModalSlideFromBottomIOS,
}}
/>
<HomeStack.Screen
name="scanner"
component={ScannerScreen}
options={({navigation}) => {
return {
title: i18n.t('screens.scanner'),
...TransitionPresets.ModalSlideFromBottomIOS,
};
options={{
title: i18n.t('screens.scanner'),
...TransitionPresets.ModalSlideFromBottomIOS,
}}
/>
</HomeStack.Navigator>
@ -287,14 +273,12 @@ function PlanexStackComponent() {
<PlanexStack.Screen
name="group-select"
component={GroupSelectionScreen}
options={({navigation}) => {
return {
title: 'GroupSelectionScreen',
headerStyle: {
backgroundColor: colors.surface,
},
...TransitionPresets.ModalSlideFromBottomIOS,
};
options={{
title: 'GroupSelectionScreen',
headerStyle: {
backgroundColor: colors.surface,
},
...TransitionPresets.ModalSlideFromBottomIOS,
}}
/>,
{
@ -309,44 +293,28 @@ function PlanexStackComponent() {
const Tab = createBottomTabNavigator();
type Props = {
defaultRoute: string | null,
defaultData: Object
defaultHomeRoute: string | null,
defaultHomeData: { [key: string]: any }
}
class TabNavigator extends React.Component<Props> {
createHomeStackComponent: Object;
export default class TabNavigator extends React.Component<Props> {
createHomeStackComponent: () => HomeStackComponent;
defaultRoute: string;
constructor(props) {
super(props);
this.defaultRoute = AsyncStorageManager.getInstance().preferences.defaultStartScreen.current.toLowerCase();
if (props.defaultRoute !== null)
if (props.defaultHomeRoute != null)
this.defaultRoute = 'home';
this.createHomeStackComponent = () => HomeStackComponent(props.defaultRoute, props.defaultData);
else
this.defaultRoute = AsyncStorageManager.getInstance().preferences.defaultStartScreen.current.toLowerCase();
this.createHomeStackComponent = () => HomeStackComponent(props.defaultHomeRoute, props.defaultHomeData);
}
render() {
return (
<Tab.Navigator
initialRouteName={this.defaultRoute}
barStyle={{backgroundColor: this.props.theme.colors.surface, overflow: 'visible'}}
screenOptions={({route}) => ({
tabBarIcon: ({focused, color}) => {
let icon = TAB_ICONS[route.name];
icon = focused ? icon : icon + ('-outline');
if (route.name !== "home")
return icon;
else
return null;
},
tabBarLabel: route.name !== 'home' ? undefined : '',
activeColor: this.props.theme.colors.primary,
inactiveColor: this.props.theme.colors.tabIcon,
})}
tabBar={props => <CustomTabBar {...props} />}
>
<Tab.Screen
@ -379,5 +347,3 @@ class TabNavigator extends React.Component<Props> {
);
}
}
export default withTheme(TabNavigator);