update react native collapsible
This commit is contained in:
parent
0b4f115a14
commit
286c1e6411
28 changed files with 1147 additions and 1155 deletions
44
App.tsx
44
App.tsx
|
@ -37,6 +37,7 @@ import { setupStatusBar } from './src/utils/Utils';
|
||||||
import initLocales from './src/utils/Locales';
|
import initLocales from './src/utils/Locales';
|
||||||
import { NavigationContainerRef } from '@react-navigation/core';
|
import { NavigationContainerRef } from '@react-navigation/core';
|
||||||
import GENERAL_STYLES from './src/constants/Styles';
|
import GENERAL_STYLES from './src/constants/Styles';
|
||||||
|
import CollapsibleProvider from './src/components/providers/CollapsibleProvider';
|
||||||
|
|
||||||
// Native optimizations https://reactnavigation.org/docs/react-native-screens
|
// Native optimizations https://reactnavigation.org/docs/react-native-screens
|
||||||
// Crashes app when navigating away from webview on android 9+
|
// Crashes app when navigating away from webview on android 9+
|
||||||
|
@ -210,26 +211,29 @@ export default class App extends React.Component<{}, StateType> {
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<PaperProvider theme={state.currentTheme}>
|
<PaperProvider theme={state.currentTheme}>
|
||||||
<OverflowMenuProvider>
|
<CollapsibleProvider>
|
||||||
<View
|
<OverflowMenuProvider>
|
||||||
style={{
|
<View
|
||||||
backgroundColor: ThemeManager.getCurrentTheme().colors.background,
|
style={{
|
||||||
...GENERAL_STYLES.flex,
|
backgroundColor: ThemeManager.getCurrentTheme().colors
|
||||||
}}
|
.background,
|
||||||
>
|
...GENERAL_STYLES.flex,
|
||||||
<SafeAreaView style={GENERAL_STYLES.flex}>
|
}}
|
||||||
<NavigationContainer
|
>
|
||||||
theme={state.currentTheme}
|
<SafeAreaView style={GENERAL_STYLES.flex}>
|
||||||
ref={this.navigatorRef}
|
<NavigationContainer
|
||||||
>
|
theme={state.currentTheme}
|
||||||
<MainNavigator
|
ref={this.navigatorRef}
|
||||||
defaultHomeRoute={this.defaultHomeRoute}
|
>
|
||||||
defaultHomeData={this.defaultHomeData}
|
<MainNavigator
|
||||||
/>
|
defaultHomeRoute={this.defaultHomeRoute}
|
||||||
</NavigationContainer>
|
defaultHomeData={this.defaultHomeData}
|
||||||
</SafeAreaView>
|
/>
|
||||||
</View>
|
</NavigationContainer>
|
||||||
</OverflowMenuProvider>
|
</SafeAreaView>
|
||||||
|
</View>
|
||||||
|
</OverflowMenuProvider>
|
||||||
|
</CollapsibleProvider>
|
||||||
</PaperProvider>
|
</PaperProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
46
package-lock.json
generated
46
package-lock.json
generated
|
@ -3931,7 +3931,6 @@
|
||||||
"version": "3.1.4",
|
"version": "3.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz",
|
||||||
"integrity": "sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==",
|
"integrity": "sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"node-fetch": "2.6.1"
|
"node-fetch": "2.6.1"
|
||||||
}
|
}
|
||||||
|
@ -10596,14 +10595,39 @@
|
||||||
"integrity": "sha512-beZjdgbT9Y/Pg591Xy5XkKG20HffJiVad4n9bfcUF/f783A+tvOVXnqvbS58Lkaym93mi4jcDPMuW9Vc1t6rqg=="
|
"integrity": "sha512-beZjdgbT9Y/Pg591Xy5XkKG20HffJiVad4n9bfcUF/f783A+tvOVXnqvbS58Lkaym93mi4jcDPMuW9Vc1t6rqg=="
|
||||||
},
|
},
|
||||||
"react-native-gesture-handler": {
|
"react-native-gesture-handler": {
|
||||||
"version": "1.8.0",
|
"version": "1.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.10.3.tgz",
|
||||||
"integrity": "sha512-E2FZa0qZ5Bi0Z8Jg4n9DaFomHvedSjwbO2DPmUUHYRy1lH2yxXUpSrqJd6yymu+Efzmjg2+JZzsjFYA2Iq8VEQ==",
|
"integrity": "sha512-cBGMi1IEsIVMgoox4RvMx7V2r6bNKw0uR1Mu1o7NbuHS6BRSVLq0dP34l2ecnPlC+jpWd3le6Yg1nrdCjby2Mw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@egjs/hammerjs": "^2.0.17",
|
"@egjs/hammerjs": "^2.0.17",
|
||||||
|
"fbjs": "^3.0.0",
|
||||||
"hoist-non-react-statics": "^3.3.0",
|
"hoist-non-react-statics": "^3.3.0",
|
||||||
"invariant": "^2.2.4",
|
"invariant": "^2.2.4",
|
||||||
"prop-types": "^15.7.2"
|
"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": {
|
"react-native-image-pan-zoom": {
|
||||||
|
@ -10871,11 +10895,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-navigation-collapsible": {
|
"react-navigation-collapsible": {
|
||||||
"version": "5.6.4",
|
"version": "5.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-navigation-collapsible/-/react-navigation-collapsible-5.6.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-navigation-collapsible/-/react-navigation-collapsible-5.9.1.tgz",
|
||||||
"integrity": "sha512-dXMbDw2TQ6s5XLk9h+2hUShXoS8KPChfdh/xmmLqfKmntS5YteE01+x78gU5KogB3etDraH1kvhW7xDnbG9AfA==",
|
"integrity": "sha512-yUwHe8Z7++A8ThrjPI+Mcm7LqBhIqJc+1F4XszpI7EoHz3bJElzczbfyfuEvjSbYU9AgW3MdBWzaRIDluxcEuA==",
|
||||||
"requires": {
|
"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": {
|
"react-navigation-header-buttons": {
|
||||||
|
@ -11453,6 +11478,11 @@
|
||||||
"kind-of": "^6.0.2"
|
"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": {
|
"shebang-command": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
"react-native-splash-screen": "3.2.0",
|
"react-native-splash-screen": "3.2.0",
|
||||||
"react-native-vector-icons": "8.1.0",
|
"react-native-vector-icons": "8.1.0",
|
||||||
"react-native-webview": "11.4.3",
|
"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"
|
"react-navigation-header-buttons": "7.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -28,7 +28,7 @@ import { FAB, IconButton, Surface, withTheme } from 'react-native-paper';
|
||||||
import * as Animatable from 'react-native-animatable';
|
import * as Animatable from 'react-native-animatable';
|
||||||
import { StackNavigationProp } from '@react-navigation/stack';
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
import AutoHideHandler from '../../utils/AutoHideHandler';
|
import AutoHideHandler from '../../utils/AutoHideHandler';
|
||||||
import CustomTabBar from '../Tabbar/CustomTabBar';
|
import CustomTabBar, { TAB_BAR_HEIGHT } from '../Tabbar/CustomTabBar';
|
||||||
|
|
||||||
const AnimatedFAB = Animatable.createAnimatableComponent(FAB);
|
const AnimatedFAB = Animatable.createAnimatableComponent(FAB);
|
||||||
|
|
||||||
|
@ -159,7 +159,7 @@ class AnimatedBottomBar extends React.Component<PropsType, StateType> {
|
||||||
useNativeDriver
|
useNativeDriver
|
||||||
style={{
|
style={{
|
||||||
...styles.container,
|
...styles.container,
|
||||||
bottom: 10 + CustomTabBar.TAB_BAR_HEIGHT,
|
bottom: 10 + TAB_BAR_HEIGHT,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Surface style={styles.surface}>
|
<Surface style={styles.surface}>
|
||||||
|
|
|
@ -27,7 +27,7 @@ import {
|
||||||
import { FAB } from 'react-native-paper';
|
import { FAB } from 'react-native-paper';
|
||||||
import * as Animatable from 'react-native-animatable';
|
import * as Animatable from 'react-native-animatable';
|
||||||
import AutoHideHandler from '../../utils/AutoHideHandler';
|
import AutoHideHandler from '../../utils/AutoHideHandler';
|
||||||
import CustomTabBar from '../Tabbar/CustomTabBar';
|
import CustomTabBar, { TAB_BAR_HEIGHT } from '../Tabbar/CustomTabBar';
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
icon: string;
|
icon: string;
|
||||||
|
@ -82,7 +82,7 @@ export default class AnimatedFAB extends React.Component<PropsType> {
|
||||||
useNativeDriver={true}
|
useNativeDriver={true}
|
||||||
style={{
|
style={{
|
||||||
...styles.fab,
|
...styles.fab,
|
||||||
bottom: CustomTabBar.TAB_BAR_HEIGHT,
|
bottom: TAB_BAR_HEIGHT,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FAB icon={props.icon} onPress={props.onPress} />
|
<FAB icon={props.icon} onPress={props.onPress} />
|
||||||
|
|
|
@ -17,22 +17,27 @@
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { useCollapsibleStack } from 'react-navigation-collapsible';
|
import { useCollapsibleHeader } from 'react-navigation-collapsible';
|
||||||
import CustomTabBar from '../Tabbar/CustomTabBar';
|
import { TAB_BAR_HEIGHT } from '../Tabbar/CustomTabBar';
|
||||||
import {
|
import {
|
||||||
NativeScrollEvent,
|
NativeScrollEvent,
|
||||||
NativeSyntheticEvent,
|
NativeSyntheticEvent,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
import { useTheme } from 'react-native-paper';
|
||||||
|
import { useCollapsible } from '../../utils/CollapsibleContext';
|
||||||
|
import { useFocusEffect } from '@react-navigation/core';
|
||||||
|
|
||||||
export type CollapsibleComponentPropsType = {
|
export type CollapsibleComponentPropsType = {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
hasTab?: boolean;
|
hasTab?: boolean;
|
||||||
onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
|
onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
|
||||||
|
paddedProps?: (paddingTop: number) => Record<string, any>;
|
||||||
|
headerColors: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PropsType = CollapsibleComponentPropsType & {
|
type Props = CollapsibleComponentPropsType & {
|
||||||
component: React.ComponentType<any>;
|
component: React.ComponentType<any>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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<NativeScrollEvent>) => {
|
const onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||||
if (props.onScroll) {
|
if (props.onScroll) {
|
||||||
props.onScroll(event);
|
props.onScroll(event);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const Comp = props.component;
|
|
||||||
const {
|
const pprops =
|
||||||
containerPaddingTop,
|
paddedProps !== undefined ? paddedProps(containerPaddingTop) : undefined;
|
||||||
scrollIndicatorInsetTop,
|
|
||||||
onScrollWithListener,
|
|
||||||
} = useCollapsibleStack();
|
|
||||||
const paddingBottom = props.hasTab ? CustomTabBar.TAB_BAR_HEIGHT : 0;
|
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
{...props}
|
{...props}
|
||||||
|
{...pprops}
|
||||||
onScroll={onScrollWithListener(onScroll)}
|
onScroll={onScrollWithListener(onScroll)}
|
||||||
contentContainerStyle={{
|
contentContainerStyle={{
|
||||||
paddingTop: containerPaddingTop,
|
paddingTop: containerPaddingTop,
|
||||||
|
|
|
@ -21,7 +21,7 @@ import * as React from 'react';
|
||||||
import { useTheme } from 'react-native-paper';
|
import { useTheme } from 'react-native-paper';
|
||||||
import { Modalize } from 'react-native-modalize';
|
import { Modalize } from 'react-native-modalize';
|
||||||
import { View } from 'react-native-animatable';
|
import { View } from 'react-native-animatable';
|
||||||
import CustomTabBar from '../Tabbar/CustomTabBar';
|
import CustomTabBar, { TAB_BAR_HEIGHT } from '../Tabbar/CustomTabBar';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstraction layer for Modalize component, using custom configuration
|
* Abstraction layer for Modalize component, using custom configuration
|
||||||
|
@ -45,7 +45,7 @@ function CustomModal(props: {
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
paddingBottom: CustomTabBar.TAB_BAR_HEIGHT,
|
paddingBottom: TAB_BAR_HEIGHT,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
@ -32,10 +32,10 @@ import { Collapsible } from 'react-navigation-collapsible';
|
||||||
import { StackNavigationProp } from '@react-navigation/stack';
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
import ErrorView from './ErrorView';
|
import ErrorView from './ErrorView';
|
||||||
import BasicLoadingScreen from './BasicLoadingScreen';
|
import BasicLoadingScreen from './BasicLoadingScreen';
|
||||||
import withCollapsible from '../../utils/withCollapsible';
|
import { TAB_BAR_HEIGHT } from '../Tabbar/CustomTabBar';
|
||||||
import CustomTabBar from '../Tabbar/CustomTabBar';
|
|
||||||
import { ERROR_TYPE, readData } from '../../utils/WebData';
|
import { ERROR_TYPE, readData } from '../../utils/WebData';
|
||||||
import CollapsibleSectionList from '../Collapsible/CollapsibleSectionList';
|
import CollapsibleSectionList from '../Collapsible/CollapsibleSectionList';
|
||||||
|
import GENERAL_STYLES from '../../constants/Styles';
|
||||||
|
|
||||||
export type SectionListDataType<ItemT> = Array<{
|
export type SectionListDataType<ItemT> = Array<{
|
||||||
title: string;
|
title: string;
|
||||||
|
@ -260,19 +260,20 @@ class WebSectionList<ItemT, RawData> extends React.PureComponent<
|
||||||
dataset = props.createDataset(state.fetchedData, state.refreshing);
|
dataset = props.createDataset(state.fetchedData, state.refreshing);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { containerPaddingTop } = props.collapsibleStack;
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View style={GENERAL_STYLES.flex}>
|
||||||
<CollapsibleSectionList
|
<CollapsibleSectionList
|
||||||
sections={dataset}
|
sections={dataset}
|
||||||
extraData={props.updateData}
|
extraData={props.updateData}
|
||||||
refreshControl={
|
paddedProps={(paddingTop) => ({
|
||||||
<RefreshControl
|
refreshControl: (
|
||||||
progressViewOffset={containerPaddingTop}
|
<RefreshControl
|
||||||
refreshing={state.refreshing}
|
progressViewOffset={paddingTop}
|
||||||
onRefresh={this.onRefresh}
|
refreshing={state.refreshing}
|
||||||
/>
|
onRefresh={this.onRefresh}
|
||||||
}
|
/>
|
||||||
|
),
|
||||||
|
})}
|
||||||
renderSectionHeader={this.getRenderSectionHeader}
|
renderSectionHeader={this.getRenderSectionHeader}
|
||||||
renderItem={this.getRenderItem}
|
renderItem={this.getRenderItem}
|
||||||
stickySectionHeadersEnabled={props.stickyHeader}
|
stickySectionHeadersEnabled={props.stickyHeader}
|
||||||
|
@ -299,7 +300,7 @@ class WebSectionList<ItemT, RawData> extends React.PureComponent<
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
onScroll={this.onScroll}
|
onScroll={this.onScroll}
|
||||||
hasTab
|
hasTab={true}
|
||||||
/>
|
/>
|
||||||
<Snackbar
|
<Snackbar
|
||||||
visible={state.snackbarVisible}
|
visible={state.snackbarVisible}
|
||||||
|
@ -310,7 +311,7 @@ class WebSectionList<ItemT, RawData> extends React.PureComponent<
|
||||||
}}
|
}}
|
||||||
duration={4000}
|
duration={4000}
|
||||||
style={{
|
style={{
|
||||||
bottom: CustomTabBar.TAB_BAR_HEIGHT,
|
bottom: TAB_BAR_HEIGHT,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18n.t('general.listUpdateFail')}
|
{i18n.t('general.listUpdateFail')}
|
||||||
|
@ -320,4 +321,4 @@ class WebSectionList<ItemT, RawData> extends React.PureComponent<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withCollapsible(WebSectionList);
|
export default WebSectionList;
|
||||||
|
|
|
@ -17,7 +17,13 @@
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from 'react';
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useLayoutEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import WebView from 'react-native-webview';
|
import WebView from 'react-native-webview';
|
||||||
import {
|
import {
|
||||||
Divider,
|
Divider,
|
||||||
|
@ -34,23 +40,21 @@ import {
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
import { withTheme } from 'react-native-paper';
|
import { useTheme } from 'react-native-paper';
|
||||||
import { StackNavigationProp } from '@react-navigation/stack';
|
import { useCollapsibleHeader } from 'react-navigation-collapsible';
|
||||||
import { Collapsible } from 'react-navigation-collapsible';
|
|
||||||
import withCollapsible from '../../utils/withCollapsible';
|
|
||||||
import MaterialHeaderButtons, { Item } from '../Overrides/CustomHeaderButton';
|
import MaterialHeaderButtons, { Item } from '../Overrides/CustomHeaderButton';
|
||||||
import { ERROR_TYPE } from '../../utils/WebData';
|
import { ERROR_TYPE } from '../../utils/WebData';
|
||||||
import ErrorView from './ErrorView';
|
import ErrorView from './ErrorView';
|
||||||
import BasicLoadingScreen from './BasicLoadingScreen';
|
import BasicLoadingScreen from './BasicLoadingScreen';
|
||||||
|
import { useFocusEffect, useNavigation } from '@react-navigation/core';
|
||||||
|
import { useCollapsible } from '../../utils/CollapsibleContext';
|
||||||
|
|
||||||
type PropsType = {
|
type Props = {
|
||||||
navigation: StackNavigationProp<any>;
|
|
||||||
theme: ReactNativePaper.Theme;
|
|
||||||
url: string;
|
url: string;
|
||||||
collapsibleStack: Collapsible;
|
onMessage?: (event: { nativeEvent: { data: string } }) => void;
|
||||||
onMessage: (event: { nativeEvent: { data: string } }) => void;
|
onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
|
||||||
onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
|
initialJS?: string;
|
||||||
customJS?: string;
|
injectJS?: string;
|
||||||
customPaddingFunction?: null | ((padding: number) => string);
|
customPaddingFunction?: null | ((padding: number) => string);
|
||||||
showAdvancedControls?: boolean;
|
showAdvancedControls?: boolean;
|
||||||
};
|
};
|
||||||
|
@ -66,134 +70,113 @@ const styles = StyleSheet.create({
|
||||||
/**
|
/**
|
||||||
* Class defining a webview screen.
|
* Class defining a webview screen.
|
||||||
*/
|
*/
|
||||||
class WebViewScreen extends React.PureComponent<PropsType> {
|
function WebViewScreen(props: Props) {
|
||||||
static defaultProps = {
|
const [currentUrl, setCurrentUrl] = useState(props.url);
|
||||||
customJS: '',
|
const [canGoBack, setCanGoBack] = useState(false);
|
||||||
showAdvancedControls: true,
|
const navigation = useNavigation();
|
||||||
customPaddingFunction: null,
|
const theme = useTheme();
|
||||||
};
|
const webviewRef = useRef<WebView>();
|
||||||
|
|
||||||
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;
|
useFocusEffect(
|
||||||
|
useCallback(() => {
|
||||||
constructor(props: PropsType) {
|
setCollapsible(collapsible);
|
||||||
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', () => {
|
|
||||||
BackHandler.addEventListener(
|
BackHandler.addEventListener(
|
||||||
'hardwareBackPress',
|
'hardwareBackPress',
|
||||||
this.onBackButtonPressAndroid
|
onBackButtonPressAndroid
|
||||||
);
|
);
|
||||||
});
|
return () => {
|
||||||
props.navigation.addListener('blur', () => {
|
BackHandler.removeEventListener(
|
||||||
BackHandler.removeEventListener(
|
'hardwareBackPress',
|
||||||
'hardwareBackPress',
|
onBackButtonPressAndroid
|
||||||
this.onBackButtonPressAndroid
|
);
|
||||||
);
|
};
|
||||||
});
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}
|
}, [collapsible, setCollapsible])
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
useLayoutEffect(() => {
|
||||||
* Goes back on the webview or on the navigation stack if we cannot go back anymore
|
navigation.setOptions({
|
||||||
*
|
headerRight: props.showAdvancedControls
|
||||||
* @returns {boolean}
|
? getAdvancedButtons
|
||||||
*/
|
: getBasicButton,
|
||||||
onBackButtonPressAndroid = (): boolean => {
|
});
|
||||||
if (this.canGoBack) {
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
this.onGoBackClicked();
|
}, [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 true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const getBasicButton = () => {
|
||||||
* Gets header refresh and open in browser buttons
|
|
||||||
*
|
|
||||||
* @return {*}
|
|
||||||
*/
|
|
||||||
getBasicButton = () => {
|
|
||||||
return (
|
return (
|
||||||
<MaterialHeaderButtons>
|
<MaterialHeaderButtons>
|
||||||
<Item
|
<Item
|
||||||
title="refresh"
|
title={'refresh'}
|
||||||
iconName="refresh"
|
iconName={'refresh'}
|
||||||
onPress={this.onRefreshClicked}
|
onPress={onRefreshClicked}
|
||||||
/>
|
/>
|
||||||
<Item
|
<Item
|
||||||
title={i18n.t('general.openInBrowser')}
|
title={i18n.t('general.openInBrowser')}
|
||||||
iconName="open-in-new"
|
iconName={'open-in-new'}
|
||||||
onPress={this.onOpenClicked}
|
onPress={onOpenClicked}
|
||||||
/>
|
/>
|
||||||
</MaterialHeaderButtons>
|
</MaterialHeaderButtons>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const getAdvancedButtons = () => {
|
||||||
* 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;
|
|
||||||
return (
|
return (
|
||||||
<MaterialHeaderButtons>
|
<MaterialHeaderButtons>
|
||||||
<Item
|
<Item title="refresh" iconName="refresh" onPress={onRefreshClicked} />
|
||||||
title="refresh"
|
|
||||||
iconName="refresh"
|
|
||||||
onPress={this.onRefreshClicked}
|
|
||||||
/>
|
|
||||||
<OverflowMenu
|
<OverflowMenu
|
||||||
style={styles.overflow}
|
style={styles.overflow}
|
||||||
OverflowIcon={
|
OverflowIcon={
|
||||||
<MaterialCommunityIcons
|
<MaterialCommunityIcons
|
||||||
name="dots-vertical"
|
name="dots-vertical"
|
||||||
size={26}
|
size={26}
|
||||||
color={props.theme.colors.text}
|
color={theme.colors.text}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<HiddenItem
|
<HiddenItem
|
||||||
title={i18n.t('general.goBack')}
|
title={i18n.t('general.goBack')}
|
||||||
onPress={this.onGoBackClicked}
|
onPress={onGoBackClicked}
|
||||||
/>
|
/>
|
||||||
<HiddenItem
|
<HiddenItem
|
||||||
title={i18n.t('general.goForward')}
|
title={i18n.t('general.goForward')}
|
||||||
onPress={this.onGoForwardClicked}
|
onPress={onGoForwardClicked}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
<HiddenItem
|
<HiddenItem
|
||||||
title={i18n.t('general.openInBrowser')}
|
title={i18n.t('general.openInBrowser')}
|
||||||
onPress={this.onOpenClicked}
|
onPress={onOpenClicked}
|
||||||
/>
|
/>
|
||||||
</OverflowMenu>
|
</OverflowMenu>
|
||||||
</MaterialHeaderButtons>
|
</MaterialHeaderButtons>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const getRenderLoading = () => <BasicLoadingScreen isAbsolute={true} />;
|
||||||
* Gets the loading indicator
|
|
||||||
*
|
|
||||||
* @return {*}
|
|
||||||
*/
|
|
||||||
getRenderLoading = () => <BasicLoadingScreen isAbsolute />;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the javascript needed to generate a padding on top of the page
|
* Gets the javascript needed to generate a padding on top of the page
|
||||||
|
@ -202,91 +185,78 @@ class WebViewScreen extends React.PureComponent<PropsType> {
|
||||||
* @param padding The padding to add in pixels
|
* @param padding The padding to add in pixels
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
getJavascriptPadding(padding: number): string {
|
const getJavascriptPadding = (padding: number) => {
|
||||||
const { props } = this;
|
|
||||||
const customPadding =
|
const customPadding =
|
||||||
props.customPaddingFunction != null
|
props.customPaddingFunction != null
|
||||||
? props.customPaddingFunction(padding)
|
? props.customPaddingFunction(padding)
|
||||||
: '';
|
: '';
|
||||||
return `document.getElementsByTagName('body')[0].style.paddingTop = '${padding}px';${customPadding}true;`;
|
return `document.getElementsByTagName('body')[0].style.paddingTop = '${padding}px';${customPadding}true;`;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
const onRefreshClicked = () => {
|
||||||
* Callback to use when refresh button is clicked. Reloads the webview.
|
//@ts-ignore
|
||||||
*/
|
if (webviewRef.current) {
|
||||||
onRefreshClicked = () => {
|
//@ts-ignore
|
||||||
if (this.webviewRef.current != null) {
|
webviewRef.current.reload();
|
||||||
this.webviewRef.current.reload();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onGoBackClicked = () => {
|
const onGoBackClicked = () => {
|
||||||
if (this.webviewRef.current != null) {
|
//@ts-ignore
|
||||||
this.webviewRef.current.goBack();
|
if (webviewRef.current) {
|
||||||
|
//@ts-ignore
|
||||||
|
webviewRef.current.goBack();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onGoForwardClicked = () => {
|
const onGoForwardClicked = () => {
|
||||||
if (this.webviewRef.current != null) {
|
//@ts-ignore
|
||||||
this.webviewRef.current.goForward();
|
if (webviewRef.current) {
|
||||||
|
//@ts-ignore
|
||||||
|
webviewRef.current.goForward();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onOpenClicked = () => {
|
const onOpenClicked = () => Linking.openURL(currentUrl);
|
||||||
Linking.openURL(this.currentUrl);
|
|
||||||
};
|
|
||||||
|
|
||||||
onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
const onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||||
const { onScroll } = this.props;
|
if (props.onScroll) {
|
||||||
if (onScroll) {
|
props.onScroll(event);
|
||||||
onScroll(event);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const injectJavaScript = (script: string) => {
|
||||||
* Injects the given javascript string into the web page
|
//@ts-ignore
|
||||||
*
|
if (webviewRef.current) {
|
||||||
* @param script The script to inject
|
//@ts-ignore
|
||||||
*/
|
webviewRef.current.injectJavaScript(script);
|
||||||
injectJavaScript = (script: string) => {
|
|
||||||
if (this.webviewRef.current != null) {
|
|
||||||
this.webviewRef.current.injectJavaScript(script);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
return (
|
||||||
const { props } = this;
|
<AnimatedWebView
|
||||||
const {
|
ref={webviewRef}
|
||||||
containerPaddingTop,
|
source={{ uri: props.url }}
|
||||||
onScrollWithListener,
|
startInLoadingState={true}
|
||||||
} = props.collapsibleStack;
|
injectedJavaScript={props.initialJS}
|
||||||
return (
|
javaScriptEnabled={true}
|
||||||
<AnimatedWebView
|
renderLoading={getRenderLoading}
|
||||||
ref={this.webviewRef}
|
renderError={() => (
|
||||||
source={{ uri: props.url }}
|
<ErrorView
|
||||||
startInLoadingState
|
errorCode={ERROR_TYPE.CONNECTION_ERROR}
|
||||||
injectedJavaScript={props.customJS}
|
onRefresh={onRefreshClicked}
|
||||||
javaScriptEnabled
|
/>
|
||||||
renderLoading={this.getRenderLoading}
|
)}
|
||||||
renderError={() => (
|
onNavigationStateChange={(navState) => {
|
||||||
<ErrorView
|
setCurrentUrl(navState.url);
|
||||||
errorCode={ERROR_TYPE.CONNECTION_ERROR}
|
setCanGoBack(navState.canGoBack);
|
||||||
onRefresh={this.onRefreshClicked}
|
}}
|
||||||
/>
|
onMessage={props.onMessage}
|
||||||
)}
|
onLoad={() => injectJavaScript(getJavascriptPadding(containerPaddingTop))}
|
||||||
onNavigationStateChange={(navState) => {
|
// Animations
|
||||||
this.currentUrl = navState.url;
|
onScroll={onScrollWithListener(onScroll)}
|
||||||
this.canGoBack = navState.canGoBack;
|
/>
|
||||||
}}
|
);
|
||||||
onMessage={props.onMessage}
|
|
||||||
onLoad={() => {
|
|
||||||
this.injectJavaScript(this.getJavascriptPadding(containerPaddingTop));
|
|
||||||
}}
|
|
||||||
// Animations
|
|
||||||
onScroll={(event) => onScrollWithListener(this.onScroll)(event)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withCollapsible(withTheme(WebViewScreen));
|
export default WebViewScreen;
|
||||||
|
|
|
@ -17,211 +17,88 @@
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from 'react';
|
import React from 'react';
|
||||||
|
import type { BottomTabBarProps } from '@react-navigation/bottom-tabs';
|
||||||
import { Animated, StyleSheet } from 'react-native';
|
import { Animated, StyleSheet } from 'react-native';
|
||||||
import { withTheme } from 'react-native-paper';
|
|
||||||
import { Collapsible } from 'react-navigation-collapsible';
|
|
||||||
import TabIcon from './TabIcon';
|
import TabIcon from './TabIcon';
|
||||||
import TabHomeIcon from './TabHomeIcon';
|
import { useTheme } from 'react-native-paper';
|
||||||
import { BottomTabBarProps } from '@react-navigation/bottom-tabs';
|
import { useCollapsible } from '../../utils/CollapsibleContext';
|
||||||
import { NavigationState } from '@react-navigation/native';
|
|
||||||
import {
|
|
||||||
PartialState,
|
|
||||||
Route,
|
|
||||||
} from '@react-navigation/routers/lib/typescript/src/types';
|
|
||||||
|
|
||||||
type RouteType = Route<string> & {
|
export const TAB_BAR_HEIGHT = 50;
|
||||||
state?: NavigationState | PartialState<NavigationState>;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface PropsType extends BottomTabBarProps {
|
function CustomTabBar(
|
||||||
theme: ReactNativePaper.Theme;
|
props: BottomTabBarProps<any> & {
|
||||||
|
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 (
|
||||||
|
<Animated.View
|
||||||
|
style={{
|
||||||
|
...styles.bar,
|
||||||
|
backgroundColor: theme.colors.surface,
|
||||||
|
transform: [{ translateY: translateY }],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{state.routes.map(
|
||||||
|
(
|
||||||
|
route: {
|
||||||
|
key: string;
|
||||||
|
name: string;
|
||||||
|
params?: object | undefined;
|
||||||
|
},
|
||||||
|
index: number
|
||||||
|
) => {
|
||||||
|
const iconData = props.icons[route.name];
|
||||||
|
return (
|
||||||
|
<TabIcon
|
||||||
|
isMiddle={index === 2}
|
||||||
|
onPress={() => props.navigation.navigate(route.name)}
|
||||||
|
icon={iconData.normal}
|
||||||
|
focusedIcon={iconData.focused}
|
||||||
|
label={props.labels[route.name]}
|
||||||
|
focused={state.index === index}
|
||||||
|
key={route.key}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</Animated.View>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
bar: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
height: 50,
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
class CustomTabBar extends React.Component<PropsType, StateType> {
|
function areEqual(
|
||||||
static TAB_BAR_HEIGHT = 48;
|
prevProps: BottomTabBarProps<any>,
|
||||||
|
nextProps: BottomTabBarProps<any>
|
||||||
constructor(props: PropsType) {
|
) {
|
||||||
super(props);
|
return prevProps.state.index === nextProps.state.index;
|
||||||
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 (
|
|
||||||
<TabIcon
|
|
||||||
onPress={onPress}
|
|
||||||
onLongPress={onLongPress}
|
|
||||||
icon={this.getTabBarIcon(route, isFocused)}
|
|
||||||
color={color}
|
|
||||||
label={label as string}
|
|
||||||
focused={isFocused}
|
|
||||||
extraData={state.index > index}
|
|
||||||
key={route.key}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<TabHomeIcon
|
|
||||||
onPress={onPress}
|
|
||||||
onLongPress={onLongPress}
|
|
||||||
focused={isFocused}
|
|
||||||
key={route.key}
|
|
||||||
tabBarHeight={CustomTabBar.TAB_BAR_HEIGHT}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<Animated.View
|
|
||||||
style={{
|
|
||||||
height: CustomTabBar.TAB_BAR_HEIGHT,
|
|
||||||
backgroundColor: props.theme.colors.surface,
|
|
||||||
transform: [{ translateY: state.translateY }],
|
|
||||||
...styles.container,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{icons}
|
|
||||||
</Animated.View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withTheme(CustomTabBar);
|
export default React.memo(CustomTabBar, areEqual);
|
||||||
|
|
|
@ -1,135 +1,114 @@
|
||||||
/*
|
import React from 'react';
|
||||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
import { View, StyleSheet, Image } from 'react-native';
|
||||||
*
|
|
||||||
* 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as React from 'react';
|
|
||||||
import { Image, StyleSheet, View } from 'react-native';
|
|
||||||
import { FAB } from 'react-native-paper';
|
import { FAB } from 'react-native-paper';
|
||||||
import * as Animatable from 'react-native-animatable';
|
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;
|
focused: boolean;
|
||||||
onPress: () => void;
|
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({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
outer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
subcontainer: {
|
inner: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
marginBottom: -15,
|
height: 60,
|
||||||
},
|
},
|
||||||
fab: {
|
fab: {
|
||||||
marginTop: 15,
|
|
||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
marginRight: 'auto',
|
marginRight: 'auto',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
const FOCUSED_ICON = require('../../../assets/tab-icon.png');
|
||||||
* Abstraction layer for Agenda component, using custom configuration
|
const UNFOCUSED_ICON = require('../../../assets/tab-icon-outline.png');
|
||||||
*/
|
|
||||||
class TabHomeIcon extends React.Component<PropsType> {
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: PropsType): boolean {
|
function TabHomeIcon(props: Props) {
|
||||||
const { focused } = this.props;
|
const getImage = (iconProps: { size: number; color: string }) => {
|
||||||
return nextProps.focused !== focused;
|
|
||||||
}
|
|
||||||
|
|
||||||
getIconRender = ({ size, color }: { size: number; color: string }) => {
|
|
||||||
const { focused } = this.props;
|
|
||||||
return (
|
return (
|
||||||
<Image
|
<Animatable.View useNativeDriver={true} animation={'rubberBand'}>
|
||||||
source={focused ? FOCUSED_ICON : UNFOCUSED_ICON}
|
<Image
|
||||||
style={{
|
source={props.focused ? FOCUSED_ICON : UNFOCUSED_ICON}
|
||||||
width: size,
|
style={{
|
||||||
height: size,
|
width: iconProps.size,
|
||||||
tintColor: color,
|
height: iconProps.size,
|
||||||
}}
|
tintColor: iconProps.color,
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</Animatable.View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
return (
|
||||||
const { props } = this;
|
<View style={styles.outer}>
|
||||||
return (
|
<View style={styles.inner}>
|
||||||
<View style={styles.container}>
|
<Animatable.View
|
||||||
<View
|
style={styles.fab}
|
||||||
style={{
|
useNativeDriver={true}
|
||||||
height: props.tabBarHeight + 30,
|
duration={props.focused ? 500 : 200}
|
||||||
...styles.subcontainer,
|
animation={props.focused ? 'fabFocusIn' : 'fabFocusOut'}
|
||||||
}}
|
easing={'ease-out'}
|
||||||
>
|
>
|
||||||
<AnimatedFAB
|
<FAB
|
||||||
duration={200}
|
|
||||||
easing="ease-out"
|
|
||||||
animation={props.focused ? 'fabFocusIn' : 'fabFocusOut'}
|
|
||||||
icon={this.getIconRender}
|
|
||||||
onPress={props.onPress}
|
onPress={props.onPress}
|
||||||
onLongPress={props.onLongPress}
|
animated={false}
|
||||||
style={styles.fab}
|
icon={getImage}
|
||||||
|
color={'#fff'}
|
||||||
/>
|
/>
|
||||||
</View>
|
</Animatable.View>
|
||||||
</View>
|
</View>
|
||||||
);
|
</View>
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TabHomeIcon;
|
export default TabHomeIcon;
|
||||||
|
|
|
@ -1,143 +1,41 @@
|
||||||
/*
|
import React from 'react';
|
||||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
import TabHomeIcon from './TabHomeIcon';
|
||||||
*
|
import TabSideIcon from './TabSideIcon';
|
||||||
* 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as React from 'react';
|
interface Props {
|
||||||
import { StyleSheet, View } from 'react-native';
|
isMiddle: boolean;
|
||||||
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 = {
|
|
||||||
focused: boolean;
|
focused: boolean;
|
||||||
color: string;
|
label: string | undefined;
|
||||||
label: string;
|
|
||||||
icon: string;
|
icon: string;
|
||||||
|
focusedIcon: string;
|
||||||
onPress: () => void;
|
onPress: () => void;
|
||||||
onLongPress: () => void;
|
}
|
||||||
theme: ReactNativePaper.Theme;
|
|
||||||
extraData: null | boolean | number | string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
function TabIcon(props: Props) {
|
||||||
container: {
|
if (props.isMiddle) {
|
||||||
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<PropsType> {
|
|
||||||
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;
|
|
||||||
return (
|
return (
|
||||||
nextProps.focused !== props.focused ||
|
<TabHomeIcon
|
||||||
nextProps.theme.dark !== props.theme.dark ||
|
icon={props.icon}
|
||||||
nextProps.extraData !== props.extraData
|
focusedIcon={props.focusedIcon}
|
||||||
);
|
focused={props.focused}
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { props } = this;
|
|
||||||
return (
|
|
||||||
<TouchableRipple
|
|
||||||
onPress={props.onPress}
|
onPress={props.onPress}
|
||||||
onLongPress={props.onLongPress}
|
/>
|
||||||
rippleColor={props.theme.colors.primary}
|
);
|
||||||
borderless={true}
|
} else {
|
||||||
style={styles.container}
|
return (
|
||||||
>
|
<TabSideIcon
|
||||||
<View>
|
focused={props.focused}
|
||||||
<Animatable.View
|
label={props.label}
|
||||||
duration={200}
|
icon={props.icon}
|
||||||
easing="ease-out"
|
focusedIcon={props.focusedIcon}
|
||||||
animation={props.focused ? 'focusIn' : 'focusOut'}
|
onPress={props.onPress}
|
||||||
useNativeDriver
|
/>
|
||||||
>
|
|
||||||
<MaterialCommunityIcons
|
|
||||||
name={props.icon}
|
|
||||||
color={props.color}
|
|
||||||
size={26}
|
|
||||||
style={GENERAL_STYLES.centerHorizontal}
|
|
||||||
/>
|
|
||||||
</Animatable.View>
|
|
||||||
<Animatable.Text
|
|
||||||
animation={props.focused ? 'fadeOutDown' : 'fadeIn'}
|
|
||||||
useNativeDriver
|
|
||||||
style={{
|
|
||||||
color: props.color,
|
|
||||||
...styles.text,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.label}
|
|
||||||
</Animatable.Text>
|
|
||||||
</View>
|
|
||||||
</TouchableRipple>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withTheme(TabIcon);
|
function areEqual(prevProps: Props, nextProps: Props) {
|
||||||
|
return prevProps.focused === nextProps.focused;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(TabIcon, areEqual);
|
||||||
|
|
113
src/components/Tabbar/TabSideIcon.tsx
Normal file
113
src/components/Tabbar/TabSideIcon.tsx
Normal file
|
@ -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 (
|
||||||
|
<TouchableRipple
|
||||||
|
onPress={props.onPress}
|
||||||
|
borderless
|
||||||
|
rippleColor={theme.colors.primary}
|
||||||
|
style={{
|
||||||
|
...styles.ripple,
|
||||||
|
borderTopEndRadius: theme.roundness,
|
||||||
|
borderTopStartRadius: theme.roundness,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View>
|
||||||
|
<Animatable.View
|
||||||
|
duration={props.focused ? 500 : 200}
|
||||||
|
easing="ease-out"
|
||||||
|
animation={props.focused ? 'focusIn' : 'focusOut'}
|
||||||
|
useNativeDriver
|
||||||
|
>
|
||||||
|
<MaterialCommunityIcons
|
||||||
|
name={icon}
|
||||||
|
color={color}
|
||||||
|
size={26}
|
||||||
|
style={GENERAL_STYLES.centerHorizontal}
|
||||||
|
/>
|
||||||
|
</Animatable.View>
|
||||||
|
<Animatable.Text
|
||||||
|
animation={props.focused ? 'fadeOutDown' : 'fadeIn'}
|
||||||
|
useNativeDriver
|
||||||
|
style={{
|
||||||
|
color: color,
|
||||||
|
...styles.text,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.label}
|
||||||
|
</Animatable.Text>
|
||||||
|
</View>
|
||||||
|
</TouchableRipple>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
ripple: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
...GENERAL_STYLES.centerHorizontal,
|
||||||
|
fontSize: 10,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default TabSideIcon;
|
33
src/components/providers/CollapsibleProvider.tsx
Normal file
33
src/components/providers/CollapsibleProvider.tsx
Normal file
|
@ -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<CollapsibleContextType>({
|
||||||
|
collapsible: undefined,
|
||||||
|
setCollapsible: setCollapsible,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CollapsibleContext.Provider value={currentCollapsible}>
|
||||||
|
{props.children}
|
||||||
|
</CollapsibleContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
|
@ -18,12 +18,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {
|
import { createStackNavigator } from '@react-navigation/stack';
|
||||||
createStackNavigator,
|
|
||||||
TransitionPresets,
|
|
||||||
} from '@react-navigation/stack';
|
|
||||||
import i18n from 'i18n-js';
|
import i18n from 'i18n-js';
|
||||||
import { Platform } from 'react-native';
|
|
||||||
import SettingsScreen from '../screens/Other/Settings/SettingsScreen';
|
import SettingsScreen from '../screens/Other/Settings/SettingsScreen';
|
||||||
import AboutScreen from '../screens/About/AboutScreen';
|
import AboutScreen from '../screens/About/AboutScreen';
|
||||||
import AboutDependenciesScreen from '../screens/About/AboutDependenciesScreen';
|
import AboutDependenciesScreen from '../screens/About/AboutDependenciesScreen';
|
||||||
|
@ -40,10 +36,6 @@ import ProfileScreen from '../screens/Amicale/ProfileScreen';
|
||||||
import ClubListScreen from '../screens/Amicale/Clubs/ClubListScreen';
|
import ClubListScreen from '../screens/Amicale/Clubs/ClubListScreen';
|
||||||
import ClubAboutScreen from '../screens/Amicale/Clubs/ClubAboutScreen';
|
import ClubAboutScreen from '../screens/Amicale/Clubs/ClubAboutScreen';
|
||||||
import ClubDisplayScreen from '../screens/Amicale/Clubs/ClubDisplayScreen';
|
import ClubDisplayScreen from '../screens/Amicale/Clubs/ClubDisplayScreen';
|
||||||
import {
|
|
||||||
CreateScreenCollapsibleStack,
|
|
||||||
getWebsiteStack,
|
|
||||||
} from '../utils/CollapsibleUtils';
|
|
||||||
import BugReportScreen from '../screens/Other/FeedbackScreen';
|
import BugReportScreen from '../screens/Other/FeedbackScreen';
|
||||||
import WebsiteScreen from '../screens/Services/WebsiteScreen';
|
import WebsiteScreen from '../screens/Services/WebsiteScreen';
|
||||||
import EquipmentScreen, {
|
import EquipmentScreen, {
|
||||||
|
@ -54,6 +46,7 @@ import EquipmentConfirmScreen from '../screens/Amicale/Equipment/EquipmentConfir
|
||||||
import DashboardEditScreen from '../screens/Other/Settings/DashboardEditScreen';
|
import DashboardEditScreen from '../screens/Other/Settings/DashboardEditScreen';
|
||||||
import GameStartScreen from '../screens/Game/screens/GameStartScreen';
|
import GameStartScreen from '../screens/Game/screens/GameStartScreen';
|
||||||
import ImageGalleryScreen from '../screens/Media/ImageGalleryScreen';
|
import ImageGalleryScreen from '../screens/Media/ImageGalleryScreen';
|
||||||
|
import Test from '../screens/Test';
|
||||||
|
|
||||||
export enum MainRoutes {
|
export enum MainRoutes {
|
||||||
Main = 'main',
|
Main = 'main',
|
||||||
|
@ -83,7 +76,7 @@ export enum MainRoutes {
|
||||||
|
|
||||||
type DefaultParams = { [key in MainRoutes]: object | undefined };
|
type DefaultParams = { [key in MainRoutes]: object | undefined };
|
||||||
|
|
||||||
export interface FullParamsList extends DefaultParams {
|
export type FullParamsList = DefaultParams & {
|
||||||
'login': { nextScreen: string };
|
'login': { nextScreen: string };
|
||||||
'equipment-confirm': {
|
'equipment-confirm': {
|
||||||
item?: DeviceType;
|
item?: DeviceType;
|
||||||
|
@ -91,34 +84,22 @@ export interface FullParamsList extends DefaultParams {
|
||||||
};
|
};
|
||||||
'equipment-rent': { item?: DeviceType };
|
'equipment-rent': { item?: DeviceType };
|
||||||
'gallery': { images: Array<{ url: string }> };
|
'gallery': { images: Array<{ url: string }> };
|
||||||
}
|
};
|
||||||
|
|
||||||
// Don't know why but TS is complaining without this
|
// 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
|
// See: https://stackoverflow.com/questions/63652687/interface-does-not-satisfy-the-constraint-recordstring-object-undefined
|
||||||
export type MainStackParamsList = FullParamsList &
|
export type MainStackParamsList = FullParamsList &
|
||||||
Record<string, object | undefined>;
|
Record<string, object | undefined>;
|
||||||
|
|
||||||
const modalTransition =
|
|
||||||
Platform.OS === 'ios'
|
|
||||||
? TransitionPresets.ModalPresentationIOS
|
|
||||||
: TransitionPresets.ModalTransition;
|
|
||||||
|
|
||||||
const defaultScreenOptions = {
|
|
||||||
gestureEnabled: true,
|
|
||||||
cardOverlayEnabled: true,
|
|
||||||
...TransitionPresets.SlideFromRightIOS,
|
|
||||||
};
|
|
||||||
|
|
||||||
const MainStack = createStackNavigator<MainStackParamsList>();
|
const MainStack = createStackNavigator<MainStackParamsList>();
|
||||||
|
|
||||||
function MainStackComponent(props: { createTabNavigator: () => JSX.Element }) {
|
function MainStackComponent(props: {
|
||||||
|
createTabNavigator: () => React.ReactElement;
|
||||||
|
}) {
|
||||||
const { createTabNavigator } = props;
|
const { createTabNavigator } = props;
|
||||||
return (
|
return (
|
||||||
<MainStack.Navigator
|
<MainStack.Navigator initialRouteName={MainRoutes.Main} headerMode="screen">
|
||||||
initialRouteName={MainRoutes.Main}
|
<MainStack.Screen name={'test'} component={Test} />
|
||||||
headerMode="screen"
|
|
||||||
screenOptions={defaultScreenOptions}
|
|
||||||
>
|
|
||||||
<MainStack.Screen
|
<MainStack.Screen
|
||||||
name={MainRoutes.Main}
|
name={MainRoutes.Main}
|
||||||
component={createTabNavigator}
|
component={createTabNavigator}
|
||||||
|
@ -132,49 +113,53 @@ function MainStackComponent(props: { createTabNavigator: () => JSX.Element }) {
|
||||||
component={ImageGalleryScreen}
|
component={ImageGalleryScreen}
|
||||||
options={{
|
options={{
|
||||||
headerShown: false,
|
headerShown: false,
|
||||||
...modalTransition,
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{CreateScreenCollapsibleStack(
|
<MainStack.Screen
|
||||||
MainRoutes.Settings,
|
name={MainRoutes.Settings}
|
||||||
MainStack,
|
component={SettingsScreen}
|
||||||
SettingsScreen,
|
options={{
|
||||||
i18n.t('screens.settings.title')
|
title: i18n.t('screens.settings.title'),
|
||||||
)}
|
}}
|
||||||
{CreateScreenCollapsibleStack(
|
/>
|
||||||
MainRoutes.DashboardEdit,
|
<MainStack.Screen
|
||||||
MainStack,
|
name={MainRoutes.DashboardEdit}
|
||||||
DashboardEditScreen,
|
component={DashboardEditScreen}
|
||||||
i18n.t('screens.settings.dashboardEdit.title')
|
options={{
|
||||||
)}
|
title: i18n.t('screens.settings.dashboardEdit.title'),
|
||||||
{CreateScreenCollapsibleStack(
|
}}
|
||||||
MainRoutes.About,
|
/>
|
||||||
MainStack,
|
<MainStack.Screen
|
||||||
AboutScreen,
|
name={MainRoutes.About}
|
||||||
i18n.t('screens.about.title')
|
component={AboutScreen}
|
||||||
)}
|
options={{
|
||||||
{CreateScreenCollapsibleStack(
|
title: i18n.t('screens.about.title'),
|
||||||
MainRoutes.Dependencies,
|
}}
|
||||||
MainStack,
|
/>
|
||||||
AboutDependenciesScreen,
|
<MainStack.Screen
|
||||||
i18n.t('screens.about.libs')
|
name={MainRoutes.Dependencies}
|
||||||
)}
|
component={AboutDependenciesScreen}
|
||||||
{CreateScreenCollapsibleStack(
|
options={{
|
||||||
MainRoutes.Debug,
|
title: i18n.t('screens.about.libs'),
|
||||||
MainStack,
|
}}
|
||||||
DebugScreen,
|
/>
|
||||||
i18n.t('screens.about.debug')
|
<MainStack.Screen
|
||||||
)}
|
name={MainRoutes.Debug}
|
||||||
|
component={DebugScreen}
|
||||||
{CreateScreenCollapsibleStack(
|
options={{
|
||||||
MainRoutes.GameStart,
|
title: i18n.t('screens.about.debug'),
|
||||||
MainStack,
|
}}
|
||||||
GameStartScreen,
|
/>
|
||||||
i18n.t('screens.game.title'),
|
<MainStack.Screen
|
||||||
true,
|
name={MainRoutes.GameStart}
|
||||||
undefined,
|
component={GameStartScreen}
|
||||||
'transparent'
|
options={{
|
||||||
)}
|
title: i18n.t('screens.game.title'),
|
||||||
|
headerStyle: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<MainStack.Screen
|
<MainStack.Screen
|
||||||
name={MainRoutes.GameMain}
|
name={MainRoutes.GameMain}
|
||||||
component={GameMainScreen}
|
component={GameMainScreen}
|
||||||
|
@ -182,102 +167,114 @@ function MainStackComponent(props: { createTabNavigator: () => JSX.Element }) {
|
||||||
title: i18n.t('screens.game.title'),
|
title: i18n.t('screens.game.title'),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{CreateScreenCollapsibleStack(
|
<MainStack.Screen
|
||||||
MainRoutes.Login,
|
name={MainRoutes.Login}
|
||||||
MainStack,
|
component={LoginScreen}
|
||||||
LoginScreen,
|
options={{
|
||||||
i18n.t('screens.login.title'),
|
title: i18n.t('screens.login.title'),
|
||||||
true,
|
headerStyle: {
|
||||||
{ headerTintColor: '#fff' },
|
backgroundColor: 'transparent',
|
||||||
'transparent'
|
},
|
||||||
)}
|
}}
|
||||||
{getWebsiteStack('website', MainStack, WebsiteScreen, '')}
|
/>
|
||||||
|
<MainStack.Screen
|
||||||
{CreateScreenCollapsibleStack(
|
name={'website'}
|
||||||
MainRoutes.SelfMenu,
|
component={WebsiteScreen}
|
||||||
MainStack,
|
options={{
|
||||||
SelfMenuScreen,
|
title: '',
|
||||||
i18n.t('screens.menu.title')
|
}}
|
||||||
)}
|
/>
|
||||||
{CreateScreenCollapsibleStack(
|
<MainStack.Screen
|
||||||
MainRoutes.Proximo,
|
name={MainRoutes.SelfMenu}
|
||||||
MainStack,
|
component={SelfMenuScreen}
|
||||||
ProximoMainScreen,
|
options={{
|
||||||
i18n.t('screens.proximo.title')
|
title: i18n.t('screens.menu.title'),
|
||||||
)}
|
}}
|
||||||
{CreateScreenCollapsibleStack(
|
/>
|
||||||
MainRoutes.ProximoList,
|
<MainStack.Screen
|
||||||
MainStack,
|
name={MainRoutes.Proximo}
|
||||||
ProximoListScreen,
|
component={ProximoMainScreen}
|
||||||
i18n.t('screens.proximo.articleList')
|
options={{
|
||||||
)}
|
title: i18n.t('screens.proximo.title'),
|
||||||
{CreateScreenCollapsibleStack(
|
}}
|
||||||
MainRoutes.ProximoAbout,
|
/>
|
||||||
MainStack,
|
<MainStack.Screen
|
||||||
ProximoAboutScreen,
|
name={MainRoutes.ProximoList}
|
||||||
i18n.t('screens.proximo.title'),
|
component={ProximoListScreen}
|
||||||
true,
|
options={{
|
||||||
{ ...modalTransition }
|
title: i18n.t('screens.proximo.articleList'),
|
||||||
)}
|
}}
|
||||||
|
/>
|
||||||
{CreateScreenCollapsibleStack(
|
<MainStack.Screen
|
||||||
MainRoutes.Profile,
|
name={MainRoutes.ProximoAbout}
|
||||||
MainStack,
|
component={ProximoAboutScreen}
|
||||||
ProfileScreen,
|
options={{
|
||||||
i18n.t('screens.profile.title')
|
title: i18n.t('screens.proximo.title'),
|
||||||
)}
|
}}
|
||||||
{CreateScreenCollapsibleStack(
|
/>
|
||||||
MainRoutes.ClubList,
|
<MainStack.Screen
|
||||||
MainStack,
|
name={MainRoutes.Profile}
|
||||||
ClubListScreen,
|
component={ProfileScreen}
|
||||||
i18n.t('screens.clubs.title')
|
options={{
|
||||||
)}
|
title: i18n.t('screens.profile.title'),
|
||||||
{CreateScreenCollapsibleStack(
|
}}
|
||||||
MainRoutes.ClubInformation,
|
/>
|
||||||
MainStack,
|
<MainStack.Screen
|
||||||
ClubDisplayScreen,
|
name={MainRoutes.ClubList}
|
||||||
i18n.t('screens.clubs.details'),
|
component={ClubListScreen}
|
||||||
true,
|
options={{
|
||||||
{ ...modalTransition }
|
title: i18n.t('screens.clubs.title'),
|
||||||
)}
|
}}
|
||||||
{CreateScreenCollapsibleStack(
|
/>
|
||||||
MainRoutes.ClubAbout,
|
<MainStack.Screen
|
||||||
MainStack,
|
name={MainRoutes.ClubInformation}
|
||||||
ClubAboutScreen,
|
component={ClubDisplayScreen}
|
||||||
i18n.t('screens.clubs.title'),
|
options={{
|
||||||
true,
|
title: i18n.t('screens.clubs.details'),
|
||||||
{ ...modalTransition }
|
}}
|
||||||
)}
|
/>
|
||||||
{CreateScreenCollapsibleStack(
|
<MainStack.Screen
|
||||||
MainRoutes.EquipmentList,
|
name={MainRoutes.ClubAbout}
|
||||||
MainStack,
|
component={ClubAboutScreen}
|
||||||
EquipmentScreen,
|
options={{
|
||||||
i18n.t('screens.equipment.title')
|
title: i18n.t('screens.clubs.title'),
|
||||||
)}
|
}}
|
||||||
{CreateScreenCollapsibleStack(
|
/>
|
||||||
MainRoutes.EquipmentRent,
|
<MainStack.Screen
|
||||||
MainStack,
|
name={MainRoutes.EquipmentList}
|
||||||
EquipmentLendScreen,
|
component={EquipmentScreen}
|
||||||
i18n.t('screens.equipment.book')
|
options={{
|
||||||
)}
|
title: i18n.t('screens.equipment.title'),
|
||||||
{CreateScreenCollapsibleStack(
|
}}
|
||||||
MainRoutes.EquipmentConfirm,
|
/>
|
||||||
MainStack,
|
<MainStack.Screen
|
||||||
EquipmentConfirmScreen,
|
name={MainRoutes.EquipmentRent}
|
||||||
i18n.t('screens.equipment.confirm')
|
component={EquipmentLendScreen}
|
||||||
)}
|
options={{
|
||||||
{CreateScreenCollapsibleStack(
|
title: i18n.t('screens.equipment.book'),
|
||||||
MainRoutes.Vote,
|
}}
|
||||||
MainStack,
|
/>
|
||||||
VoteScreen,
|
<MainStack.Screen
|
||||||
i18n.t('screens.vote.title')
|
name={MainRoutes.EquipmentConfirm}
|
||||||
)}
|
component={EquipmentConfirmScreen}
|
||||||
{CreateScreenCollapsibleStack(
|
options={{
|
||||||
MainRoutes.Feedback,
|
title: i18n.t('screens.equipment.confirm'),
|
||||||
MainStack,
|
}}
|
||||||
BugReportScreen,
|
/>
|
||||||
i18n.t('screens.feedback.title')
|
<MainStack.Screen
|
||||||
)}
|
name={MainRoutes.Vote}
|
||||||
|
component={VoteScreen}
|
||||||
|
options={{
|
||||||
|
title: i18n.t('screens.vote.title'),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<MainStack.Screen
|
||||||
|
name={MainRoutes.Feedback}
|
||||||
|
component={BugReportScreen}
|
||||||
|
options={{
|
||||||
|
title: i18n.t('screens.feedback.title'),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</MainStack.Navigator>
|
</MainStack.Navigator>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,16 +18,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {
|
import { createStackNavigator } from '@react-navigation/stack';
|
||||||
createStackNavigator,
|
|
||||||
TransitionPresets,
|
|
||||||
} from '@react-navigation/stack';
|
|
||||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||||
|
|
||||||
import { Title, useTheme } from 'react-native-paper';
|
import { Title, useTheme } from 'react-native-paper';
|
||||||
import { Platform, StyleSheet } from 'react-native';
|
import { StyleSheet } from 'react-native';
|
||||||
import i18n from 'i18n-js';
|
import i18n from 'i18n-js';
|
||||||
import { createCollapsibleStack } from 'react-navigation-collapsible';
|
|
||||||
import { View } from 'react-native-animatable';
|
import { View } from 'react-native-animatable';
|
||||||
import HomeScreen from '../screens/Home/HomeScreen';
|
import HomeScreen from '../screens/Home/HomeScreen';
|
||||||
import PlanningScreen from '../screens/Planning/PlanningScreen';
|
import PlanningScreen from '../screens/Planning/PlanningScreen';
|
||||||
|
@ -44,23 +40,8 @@ import CustomTabBar from '../components/Tabbar/CustomTabBar';
|
||||||
import WebsitesHomeScreen from '../screens/Services/ServicesScreen';
|
import WebsitesHomeScreen from '../screens/Services/ServicesScreen';
|
||||||
import ServicesSectionScreen from '../screens/Services/ServicesSectionScreen';
|
import ServicesSectionScreen from '../screens/Services/ServicesSectionScreen';
|
||||||
import AmicaleContactScreen from '../screens/Amicale/AmicaleContactScreen';
|
import AmicaleContactScreen from '../screens/Amicale/AmicaleContactScreen';
|
||||||
import {
|
|
||||||
CreateScreenCollapsibleStack,
|
|
||||||
getWebsiteStack,
|
|
||||||
} from '../utils/CollapsibleUtils';
|
|
||||||
import Mascot, { MASCOT_STYLE } from '../components/Mascot/Mascot';
|
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({
|
const styles = StyleSheet.create({
|
||||||
header: {
|
header: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
@ -79,29 +60,22 @@ const ServicesStack = createStackNavigator();
|
||||||
|
|
||||||
function ServicesStackComponent() {
|
function ServicesStackComponent() {
|
||||||
return (
|
return (
|
||||||
<ServicesStack.Navigator
|
<ServicesStack.Navigator initialRouteName={'index'} headerMode={'screen'}>
|
||||||
initialRouteName="index"
|
<ServicesStack.Screen
|
||||||
headerMode="screen"
|
name={'index'}
|
||||||
screenOptions={defaultScreenOptions}
|
component={WebsitesHomeScreen}
|
||||||
>
|
options={{ title: i18n.t('screens.services.title') }}
|
||||||
{CreateScreenCollapsibleStack(
|
/>
|
||||||
'index',
|
<ServicesStack.Screen
|
||||||
ServicesStack,
|
name={'services-section'}
|
||||||
WebsitesHomeScreen,
|
component={ServicesSectionScreen}
|
||||||
i18n.t('screens.services.title')
|
options={{ title: 'SECTION' }}
|
||||||
)}
|
/>
|
||||||
{CreateScreenCollapsibleStack(
|
<ServicesStack.Screen
|
||||||
'services-section',
|
name={'amicale-contact'}
|
||||||
ServicesStack,
|
component={AmicaleContactScreen}
|
||||||
ServicesSectionScreen,
|
options={{ title: i18n.t('screens.amicaleAbout.title') }}
|
||||||
'SECTION'
|
/>
|
||||||
)}
|
|
||||||
{CreateScreenCollapsibleStack(
|
|
||||||
'amicale-contact',
|
|
||||||
ServicesStack,
|
|
||||||
AmicaleContactScreen,
|
|
||||||
i18n.t('screens.amicaleAbout.title')
|
|
||||||
)}
|
|
||||||
</ServicesStack.Navigator>
|
</ServicesStack.Navigator>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -110,23 +84,17 @@ const ProxiwashStack = createStackNavigator();
|
||||||
|
|
||||||
function ProxiwashStackComponent() {
|
function ProxiwashStackComponent() {
|
||||||
return (
|
return (
|
||||||
<ProxiwashStack.Navigator
|
<ProxiwashStack.Navigator initialRouteName={'index'} headerMode={'screen'}>
|
||||||
initialRouteName="index"
|
<ProxiwashStack.Screen
|
||||||
headerMode="screen"
|
name={'index-contact'}
|
||||||
screenOptions={defaultScreenOptions}
|
component={ProxiwashScreen}
|
||||||
>
|
options={{ title: i18n.t('screens.proxiwash.title') }}
|
||||||
{CreateScreenCollapsibleStack(
|
/>
|
||||||
'index',
|
<ProxiwashStack.Screen
|
||||||
ProxiwashStack,
|
name={'proxiwash-about'}
|
||||||
ProxiwashScreen,
|
component={ProxiwashAboutScreen}
|
||||||
i18n.t('screens.proxiwash.title')
|
options={{ title: i18n.t('screens.proxiwash.title') }}
|
||||||
)}
|
/>
|
||||||
{CreateScreenCollapsibleStack(
|
|
||||||
'proxiwash-about',
|
|
||||||
ProxiwashStack,
|
|
||||||
ProxiwashAboutScreen,
|
|
||||||
i18n.t('screens.proxiwash.title')
|
|
||||||
)}
|
|
||||||
</ProxiwashStack.Navigator>
|
</ProxiwashStack.Navigator>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -135,22 +103,17 @@ const PlanningStack = createStackNavigator();
|
||||||
|
|
||||||
function PlanningStackComponent() {
|
function PlanningStackComponent() {
|
||||||
return (
|
return (
|
||||||
<PlanningStack.Navigator
|
<PlanningStack.Navigator initialRouteName={'index'} headerMode={'screen'}>
|
||||||
initialRouteName="index"
|
|
||||||
headerMode="screen"
|
|
||||||
screenOptions={defaultScreenOptions}
|
|
||||||
>
|
|
||||||
<PlanningStack.Screen
|
<PlanningStack.Screen
|
||||||
name="index"
|
name={'index'}
|
||||||
component={PlanningScreen}
|
component={PlanningScreen}
|
||||||
options={{ title: i18n.t('screens.planning.title') }}
|
options={{ title: i18n.t('screens.planning.title') }}
|
||||||
/>
|
/>
|
||||||
{CreateScreenCollapsibleStack(
|
<PlanningStack.Screen
|
||||||
'planning-information',
|
name={'planning-information'}
|
||||||
PlanningStack,
|
component={PlanningDisplayScreen}
|
||||||
PlanningDisplayScreen,
|
options={{ title: i18n.t('screens.planning.eventDetails') }}
|
||||||
i18n.t('screens.planning.eventDetails')
|
/>
|
||||||
)}
|
|
||||||
</PlanningStack.Navigator>
|
</PlanningStack.Navigator>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -167,73 +130,63 @@ function HomeStackComponent(
|
||||||
}
|
}
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
return (
|
return (
|
||||||
<HomeStack.Navigator
|
<HomeStack.Navigator initialRouteName={'index'} headerMode={'screen'}>
|
||||||
initialRouteName="index"
|
|
||||||
headerMode="screen"
|
|
||||||
screenOptions={defaultScreenOptions}
|
|
||||||
>
|
|
||||||
{createCollapsibleStack(
|
|
||||||
<HomeStack.Screen
|
|
||||||
name="index"
|
|
||||||
component={HomeScreen}
|
|
||||||
options={{
|
|
||||||
title: i18n.t('screens.home.title'),
|
|
||||||
headerStyle: {
|
|
||||||
backgroundColor: colors.surface,
|
|
||||||
},
|
|
||||||
headerTitle: () => (
|
|
||||||
<View style={styles.header}>
|
|
||||||
<Mascot
|
|
||||||
style={styles.mascot}
|
|
||||||
emotion={MASCOT_STYLE.RANDOM}
|
|
||||||
animated
|
|
||||||
entryAnimation={{
|
|
||||||
animation: 'bounceIn',
|
|
||||||
duration: 1000,
|
|
||||||
}}
|
|
||||||
loopAnimation={{
|
|
||||||
animation: 'pulse',
|
|
||||||
duration: 2000,
|
|
||||||
iterationCount: 'infinite',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Title style={styles.title}>
|
|
||||||
{i18n.t('screens.home.title')}
|
|
||||||
</Title>
|
|
||||||
</View>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
initialParams={params}
|
|
||||||
/>,
|
|
||||||
{
|
|
||||||
collapsedColor: colors.surface,
|
|
||||||
useNativeDriver: true,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
<HomeStack.Screen
|
<HomeStack.Screen
|
||||||
name="scanner"
|
name={'index'}
|
||||||
|
component={HomeScreen}
|
||||||
|
options={{
|
||||||
|
title: i18n.t('screens.home.title'),
|
||||||
|
headerStyle: {
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
},
|
||||||
|
headerTitle: (headerProps) => (
|
||||||
|
<View style={styles.header}>
|
||||||
|
<Mascot
|
||||||
|
style={styles.mascot}
|
||||||
|
emotion={MASCOT_STYLE.RANDOM}
|
||||||
|
animated
|
||||||
|
entryAnimation={{
|
||||||
|
animation: 'bounceIn',
|
||||||
|
duration: 1000,
|
||||||
|
}}
|
||||||
|
loopAnimation={{
|
||||||
|
animation: 'pulse',
|
||||||
|
duration: 2000,
|
||||||
|
iterationCount: 'infinite',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Title style={styles.title}>{headerProps.children}</Title>
|
||||||
|
</View>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
initialParams={params}
|
||||||
|
/>
|
||||||
|
<HomeStack.Screen
|
||||||
|
name={'scanner'}
|
||||||
component={ScannerScreen}
|
component={ScannerScreen}
|
||||||
options={{ title: i18n.t('screens.scanner.title') }}
|
options={{ title: i18n.t('screens.scanner.title') }}
|
||||||
/>
|
/>
|
||||||
|
<HomeStack.Screen
|
||||||
{CreateScreenCollapsibleStack(
|
name={'club-information'}
|
||||||
'club-information',
|
component={ClubDisplayScreen}
|
||||||
HomeStack,
|
options={{
|
||||||
ClubDisplayScreen,
|
title: i18n.t('screens.clubs.details'),
|
||||||
i18n.t('screens.clubs.details')
|
}}
|
||||||
)}
|
/>
|
||||||
{CreateScreenCollapsibleStack(
|
<HomeStack.Screen
|
||||||
'feed-information',
|
name={'feed-information'}
|
||||||
HomeStack,
|
component={FeedItemScreen}
|
||||||
FeedItemScreen,
|
options={{
|
||||||
i18n.t('screens.home.feed')
|
title: i18n.t('screens.home.feed'),
|
||||||
)}
|
}}
|
||||||
{CreateScreenCollapsibleStack(
|
/>
|
||||||
'planning-information',
|
<HomeStack.Screen
|
||||||
HomeStack,
|
name={'planning-information'}
|
||||||
PlanningDisplayScreen,
|
component={PlanningDisplayScreen}
|
||||||
i18n.t('screens.planning.eventDetails')
|
options={{
|
||||||
)}
|
title: i18n.t('screens.planning.eventDetails'),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</HomeStack.Navigator>
|
</HomeStack.Navigator>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -242,23 +195,21 @@ const PlanexStack = createStackNavigator();
|
||||||
|
|
||||||
function PlanexStackComponent() {
|
function PlanexStackComponent() {
|
||||||
return (
|
return (
|
||||||
<PlanexStack.Navigator
|
<PlanexStack.Navigator initialRouteName={'index'} headerMode={'screen'}>
|
||||||
initialRouteName="index"
|
<PlanexStack.Screen
|
||||||
headerMode="screen"
|
name={'index'}
|
||||||
screenOptions={defaultScreenOptions}
|
component={PlanexScreen}
|
||||||
>
|
options={{
|
||||||
{getWebsiteStack(
|
title: i18n.t('screens.planex.title'),
|
||||||
'index',
|
}}
|
||||||
PlanexStack,
|
/>
|
||||||
PlanexScreen,
|
<PlanexStack.Screen
|
||||||
i18n.t('screens.planex.title')
|
name={'group-select'}
|
||||||
)}
|
component={GroupSelectionScreen}
|
||||||
{CreateScreenCollapsibleStack(
|
options={{
|
||||||
'group-select',
|
title: '',
|
||||||
PlanexStack,
|
}}
|
||||||
GroupSelectionScreen,
|
/>
|
||||||
''
|
|
||||||
)}
|
|
||||||
</PlanexStack.Navigator>
|
</PlanexStack.Navigator>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -270,6 +221,34 @@ type PropsType = {
|
||||||
defaultHomeData: { [key: string]: string };
|
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<PropsType> {
|
export default class TabNavigator extends React.Component<PropsType> {
|
||||||
defaultRoute: string;
|
defaultRoute: string;
|
||||||
createHomeStackComponent: () => any;
|
createHomeStackComponent: () => any;
|
||||||
|
@ -287,33 +266,44 @@ export default class TabNavigator extends React.Component<PropsType> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
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 (
|
return (
|
||||||
<Tab.Navigator
|
<Tab.Navigator
|
||||||
initialRouteName={this.defaultRoute}
|
initialRouteName={this.defaultRoute}
|
||||||
tabBar={(tabProps) => <CustomTabBar {...tabProps} />}
|
tabBar={(tabProps) => (
|
||||||
|
<CustomTabBar {...tabProps} labels={LABELS} icons={ICONS} />
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<Tab.Screen
|
<Tab.Screen
|
||||||
name="services"
|
name={'services'}
|
||||||
component={ServicesStackComponent}
|
component={ServicesStackComponent}
|
||||||
options={{ title: i18n.t('screens.services.title') }}
|
options={{ title: i18n.t('screens.services.title') }}
|
||||||
/>
|
/>
|
||||||
<Tab.Screen
|
<Tab.Screen
|
||||||
name="proxiwash"
|
name={'proxiwash'}
|
||||||
component={ProxiwashStackComponent}
|
component={ProxiwashStackComponent}
|
||||||
options={{ title: i18n.t('screens.proxiwash.title') }}
|
options={{ title: i18n.t('screens.proxiwash.title') }}
|
||||||
/>
|
/>
|
||||||
<Tab.Screen
|
<Tab.Screen
|
||||||
name="home"
|
name={'home'}
|
||||||
component={this.createHomeStackComponent}
|
component={this.createHomeStackComponent}
|
||||||
options={{ title: i18n.t('screens.home.title') }}
|
options={{ title: i18n.t('screens.home.title') }}
|
||||||
/>
|
/>
|
||||||
<Tab.Screen
|
<Tab.Screen
|
||||||
name="planning"
|
name={'planning'}
|
||||||
component={PlanningStackComponent}
|
component={PlanningStackComponent}
|
||||||
options={{ title: i18n.t('screens.planning.title') }}
|
options={{ title: i18n.t('screens.planning.title') }}
|
||||||
/>
|
/>
|
||||||
<Tab.Screen
|
<Tab.Screen
|
||||||
name="planex"
|
name={'planex'}
|
||||||
component={PlanexStackComponent}
|
component={PlanexStackComponent}
|
||||||
options={{ title: i18n.t('screens.planex.title') }}
|
options={{ title: i18n.t('screens.planex.title') }}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -31,7 +31,9 @@ import i18n from 'i18n-js';
|
||||||
import { StackNavigationProp } from '@react-navigation/stack';
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
import AuthenticatedScreen from '../../../components/Amicale/AuthenticatedScreen';
|
import AuthenticatedScreen from '../../../components/Amicale/AuthenticatedScreen';
|
||||||
import CustomHTML from '../../../components/Overrides/CustomHTML';
|
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 type { ClubCategoryType, ClubType } from './ClubListScreen';
|
||||||
import { ERROR_TYPE } from '../../../utils/WebData';
|
import { ERROR_TYPE } from '../../../utils/WebData';
|
||||||
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
|
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
|
||||||
|
@ -174,7 +176,7 @@ class ClubDisplayScreen extends React.Component<PropsType> {
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
style={{
|
style={{
|
||||||
marginBottom: CustomTabBar.TAB_BAR_HEIGHT + 20,
|
marginBottom: TAB_BAR_HEIGHT + 20,
|
||||||
...styles.card,
|
...styles.card,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -441,7 +441,7 @@ class LoginScreen extends React.Component<Props, StateType> {
|
||||||
enabled
|
enabled
|
||||||
keyboardVerticalOffset={100}
|
keyboardVerticalOffset={100}
|
||||||
>
|
>
|
||||||
<CollapsibleScrollView>
|
<CollapsibleScrollView headerColors={'transparent'}>
|
||||||
<View style={GENERAL_STYLES.flex}>{this.getMainCard()}</View>
|
<View style={GENERAL_STYLES.flex}>{this.getMainCard()}</View>
|
||||||
<MascotPopup
|
<MascotPopup
|
||||||
visible={mascotDialogVisible}
|
visible={mascotDialogVisible}
|
||||||
|
|
|
@ -25,7 +25,9 @@ import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
import MaterialHeaderButtons, {
|
import MaterialHeaderButtons, {
|
||||||
Item,
|
Item,
|
||||||
} from '../../components/Overrides/CustomHeaderButton';
|
} from '../../components/Overrides/CustomHeaderButton';
|
||||||
import CustomTabBar from '../../components/Tabbar/CustomTabBar';
|
import CustomTabBar, {
|
||||||
|
TAB_BAR_HEIGHT,
|
||||||
|
} from '../../components/Tabbar/CustomTabBar';
|
||||||
import type { FeedItemType } from './HomeScreen';
|
import type { FeedItemType } from './HomeScreen';
|
||||||
import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView';
|
import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView';
|
||||||
import ImageGalleryButton from '../../components/Media/ImageGalleryButton';
|
import ImageGalleryButton from '../../components/Media/ImageGalleryButton';
|
||||||
|
@ -117,9 +119,7 @@ class FeedItemScreen extends React.Component<PropsType> {
|
||||||
style={styles.button}
|
style={styles.button}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<Card.Content
|
<Card.Content style={{ paddingBottom: TAB_BAR_HEIGHT + 20 }}>
|
||||||
style={{ paddingBottom: CustomTabBar.TAB_BAR_HEIGHT + 20 }}
|
|
||||||
>
|
|
||||||
{this.displayData.message !== undefined ? (
|
{this.displayData.message !== undefined ? (
|
||||||
<Autolink
|
<Autolink
|
||||||
text={this.displayData.message}
|
text={this.displayData.message}
|
||||||
|
|
|
@ -26,7 +26,9 @@ import i18n from 'i18n-js';
|
||||||
import { PERMISSIONS, request, RESULTS } from 'react-native-permissions';
|
import { PERMISSIONS, request, RESULTS } from 'react-native-permissions';
|
||||||
import URLHandler from '../../utils/URLHandler';
|
import URLHandler from '../../utils/URLHandler';
|
||||||
import AlertDialog from '../../components/Dialogs/AlertDialog';
|
import AlertDialog from '../../components/Dialogs/AlertDialog';
|
||||||
import CustomTabBar from '../../components/Tabbar/CustomTabBar';
|
import CustomTabBar, {
|
||||||
|
TAB_BAR_HEIGHT,
|
||||||
|
} from '../../components/Tabbar/CustomTabBar';
|
||||||
import LoadingConfirmDialog from '../../components/Dialogs/LoadingConfirmDialog';
|
import LoadingConfirmDialog from '../../components/Dialogs/LoadingConfirmDialog';
|
||||||
import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
|
import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
|
||||||
import MascotPopup from '../../components/Mascot/MascotPopup';
|
import MascotPopup from '../../components/Mascot/MascotPopup';
|
||||||
|
@ -218,7 +220,7 @@ class ScannerScreen extends React.Component<{}, StateType> {
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
...styles.container,
|
...styles.container,
|
||||||
marginBottom: CustomTabBar.TAB_BAR_HEIGHT,
|
marginBottom: TAB_BAR_HEIGHT,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{state.hasPermission ? this.getScanner() : this.getPermissionScreen()}
|
{state.hasPermission ? this.getScanner() : this.getPermissionScreen()}
|
||||||
|
|
|
@ -54,6 +54,7 @@ type StateType = {
|
||||||
dialogTitle: string | React.ReactNode;
|
dialogTitle: string | React.ReactNode;
|
||||||
dialogMessage: string;
|
dialogMessage: string;
|
||||||
currentGroup: PlanexGroupType;
|
currentGroup: PlanexGroupType;
|
||||||
|
injectJS: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PLANEX_URL = 'http://planex.insa-toulouse.fr/';
|
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
|
* This screen uses a webview to render the page
|
||||||
*/
|
*/
|
||||||
class PlanexScreen extends React.Component<PropsType, StateType> {
|
class PlanexScreen extends React.Component<PropsType, StateType> {
|
||||||
webScreenRef: { current: null | WebViewScreen };
|
|
||||||
|
|
||||||
barRef: { current: null | AnimatedBottomBar };
|
barRef: { current: null | AnimatedBottomBar };
|
||||||
|
|
||||||
customInjectedJS: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines custom injected JavaScript to improve the page display on mobile
|
* Defines custom injected JavaScript to improve the page display on mobile
|
||||||
*/
|
*/
|
||||||
constructor(props: PropsType) {
|
constructor(props: PropsType) {
|
||||||
super(props);
|
super(props);
|
||||||
this.webScreenRef = React.createRef();
|
|
||||||
this.barRef = React.createRef();
|
this.barRef = React.createRef();
|
||||||
this.customInjectedJS = '';
|
|
||||||
let currentGroupString = AsyncStorageManager.getString(
|
let currentGroupString = AsyncStorageManager.getString(
|
||||||
AsyncStorageManager.PREFERENCES.planexCurrentGroup.key
|
AsyncStorageManager.PREFERENCES.planexCurrentGroup.key
|
||||||
);
|
);
|
||||||
|
@ -184,8 +179,8 @@ class PlanexScreen extends React.Component<PropsType, StateType> {
|
||||||
dialogTitle: '',
|
dialogTitle: '',
|
||||||
dialogMessage: '',
|
dialogMessage: '',
|
||||||
currentGroup,
|
currentGroup,
|
||||||
|
injectJS: '',
|
||||||
};
|
};
|
||||||
this.generateInjectedJS(currentGroup.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -196,20 +191,6 @@ class PlanexScreen extends React.Component<PropsType, StateType> {
|
||||||
navigation.addListener('focus', this.onScreenFocus);
|
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.
|
* Gets the Webview, with an error view on top if no group is selected.
|
||||||
*
|
*
|
||||||
|
@ -218,6 +199,7 @@ class PlanexScreen extends React.Component<PropsType, StateType> {
|
||||||
getWebView() {
|
getWebView() {
|
||||||
const { props, state } = this;
|
const { props, state } = this;
|
||||||
const showWebview = state.currentGroup.id !== -1;
|
const showWebview = state.currentGroup.id !== -1;
|
||||||
|
console.log(state.injectJS);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={GENERAL_STYLES.flex}>
|
<View style={GENERAL_STYLES.flex}>
|
||||||
|
@ -230,10 +212,9 @@ class PlanexScreen extends React.Component<PropsType, StateType> {
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<WebViewScreen
|
<WebViewScreen
|
||||||
ref={this.webScreenRef}
|
|
||||||
navigation={props.navigation}
|
|
||||||
url={PLANEX_URL}
|
url={PLANEX_URL}
|
||||||
customJS={this.customInjectedJS}
|
initialJS={this.generateInjectedJS(this.state.currentGroup.id)}
|
||||||
|
injectJS={this.state.injectJS}
|
||||||
onMessage={this.onMessage}
|
onMessage={this.onMessage}
|
||||||
onScroll={this.onScroll}
|
onScroll={this.onScroll}
|
||||||
showAdvancedControls={false}
|
showAdvancedControls={false}
|
||||||
|
@ -269,9 +250,13 @@ class PlanexScreen extends React.Component<PropsType, StateType> {
|
||||||
} else {
|
} else {
|
||||||
command = `$('#calendar').fullCalendar('${action}', '${data}')`;
|
command = `$('#calendar').fullCalendar('${action}', '${data}')`;
|
||||||
}
|
}
|
||||||
if (this.webScreenRef.current != null) {
|
// String must resolve to true to prevent crash on iOS
|
||||||
this.webScreenRef.current.injectJavaScript(`${command};true;`);
|
command += ';true;';
|
||||||
} // Injected javascript must end with true
|
// Change the injected
|
||||||
|
if (command === this.state.injectJS) {
|
||||||
|
command += ';true;';
|
||||||
|
}
|
||||||
|
this.setState({ injectJS: command });
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -373,7 +358,6 @@ class PlanexScreen extends React.Component<PropsType, StateType> {
|
||||||
group
|
group
|
||||||
);
|
);
|
||||||
navigation.setOptions({ title: getPrettierPlanexGroupName(group.name) });
|
navigation.setOptions({ title: getPrettierPlanexGroupName(group.name) });
|
||||||
this.generateInjectedJS(group.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -382,16 +366,20 @@ class PlanexScreen extends React.Component<PropsType, StateType> {
|
||||||
* @param groupID The current group selected
|
* @param groupID The current group selected
|
||||||
*/
|
*/
|
||||||
generateInjectedJS(groupID: number) {
|
generateInjectedJS(groupID: number) {
|
||||||
this.customInjectedJS = `$(document).ready(function() {${OBSERVE_MUTATIONS_INJECTED}${FULL_CALENDAR_SETTINGS}displayAde(${groupID});${
|
let customInjectedJS = `$(document).ready(function() {
|
||||||
// Reset Ade
|
${OBSERVE_MUTATIONS_INJECTED}
|
||||||
DateManager.isWeekend(new Date()) ? 'calendar.next()' : ''
|
${FULL_CALENDAR_SETTINGS}
|
||||||
}${INJECT_STYLE}`;
|
displayAde(${groupID});
|
||||||
|
${INJECT_STYLE}`;
|
||||||
|
if (DateManager.isWeekend(new Date())) {
|
||||||
|
customInjectedJS += `calendar.next();`;
|
||||||
|
}
|
||||||
if (ThemeManager.getNightMode()) {
|
if (ThemeManager.getNightMode()) {
|
||||||
this.customInjectedJS += `$('head').append('<style>${CUSTOM_CSS_DARK}</style>');`;
|
customInjectedJS += `$('head').append('<style>${CUSTOM_CSS_DARK}</style>');`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.customInjectedJS += 'removeAlpha();});true;'; // Prevents crash on ios
|
customInjectedJS += 'removeAlpha();});true;'; // Prevents crash on ios
|
||||||
|
return customInjectedJS;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -28,7 +28,9 @@ import BasicLoadingScreen from '../../components/Screens/BasicLoadingScreen';
|
||||||
import { apiRequest, ERROR_TYPE } from '../../utils/WebData';
|
import { apiRequest, ERROR_TYPE } from '../../utils/WebData';
|
||||||
import ErrorView from '../../components/Screens/ErrorView';
|
import ErrorView from '../../components/Screens/ErrorView';
|
||||||
import CustomHTML from '../../components/Overrides/CustomHTML';
|
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 CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView';
|
||||||
import type { PlanningEventType } from '../../utils/Planning';
|
import type { PlanningEventType } from '../../utils/Planning';
|
||||||
import ImageGalleryButton from '../../components/Media/ImageGalleryButton';
|
import ImageGalleryButton from '../../components/Media/ImageGalleryButton';
|
||||||
|
@ -145,9 +147,7 @@ class PlanningDisplayScreen extends React.Component<PropsType, StateType> {
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{displayData.description !== null ? (
|
{displayData.description !== null ? (
|
||||||
<Card.Content
|
<Card.Content style={{ paddingBottom: TAB_BAR_HEIGHT + 20 }}>
|
||||||
style={{ paddingBottom: CustomTabBar.TAB_BAR_HEIGHT + 20 }}
|
|
||||||
>
|
|
||||||
<CustomHTML html={displayData.description} />
|
<CustomHTML html={displayData.description} />
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -21,7 +21,9 @@ import * as React from 'react';
|
||||||
import { Image, StyleSheet, View } from 'react-native';
|
import { Image, StyleSheet, View } from 'react-native';
|
||||||
import i18n from 'i18n-js';
|
import i18n from 'i18n-js';
|
||||||
import { Card, Avatar, Paragraph, Text } from 'react-native-paper';
|
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';
|
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
|
||||||
|
|
||||||
const LOGO = 'https://etud.insa-toulouse.fr/~amicale_app/images/Proximo.png';
|
const LOGO = 'https://etud.insa-toulouse.fr/~amicale_app/images/Proximo.png';
|
||||||
|
@ -72,7 +74,7 @@ export default function ProximoAboutScreen() {
|
||||||
<Card
|
<Card
|
||||||
style={{
|
style={{
|
||||||
...styles.card,
|
...styles.card,
|
||||||
marginBottom: CustomTabBar.TAB_BAR_HEIGHT + 20,
|
marginBottom: TAB_BAR_HEIGHT + 20,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Card.Title
|
<Card.Title
|
||||||
|
|
|
@ -23,11 +23,15 @@ import WebViewScreen from '../../components/Screens/WebViewScreen';
|
||||||
import AvailableWebsites from '../../constants/AvailableWebsites';
|
import AvailableWebsites from '../../constants/AvailableWebsites';
|
||||||
import BasicLoadingScreen from '../../components/Screens/BasicLoadingScreen';
|
import BasicLoadingScreen from '../../components/Screens/BasicLoadingScreen';
|
||||||
|
|
||||||
type PropsType = {
|
type Props = {
|
||||||
navigation: StackNavigationProp<any>;
|
navigation: StackNavigationProp<any>;
|
||||||
route: { params: { host: string; path: string | null; title: string } };
|
route: { params: { host: string; path: string | null; title: string } };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
|
||||||
const ENABLE_MOBILE_STRING =
|
const ENABLE_MOBILE_STRING =
|
||||||
'<meta name="viewport" content="width=device-width, initial-scale=1.0">';
|
'<meta name="viewport" content="width=device-width, initial-scale=1.0">';
|
||||||
|
|
||||||
|
@ -43,18 +47,18 @@ const BIB_BACK_BUTTON =
|
||||||
'</a>' +
|
'</a>' +
|
||||||
'</div>';
|
'</div>';
|
||||||
|
|
||||||
class WebsiteScreen extends React.Component<PropsType> {
|
class WebsiteScreen extends React.Component<Props, State> {
|
||||||
fullUrl: string;
|
|
||||||
|
|
||||||
injectedJS: { [key: string]: string };
|
injectedJS: { [key: string]: string };
|
||||||
|
|
||||||
customPaddingFunctions: { [key: string]: (padding: string) => string };
|
customPaddingFunctions: { [key: string]: (padding: number) => string };
|
||||||
|
|
||||||
host: string;
|
host: string;
|
||||||
|
|
||||||
constructor(props: PropsType) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.fullUrl = '';
|
this.state = {
|
||||||
|
url: '',
|
||||||
|
};
|
||||||
this.host = '';
|
this.host = '';
|
||||||
props.navigation.addListener('focus', this.onScreenFocus);
|
props.navigation.addListener('focus', this.onScreenFocus);
|
||||||
this.injectedJS = {};
|
this.injectedJS = {};
|
||||||
|
@ -70,7 +74,7 @@ class WebsiteScreen extends React.Component<PropsType> {
|
||||||
`$(".hero-unit-form").append("${BIB_BACK_BUTTON}");true;`;
|
`$(".hero-unit-form").append("${BIB_BACK_BUTTON}");true;`;
|
||||||
|
|
||||||
this.customPaddingFunctions[AvailableWebsites.websites.BLUEMIND] = (
|
this.customPaddingFunctions[AvailableWebsites.websites.BLUEMIND] = (
|
||||||
padding: string
|
padding: number
|
||||||
): string => {
|
): string => {
|
||||||
return (
|
return (
|
||||||
`$('head').append('${ENABLE_MOBILE_STRING}');` +
|
`$('head').append('${ENABLE_MOBILE_STRING}');` +
|
||||||
|
@ -79,7 +83,7 @@ class WebsiteScreen extends React.Component<PropsType> {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
this.customPaddingFunctions[AvailableWebsites.websites.WIKETUD] = (
|
this.customPaddingFunctions[AvailableWebsites.websites.WIKETUD] = (
|
||||||
padding: string
|
padding: number
|
||||||
): string => {
|
): string => {
|
||||||
return (
|
return (
|
||||||
`$('#p-logo-text').css('top', 10 + ${padding});` +
|
`$('#p-logo-text').css('top', 10 + ${padding});` +
|
||||||
|
@ -99,16 +103,20 @@ class WebsiteScreen extends React.Component<PropsType> {
|
||||||
*/
|
*/
|
||||||
handleNavigationParams() {
|
handleNavigationParams() {
|
||||||
const { route, navigation } = this.props;
|
const { route, navigation } = this.props;
|
||||||
|
|
||||||
if (route.params != null) {
|
if (route.params != null) {
|
||||||
|
console.log(route.params);
|
||||||
this.host = route.params.host;
|
this.host = route.params.host;
|
||||||
let { path } = route.params;
|
let { path } = route.params;
|
||||||
const { title } = route.params;
|
const { title } = route.params;
|
||||||
|
let fullUrl = '';
|
||||||
if (this.host != null && path != null) {
|
if (this.host != null && path != null) {
|
||||||
path = path.replace(this.host, '');
|
path = path.replace(this.host, '');
|
||||||
this.fullUrl = this.host + path;
|
fullUrl = this.host + path;
|
||||||
} else {
|
} else {
|
||||||
this.fullUrl = this.host;
|
fullUrl = this.host;
|
||||||
}
|
}
|
||||||
|
this.setState({ url: fullUrl });
|
||||||
|
|
||||||
if (title != null) {
|
if (title != null) {
|
||||||
navigation.setOptions({ title });
|
navigation.setOptions({ title });
|
||||||
|
@ -117,7 +125,6 @@ class WebsiteScreen extends React.Component<PropsType> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { navigation } = this.props;
|
|
||||||
let injectedJavascript = '';
|
let injectedJavascript = '';
|
||||||
let customPadding = null;
|
let customPadding = null;
|
||||||
if (this.host != null && this.injectedJS[this.host] != null) {
|
if (this.host != null && this.injectedJS[this.host] != null) {
|
||||||
|
@ -127,12 +134,11 @@ class WebsiteScreen extends React.Component<PropsType> {
|
||||||
customPadding = this.customPaddingFunctions[this.host];
|
customPadding = this.customPaddingFunctions[this.host];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.fullUrl != null) {
|
if (this.state.url) {
|
||||||
return (
|
return (
|
||||||
<WebViewScreen
|
<WebViewScreen
|
||||||
navigation={navigation}
|
url={this.state.url}
|
||||||
url={this.fullUrl}
|
initialJS={injectedJavascript}
|
||||||
customJS={injectedJavascript}
|
|
||||||
customPaddingFunction={customPadding}
|
customPaddingFunction={customPadding}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
157
src/screens/Test.tsx
Normal file
157
src/screens/Test.tsx
Normal file
|
@ -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 (
|
||||||
|
// <View
|
||||||
|
// style={{
|
||||||
|
// marginTop: 50,
|
||||||
|
// marginBottom: 50,
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// <Text>TEST</Text>
|
||||||
|
// </View>
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <Animated.FlatList
|
||||||
|
// onScroll={onScroll}
|
||||||
|
// contentContainerStyle={{ paddingTop: containerPaddingTop }}
|
||||||
|
// scrollIndicatorInsets={{ top: scrollIndicatorInsetTop }}
|
||||||
|
// data={[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}
|
||||||
|
// renderItem={renderItem}
|
||||||
|
// />
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
navigation: StackNavigationProp<any>;
|
||||||
|
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<Props> {
|
||||||
|
createDataset = (): Array<{
|
||||||
|
title: string;
|
||||||
|
data: [] | Array<FeedItemType>;
|
||||||
|
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 }) => (
|
||||||
|
<FeedItem item={item} height={FEED_ITEM_HEIGHT} />
|
||||||
|
);
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const renderItem = () => {
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
marginTop: 50,
|
||||||
|
marginBottom: 50,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>TEST</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = this.props;
|
||||||
|
// return (
|
||||||
|
// <CollapsibleFlatList
|
||||||
|
// data={[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}
|
||||||
|
// renderItem={renderItem}
|
||||||
|
// />
|
||||||
|
// );
|
||||||
|
// return (
|
||||||
|
// <CollapsibleSectionList
|
||||||
|
// sections={[{ title: '', data: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }]}
|
||||||
|
// renderItem={renderItem}
|
||||||
|
// />
|
||||||
|
// );
|
||||||
|
return (
|
||||||
|
<WebSectionList
|
||||||
|
navigation={props.navigation}
|
||||||
|
createDataset={this.createDataset}
|
||||||
|
autoRefreshTime={REFRESH_TIME}
|
||||||
|
refreshOnFocus
|
||||||
|
fetchUrl={DATA_URL}
|
||||||
|
renderItem={this.getRenderItem}
|
||||||
|
itemHeight={FEED_ITEM_HEIGHT}
|
||||||
|
showError={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Test;
|
16
src/utils/CollapsibleContext.ts
Normal file
16
src/utils/CollapsibleContext.ts
Normal file
|
@ -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<CollapsibleContextType>({
|
||||||
|
collapsible: undefined,
|
||||||
|
setCollapsible: () => undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function useCollapsible() {
|
||||||
|
return useContext(CollapsibleContext);
|
||||||
|
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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<string, object | undefined>,
|
|
||||||
StackNavigationState<any>,
|
|
||||||
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<any, any, any, any, any>,
|
|
||||||
component: React.ComponentType<any>,
|
|
||||||
title: string,
|
|
||||||
useNativeDriver: boolean = true,
|
|
||||||
options: StackNavigationOptions = {},
|
|
||||||
headerColor?: string
|
|
||||||
) {
|
|
||||||
const { colors } = useTheme();
|
|
||||||
return createCollapsibleStack(
|
|
||||||
<Stack.Screen
|
|
||||||
name={name}
|
|
||||||
component={component}
|
|
||||||
options={{
|
|
||||||
title,
|
|
||||||
headerStyle: {
|
|
||||||
backgroundColor: headerColor != null ? headerColor : colors.surface,
|
|
||||||
},
|
|
||||||
...options,
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
{
|
|
||||||
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<any, any, any, any, any>,
|
|
||||||
component: React.ComponentType<any>,
|
|
||||||
title: string
|
|
||||||
) {
|
|
||||||
return CreateScreenCollapsibleStack(name, Stack, component, title, false);
|
|
||||||
}
|
|
|
@ -18,29 +18,28 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useCollapsibleStack } from 'react-navigation-collapsible';
|
import { useTheme } from 'react-native-paper';
|
||||||
|
import {
|
||||||
|
useCollapsibleHeader,
|
||||||
|
UseCollapsibleOptions,
|
||||||
|
} from 'react-navigation-collapsible';
|
||||||
|
|
||||||
/**
|
export default function withCollapsible<T>(
|
||||||
* Function used to manipulate Collapsible Hooks from a class.
|
Component: React.ComponentType<any>,
|
||||||
*
|
options?: UseCollapsibleOptions
|
||||||
* Usage :
|
) {
|
||||||
*
|
return function WrappedComponent(props: T) {
|
||||||
* export withCollapsible(Component)
|
const theme = useTheme();
|
||||||
*
|
if (!options?.config?.collapsedColor) {
|
||||||
* replacing Component with the one you want to use.
|
options = {
|
||||||
* This component will then receive the collapsibleStack prop.
|
...options,
|
||||||
*
|
config: {
|
||||||
* @param Component The component to use Collapsible with
|
...options?.config,
|
||||||
* @returns {React.ComponentType<any>}
|
collapsedColor: theme.colors.surface,
|
||||||
*/
|
},
|
||||||
export default function withCollapsible(Component: React.ComponentType<any>) {
|
};
|
||||||
return React.forwardRef((props: any, ref: any) => {
|
}
|
||||||
return (
|
const collapsible = useCollapsibleHeader(options);
|
||||||
<Component
|
return <Component {...props} collapsible={collapsible} />;
|
||||||
collapsibleStack={useCollapsibleStack()}
|
};
|
||||||
ref={ref}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue