diff --git a/App.tsx b/App.tsx
index 234895d..53ecbfd 100644
--- a/App.tsx
+++ b/App.tsx
@@ -37,6 +37,7 @@ import { setupStatusBar } from './src/utils/Utils';
import initLocales from './src/utils/Locales';
import { NavigationContainerRef } from '@react-navigation/core';
import GENERAL_STYLES from './src/constants/Styles';
+import CollapsibleProvider from './src/components/providers/CollapsibleProvider';
// Native optimizations https://reactnavigation.org/docs/react-native-screens
// Crashes app when navigating away from webview on android 9+
@@ -210,26 +211,29 @@ export default class App extends React.Component<{}, StateType> {
}
return (
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/package-lock.json b/package-lock.json
index 7577f26..dea2b78 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3931,7 +3931,6 @@
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz",
"integrity": "sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==",
- "dev": true,
"requires": {
"node-fetch": "2.6.1"
}
@@ -10596,14 +10595,39 @@
"integrity": "sha512-beZjdgbT9Y/Pg591Xy5XkKG20HffJiVad4n9bfcUF/f783A+tvOVXnqvbS58Lkaym93mi4jcDPMuW9Vc1t6rqg=="
},
"react-native-gesture-handler": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.8.0.tgz",
- "integrity": "sha512-E2FZa0qZ5Bi0Z8Jg4n9DaFomHvedSjwbO2DPmUUHYRy1lH2yxXUpSrqJd6yymu+Efzmjg2+JZzsjFYA2Iq8VEQ==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.10.3.tgz",
+ "integrity": "sha512-cBGMi1IEsIVMgoox4RvMx7V2r6bNKw0uR1Mu1o7NbuHS6BRSVLq0dP34l2ecnPlC+jpWd3le6Yg1nrdCjby2Mw==",
"requires": {
"@egjs/hammerjs": "^2.0.17",
+ "fbjs": "^3.0.0",
"hoist-non-react-statics": "^3.3.0",
"invariant": "^2.2.4",
"prop-types": "^15.7.2"
+ },
+ "dependencies": {
+ "fbjs": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.0.tgz",
+ "integrity": "sha512-dJd4PiDOFuhe7vk4F80Mba83Vr2QuK86FoxtgPmzBqEJahncp+13YCmfoa53KHCo6OnlXLG7eeMWPfB5CrpVKg==",
+ "requires": {
+ "cross-fetch": "^3.0.4",
+ "fbjs-css-vars": "^1.0.0",
+ "loose-envify": "^1.0.0",
+ "object-assign": "^4.1.0",
+ "promise": "^7.1.1",
+ "setimmediate": "^1.0.5",
+ "ua-parser-js": "^0.7.18"
+ }
+ },
+ "promise": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
+ "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
+ "requires": {
+ "asap": "~2.0.3"
+ }
+ }
}
},
"react-native-image-pan-zoom": {
@@ -10871,11 +10895,12 @@
}
},
"react-navigation-collapsible": {
- "version": "5.6.4",
- "resolved": "https://registry.npmjs.org/react-navigation-collapsible/-/react-navigation-collapsible-5.6.4.tgz",
- "integrity": "sha512-dXMbDw2TQ6s5XLk9h+2hUShXoS8KPChfdh/xmmLqfKmntS5YteE01+x78gU5KogB3etDraH1kvhW7xDnbG9AfA==",
+ "version": "5.9.1",
+ "resolved": "https://registry.npmjs.org/react-navigation-collapsible/-/react-navigation-collapsible-5.9.1.tgz",
+ "integrity": "sha512-yUwHe8Z7++A8ThrjPI+Mcm7LqBhIqJc+1F4XszpI7EoHz3bJElzczbfyfuEvjSbYU9AgW3MdBWzaRIDluxcEuA==",
"requires": {
- "react-native-iphone-x-helper": "^1.2.1"
+ "react-native-iphone-x-helper": "^1.3.0",
+ "shallowequal": "^1.1.0"
}
},
"react-navigation-header-buttons": {
@@ -11453,6 +11478,11 @@
"kind-of": "^6.0.2"
}
},
+ "shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
+ },
"shebang-command": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
diff --git a/package.json b/package.json
index f8eedc3..ed2a768 100644
--- a/package.json
+++ b/package.json
@@ -53,7 +53,7 @@
"react-native-splash-screen": "3.2.0",
"react-native-vector-icons": "8.1.0",
"react-native-webview": "11.4.3",
- "react-navigation-collapsible": "5.6.4",
+ "react-navigation-collapsible": "5.9.1",
"react-navigation-header-buttons": "7.0.1"
},
"devDependencies": {
diff --git a/src/components/Animations/AnimatedBottomBar.tsx b/src/components/Animations/AnimatedBottomBar.tsx
index 5237a0c..600526a 100644
--- a/src/components/Animations/AnimatedBottomBar.tsx
+++ b/src/components/Animations/AnimatedBottomBar.tsx
@@ -28,7 +28,7 @@ import { FAB, IconButton, Surface, withTheme } from 'react-native-paper';
import * as Animatable from 'react-native-animatable';
import { StackNavigationProp } from '@react-navigation/stack';
import AutoHideHandler from '../../utils/AutoHideHandler';
-import CustomTabBar from '../Tabbar/CustomTabBar';
+import CustomTabBar, { TAB_BAR_HEIGHT } from '../Tabbar/CustomTabBar';
const AnimatedFAB = Animatable.createAnimatableComponent(FAB);
@@ -159,7 +159,7 @@ class AnimatedBottomBar extends React.Component {
useNativeDriver
style={{
...styles.container,
- bottom: 10 + CustomTabBar.TAB_BAR_HEIGHT,
+ bottom: 10 + TAB_BAR_HEIGHT,
}}
>
diff --git a/src/components/Animations/AnimatedFAB.tsx b/src/components/Animations/AnimatedFAB.tsx
index 63b3174..debd674 100644
--- a/src/components/Animations/AnimatedFAB.tsx
+++ b/src/components/Animations/AnimatedFAB.tsx
@@ -27,7 +27,7 @@ import {
import { FAB } from 'react-native-paper';
import * as Animatable from 'react-native-animatable';
import AutoHideHandler from '../../utils/AutoHideHandler';
-import CustomTabBar from '../Tabbar/CustomTabBar';
+import CustomTabBar, { TAB_BAR_HEIGHT } from '../Tabbar/CustomTabBar';
type PropsType = {
icon: string;
@@ -82,7 +82,7 @@ export default class AnimatedFAB extends React.Component {
useNativeDriver={true}
style={{
...styles.fab,
- bottom: CustomTabBar.TAB_BAR_HEIGHT,
+ bottom: TAB_BAR_HEIGHT,
}}
>
diff --git a/src/components/Collapsible/CollapsibleComponent.tsx b/src/components/Collapsible/CollapsibleComponent.tsx
index f34d55a..5b2b59d 100644
--- a/src/components/Collapsible/CollapsibleComponent.tsx
+++ b/src/components/Collapsible/CollapsibleComponent.tsx
@@ -17,22 +17,27 @@
* along with Campus INSAT. If not, see .
*/
-import * as React from 'react';
-import { useCollapsibleStack } from 'react-navigation-collapsible';
-import CustomTabBar from '../Tabbar/CustomTabBar';
+import React, { useCallback } from 'react';
+import { useCollapsibleHeader } from 'react-navigation-collapsible';
+import { TAB_BAR_HEIGHT } from '../Tabbar/CustomTabBar';
import {
NativeScrollEvent,
NativeSyntheticEvent,
StyleSheet,
} from 'react-native';
+import { useTheme } from 'react-native-paper';
+import { useCollapsible } from '../../utils/CollapsibleContext';
+import { useFocusEffect } from '@react-navigation/core';
export type CollapsibleComponentPropsType = {
children?: React.ReactNode;
hasTab?: boolean;
onScroll?: (event: NativeSyntheticEvent) => void;
+ paddedProps?: (paddingTop: number) => Record;
+ headerColors: string;
};
-type PropsType = CollapsibleComponentPropsType & {
+type Props = CollapsibleComponentPropsType & {
component: React.ComponentType;
};
@@ -42,22 +47,46 @@ const styles = StyleSheet.create({
},
});
-function CollapsibleComponent(props: PropsType) {
+function CollapsibleComponent(props: Props) {
+ const { paddedProps, headerColors } = props;
+ const Comp = props.component;
+ const theme = useTheme();
+ const { setCollapsible } = useCollapsible();
+
+ const collapsible = useCollapsibleHeader({
+ config: {
+ collapsedColor: headerColors ? headerColors : theme.colors.surface,
+ useNativeDriver: true,
+ },
+ });
+
+ useFocusEffect(
+ useCallback(() => {
+ setCollapsible(collapsible);
+ }, [collapsible, setCollapsible])
+ );
+
+ const {
+ containerPaddingTop,
+ scrollIndicatorInsetTop,
+ onScrollWithListener,
+ } = collapsible;
+
+ const paddingBottom = props.hasTab ? TAB_BAR_HEIGHT : 0;
+
const onScroll = (event: NativeSyntheticEvent) => {
if (props.onScroll) {
props.onScroll(event);
}
};
- const Comp = props.component;
- const {
- containerPaddingTop,
- scrollIndicatorInsetTop,
- onScrollWithListener,
- } = useCollapsibleStack();
- const paddingBottom = props.hasTab ? CustomTabBar.TAB_BAR_HEIGHT : 0;
+
+ const pprops =
+ paddedProps !== undefined ? paddedProps(containerPaddingTop) : undefined;
+
return (
{children}
diff --git a/src/components/Screens/WebSectionList.tsx b/src/components/Screens/WebSectionList.tsx
index 9abd227..b061d18 100644
--- a/src/components/Screens/WebSectionList.tsx
+++ b/src/components/Screens/WebSectionList.tsx
@@ -32,10 +32,10 @@ import { Collapsible } from 'react-navigation-collapsible';
import { StackNavigationProp } from '@react-navigation/stack';
import ErrorView from './ErrorView';
import BasicLoadingScreen from './BasicLoadingScreen';
-import withCollapsible from '../../utils/withCollapsible';
-import CustomTabBar from '../Tabbar/CustomTabBar';
+import { TAB_BAR_HEIGHT } from '../Tabbar/CustomTabBar';
import { ERROR_TYPE, readData } from '../../utils/WebData';
import CollapsibleSectionList from '../Collapsible/CollapsibleSectionList';
+import GENERAL_STYLES from '../../constants/Styles';
export type SectionListDataType = Array<{
title: string;
@@ -260,19 +260,20 @@ class WebSectionList extends React.PureComponent<
dataset = props.createDataset(state.fetchedData, state.refreshing);
}
- const { containerPaddingTop } = props.collapsibleStack;
return (
-
+
- }
+ paddedProps={(paddingTop) => ({
+ refreshControl: (
+
+ ),
+ })}
renderSectionHeader={this.getRenderSectionHeader}
renderItem={this.getRenderItem}
stickySectionHeadersEnabled={props.stickyHeader}
@@ -299,7 +300,7 @@ class WebSectionList extends React.PureComponent<
: undefined
}
onScroll={this.onScroll}
- hasTab
+ hasTab={true}
/>
extends React.PureComponent<
}}
duration={4000}
style={{
- bottom: CustomTabBar.TAB_BAR_HEIGHT,
+ bottom: TAB_BAR_HEIGHT,
}}
>
{i18n.t('general.listUpdateFail')}
@@ -320,4 +321,4 @@ class WebSectionList extends React.PureComponent<
}
}
-export default withCollapsible(WebSectionList);
+export default WebSectionList;
diff --git a/src/components/Screens/WebViewScreen.tsx b/src/components/Screens/WebViewScreen.tsx
index 38cef0e..bcae42f 100644
--- a/src/components/Screens/WebViewScreen.tsx
+++ b/src/components/Screens/WebViewScreen.tsx
@@ -17,7 +17,13 @@
* along with Campus INSAT. If not, see .
*/
-import * as React from 'react';
+import React, {
+ useCallback,
+ useEffect,
+ useLayoutEffect,
+ useRef,
+ useState,
+} from 'react';
import WebView from 'react-native-webview';
import {
Divider,
@@ -34,23 +40,21 @@ import {
StyleSheet,
} from 'react-native';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
-import { withTheme } from 'react-native-paper';
-import { StackNavigationProp } from '@react-navigation/stack';
-import { Collapsible } from 'react-navigation-collapsible';
-import withCollapsible from '../../utils/withCollapsible';
+import { useTheme } from 'react-native-paper';
+import { useCollapsibleHeader } from 'react-navigation-collapsible';
import MaterialHeaderButtons, { Item } from '../Overrides/CustomHeaderButton';
import { ERROR_TYPE } from '../../utils/WebData';
import ErrorView from './ErrorView';
import BasicLoadingScreen from './BasicLoadingScreen';
+import { useFocusEffect, useNavigation } from '@react-navigation/core';
+import { useCollapsible } from '../../utils/CollapsibleContext';
-type PropsType = {
- navigation: StackNavigationProp;
- theme: ReactNativePaper.Theme;
+type Props = {
url: string;
- collapsibleStack: Collapsible;
- onMessage: (event: { nativeEvent: { data: string } }) => void;
- onScroll: (event: NativeSyntheticEvent) => void;
- customJS?: string;
+ onMessage?: (event: { nativeEvent: { data: string } }) => void;
+ onScroll?: (event: NativeSyntheticEvent) => void;
+ initialJS?: string;
+ injectJS?: string;
customPaddingFunction?: null | ((padding: number) => string);
showAdvancedControls?: boolean;
};
@@ -66,134 +70,113 @@ const styles = StyleSheet.create({
/**
* Class defining a webview screen.
*/
-class WebViewScreen extends React.PureComponent {
- static defaultProps = {
- customJS: '',
- showAdvancedControls: true,
- customPaddingFunction: null,
- };
+function WebViewScreen(props: Props) {
+ const [currentUrl, setCurrentUrl] = useState(props.url);
+ const [canGoBack, setCanGoBack] = useState(false);
+ const navigation = useNavigation();
+ const theme = useTheme();
+ const webviewRef = useRef();
- currentUrl: string;
+ const { setCollapsible } = useCollapsible();
+ const collapsible = useCollapsibleHeader({
+ config: { collapsedColor: theme.colors.surface, useNativeDriver: false },
+ });
+ const { containerPaddingTop, onScrollWithListener } = collapsible;
- webviewRef: { current: null | WebView };
+ const [currentInjectedJS, setCurrentInjectedJS] = useState(props.injectJS);
- canGoBack: boolean;
-
- constructor(props: PropsType) {
- super(props);
- this.webviewRef = React.createRef();
- this.canGoBack = false;
- this.currentUrl = props.url;
- }
-
- /**
- * Creates header buttons and listens to events after mounting
- */
- componentDidMount() {
- const { props } = this;
- props.navigation.setOptions({
- headerRight: props.showAdvancedControls
- ? this.getAdvancedButtons
- : this.getBasicButton,
- });
- props.navigation.addListener('focus', () => {
+ useFocusEffect(
+ useCallback(() => {
+ setCollapsible(collapsible);
BackHandler.addEventListener(
'hardwareBackPress',
- this.onBackButtonPressAndroid
+ onBackButtonPressAndroid
);
- });
- props.navigation.addListener('blur', () => {
- BackHandler.removeEventListener(
- 'hardwareBackPress',
- this.onBackButtonPressAndroid
- );
- });
- }
+ return () => {
+ BackHandler.removeEventListener(
+ 'hardwareBackPress',
+ onBackButtonPressAndroid
+ );
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [collapsible, setCollapsible])
+ );
- /**
- * Goes back on the webview or on the navigation stack if we cannot go back anymore
- *
- * @returns {boolean}
- */
- onBackButtonPressAndroid = (): boolean => {
- if (this.canGoBack) {
- this.onGoBackClicked();
+ useLayoutEffect(() => {
+ navigation.setOptions({
+ headerRight: props.showAdvancedControls
+ ? getAdvancedButtons
+ : getBasicButton,
+ });
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [navigation, props.showAdvancedControls]);
+
+ useEffect(() => {
+ if (props.injectJS && props.injectJS !== currentInjectedJS) {
+ injectJavaScript(props.injectJS);
+ setCurrentInjectedJS(props.injectJS);
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [props.injectJS]);
+
+ const onBackButtonPressAndroid = () => {
+ if (canGoBack) {
+ onGoBackClicked();
return true;
}
return false;
};
- /**
- * Gets header refresh and open in browser buttons
- *
- * @return {*}
- */
- getBasicButton = () => {
+ const getBasicButton = () => {
return (
);
};
- /**
- * Creates advanced header control buttons.
- * These buttons allows the user to refresh, go back, go forward and open in the browser.
- *
- * @returns {*}
- */
- getAdvancedButtons = () => {
- const { props } = this;
+ const getAdvancedButtons = () => {
return (
-
+
}
>
);
};
- /**
- * Gets the loading indicator
- *
- * @return {*}
- */
- getRenderLoading = () => ;
+ const getRenderLoading = () => ;
/**
* Gets the javascript needed to generate a padding on top of the page
@@ -202,91 +185,78 @@ class WebViewScreen extends React.PureComponent {
* @param padding The padding to add in pixels
* @returns {string}
*/
- getJavascriptPadding(padding: number): string {
- const { props } = this;
+ const getJavascriptPadding = (padding: number) => {
const customPadding =
props.customPaddingFunction != null
? props.customPaddingFunction(padding)
: '';
return `document.getElementsByTagName('body')[0].style.paddingTop = '${padding}px';${customPadding}true;`;
- }
+ };
- /**
- * Callback to use when refresh button is clicked. Reloads the webview.
- */
- onRefreshClicked = () => {
- if (this.webviewRef.current != null) {
- this.webviewRef.current.reload();
+ const onRefreshClicked = () => {
+ //@ts-ignore
+ if (webviewRef.current) {
+ //@ts-ignore
+ webviewRef.current.reload();
}
};
- onGoBackClicked = () => {
- if (this.webviewRef.current != null) {
- this.webviewRef.current.goBack();
+ const onGoBackClicked = () => {
+ //@ts-ignore
+ if (webviewRef.current) {
+ //@ts-ignore
+ webviewRef.current.goBack();
}
};
- onGoForwardClicked = () => {
- if (this.webviewRef.current != null) {
- this.webviewRef.current.goForward();
+ const onGoForwardClicked = () => {
+ //@ts-ignore
+ if (webviewRef.current) {
+ //@ts-ignore
+ webviewRef.current.goForward();
}
};
- onOpenClicked = () => {
- Linking.openURL(this.currentUrl);
- };
+ const onOpenClicked = () => Linking.openURL(currentUrl);
- onScroll = (event: NativeSyntheticEvent) => {
- const { onScroll } = this.props;
- if (onScroll) {
- onScroll(event);
+ const onScroll = (event: NativeSyntheticEvent) => {
+ if (props.onScroll) {
+ props.onScroll(event);
}
};
- /**
- * Injects the given javascript string into the web page
- *
- * @param script The script to inject
- */
- injectJavaScript = (script: string) => {
- if (this.webviewRef.current != null) {
- this.webviewRef.current.injectJavaScript(script);
+ const injectJavaScript = (script: string) => {
+ //@ts-ignore
+ if (webviewRef.current) {
+ //@ts-ignore
+ webviewRef.current.injectJavaScript(script);
}
};
- render() {
- const { props } = this;
- const {
- containerPaddingTop,
- onScrollWithListener,
- } = props.collapsibleStack;
- return (
- (
-
- )}
- onNavigationStateChange={(navState) => {
- this.currentUrl = navState.url;
- this.canGoBack = navState.canGoBack;
- }}
- onMessage={props.onMessage}
- onLoad={() => {
- this.injectJavaScript(this.getJavascriptPadding(containerPaddingTop));
- }}
- // Animations
- onScroll={(event) => onScrollWithListener(this.onScroll)(event)}
- />
- );
- }
+ return (
+ (
+
+ )}
+ onNavigationStateChange={(navState) => {
+ setCurrentUrl(navState.url);
+ setCanGoBack(navState.canGoBack);
+ }}
+ onMessage={props.onMessage}
+ onLoad={() => injectJavaScript(getJavascriptPadding(containerPaddingTop))}
+ // Animations
+ onScroll={onScrollWithListener(onScroll)}
+ />
+ );
}
-export default withCollapsible(withTheme(WebViewScreen));
+export default WebViewScreen;
diff --git a/src/components/Tabbar/CustomTabBar.tsx b/src/components/Tabbar/CustomTabBar.tsx
index 5757de1..11748a3 100644
--- a/src/components/Tabbar/CustomTabBar.tsx
+++ b/src/components/Tabbar/CustomTabBar.tsx
@@ -17,211 +17,88 @@
* along with Campus INSAT. If not, see .
*/
-import * as React from 'react';
+import React from 'react';
+import type { BottomTabBarProps } from '@react-navigation/bottom-tabs';
import { Animated, StyleSheet } from 'react-native';
-import { withTheme } from 'react-native-paper';
-import { Collapsible } from 'react-navigation-collapsible';
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';
+import { useTheme } from 'react-native-paper';
+import { useCollapsible } from '../../utils/CollapsibleContext';
-type RouteType = Route & {
- state?: NavigationState | PartialState;
-};
+export const TAB_BAR_HEIGHT = 50;
-interface PropsType extends BottomTabBarProps {
- theme: ReactNativePaper.Theme;
+function CustomTabBar(
+ props: BottomTabBarProps & {
+ icons: {
+ [key: string]: {
+ normal: string;
+ focused: string;
+ };
+ };
+ labels: {
+ [key: string]: string;
+ };
+ }
+) {
+ const state = props.state;
+ const theme = useTheme();
+
+ const { collapsible } = useCollapsible();
+ let translateY: number | Animated.AnimatedInterpolation = 0;
+ if (collapsible) {
+ translateY = Animated.multiply(-1.5, collapsible.translateY);
+ }
+
+ return (
+
+ {state.routes.map(
+ (
+ route: {
+ key: string;
+ name: string;
+ params?: object | undefined;
+ },
+ index: number
+ ) => {
+ const iconData = props.icons[route.name];
+ return (
+ props.navigation.navigate(route.name)}
+ icon={iconData.normal}
+ focusedIcon={iconData.focused}
+ label={props.labels[route.name]}
+ focused={state.index === index}
+ key={route.key}
+ />
+ );
+ }
+ )}
+
+ );
}
-type StateType = {
- translateY: any;
-};
-
-type validRoutes = 'proxiwash' | 'services' | 'planning' | 'planex';
-
-const TAB_ICONS = {
- proxiwash: 'tshirt-crew',
- services: 'account-circle',
- planning: 'calendar-range',
- planex: 'clock',
-};
-
const styles = StyleSheet.create({
- container: {
+ bar: {
flexDirection: 'row',
width: '100%',
+ height: 50,
position: 'absolute',
bottom: 0,
left: 0,
},
});
-class CustomTabBar extends React.Component {
- static TAB_BAR_HEIGHT = 48;
-
- constructor(props: PropsType) {
- super(props);
- this.state = {
- translateY: new Animated.Value(0),
- };
- // @ts-ignore
- props.navigation.addListener('state', this.onRouteChange);
- }
-
- /**
- * Navigates to the given route if it is different from the current one
- *
- * @param route Destination route
- * @param currentIndex The current route index
- * @param destIndex The destination route index
- */
- onItemPress(route: RouteType, currentIndex: number, destIndex: number) {
- const { navigation } = this.props;
- if (currentIndex !== destIndex) {
- navigation.navigate(route.name);
- }
- }
-
- /**
- * Navigates to tetris screen on home button long press
- *
- * @param route
- */
- onItemLongPress(route: RouteType) {
- const { navigation } = this.props;
- if (route.name === 'home') {
- navigation.navigate('game-start');
- }
- }
-
- /**
- * Finds the active route and syncs the tab bar animation with the header bar
- */
- onRouteChange = () => {
- const { props } = this;
- props.state.routes.map(this.syncTabBar);
- };
-
- /**
- * Gets an icon for the given route if it is not the home one as it uses a custom button
- *
- * @param route
- * @param focused
- * @returns {null}
- */
- getTabBarIcon = (route: RouteType, focused: boolean) => {
- let icon = TAB_ICONS[route.name as validRoutes];
- icon = focused ? icon : `${icon}-outline`;
- if (route.name !== 'home') {
- return icon;
- }
- return '';
- };
-
- /**
- * Gets a tab icon render.
- * If the given route is focused, it syncs the tab bar and header bar animations together
- *
- * @param route The route for the icon
- * @param index The index of the current route
- * @returns {*}
- */
- getRenderIcon = (route: RouteType, index: number) => {
- const { props } = this;
- const { state } = props;
- const { options } = props.descriptors[route.key];
- let label;
- if (options.tabBarLabel != null) {
- label = options.tabBarLabel;
- } else if (options.title != null) {
- label = options.title;
- } else {
- label = route.name;
- }
-
- const onPress = () => {
- this.onItemPress(route, state.index, index);
- };
- const onLongPress = () => {
- this.onItemLongPress(route);
- };
- const isFocused = state.index === index;
-
- const color = isFocused
- ? props.theme.colors.primary
- : props.theme.colors.tabIcon;
- if (route.name !== 'home') {
- return (
- index}
- key={route.key}
- />
- );
- }
- return (
-
- );
- };
-
- getIcons() {
- const { props } = this;
- return props.state.routes.map(this.getRenderIcon);
- }
-
- syncTabBar = (route: RouteType, index: number) => {
- const { state } = this.props;
- const isFocused = state.index === index;
- if (isFocused) {
- const stackState = route.state;
- const stackRoute =
- stackState && stackState.index != null
- ? 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({
- translateY: Animated.multiply(-1.5, collapsible.translateY), // Hide tab bar faster than header bar
- });
- }
- }
- };
-
- render() {
- const { props, state } = this;
- const icons = this.getIcons();
- return (
-
- {icons}
-
- );
- }
+function areEqual(
+ prevProps: BottomTabBarProps,
+ nextProps: BottomTabBarProps
+) {
+ return prevProps.state.index === nextProps.state.index;
}
-export default withTheme(CustomTabBar);
+export default React.memo(CustomTabBar, areEqual);
diff --git a/src/components/Tabbar/TabHomeIcon.tsx b/src/components/Tabbar/TabHomeIcon.tsx
index d5c2614..a5e41d5 100644
--- a/src/components/Tabbar/TabHomeIcon.tsx
+++ b/src/components/Tabbar/TabHomeIcon.tsx
@@ -1,135 +1,114 @@
-/*
- * Copyright (c) 2019 - 2020 Arnaud Vergnet.
- *
- * This file is part of Campus INSAT.
- *
- * Campus INSAT is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Campus INSAT is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Campus INSAT. If not, see .
- */
-
-import * as React from 'react';
-import { Image, StyleSheet, View } from 'react-native';
+import React from 'react';
+import { View, StyleSheet, Image } from 'react-native';
import { FAB } from 'react-native-paper';
import * as Animatable from 'react-native-animatable';
-const FOCUSED_ICON = require('../../../assets/tab-icon.png');
-const UNFOCUSED_ICON = require('../../../assets/tab-icon-outline.png');
-type PropsType = {
+interface Props {
+ icon: string;
+ focusedIcon: string;
focused: boolean;
onPress: () => void;
- onLongPress: () => void;
- tabBarHeight: number;
-};
+}
-const AnimatedFAB = Animatable.createAnimatableComponent(FAB);
+Animatable.initializeRegistryWithDefinitions({
+ fabFocusIn: {
+ 0: {
+ // @ts-ignore
+ scale: 1,
+ translateY: 0,
+ },
+ 0.4: {
+ // @ts-ignore
+ scale: 1.2,
+ translateY: -9,
+ },
+ 0.6: {
+ // @ts-ignore
+ scale: 1.05,
+ translateY: -6,
+ },
+ 0.8: {
+ // @ts-ignore
+ scale: 1.15,
+ translateY: -6,
+ },
+ 1: {
+ // @ts-ignore
+ scale: 1.1,
+ translateY: -6,
+ },
+ },
+ fabFocusOut: {
+ 0: {
+ // @ts-ignore
+ scale: 1.1,
+ translateY: -6,
+ },
+ 1: {
+ // @ts-ignore
+ scale: 1,
+ translateY: 0,
+ },
+ },
+});
const styles = StyleSheet.create({
- container: {
+ outer: {
flex: 1,
justifyContent: 'center',
},
- subcontainer: {
+ inner: {
position: 'absolute',
bottom: 0,
left: 0,
width: '100%',
- marginBottom: -15,
+ height: 60,
},
fab: {
- marginTop: 15,
marginLeft: 'auto',
marginRight: 'auto',
},
});
-/**
- * Abstraction layer for Agenda component, using custom configuration
- */
-class TabHomeIcon extends React.Component {
- constructor(props: PropsType) {
- super(props);
- Animatable.initializeRegistryWithDefinitions({
- fabFocusIn: {
- '0': {
- // @ts-ignore
- scale: 1,
- translateY: 0,
- },
- '0.9': {
- scale: 1.2,
- translateY: -9,
- },
- '1': {
- scale: 1.1,
- translateY: -7,
- },
- },
- fabFocusOut: {
- '0': {
- // @ts-ignore
- scale: 1.1,
- translateY: -6,
- },
- '1': {
- scale: 1,
- translateY: 0,
- },
- },
- });
- }
+const FOCUSED_ICON = require('../../../assets/tab-icon.png');
+const UNFOCUSED_ICON = require('../../../assets/tab-icon-outline.png');
- shouldComponentUpdate(nextProps: PropsType): boolean {
- const { focused } = this.props;
- return nextProps.focused !== focused;
- }
-
- getIconRender = ({ size, color }: { size: number; color: string }) => {
- const { focused } = this.props;
+function TabHomeIcon(props: Props) {
+ const getImage = (iconProps: { size: number; color: string }) => {
return (
-
+
+
+
);
};
- render() {
- const { props } = this;
- return (
-
-
+
+
-
-
+
- );
- }
+
+ );
}
export default TabHomeIcon;
diff --git a/src/components/Tabbar/TabIcon.tsx b/src/components/Tabbar/TabIcon.tsx
index e674741..0fd0fad 100644
--- a/src/components/Tabbar/TabIcon.tsx
+++ b/src/components/Tabbar/TabIcon.tsx
@@ -1,143 +1,41 @@
-/*
- * Copyright (c) 2019 - 2020 Arnaud Vergnet.
- *
- * This file is part of Campus INSAT.
- *
- * Campus INSAT is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Campus INSAT is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Campus INSAT. If not, see .
- */
+import React from 'react';
+import TabHomeIcon from './TabHomeIcon';
+import TabSideIcon from './TabSideIcon';
-import * as React from 'react';
-import { StyleSheet, View } from 'react-native';
-import { TouchableRipple, withTheme } from 'react-native-paper';
-import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
-import * as Animatable from 'react-native-animatable';
-import GENERAL_STYLES from '../../constants/Styles';
-
-type PropsType = {
+interface Props {
+ isMiddle: boolean;
focused: boolean;
- color: string;
- label: string;
+ label: string | undefined;
icon: string;
+ focusedIcon: string;
onPress: () => void;
- onLongPress: () => void;
- theme: ReactNativePaper.Theme;
- extraData: null | boolean | number | string;
-};
+}
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- justifyContent: 'center',
- borderRadius: 10,
- },
- text: {
- marginLeft: 'auto',
- marginRight: 'auto',
- fontSize: 10,
- },
-});
-
-/**
- * Abstraction layer for Agenda component, using custom configuration
- */
-class TabIcon extends React.Component {
- firstRender: boolean;
-
- constructor(props: PropsType) {
- super(props);
- Animatable.initializeRegistryWithDefinitions({
- focusIn: {
- '0': {
- // @ts-ignore
- scale: 1,
- translateY: 0,
- },
- '0.9': {
- scale: 1.3,
- translateY: 7,
- },
- '1': {
- scale: 1.2,
- translateY: 6,
- },
- },
- focusOut: {
- '0': {
- // @ts-ignore
- scale: 1.2,
- translateY: 6,
- },
- '1': {
- scale: 1,
- translateY: 0,
- },
- },
- });
- this.firstRender = true;
- }
-
- componentDidMount() {
- this.firstRender = false;
- }
-
- shouldComponentUpdate(nextProps: PropsType): boolean {
- const { props } = this;
+function TabIcon(props: Props) {
+ if (props.isMiddle) {
return (
- nextProps.focused !== props.focused ||
- nextProps.theme.dark !== props.theme.dark ||
- nextProps.extraData !== props.extraData
- );
- }
-
- render() {
- const { props } = this;
- return (
-
-
-
-
-
-
- {props.label}
-
-
-
+ />
+ );
+ } else {
+ return (
+
);
}
}
-export default withTheme(TabIcon);
+function areEqual(prevProps: Props, nextProps: Props) {
+ return prevProps.focused === nextProps.focused;
+}
+
+export default React.memo(TabIcon, areEqual);
diff --git a/src/components/Tabbar/TabSideIcon.tsx b/src/components/Tabbar/TabSideIcon.tsx
new file mode 100644
index 0000000..c1ab538
--- /dev/null
+++ b/src/components/Tabbar/TabSideIcon.tsx
@@ -0,0 +1,113 @@
+import React from 'react';
+import { TouchableRipple, useTheme } from 'react-native-paper';
+import { StyleSheet, View } from 'react-native';
+import * as Animatable from 'react-native-animatable';
+import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
+import GENERAL_STYLES from '../../constants/Styles';
+
+interface Props {
+ focused: boolean;
+ label: string | undefined;
+ icon: string;
+ focusedIcon: string;
+ onPress: () => void;
+}
+
+Animatable.initializeRegistryWithDefinitions({
+ focusIn: {
+ 0: {
+ // @ts-ignore
+ scale: 1,
+ translateY: 0,
+ },
+ 0.4: {
+ // @ts-ignore
+ scale: 1.3,
+ translateY: 6,
+ },
+ 0.6: {
+ // @ts-ignore
+ scale: 1.1,
+ translateY: 6,
+ },
+ 0.8: {
+ // @ts-ignore
+ scale: 1.25,
+ translateY: 6,
+ },
+ 1: {
+ // @ts-ignore
+ scale: 1.2,
+ translateY: 6,
+ },
+ },
+ focusOut: {
+ 0: {
+ // @ts-ignore
+ scale: 1.2,
+ translateY: 6,
+ },
+ 1: {
+ // @ts-ignore
+ scale: 1,
+ translateY: 0,
+ },
+ },
+});
+
+function TabSideIcon(props: Props) {
+ const theme = useTheme();
+ const color = props.focused ? theme.colors.primary : theme.colors.disabled;
+ let icon = props.focused ? props.focusedIcon : props.icon;
+ return (
+
+
+
+
+
+
+ {props.label}
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ ripple: {
+ flex: 1,
+ justifyContent: 'center',
+ },
+ text: {
+ ...GENERAL_STYLES.centerHorizontal,
+ fontSize: 10,
+ },
+});
+
+export default TabSideIcon;
diff --git a/src/components/providers/CollapsibleProvider.tsx b/src/components/providers/CollapsibleProvider.tsx
new file mode 100644
index 0000000..4820248
--- /dev/null
+++ b/src/components/providers/CollapsibleProvider.tsx
@@ -0,0 +1,33 @@
+import React, { useState } from 'react';
+import { Collapsible } from 'react-navigation-collapsible';
+import {
+ CollapsibleContext,
+ CollapsibleContextType,
+} from '../../utils/CollapsibleContext';
+
+type Props = {
+ children: React.ReactChild;
+};
+
+export default function CollapsibleProvider(props: Props) {
+ const setCollapsible = (collapsible: Collapsible) => {
+ setCurrentCollapsible((prevState) => ({
+ ...prevState,
+ collapsible,
+ }));
+ };
+
+ const [
+ currentCollapsible,
+ setCurrentCollapsible,
+ ] = useState({
+ collapsible: undefined,
+ setCollapsible: setCollapsible,
+ });
+
+ return (
+
+ {props.children}
+
+ );
+}
diff --git a/src/navigation/MainNavigator.tsx b/src/navigation/MainNavigator.tsx
index e11f863..4f3d931 100644
--- a/src/navigation/MainNavigator.tsx
+++ b/src/navigation/MainNavigator.tsx
@@ -18,12 +18,8 @@
*/
import * as React from 'react';
-import {
- createStackNavigator,
- TransitionPresets,
-} from '@react-navigation/stack';
+import { createStackNavigator } from '@react-navigation/stack';
import i18n from 'i18n-js';
-import { Platform } from 'react-native';
import SettingsScreen from '../screens/Other/Settings/SettingsScreen';
import AboutScreen from '../screens/About/AboutScreen';
import AboutDependenciesScreen from '../screens/About/AboutDependenciesScreen';
@@ -40,10 +36,6 @@ import ProfileScreen from '../screens/Amicale/ProfileScreen';
import ClubListScreen from '../screens/Amicale/Clubs/ClubListScreen';
import ClubAboutScreen from '../screens/Amicale/Clubs/ClubAboutScreen';
import ClubDisplayScreen from '../screens/Amicale/Clubs/ClubDisplayScreen';
-import {
- CreateScreenCollapsibleStack,
- getWebsiteStack,
-} from '../utils/CollapsibleUtils';
import BugReportScreen from '../screens/Other/FeedbackScreen';
import WebsiteScreen from '../screens/Services/WebsiteScreen';
import EquipmentScreen, {
@@ -54,6 +46,7 @@ import EquipmentConfirmScreen from '../screens/Amicale/Equipment/EquipmentConfir
import DashboardEditScreen from '../screens/Other/Settings/DashboardEditScreen';
import GameStartScreen from '../screens/Game/screens/GameStartScreen';
import ImageGalleryScreen from '../screens/Media/ImageGalleryScreen';
+import Test from '../screens/Test';
export enum MainRoutes {
Main = 'main',
@@ -83,7 +76,7 @@ export enum MainRoutes {
type DefaultParams = { [key in MainRoutes]: object | undefined };
-export interface FullParamsList extends DefaultParams {
+export type FullParamsList = DefaultParams & {
'login': { nextScreen: string };
'equipment-confirm': {
item?: DeviceType;
@@ -91,34 +84,22 @@ export interface FullParamsList extends DefaultParams {
};
'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
- : TransitionPresets.ModalTransition;
-
-const defaultScreenOptions = {
- gestureEnabled: true,
- cardOverlayEnabled: true,
- ...TransitionPresets.SlideFromRightIOS,
-};
-
const MainStack = createStackNavigator();
-function MainStackComponent(props: { createTabNavigator: () => JSX.Element }) {
+function MainStackComponent(props: {
+ createTabNavigator: () => React.ReactElement;
+}) {
const { createTabNavigator } = props;
return (
-
+
+
JSX.Element }) {
component={ImageGalleryScreen}
options={{
headerShown: false,
- ...modalTransition,
}}
/>
- {CreateScreenCollapsibleStack(
- MainRoutes.Settings,
- MainStack,
- SettingsScreen,
- i18n.t('screens.settings.title')
- )}
- {CreateScreenCollapsibleStack(
- MainRoutes.DashboardEdit,
- MainStack,
- DashboardEditScreen,
- i18n.t('screens.settings.dashboardEdit.title')
- )}
- {CreateScreenCollapsibleStack(
- MainRoutes.About,
- MainStack,
- AboutScreen,
- i18n.t('screens.about.title')
- )}
- {CreateScreenCollapsibleStack(
- MainRoutes.Dependencies,
- MainStack,
- AboutDependenciesScreen,
- i18n.t('screens.about.libs')
- )}
- {CreateScreenCollapsibleStack(
- MainRoutes.Debug,
- MainStack,
- DebugScreen,
- i18n.t('screens.about.debug')
- )}
-
- {CreateScreenCollapsibleStack(
- MainRoutes.GameStart,
- MainStack,
- GameStartScreen,
- i18n.t('screens.game.title'),
- true,
- undefined,
- 'transparent'
- )}
+
+
+
+
+
+
JSX.Element }) {
title: i18n.t('screens.game.title'),
}}
/>
- {CreateScreenCollapsibleStack(
- MainRoutes.Login,
- MainStack,
- LoginScreen,
- i18n.t('screens.login.title'),
- true,
- { headerTintColor: '#fff' },
- 'transparent'
- )}
- {getWebsiteStack('website', MainStack, WebsiteScreen, '')}
-
- {CreateScreenCollapsibleStack(
- MainRoutes.SelfMenu,
- MainStack,
- SelfMenuScreen,
- i18n.t('screens.menu.title')
- )}
- {CreateScreenCollapsibleStack(
- MainRoutes.Proximo,
- MainStack,
- ProximoMainScreen,
- i18n.t('screens.proximo.title')
- )}
- {CreateScreenCollapsibleStack(
- MainRoutes.ProximoList,
- MainStack,
- ProximoListScreen,
- i18n.t('screens.proximo.articleList')
- )}
- {CreateScreenCollapsibleStack(
- MainRoutes.ProximoAbout,
- MainStack,
- ProximoAboutScreen,
- i18n.t('screens.proximo.title'),
- true,
- { ...modalTransition }
- )}
-
- {CreateScreenCollapsibleStack(
- MainRoutes.Profile,
- MainStack,
- ProfileScreen,
- i18n.t('screens.profile.title')
- )}
- {CreateScreenCollapsibleStack(
- MainRoutes.ClubList,
- MainStack,
- ClubListScreen,
- i18n.t('screens.clubs.title')
- )}
- {CreateScreenCollapsibleStack(
- MainRoutes.ClubInformation,
- MainStack,
- ClubDisplayScreen,
- i18n.t('screens.clubs.details'),
- true,
- { ...modalTransition }
- )}
- {CreateScreenCollapsibleStack(
- MainRoutes.ClubAbout,
- MainStack,
- ClubAboutScreen,
- i18n.t('screens.clubs.title'),
- true,
- { ...modalTransition }
- )}
- {CreateScreenCollapsibleStack(
- MainRoutes.EquipmentList,
- MainStack,
- EquipmentScreen,
- i18n.t('screens.equipment.title')
- )}
- {CreateScreenCollapsibleStack(
- MainRoutes.EquipmentRent,
- MainStack,
- EquipmentLendScreen,
- i18n.t('screens.equipment.book')
- )}
- {CreateScreenCollapsibleStack(
- MainRoutes.EquipmentConfirm,
- MainStack,
- EquipmentConfirmScreen,
- i18n.t('screens.equipment.confirm')
- )}
- {CreateScreenCollapsibleStack(
- MainRoutes.Vote,
- MainStack,
- VoteScreen,
- i18n.t('screens.vote.title')
- )}
- {CreateScreenCollapsibleStack(
- MainRoutes.Feedback,
- MainStack,
- BugReportScreen,
- i18n.t('screens.feedback.title')
- )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/src/navigation/TabNavigator.tsx b/src/navigation/TabNavigator.tsx
index 972eb4e..437a380 100644
--- a/src/navigation/TabNavigator.tsx
+++ b/src/navigation/TabNavigator.tsx
@@ -18,16 +18,12 @@
*/
import * as React from 'react';
-import {
- createStackNavigator,
- TransitionPresets,
-} from '@react-navigation/stack';
+import { createStackNavigator } from '@react-navigation/stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Title, useTheme } from 'react-native-paper';
-import { Platform, StyleSheet } from 'react-native';
+import { StyleSheet } from 'react-native';
import i18n from 'i18n-js';
-import { createCollapsibleStack } from 'react-navigation-collapsible';
import { View } from 'react-native-animatable';
import HomeScreen from '../screens/Home/HomeScreen';
import PlanningScreen from '../screens/Planning/PlanningScreen';
@@ -44,23 +40,8 @@ import CustomTabBar from '../components/Tabbar/CustomTabBar';
import WebsitesHomeScreen from '../screens/Services/ServicesScreen';
import ServicesSectionScreen from '../screens/Services/ServicesSectionScreen';
import AmicaleContactScreen from '../screens/Amicale/AmicaleContactScreen';
-import {
- CreateScreenCollapsibleStack,
- getWebsiteStack,
-} from '../utils/CollapsibleUtils';
import Mascot, { MASCOT_STYLE } from '../components/Mascot/Mascot';
-const modalTransition =
- Platform.OS === 'ios'
- ? TransitionPresets.ModalPresentationIOS
- : TransitionPresets.ModalTransition;
-
-const defaultScreenOptions = {
- gestureEnabled: true,
- cardOverlayEnabled: true,
- ...modalTransition,
-};
-
const styles = StyleSheet.create({
header: {
flexDirection: 'row',
@@ -79,29 +60,22 @@ const ServicesStack = createStackNavigator();
function ServicesStackComponent() {
return (
-
- {CreateScreenCollapsibleStack(
- 'index',
- ServicesStack,
- WebsitesHomeScreen,
- i18n.t('screens.services.title')
- )}
- {CreateScreenCollapsibleStack(
- 'services-section',
- ServicesStack,
- ServicesSectionScreen,
- 'SECTION'
- )}
- {CreateScreenCollapsibleStack(
- 'amicale-contact',
- ServicesStack,
- AmicaleContactScreen,
- i18n.t('screens.amicaleAbout.title')
- )}
+
+
+
+
);
}
@@ -110,23 +84,17 @@ const ProxiwashStack = createStackNavigator();
function ProxiwashStackComponent() {
return (
-
- {CreateScreenCollapsibleStack(
- 'index',
- ProxiwashStack,
- ProxiwashScreen,
- i18n.t('screens.proxiwash.title')
- )}
- {CreateScreenCollapsibleStack(
- 'proxiwash-about',
- ProxiwashStack,
- ProxiwashAboutScreen,
- i18n.t('screens.proxiwash.title')
- )}
+
+
+
);
}
@@ -135,22 +103,17 @@ const PlanningStack = createStackNavigator();
function PlanningStackComponent() {
return (
-
+
- {CreateScreenCollapsibleStack(
- 'planning-information',
- PlanningStack,
- PlanningDisplayScreen,
- i18n.t('screens.planning.eventDetails')
- )}
+
);
}
@@ -167,73 +130,63 @@ function HomeStackComponent(
}
const { colors } = useTheme();
return (
-
- {createCollapsibleStack(
- (
-
-
-
- {i18n.t('screens.home.title')}
-
-
- ),
- }}
- initialParams={params}
- />,
- {
- collapsedColor: colors.surface,
- useNativeDriver: true,
- }
- )}
+
(
+
+
+ {headerProps.children}
+
+ ),
+ }}
+ initialParams={params}
+ />
+
-
- {CreateScreenCollapsibleStack(
- 'club-information',
- HomeStack,
- ClubDisplayScreen,
- i18n.t('screens.clubs.details')
- )}
- {CreateScreenCollapsibleStack(
- 'feed-information',
- HomeStack,
- FeedItemScreen,
- i18n.t('screens.home.feed')
- )}
- {CreateScreenCollapsibleStack(
- 'planning-information',
- HomeStack,
- PlanningDisplayScreen,
- i18n.t('screens.planning.eventDetails')
- )}
+
+
+
);
}
@@ -242,23 +195,21 @@ const PlanexStack = createStackNavigator();
function PlanexStackComponent() {
return (
-
- {getWebsiteStack(
- 'index',
- PlanexStack,
- PlanexScreen,
- i18n.t('screens.planex.title')
- )}
- {CreateScreenCollapsibleStack(
- 'group-select',
- PlanexStack,
- GroupSelectionScreen,
- ''
- )}
+
+
+
);
}
@@ -270,6 +221,34 @@ type PropsType = {
defaultHomeData: { [key: string]: string };
};
+const ICONS: {
+ [key: string]: {
+ normal: string;
+ focused: string;
+ };
+} = {
+ services: {
+ normal: 'account-circle-outline',
+ focused: 'account-circle',
+ },
+ proxiwash: {
+ normal: 'tshirt-crew-outline',
+ focused: 'tshirt-crew',
+ },
+ home: {
+ normal: '',
+ focused: '',
+ },
+ planning: {
+ normal: 'calendar-range-outline',
+ focused: 'calendar-range',
+ },
+ planex: {
+ normal: 'clock-outline',
+ focused: 'clock',
+ },
+};
+
export default class TabNavigator extends React.Component {
defaultRoute: string;
createHomeStackComponent: () => any;
@@ -287,33 +266,44 @@ export default class TabNavigator extends React.Component {
}
render() {
+ const LABELS: {
+ [key: string]: string;
+ } = {
+ services: i18n.t('screens.services.title'),
+ proxiwash: i18n.t('screens.proxiwash.title'),
+ home: i18n.t('screens.home.title'),
+ planning: i18n.t('screens.planning.title'),
+ planex: i18n.t('screens.planex.title'),
+ };
return (
}
+ tabBar={(tabProps) => (
+
+ )}
>
diff --git a/src/screens/Amicale/Clubs/ClubDisplayScreen.tsx b/src/screens/Amicale/Clubs/ClubDisplayScreen.tsx
index b59f2f2..fdae27f 100644
--- a/src/screens/Amicale/Clubs/ClubDisplayScreen.tsx
+++ b/src/screens/Amicale/Clubs/ClubDisplayScreen.tsx
@@ -31,7 +31,9 @@ import i18n from 'i18n-js';
import { StackNavigationProp } from '@react-navigation/stack';
import AuthenticatedScreen from '../../../components/Amicale/AuthenticatedScreen';
import CustomHTML from '../../../components/Overrides/CustomHTML';
-import CustomTabBar from '../../../components/Tabbar/CustomTabBar';
+import CustomTabBar, {
+ TAB_BAR_HEIGHT,
+} from '../../../components/Tabbar/CustomTabBar';
import type { ClubCategoryType, ClubType } from './ClubListScreen';
import { ERROR_TYPE } from '../../../utils/WebData';
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
@@ -174,7 +176,7 @@ class ClubDisplayScreen extends React.Component {
return (
diff --git a/src/screens/Amicale/LoginScreen.tsx b/src/screens/Amicale/LoginScreen.tsx
index 0394736..5bbb0b0 100644
--- a/src/screens/Amicale/LoginScreen.tsx
+++ b/src/screens/Amicale/LoginScreen.tsx
@@ -441,7 +441,7 @@ class LoginScreen extends React.Component {
enabled
keyboardVerticalOffset={100}
>
-
+
{this.getMainCard()}
{
style={styles.button}
/>
) : null}
-
+
{this.displayData.message !== undefined ? (
{
{state.hasPermission ? this.getScanner() : this.getPermissionScreen()}
diff --git a/src/screens/Planex/PlanexScreen.tsx b/src/screens/Planex/PlanexScreen.tsx
index 818a16f..8828864 100644
--- a/src/screens/Planex/PlanexScreen.tsx
+++ b/src/screens/Planex/PlanexScreen.tsx
@@ -54,6 +54,7 @@ type StateType = {
dialogTitle: string | React.ReactNode;
dialogMessage: string;
currentGroup: PlanexGroupType;
+ injectJS: string;
};
const PLANEX_URL = 'http://planex.insa-toulouse.fr/';
@@ -153,20 +154,14 @@ const styles = StyleSheet.create({
* This screen uses a webview to render the page
*/
class PlanexScreen extends React.Component {
- webScreenRef: { current: null | WebViewScreen };
-
barRef: { current: null | AnimatedBottomBar };
- customInjectedJS: string;
-
/**
* Defines custom injected JavaScript to improve the page display on mobile
*/
constructor(props: PropsType) {
super(props);
- this.webScreenRef = React.createRef();
this.barRef = React.createRef();
- this.customInjectedJS = '';
let currentGroupString = AsyncStorageManager.getString(
AsyncStorageManager.PREFERENCES.planexCurrentGroup.key
);
@@ -184,8 +179,8 @@ class PlanexScreen extends React.Component {
dialogTitle: '',
dialogMessage: '',
currentGroup,
+ injectJS: '',
};
- this.generateInjectedJS(currentGroup.id);
}
/**
@@ -196,20 +191,6 @@ class PlanexScreen extends React.Component {
navigation.addListener('focus', this.onScreenFocus);
}
- /**
- * Only update the screen if the dark theme changed
- *
- * @param nextProps
- * @returns {boolean}
- */
- shouldComponentUpdate(nextProps: PropsType): boolean {
- const { props, state } = this;
- if (nextProps.theme.dark !== props.theme.dark) {
- this.generateInjectedJS(state.currentGroup.id);
- }
- return true;
- }
-
/**
* Gets the Webview, with an error view on top if no group is selected.
*
@@ -218,6 +199,7 @@ class PlanexScreen extends React.Component {
getWebView() {
const { props, state } = this;
const showWebview = state.currentGroup.id !== -1;
+ console.log(state.injectJS);
return (
@@ -230,10 +212,9 @@ class PlanexScreen extends React.Component {
/>
) : null}
{
} else {
command = `$('#calendar').fullCalendar('${action}', '${data}')`;
}
- if (this.webScreenRef.current != null) {
- this.webScreenRef.current.injectJavaScript(`${command};true;`);
- } // Injected javascript must end with true
+ // String must resolve to true to prevent crash on iOS
+ command += ';true;';
+ // Change the injected
+ if (command === this.state.injectJS) {
+ command += ';true;';
+ }
+ this.setState({ injectJS: command });
};
/**
@@ -373,7 +358,6 @@ class PlanexScreen extends React.Component {
group
);
navigation.setOptions({ title: getPrettierPlanexGroupName(group.name) });
- this.generateInjectedJS(group.id);
}
/**
@@ -382,16 +366,20 @@ class PlanexScreen extends React.Component {
* @param groupID The current group selected
*/
generateInjectedJS(groupID: number) {
- this.customInjectedJS = `$(document).ready(function() {${OBSERVE_MUTATIONS_INJECTED}${FULL_CALENDAR_SETTINGS}displayAde(${groupID});${
- // Reset Ade
- DateManager.isWeekend(new Date()) ? 'calendar.next()' : ''
- }${INJECT_STYLE}`;
-
+ let customInjectedJS = `$(document).ready(function() {
+ ${OBSERVE_MUTATIONS_INJECTED}
+ ${FULL_CALENDAR_SETTINGS}
+ displayAde(${groupID});
+ ${INJECT_STYLE}`;
+ if (DateManager.isWeekend(new Date())) {
+ customInjectedJS += `calendar.next();`;
+ }
if (ThemeManager.getNightMode()) {
- this.customInjectedJS += `$('head').append('');`;
+ customInjectedJS += `$('head').append('');`;
}
- this.customInjectedJS += 'removeAlpha();});true;'; // Prevents crash on ios
+ customInjectedJS += 'removeAlpha();});true;'; // Prevents crash on ios
+ return customInjectedJS;
}
render() {
diff --git a/src/screens/Planning/PlanningDisplayScreen.tsx b/src/screens/Planning/PlanningDisplayScreen.tsx
index 7dc8f11..5acc106 100644
--- a/src/screens/Planning/PlanningDisplayScreen.tsx
+++ b/src/screens/Planning/PlanningDisplayScreen.tsx
@@ -28,7 +28,9 @@ import BasicLoadingScreen from '../../components/Screens/BasicLoadingScreen';
import { apiRequest, ERROR_TYPE } from '../../utils/WebData';
import ErrorView from '../../components/Screens/ErrorView';
import CustomHTML from '../../components/Overrides/CustomHTML';
-import CustomTabBar from '../../components/Tabbar/CustomTabBar';
+import CustomTabBar, {
+ TAB_BAR_HEIGHT,
+} from '../../components/Tabbar/CustomTabBar';
import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView';
import type { PlanningEventType } from '../../utils/Planning';
import ImageGalleryButton from '../../components/Media/ImageGalleryButton';
@@ -145,9 +147,7 @@ class PlanningDisplayScreen extends React.Component {
) : null}
{displayData.description !== null ? (
-
+
) : (
diff --git a/src/screens/Services/Proximo/ProximoAboutScreen.tsx b/src/screens/Services/Proximo/ProximoAboutScreen.tsx
index 3df631a..030ea29 100644
--- a/src/screens/Services/Proximo/ProximoAboutScreen.tsx
+++ b/src/screens/Services/Proximo/ProximoAboutScreen.tsx
@@ -21,7 +21,9 @@ import * as React from 'react';
import { Image, StyleSheet, View } from 'react-native';
import i18n from 'i18n-js';
import { Card, Avatar, Paragraph, Text } from 'react-native-paper';
-import CustomTabBar from '../../../components/Tabbar/CustomTabBar';
+import CustomTabBar, {
+ TAB_BAR_HEIGHT,
+} from '../../../components/Tabbar/CustomTabBar';
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
const LOGO = 'https://etud.insa-toulouse.fr/~amicale_app/images/Proximo.png';
@@ -72,7 +74,7 @@ export default function ProximoAboutScreen() {
;
route: { params: { host: string; path: string | null; title: string } };
};
+type State = {
+ url: string;
+};
+
const ENABLE_MOBILE_STRING =
'';
@@ -43,18 +47,18 @@ const BIB_BACK_BUTTON =
'' +
'';
-class WebsiteScreen extends React.Component {
- fullUrl: string;
-
+class WebsiteScreen extends React.Component {
injectedJS: { [key: string]: string };
- customPaddingFunctions: { [key: string]: (padding: string) => string };
+ customPaddingFunctions: { [key: string]: (padding: number) => string };
host: string;
- constructor(props: PropsType) {
+ constructor(props: Props) {
super(props);
- this.fullUrl = '';
+ this.state = {
+ url: '',
+ };
this.host = '';
props.navigation.addListener('focus', this.onScreenFocus);
this.injectedJS = {};
@@ -70,7 +74,7 @@ class WebsiteScreen extends React.Component {
`$(".hero-unit-form").append("${BIB_BACK_BUTTON}");true;`;
this.customPaddingFunctions[AvailableWebsites.websites.BLUEMIND] = (
- padding: string
+ padding: number
): string => {
return (
`$('head').append('${ENABLE_MOBILE_STRING}');` +
@@ -79,7 +83,7 @@ class WebsiteScreen extends React.Component {
);
};
this.customPaddingFunctions[AvailableWebsites.websites.WIKETUD] = (
- padding: string
+ padding: number
): string => {
return (
`$('#p-logo-text').css('top', 10 + ${padding});` +
@@ -99,16 +103,20 @@ class WebsiteScreen extends React.Component {
*/
handleNavigationParams() {
const { route, navigation } = this.props;
+
if (route.params != null) {
+ console.log(route.params);
this.host = route.params.host;
let { path } = route.params;
const { title } = route.params;
+ let fullUrl = '';
if (this.host != null && path != null) {
path = path.replace(this.host, '');
- this.fullUrl = this.host + path;
+ fullUrl = this.host + path;
} else {
- this.fullUrl = this.host;
+ fullUrl = this.host;
}
+ this.setState({ url: fullUrl });
if (title != null) {
navigation.setOptions({ title });
@@ -117,7 +125,6 @@ class WebsiteScreen extends React.Component {
}
render() {
- const { navigation } = this.props;
let injectedJavascript = '';
let customPadding = null;
if (this.host != null && this.injectedJS[this.host] != null) {
@@ -127,12 +134,11 @@ class WebsiteScreen extends React.Component {
customPadding = this.customPaddingFunctions[this.host];
}
- if (this.fullUrl != null) {
+ if (this.state.url) {
return (
);
diff --git a/src/screens/Test.tsx b/src/screens/Test.tsx
new file mode 100644
index 0000000..ad99763
--- /dev/null
+++ b/src/screens/Test.tsx
@@ -0,0 +1,157 @@
+import { useNavigation } from '@react-navigation/core';
+import { StackNavigationProp } from '@react-navigation/stack';
+import React from 'react';
+import { Animated, View } from 'react-native';
+import { Text } from 'react-native-paper';
+import {
+ Collapsible,
+ useCollapsibleHeader,
+} from 'react-navigation-collapsible';
+import CollapsibleFlatList from '../components/Collapsible/CollapsibleFlatList';
+import FeedItem from '../components/Home/FeedItem';
+import WebSectionList from '../components/Screens/WebSectionList';
+import withCollapsible from '../utils/withCollapsible';
+import { FeedItemType } from './Home/HomeScreen';
+import i18n from 'i18n-js';
+import CollapsibleSectionList from '../components/Collapsible/CollapsibleSectionList';
+
+// export default function Test() {
+// const {
+// onScroll /* Event handler */,
+// onScrollWithListener /* Event handler creator */,
+// containerPaddingTop /* number */,
+// scrollIndicatorInsetTop /* number */,
+// /* Animated.AnimatedValue contentOffset from scrolling */
+// positionY /* 0.0 ~ length of scrollable component (contentOffset)
+// /* Animated.AnimatedInterpolation by scrolling */,
+// translateY /* 0.0 ~ -headerHeight */,
+// progress /* 0.0 ~ 1.0 */,
+// opacity /* 1.0 ~ 0.0 */,
+// } = useCollapsibleHeader();
+
+// const renderItem = () => {
+// return (
+//
+// TEST
+//
+// );
+// };
+
+// return (
+//
+// );
+// }
+
+type Props = {
+ navigation: StackNavigationProp;
+ collapsibleStack: Collapsible;
+};
+
+const DATA_URL =
+ 'https://etud.insa-toulouse.fr/~amicale_app/v2/dashboard/dashboard_data.json';
+const FEED_ITEM_HEIGHT = 500;
+
+const REFRESH_TIME = 1000 * 20; // Refresh every 20 seconds
+class Test extends React.Component {
+ createDataset = (): Array<{
+ title: string;
+ data: [] | Array;
+ id: string;
+ }> => {
+ return [
+ {
+ title: 'title',
+ data: [
+ {
+ id: '0',
+ message: 'message',
+ image: '',
+ link: '',
+ page_id: 'amicale.deseleves',
+ time: 0,
+ url: '',
+ video: '',
+ },
+ {
+ id: '1',
+ message: 'message',
+ image: '',
+ link: '',
+ page_id: 'amicale.deseleves',
+ time: 0,
+ url: '',
+ video: '',
+ },
+ {
+ id: '2',
+ message: 'message',
+ image: '',
+ link: '',
+ page_id: 'amicale.deseleves',
+ time: 0,
+ url: '',
+ video: '',
+ },
+ ],
+ id: '0',
+ },
+ ];
+ };
+ getRenderItem = ({ item }: { item: FeedItemType }) => (
+
+ );
+
+ render() {
+ const renderItem = () => {
+ return (
+
+ TEST
+
+ );
+ };
+
+ const props = this.props;
+ // return (
+ //
+ // );
+ // return (
+ //
+ // );
+ return (
+
+ );
+ }
+}
+
+export default Test;
diff --git a/src/utils/CollapsibleContext.ts b/src/utils/CollapsibleContext.ts
new file mode 100644
index 0000000..8bd13e4
--- /dev/null
+++ b/src/utils/CollapsibleContext.ts
@@ -0,0 +1,16 @@
+import React, { useContext } from 'react';
+import { Collapsible } from 'react-navigation-collapsible';
+
+export type CollapsibleContextType = {
+ collapsible?: Collapsible;
+ setCollapsible: (collapsible: Collapsible) => void;
+};
+
+export const CollapsibleContext = React.createContext({
+ collapsible: undefined,
+ setCollapsible: () => undefined,
+});
+
+export function useCollapsible() {
+ return useContext(CollapsibleContext);
+}
diff --git a/src/utils/CollapsibleUtils.tsx b/src/utils/CollapsibleUtils.tsx
deleted file mode 100644
index 11a7ed1..0000000
--- a/src/utils/CollapsibleUtils.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (c) 2019 - 2020 Arnaud Vergnet.
- *
- * This file is part of Campus INSAT.
- *
- * Campus INSAT is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Campus INSAT is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Campus INSAT. If not, see .
- */
-
-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, TypedNavigator } from '@react-navigation/native';
-import { StackNavigationEventMap } from '@react-navigation/stack/lib/typescript/src/types';
-
-type StackNavigatorType = import('@react-navigation/native').TypedNavigator<
- Record,
- StackNavigationState,
- StackNavigationOptions,
- StackNavigationEventMap,
- typeof StackNavigator
->;
-
-/**
- * Creates a navigation stack with the collapsible library, allowing the header to collapse on scroll.
- *
- * Please use the getWebsiteStack function if your screen uses a webview as their main component as it needs special parameters.
- *
- * @param name The screen name in the navigation stack
- * @param Stack The stack component
- * @param component The screen component
- * @param title The screen title shown in the header (needs to be translated)
- * @param useNativeDriver Whether to use the native driver for animations.
- * Set to false if the screen uses a webview as this component does not support native driver.
- * In all other cases, set it to true for increase performance.
- * @param options Screen options to use, or null if no options are necessary.
- * @param headerColor The color of the header. Uses default color if not specified
- * @returns {JSX.Element}
- */
-export function CreateScreenCollapsibleStack(
- name: string,
- Stack: TypedNavigator,
- component: React.ComponentType,
- title: string,
- useNativeDriver: boolean = true,
- options: StackNavigationOptions = {},
- headerColor?: string
-) {
- const { colors } = useTheme();
- return createCollapsibleStack(
- ,
- {
- collapsedColor: headerColor != null ? headerColor : colors.surface,
- useNativeDriver: useNativeDriver, // native driver does not work with webview
- }
- );
-}
-
-/**
- * Creates a navigation stack with the collapsible library, allowing the header to collapse on scroll.
- *
- * This is a preset for screens using a webview as their main component, as it uses special parameters to work.
- * (aka a dirty workaround)
- *
- * @param name
- * @param Stack
- * @param component
- * @param title
- * @returns {JSX.Element}
- */
-export function getWebsiteStack(
- name: string,
- Stack: TypedNavigator,
- component: React.ComponentType,
- title: string
-) {
- return CreateScreenCollapsibleStack(name, Stack, component, title, false);
-}
diff --git a/src/utils/withCollapsible.tsx b/src/utils/withCollapsible.tsx
index 6cfb2d1..52d981a 100644
--- a/src/utils/withCollapsible.tsx
+++ b/src/utils/withCollapsible.tsx
@@ -18,29 +18,28 @@
*/
import * as React from 'react';
-import { useCollapsibleStack } from 'react-navigation-collapsible';
+import { useTheme } from 'react-native-paper';
+import {
+ useCollapsibleHeader,
+ UseCollapsibleOptions,
+} from 'react-navigation-collapsible';
-/**
- * Function used to manipulate Collapsible Hooks from a class.
- *
- * Usage :
- *
- * export withCollapsible(Component)
- *
- * replacing Component with the one you want to use.
- * This component will then receive the collapsibleStack prop.
- *
- * @param Component The component to use Collapsible with
- * @returns {React.ComponentType}
- */
-export default function withCollapsible(Component: React.ComponentType) {
- return React.forwardRef((props: any, ref: any) => {
- return (
-
- );
- });
+export default function withCollapsible(
+ Component: React.ComponentType,
+ options?: UseCollapsibleOptions
+) {
+ return function WrappedComponent(props: T) {
+ const theme = useTheme();
+ if (!options?.config?.collapsedColor) {
+ options = {
+ ...options,
+ config: {
+ ...options?.config,
+ collapsedColor: theme.colors.surface,
+ },
+ };
+ }
+ const collapsible = useCollapsibleHeader(options);
+ return ;
+ };
}