diff --git a/App.js b/App.js index 090893c..19d79b0 100644 --- a/App.js +++ b/App.js @@ -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 { 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 { 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 { 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 { 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} + */ 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 = () => ; - 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 = () => ; + 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(); } diff --git a/src/components/Sidebar/Sidebar.js b/src/components/Sidebar/Sidebar.js index 8368b14..44d9ea3 100644 --- a/src/components/Sidebar/Sidebar.js +++ b/src/components/Sidebar/Sidebar.js @@ -12,8 +12,7 @@ const deviceWidth = Dimensions.get("window").width; type Props = { navigation: Object, - state: Object, - theme: Object, + theme?: Object, }; type State = { diff --git a/src/components/Tabbar/CustomTabBar.js b/src/components/Tabbar/CustomTabBar.js index fa3a486..5b744aa 100644 --- a/src/components/Tabbar/CustomTabBar.js +++ b/src/components/Tabbar/CustomTabBar.js @@ -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 { 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 { 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 { } } + 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 index} + key={route.key} + /> + } else + return + }; + + render() { this.props.navigation.addListener('state', this.onRouteChange); return ( { 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 index} - key={route.key} - /> - } else - return - })} + {this.props.state.routes.map(this.renderIcon)} ); } diff --git a/src/navigation/DrawerNavigator.js b/src/navigation/DrawerNavigator.js index c982cbc..ec784d5 100644 --- a/src/navigation/DrawerNavigator.js +++ b/src/navigation/DrawerNavigator.js @@ -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 ( @@ -415,11 +415,9 @@ function ProfileStackComponent() { { - return { - title: i18n.t('screens.clubDisplayScreen'), - ...TransitionPresets.ModalSlideFromBottomIOS, - }; + options={{ + title: i18n.t('screens.clubDisplayScreen'), + ...TransitionPresets.ModalSlideFromBottomIOS, }} /> @@ -509,21 +507,17 @@ function ClubStackComponent() { { - return { - title: i18n.t('screens.clubDisplayScreen'), - ...TransitionPresets.ModalSlideFromBottomIOS, - }; + options={{ + title: i18n.t('screens.clubDisplayScreen'), + ...TransitionPresets.ModalSlideFromBottomIOS, }} /> { - return { - title: i18n.t('screens.clubsAbout'), - ...TransitionPresets.ModalSlideFromBottomIOS, - }; + options={{ + title: i18n.t('screens.clubsAbout'), + ...TransitionPresets.ModalSlideFromBottomIOS, }} /> @@ -533,27 +527,22 @@ function ClubStackComponent() { const Drawer = createDrawerNavigator(); -function getDrawerContent(props) { - return -} - type Props = { - defaultRoute: string | null, - defaultData: Object + defaultHomeRoute: string | null, + defaultHomeData: { [key: string]: any } } - export default class DrawerNavigator extends React.Component { - createTabNavigator: Object; + createTabNavigator: () => React.Element; - constructor(props: Object) { + constructor(props: Props) { super(props); - - this.createTabNavigator = () => + this.createTabNavigator = () => } + getDrawerContent = (props: { navigation: DrawerNavigationProp }) => + render() { return ( { headerMode={'float'} backBehavior={'initialRoute'} drawerType={'front'} - drawerContent={(props) => getDrawerContent(props)} + drawerContent={this.getDrawerContent} screenOptions={defaultScreenOptions} > @@ -147,7 +140,6 @@ function ProxiwashStackComponent() { const PlanningStack = createStackNavigator(); function PlanningStackComponent() { - const {colors} = useTheme(); return ( , { collapsedColor: 'transparent', @@ -222,31 +214,25 @@ function HomeStackComponent(initialRoute: string | null, defaultData: Object) { { - return { - title: i18n.t('screens.clubDisplayScreen'), - ...TransitionPresets.ModalSlideFromBottomIOS, - }; + options={{ + title: i18n.t('screens.clubDisplayScreen'), + ...TransitionPresets.ModalSlideFromBottomIOS, }} /> { - return { - title: i18n.t('screens.feedDisplayScreen'), - ...TransitionPresets.ModalSlideFromBottomIOS, - }; + options={{ + title: i18n.t('screens.feedDisplayScreen'), + ...TransitionPresets.ModalSlideFromBottomIOS, }} /> { - return { - title: i18n.t('screens.scanner'), - ...TransitionPresets.ModalSlideFromBottomIOS, - }; + options={{ + title: i18n.t('screens.scanner'), + ...TransitionPresets.ModalSlideFromBottomIOS, }} /> @@ -287,14 +273,12 @@ function PlanexStackComponent() { { - 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 { - - createHomeStackComponent: Object; +export default class TabNavigator extends React.Component { + 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 ( ({ - 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 => } > { ); } } - -export default withTheme(TabNavigator);