Implemented collapsible header on major lists and webviews

This commit is contained in:
Arnaud Vergnet 2020-04-13 18:11:53 +02:00
parent 5d08134511
commit c67e2fa405
9 changed files with 373 additions and 163 deletions

View file

@ -51,6 +51,7 @@
"react-native-safe-area-context": "0.7.3", "react-native-safe-area-context": "0.7.3",
"react-native-screens": "~2.2.0", "react-native-screens": "~2.2.0",
"react-native-webview": "8.1.1", "react-native-webview": "8.1.1",
"react-navigation-collapsible": "^5.4.0",
"react-navigation-header-buttons": "^3.0.5" "react-navigation-header-buttons": "^3.0.5"
}, },
"devDependencies": { "devDependencies": {

View file

@ -1,8 +1,8 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {View} from 'react-native';
import {ActivityIndicator, withTheme} from 'react-native-paper'; import {ActivityIndicator, withTheme} from 'react-native-paper';
import {View} from "react-native";
/** /**
* Component used to display a header button * Component used to display a header button
@ -12,17 +12,19 @@ import {View} from "react-native";
*/ */
function BasicLoadingScreen(props) { function BasicLoadingScreen(props) {
const {colors} = props.theme; const {colors} = props.theme;
let position = undefined;
if (props.isAbsolute !== undefined && props.isAbsolute)
position = 'absolute';
return ( return (
<View style={{ <View style={{
backgroundColor: colors.background, backgroundColor: colors.background,
position: 'absolute', position: position,
top: 0, top: 0,
right: 0, right: 0,
width: '100%', width: '100%',
height: '100%', height: '100%',
flex: 1, justifyContent: 'center',
alignItems: 'center',
justifyContent: 'center'
}}> }}>
<ActivityIndicator <ActivityIndicator
animating={true} animating={true}

View file

@ -7,6 +7,7 @@ import {Snackbar} from 'react-native-paper';
import {Animated, RefreshControl, View} from "react-native"; import {Animated, RefreshControl, View} from "react-native";
import ErrorView from "../Custom/ErrorView"; import ErrorView from "../Custom/ErrorView";
import BasicLoadingScreen from "../Custom/BasicLoadingScreen"; import BasicLoadingScreen from "../Custom/BasicLoadingScreen";
import {withCollapsible} from "../../utils/withCollapsible";
type Props = { type Props = {
navigation: Object, navigation: Object,
@ -20,6 +21,7 @@ type Props = {
updateData: number, updateData: number,
itemHeight: number | null, itemHeight: number | null,
onScroll: Function, onScroll: Function,
collapsibleStack: Object,
} }
type State = { type State = {
@ -31,13 +33,14 @@ type State = {
const MIN_REFRESH_TIME = 5 * 1000; const MIN_REFRESH_TIME = 5 * 1000;
/** /**
* Component used to render a SectionList with data fetched from the web * Component used to render a SectionList with data fetched from the web
* *
* This is a pure component, meaning it will only update if a shallow comparison of state and props is different. * This is a pure component, meaning it will only update if a shallow comparison of state and props is different.
* To force the component to update, change the value of updateData. * To force the component to update, change the value of updateData.
*/ */
export default class WebSectionList extends React.PureComponent<Props, State> { class WebSectionList extends React.PureComponent<Props, State> {
static defaultProps = { static defaultProps = {
renderSectionHeader: null, renderSectionHeader: null,
@ -171,11 +174,16 @@ export default class WebSectionList extends React.PureComponent<Props, State> {
index index
}); });
onListScroll= (event) => {
};
render() { render() {
let dataset = []; let dataset = [];
if (this.state.fetchedData !== undefined) if (this.state.fetchedData !== undefined)
dataset = this.props.createDataset(this.state.fetchedData); dataset = this.props.createDataset(this.state.fetchedData);
const shouldRenderHeader = this.props.renderSectionHeader !== null; const shouldRenderHeader = this.props.renderSectionHeader !== null;
const {containerPaddingTop, scrollIndicatorInsetTop, onScrollWithListener} = this.props.collapsibleStack;
return ( return (
<View> <View>
{/*$FlowFixMe*/} {/*$FlowFixMe*/}
@ -184,6 +192,7 @@ export default class WebSectionList extends React.PureComponent<Props, State> {
extraData={this.props.updateData} extraData={this.props.updateData}
refreshControl={ refreshControl={
<RefreshControl <RefreshControl
progressViewOffset={containerPaddingTop}
refreshing={this.state.refreshing} refreshing={this.state.refreshing}
onRefresh={this.onRefresh} onRefresh={this.onRefresh}
/> />
@ -193,7 +202,6 @@ export default class WebSectionList extends React.PureComponent<Props, State> {
//$FlowFixMe //$FlowFixMe
renderItem={this.props.renderItem} renderItem={this.props.renderItem}
stickySectionHeadersEnabled={this.props.stickyHeader} stickySectionHeadersEnabled={this.props.stickyHeader}
contentContainerStyle={{minHeight: '100%'}}
style={{minHeight: '100%'}} style={{minHeight: '100%'}}
ListEmptyComponent={this.state.refreshing ListEmptyComponent={this.state.refreshing
? <BasicLoadingScreen/> ? <BasicLoadingScreen/>
@ -204,7 +212,12 @@ export default class WebSectionList extends React.PureComponent<Props, State> {
} }
getItemLayout={this.props.itemHeight !== null ? this.itemLayout : undefined} getItemLayout={this.props.itemHeight !== null ? this.itemLayout : undefined}
// Animations // Animations
onScroll={this.props.onScroll} onScroll={onScrollWithListener(this.props.onScroll)}
contentContainerStyle={{
paddingTop: containerPaddingTop,
minHeight: '100%'
}}
scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}
/> />
<Snackbar <Snackbar
visible={this.state.snackbarVisible} visible={this.state.snackbarVisible}
@ -222,3 +235,5 @@ export default class WebSectionList extends React.PureComponent<Props, State> {
); );
} }
} }
export default withCollapsible(WebSectionList);

View file

@ -9,14 +9,18 @@ import MaterialHeaderButtons, {Item} from '../Custom/HeaderButton';
import {HiddenItem} from "react-navigation-header-buttons"; import {HiddenItem} from "react-navigation-header-buttons";
import {Linking} from "expo"; import {Linking} from "expo";
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import {BackHandler} from "react-native"; import {Animated, BackHandler} from "react-native";
import {withCollapsible} from "../../utils/withCollapsible";
type Props = { type Props = {
navigation: Object, navigation: Object,
url: string, url: string,
customJS: string, customJS: string,
collapsibleStack: Object,
} }
const AnimatedWebView = Animated.createAnimatedComponent(WebView);
/** /**
* Class defining a webview screen. * Class defining a webview screen.
*/ */
@ -63,7 +67,7 @@ class WebViewScreen extends React.PureComponent<Props> {
} }
onBackButtonPressAndroid = () => { onBackButtonPressAndroid = () => {
if (this.canGoBack){ if (this.canGoBack) {
this.onGoBackClicked(); this.onGoBackClicked();
return true; return true;
} }
@ -112,15 +116,48 @@ class WebViewScreen extends React.PureComponent<Props> {
* *
* @return {*} * @return {*}
*/ */
getRenderLoading = () => <BasicLoadingScreen/>; getRenderLoading = () => <BasicLoadingScreen isAbsolute={true}/>;
// document.getElementsByTagName('body')[0].style.paddingTop = '100px';
// $( 'body *' ).filter(function(){
// var position = $(this).css('position');
// var top = $(this).css('top');
// if((position === 'fixed') && top !== 'auto'){
// console.log(top);
// $(this).css('top', 'calc(' + top + ' + 100px)');
// console.log($(this).css('top'));
// };
// });
// document.querySelectorAll('body *').forEach(function(node){
// var style = window.getComputedStyle(node);
// var position = style.getPropertyValue('position');
// var top = style.getPropertyValue('top');
// if((position === 'fixed') && top !== 'auto'){
// console.log(top);
// node.style.top = 'calc(' + top + ' + 100px)';
// console.log(node.style.top);
// console.log(node);
// };
// });
getJavascriptPadding(padding: number) {
return (
"document.getElementsByTagName('body')[0].style.paddingTop = '" + padding + "px';\n" +
"true;"
);
}
render() { render() {
const {containerPaddingTop, onScroll} = this.props.collapsibleStack;
const customJS = this.getJavascriptPadding(containerPaddingTop);
return ( return (
<WebView <AnimatedWebView
ref={this.webviewRef} ref={this.webviewRef}
source={{uri: this.props.url}} source={{uri: this.props.url}}
startInLoadingState={true} startInLoadingState={true}
injectedJavaScript={this.props.customJS} injectedJavaScript={this.props.customJS + customJS}
javaScriptEnabled={true} javaScriptEnabled={true}
renderLoading={this.getRenderLoading} renderLoading={this.getRenderLoading}
renderError={() => <ErrorView renderError={() => <ErrorView
@ -130,9 +167,11 @@ class WebViewScreen extends React.PureComponent<Props> {
onNavigationStateChange={navState => { onNavigationStateChange={navState => {
this.canGoBack = navState.canGoBack; this.canGoBack = navState.canGoBack;
}} }}
// Animations
onScroll={onScroll}
/> />
); );
} }
} }
export default WebViewScreen; export default withCollapsible(WebViewScreen);

View file

@ -26,6 +26,8 @@ import {AmicaleWebsiteScreen} from "../screens/Websites/AmicaleWebsiteScreen";
import {TutorInsaWebsiteScreen} from "../screens/Websites/TutorInsaWebsiteScreen"; import {TutorInsaWebsiteScreen} from "../screens/Websites/TutorInsaWebsiteScreen";
import {WiketudWebsiteScreen} from "../screens/Websites/WiketudWebsiteScreen"; import {WiketudWebsiteScreen} from "../screens/Websites/WiketudWebsiteScreen";
import {ElusEtudiantsWebsiteScreen} from "../screens/Websites/ElusEtudiantsWebsiteScreen"; import {ElusEtudiantsWebsiteScreen} from "../screens/Websites/ElusEtudiantsWebsiteScreen";
import {createCollapsibleStack} from "react-navigation-collapsible";
import {useTheme} from "react-native-paper";
const defaultScreenOptions = { const defaultScreenOptions = {
gestureEnabled: true, gestureEnabled: true,
@ -106,23 +108,33 @@ function SettingsStackComponent() {
const SelfMenuStack = createStackNavigator(); const SelfMenuStack = createStackNavigator();
function SelfMenuStackComponent() { function SelfMenuStackComponent() {
const {colors} = useTheme();
return ( return (
<SelfMenuStack.Navigator <SelfMenuStack.Navigator
initialRouteName="self-menu" initialRouteName="self-menu"
headerMode="float" headerMode="float"
screenOptions={defaultScreenOptions} screenOptions={defaultScreenOptions}
> >
<SelfMenuStack.Screen {createCollapsibleStack(
name="self-menu" <SelfMenuStack.Screen
component={SelfMenuScreen} name="self-menu"
options={({navigation}) => { component={SelfMenuScreen}
const openDrawer = getDrawerButton.bind(this, navigation); options={({navigation}) => {
return { const openDrawer = getDrawerButton.bind(this, navigation);
title: i18n.t('screens.menuSelf'), return {
headerLeft: openDrawer title: i18n.t('screens.menuSelf'),
}; headerLeft: openDrawer,
}} headerStyle: {
/> backgroundColor: colors.surface,
},
};
}}
/>,
{
collapsedColor: 'transparent',
useNativeDriver: true,
}
)}
</SelfMenuStack.Navigator> </SelfMenuStack.Navigator>
); );
} }
@ -130,23 +142,33 @@ function SelfMenuStackComponent() {
const AvailableRoomStack = createStackNavigator(); const AvailableRoomStack = createStackNavigator();
function AvailableRoomStackComponent() { function AvailableRoomStackComponent() {
const {colors} = useTheme();
return ( return (
<AvailableRoomStack.Navigator <AvailableRoomStack.Navigator
initialRouteName="available-rooms" initialRouteName="available-rooms"
headerMode="float" headerMode="float"
screenOptions={defaultScreenOptions} screenOptions={defaultScreenOptions}
> >
<AvailableRoomStack.Screen {createCollapsibleStack(
name="available-rooms" <AvailableRoomStack.Screen
component={AvailableRoomScreen} name="available-rooms"
options={({navigation}) => { component={AvailableRoomScreen}
const openDrawer = getDrawerButton.bind(this, navigation); options={({navigation}) => {
return { const openDrawer = getDrawerButton.bind(this, navigation);
title: i18n.t('screens.availableRooms'), return {
headerLeft: openDrawer title: i18n.t('screens.availableRooms'),
}; headerLeft: openDrawer,
}} headerStyle: {
/> backgroundColor: colors.surface,
},
};
}}
/>,
{
collapsedColor: 'transparent',
useNativeDriver: false, // native driver does not work with webview
}
)}
</AvailableRoomStack.Navigator> </AvailableRoomStack.Navigator>
); );
} }
@ -154,23 +176,33 @@ function AvailableRoomStackComponent() {
const BibStack = createStackNavigator(); const BibStack = createStackNavigator();
function BibStackComponent() { function BibStackComponent() {
const {colors} = useTheme();
return ( return (
<BibStack.Navigator <BibStack.Navigator
initialRouteName="bib" initialRouteName="bib"
headerMode="float" headerMode="float"
screenOptions={defaultScreenOptions} screenOptions={defaultScreenOptions}
> >
<BibStack.Screen {createCollapsibleStack(
name="bib" <BibStack.Screen
component={BibScreen} name="bib"
options={({navigation}) => { component={BibScreen}
const openDrawer = getDrawerButton.bind(this, navigation); options={({navigation}) => {
return { const openDrawer = getDrawerButton.bind(this, navigation);
title: i18n.t('screens.bib'), return {
headerLeft: openDrawer title: i18n.t('screens.bib'),
}; headerLeft: openDrawer,
}} headerStyle: {
/> backgroundColor: colors.surface,
},
};
}}
/>,
{
collapsedColor: 'transparent',
useNativeDriver: false, // native driver does not work with webview
}
)}
</BibStack.Navigator> </BibStack.Navigator>
); );
} }
@ -178,23 +210,33 @@ function BibStackComponent() {
const AmicaleWebsiteStack = createStackNavigator(); const AmicaleWebsiteStack = createStackNavigator();
function AmicaleWebsiteStackComponent() { function AmicaleWebsiteStackComponent() {
const {colors} = useTheme();
return ( return (
<AmicaleWebsiteStack.Navigator <AmicaleWebsiteStack.Navigator
initialRouteName="amicale-website" initialRouteName="amicale-website"
headerMode="float" headerMode="float"
screenOptions={defaultScreenOptions} screenOptions={defaultScreenOptions}
> >
<AmicaleWebsiteStack.Screen {createCollapsibleStack(
name="amicale-website" <AmicaleWebsiteStack.Screen
component={AmicaleWebsiteScreen} name="amicale-website"
options={({navigation}) => { component={AmicaleWebsiteScreen}
const openDrawer = getDrawerButton.bind(this, navigation); options={({navigation}) => {
return { const openDrawer = getDrawerButton.bind(this, navigation);
title: "Amicale", return {
headerLeft: openDrawer title: "Amicale",
}; headerLeft: openDrawer,
}} headerStyle: {
/> backgroundColor: colors.surface,
},
};
}}
/>,
{
collapsedColor: 'transparent',
useNativeDriver: false, // native driver does not work with webview
}
)}
</AmicaleWebsiteStack.Navigator> </AmicaleWebsiteStack.Navigator>
); );
} }
@ -202,23 +244,33 @@ function AmicaleWebsiteStackComponent() {
const ElusEtudiantsStack = createStackNavigator(); const ElusEtudiantsStack = createStackNavigator();
function ElusEtudiantsStackComponent() { function ElusEtudiantsStackComponent() {
const {colors} = useTheme();
return ( return (
<ElusEtudiantsStack.Navigator <ElusEtudiantsStack.Navigator
initialRouteName="elus-etudiants" initialRouteName="elus-etudiants"
headerMode="float" headerMode="float"
screenOptions={defaultScreenOptions} screenOptions={defaultScreenOptions}
> >
<ElusEtudiantsStack.Screen {createCollapsibleStack(
name="elus-etudiants" <ElusEtudiantsStack.Screen
component={ElusEtudiantsWebsiteScreen} name="elus-etudiants"
options={({navigation}) => { component={ElusEtudiantsWebsiteScreen}
const openDrawer = getDrawerButton.bind(this, navigation); options={({navigation}) => {
return { const openDrawer = getDrawerButton.bind(this, navigation);
title: "Élus Étudiants", return {
headerLeft: openDrawer title: "Élus Étudiants",
}; headerLeft: openDrawer,
}} headerStyle: {
/> backgroundColor: colors.surface,
},
};
}}
/>,
{
collapsedColor: 'transparent',
useNativeDriver: false, // native driver does not work with webview
}
)}
</ElusEtudiantsStack.Navigator> </ElusEtudiantsStack.Navigator>
); );
} }
@ -226,23 +278,33 @@ function ElusEtudiantsStackComponent() {
const WiketudStack = createStackNavigator(); const WiketudStack = createStackNavigator();
function WiketudStackComponent() { function WiketudStackComponent() {
const {colors} = useTheme();
return ( return (
<WiketudStack.Navigator <WiketudStack.Navigator
initialRouteName="wiketud" initialRouteName="wiketud"
headerMode="float" headerMode="float"
screenOptions={defaultScreenOptions} screenOptions={defaultScreenOptions}
> >
<WiketudStack.Screen {createCollapsibleStack(
name="wiketud" <WiketudStack.Screen
component={WiketudWebsiteScreen} name="wiketud"
options={({navigation}) => { component={WiketudWebsiteScreen}
const openDrawer = getDrawerButton.bind(this, navigation); options={({navigation}) => {
return { const openDrawer = getDrawerButton.bind(this, navigation);
title: "Wiketud", return {
headerLeft: openDrawer title: "Wiketud",
}; headerLeft: openDrawer,
}} headerStyle: {
/> backgroundColor: colors.surface,
},
};
}}
/>,
{
collapsedColor: 'transparent',
useNativeDriver: false, // native driver does not work with webview
}
)}
</WiketudStack.Navigator> </WiketudStack.Navigator>
); );
} }
@ -250,29 +312,38 @@ function WiketudStackComponent() {
const TutorInsaStack = createStackNavigator(); const TutorInsaStack = createStackNavigator();
function TutorInsaStackComponent() { function TutorInsaStackComponent() {
const {colors} = useTheme();
return ( return (
<TutorInsaStack.Navigator <TutorInsaStack.Navigator
initialRouteName="tutorinsa" initialRouteName="tutorinsa"
headerMode="float" headerMode="float"
screenOptions={defaultScreenOptions} screenOptions={defaultScreenOptions}
> >
<TutorInsaStack.Screen {createCollapsibleStack(
name="tutorinsa" <TutorInsaStack.Screen
component={TutorInsaWebsiteScreen} name="tutorinsa"
options={({navigation}) => { component={TutorInsaWebsiteScreen}
const openDrawer = getDrawerButton.bind(this, navigation); options={({navigation}) => {
return { const openDrawer = getDrawerButton.bind(this, navigation);
title: "Tutor'INSA", return {
headerLeft: openDrawer title: "Tutor'INSA",
}; headerLeft: openDrawer,
}} headerStyle: {
/> backgroundColor: colors.surface,
},
};
}}
/>,
{
collapsedColor: 'transparent',
useNativeDriver: false, // native driver does not work with webview
}
)}
</TutorInsaStack.Navigator> </TutorInsaStack.Navigator>
); );
} }
const TetrisStack = createStackNavigator(); const TetrisStack = createStackNavigator();
function TetrisStackComponent() { function TetrisStackComponent() {
@ -408,23 +479,33 @@ function AmicaleContactStackComponent() {
const ClubStack = createStackNavigator(); const ClubStack = createStackNavigator();
function ClubStackComponent() { function ClubStackComponent() {
const {colors} = useTheme();
return ( return (
<ClubStack.Navigator <ClubStack.Navigator
initialRouteName={"club-list"} initialRouteName={"club-list"}
headerMode="float" headerMode="float"
screenOptions={defaultScreenOptions} screenOptions={defaultScreenOptions}
> >
<ClubStack.Screen {createCollapsibleStack(
name="club-list" <ClubStack.Screen
component={ClubListScreen} name="club-list"
options={({navigation}) => { component={ClubListScreen}
const openDrawer = getDrawerButton.bind(this, navigation); options={({navigation}) => {
return { const openDrawer = getDrawerButton.bind(this, navigation);
title: i18n.t('clubs.clubList'), return {
headerLeft: openDrawer title: i18n.t('clubs.clubList'),
}; headerLeft: openDrawer,
}} headerStyle: {
/> backgroundColor: colors.surface,
},
};
}}
/>,
{
collapsedColor: 'transparent',
useNativeDriver: true,
}
)}
<ClubStack.Screen <ClubStack.Screen
name="club-information" name="club-information"
component={ClubDisplayScreen} component={ClubDisplayScreen}

View file

@ -13,12 +13,13 @@ import ProximoAboutScreen from "../screens/Proximo/ProximoAboutScreen";
import PlanexScreen from '../screens/Websites/PlanexScreen'; import PlanexScreen from '../screens/Websites/PlanexScreen';
import {MaterialCommunityIcons} from "@expo/vector-icons"; import {MaterialCommunityIcons} from "@expo/vector-icons";
import AsyncStorageManager from "../managers/AsyncStorageManager"; import AsyncStorageManager from "../managers/AsyncStorageManager";
import {withTheme} from 'react-native-paper'; import {useTheme, withTheme} from 'react-native-paper';
import i18n from "i18n-js"; import i18n from "i18n-js";
import ClubDisplayScreen from "../screens/Amicale/Clubs/ClubDisplayScreen"; import ClubDisplayScreen from "../screens/Amicale/Clubs/ClubDisplayScreen";
import ScannerScreen from "../screens/ScannerScreen"; import ScannerScreen from "../screens/ScannerScreen";
import MaterialHeaderButtons, {Item} from "../components/Custom/HeaderButton"; import MaterialHeaderButtons, {Item} from "../components/Custom/HeaderButton";
import FeedItemScreen from "../screens/FeedItemScreen"; import FeedItemScreen from "../screens/FeedItemScreen";
import {createCollapsibleStack} from "react-navigation-collapsible";
const TAB_ICONS = { const TAB_ICONS = {
@ -46,30 +47,49 @@ function getDrawerButton(navigation: Object) {
const ProximoStack = createStackNavigator(); const ProximoStack = createStackNavigator();
function ProximoStackComponent() { function ProximoStackComponent() {
const {colors} = useTheme();
return ( return (
<ProximoStack.Navigator <ProximoStack.Navigator
initialRouteName="index" initialRouteName="index"
headerMode="float" headerMode="float"
screenOptions={defaultScreenOptions} screenOptions={defaultScreenOptions}
> >
<ProximoStack.Screen {createCollapsibleStack(
name="index" <ProximoStack.Screen
options={({navigation}) => { name="index"
const openDrawer = getDrawerButton.bind(this, navigation); options={({navigation}) => {
return { const openDrawer = getDrawerButton.bind(this, navigation);
title: 'Proximo', return {
headerLeft: openDrawer title: 'Proximo',
}; headerLeft: openDrawer,
}} headerStyle: {
component={ProximoMainScreen} backgroundColor: colors.surface,
/> },
<ProximoStack.Screen };
name="proximo-list" }}
options={{ component={ProximoMainScreen}
title: i18n.t('screens.proximoArticles') />,
}} {
component={ProximoListScreen} collapsedColor: 'transparent',
/> useNativeDriver: true,
}
)}
{createCollapsibleStack(
<ProximoStack.Screen
name="proximo-list"
options={{
title: i18n.t('screens.proximoArticles'),
headerStyle: {
backgroundColor: colors.surface,
}
}}
component={ProximoListScreen}
/>,
{
collapsedColor: 'transparent',
useNativeDriver: true,
}
)}
<ProximoStack.Screen <ProximoStack.Screen
name="proximo-about" name="proximo-about"
component={ProximoAboutScreen} component={ProximoAboutScreen}
@ -85,23 +105,33 @@ function ProximoStackComponent() {
const ProxiwashStack = createStackNavigator(); const ProxiwashStack = createStackNavigator();
function ProxiwashStackComponent() { function ProxiwashStackComponent() {
const {colors} = useTheme();
return ( return (
<ProxiwashStack.Navigator <ProxiwashStack.Navigator
initialRouteName="index" initialRouteName="index"
headerMode='float' headerMode='float'
screenOptions={defaultScreenOptions} screenOptions={defaultScreenOptions}
> >
<ProxiwashStack.Screen {createCollapsibleStack(
name="index" <ProxiwashStack.Screen
component={ProxiwashScreen} name="index"
options={({navigation}) => { component={ProxiwashScreen}
const openDrawer = getDrawerButton.bind(this, navigation); options={({navigation}) => {
return { const openDrawer = getDrawerButton.bind(this, navigation);
title: i18n.t('screens.proxiwash'), return {
headerLeft: openDrawer title: i18n.t('screens.proxiwash'),
}; headerLeft: openDrawer,
}} headerStyle: {
/> backgroundColor: colors.surface,
},
};
}}
/>,
{
collapsedColor: 'transparent',
useNativeDriver: true,
}
)}
<ProxiwashStack.Screen <ProxiwashStack.Screen
name="proxiwash-about" name="proxiwash-about"
component={ProxiwashAboutScreen} component={ProxiwashAboutScreen}
@ -117,6 +147,7 @@ function ProxiwashStackComponent() {
const PlanningStack = createStackNavigator(); const PlanningStack = createStackNavigator();
function PlanningStackComponent() { function PlanningStackComponent() {
const {colors} = useTheme();
return ( return (
<PlanningStack.Navigator <PlanningStack.Navigator
initialRouteName="index" initialRouteName="index"
@ -152,25 +183,34 @@ function HomeStackComponent(initialRoute: string | null, defaultData: Object) {
let data; let data;
if (initialRoute !== null) if (initialRoute !== null)
data = {data: defaultData, nextScreen: initialRoute, shouldOpen: true}; data = {data: defaultData, nextScreen: initialRoute, shouldOpen: true};
const {colors} = useTheme();
return ( return (
<HomeStack.Navigator <HomeStack.Navigator
initialRouteName={"index"} initialRouteName={"index"}
headerMode="float" headerMode="float"
screenOptions={defaultScreenOptions} screenOptions={defaultScreenOptions}
> >
<HomeStack.Screen {createCollapsibleStack(
name="index" <HomeStack.Screen
component={HomeScreen} name="index"
options={({navigation}) => { component={HomeScreen}
const openDrawer = getDrawerButton.bind(this, navigation); options={({navigation}) => {
return { const openDrawer = getDrawerButton.bind(this, navigation);
title: i18n.t('screens.home'), return {
headerLeft: openDrawer title: i18n.t('screens.home'),
}; headerLeft: openDrawer,
}} headerStyle: {
initialParams={data} backgroundColor: colors.surface,
/> },
};
}}
initialParams={data}
/>,
{
collapsedColor: 'transparent',
useNativeDriver: true,
}
)}
<HomeStack.Screen <HomeStack.Screen
name="home-planning-information" name="home-planning-information"
component={PlanningDisplayScreen} component={PlanningDisplayScreen}
@ -216,23 +256,33 @@ function HomeStackComponent(initialRoute: string | null, defaultData: Object) {
const PlanexStack = createStackNavigator(); const PlanexStack = createStackNavigator();
function PlanexStackComponent() { function PlanexStackComponent() {
const {colors} = useTheme();
return ( return (
<PlanexStack.Navigator <PlanexStack.Navigator
initialRouteName="index" initialRouteName="index"
headerMode="float" headerMode="float"
screenOptions={defaultScreenOptions} screenOptions={defaultScreenOptions}
> >
<PlanexStack.Screen {createCollapsibleStack(
name="index" <PlanexStack.Screen
component={PlanexScreen} name="index"
options={({navigation}) => { component={PlanexScreen}
const openDrawer = getDrawerButton.bind(this, navigation); options={({navigation}) => {
return { const openDrawer = getDrawerButton.bind(this, navigation);
title: 'Planex', return {
headerLeft: openDrawer title: 'Planex',
}; headerLeft: openDrawer,
}} headerStyle: {
/> backgroundColor: colors.surface,
},
};
}}
/>,
{
collapsedColor: 'transparent',
useNativeDriver: false, // native driver does not work with webview
}
)}
</PlanexStack.Navigator> </PlanexStack.Navigator>
); );
} }

View file

@ -1,7 +1,7 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {FlatList, Platform} from "react-native"; import {Animated, Platform} from "react-native";
import {Chip, Searchbar, withTheme} from 'react-native-paper'; import {Chip, Searchbar, withTheme} from 'react-native-paper';
import AuthenticatedScreen from "../../../components/Amicale/AuthenticatedScreen"; import AuthenticatedScreen from "../../../components/Amicale/AuthenticatedScreen";
import i18n from "i18n-js"; import i18n from "i18n-js";
@ -9,10 +9,12 @@ import ClubListItem from "../../../components/Lists/ClubListItem";
import {isItemInCategoryFilter, stringMatchQuery} from "../../../utils/Search"; import {isItemInCategoryFilter, stringMatchQuery} from "../../../utils/Search";
import ClubListHeader from "../../../components/Lists/ClubListHeader"; import ClubListHeader from "../../../components/Lists/ClubListHeader";
import MaterialHeaderButtons, {Item} from "../../../components/Custom/HeaderButton"; import MaterialHeaderButtons, {Item} from "../../../components/Custom/HeaderButton";
import {withCollapsible} from "../../../utils/withCollapsible";
type Props = { type Props = {
navigation: Object, navigation: Object,
theme: Object, theme: Object,
collapsibleStack: Object,
} }
type State = { type State = {
@ -94,9 +96,10 @@ class ClubListScreen extends React.Component<Props, State> {
getScreen = (data: Object) => { getScreen = (data: Object) => {
this.categories = data[0].categories; this.categories = data[0].categories;
const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack;
return ( return (
//$FlowFixMe //$FlowFixMe
<FlatList <Animated.FlatList
data={data[0].clubs} data={data[0].clubs}
keyExtractor={this.keyExtractor} keyExtractor={this.keyExtractor}
renderItem={this.getRenderItem} renderItem={this.getRenderItem}
@ -104,6 +107,10 @@ class ClubListScreen extends React.Component<Props, State> {
// Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration // Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
removeClippedSubviews={true} removeClippedSubviews={true}
getItemLayout={this.itemLayout} getItemLayout={this.itemLayout}
// Animations
onScroll={onScroll}
contentContainerStyle={{paddingTop: containerPaddingTop}}
scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}
/> />
) )
}; };
@ -208,4 +215,4 @@ class ClubListScreen extends React.Component<Props, State> {
} }
} }
export default withTheme(ClubListScreen); export default withCollapsible(withTheme(ClubListScreen));

View file

@ -1,13 +1,14 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {FlatList, Image, Platform, ScrollView, View} from "react-native"; import {Animated, Image, Platform, ScrollView, View} from "react-native";
import i18n from "i18n-js"; import i18n from "i18n-js";
import CustomModal from "../../components/Custom/CustomModal"; import CustomModal from "../../components/Custom/CustomModal";
import {RadioButton, Searchbar, Subheading, Text, Title, withTheme} from "react-native-paper"; import {RadioButton, Searchbar, Subheading, Text, Title, withTheme} from "react-native-paper";
import {stringMatchQuery} from "../../utils/Search"; import {stringMatchQuery} from "../../utils/Search";
import ProximoListItem from "../../components/Lists/ProximoListItem"; import ProximoListItem from "../../components/Lists/ProximoListItem";
import MaterialHeaderButtons, {Item} from "../../components/Custom/HeaderButton"; import MaterialHeaderButtons, {Item} from "../../components/Custom/HeaderButton";
import {withCollapsible} from "../../utils/withCollapsible";
function sortPrice(a, b) { function sortPrice(a, b) {
return a.price - b.price; return a.price - b.price;
@ -39,6 +40,7 @@ type Props = {
navigation: Object, navigation: Object,
route: Object, route: Object,
theme: Object, theme: Object,
collapsibleStack: Object,
} }
type State = { type State = {
@ -295,6 +297,7 @@ class ProximoListScreen extends React.Component<Props, State> {
itemLayout = (data, index) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index}); itemLayout = (data, index) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index});
render() { render() {
const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack;
return ( return (
<View style={{ <View style={{
height: '100%' height: '100%'
@ -303,7 +306,7 @@ class ProximoListScreen extends React.Component<Props, State> {
{this.state.modalCurrentDisplayItem} {this.state.modalCurrentDisplayItem}
</CustomModal> </CustomModal>
{/*$FlowFixMe*/} {/*$FlowFixMe*/}
<FlatList <Animated.FlatList
data={this.listData} data={this.listData}
extraData={this.state.currentSearchString + this.state.currentSortMode} extraData={this.state.currentSearchString + this.state.currentSortMode}
keyExtractor={this.keyExtractor} keyExtractor={this.keyExtractor}
@ -312,10 +315,14 @@ class ProximoListScreen extends React.Component<Props, State> {
removeClippedSubviews={true} removeClippedSubviews={true}
getItemLayout={this.itemLayout} getItemLayout={this.itemLayout}
initialNumToRender={10} initialNumToRender={10}
// Animations
onScroll={onScroll}
contentContainerStyle={{paddingTop: containerPaddingTop}}
scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}
/> />
</View> </View>
); );
} }
} }
export default withTheme(ProximoListScreen); export default withCollapsible(withTheme(ProximoListScreen));

View file

@ -0,0 +1,8 @@
import React from 'react';
import {useCollapsibleStack} from "react-navigation-collapsible";
export const withCollapsible = (Component: any) => {
return (props: any) => {
return <Component collapsibleStack={useCollapsibleStack()} {...props} />;
};
};