Improved home screen handling

This commit is contained in:
Arnaud Vergnet 2020-07-22 22:15:24 +02:00
parent 560c336759
commit 22d5f61fc5
4 changed files with 130 additions and 133 deletions

View file

@ -95,8 +95,10 @@
}, },
"home": { "home": {
"title": "Campus", "title": "Campus",
"feedTitle": "Amicale's News", "feedTitle": "Campus News",
"feed": "Details", "feed": "Details",
"feedLoading": "Loading News",
"feedError": "Failed to load news",
"dashboard": { "dashboard": {
"seeMore": "Click to see more", "seeMore": "Click to see more",
"todayEventsTitle": "Today's events", "todayEventsTitle": "Today's events",

View file

@ -95,8 +95,10 @@
}, },
"home": { "home": {
"title": "Campus", "title": "Campus",
"feedTitle": "News de l'Amicale", "feedTitle": "News du Campus",
"feed": "Détails", "feed": "Détails",
"feedLoading": "Chargement des news",
"feedError": "Erreur de chargement des news",
"dashboard": { "dashboard": {
"seeMore": "Clique pour plus d'infos", "seeMore": "Clique pour plus d'infos",
"todayEventsTitle": "Événements aujourd'hui", "todayEventsTitle": "Événements aujourd'hui",

View file

@ -11,21 +11,23 @@ import {withCollapsible} from "../../utils/withCollapsible";
import * as Animatable from 'react-native-animatable'; import * as Animatable from 'react-native-animatable';
import CustomTabBar from "../Tabbar/CustomTabBar"; import CustomTabBar from "../Tabbar/CustomTabBar";
import {Collapsible} from "react-navigation-collapsible"; import {Collapsible} from "react-navigation-collapsible";
import {StackNavigationProp} from "@react-navigation/stack";
type Props = { type Props = {
navigation: { [key: string]: any }, navigation: StackNavigationProp,
fetchUrl: string, fetchUrl: string,
autoRefreshTime: number, autoRefreshTime: number,
refreshOnFocus: boolean, refreshOnFocus: boolean,
renderItem: (data: { [key: string]: any }) => React.Node, renderItem: (data: { [key: string]: any }) => React.Node,
createDataset: (data: { [key: string]: any }) => Array<Object>, createDataset: (data: { [key: string]: any } | null, isLoading?: boolean) => Array<Object>,
onScroll: (event: SyntheticEvent<EventTarget>) => void, onScroll: (event: SyntheticEvent<EventTarget>) => void,
collapsibleStack: Collapsible, collapsibleStack: Collapsible,
showError: boolean, showError: boolean,
itemHeight?: number, itemHeight?: number,
updateData?: number, updateData?: number,
renderSectionHeader?: (data: { [key: string]: any }) => React.Node, renderListHeaderComponent?: (data: { [key: string]: any } | null) => React.Node,
renderSectionHeader?: (data: { section: { [key: string]: any } }, isLoading?: boolean) => React.Node,
stickyHeader?: boolean, stickyHeader?: boolean,
} }
@ -53,7 +55,6 @@ class WebSectionList extends React.PureComponent<Props, State> {
showError: true, showError: true,
}; };
scrollRef: { current: null | Animated.SectionList };
refreshInterval: IntervalID; refreshInterval: IntervalID;
lastRefresh: Date | null; lastRefresh: Date | null;
@ -69,31 +70,26 @@ class WebSectionList extends React.PureComponent<Props, State> {
* Allows to detect when the screen is focused * Allows to detect when the screen is focused
*/ */
componentDidMount() { componentDidMount() {
const onScreenFocus = this.onScreenFocus.bind(this); this.props.navigation.addListener('focus', this.onScreenFocus);
const onScreenBlur = this.onScreenBlur.bind(this); this.props.navigation.addListener('blur', this.onScreenBlur);
this.props.navigation.addListener('focus', onScreenFocus);
this.props.navigation.addListener('blur', onScreenBlur);
this.scrollRef = React.createRef();
this.onRefresh();
this.lastRefresh = null; this.lastRefresh = null;
this.onRefresh();
} }
/** /**
* Refreshes data when focusing the screen and setup a refresh interval if asked to * Refreshes data when focusing the screen and setup a refresh interval if asked to
*/ */
onScreenFocus() { onScreenFocus = () => {
if (this.props.refreshOnFocus && this.lastRefresh) if (this.props.refreshOnFocus && this.lastRefresh)
this.onRefresh(); this.onRefresh();
if (this.props.autoRefreshTime > 0) if (this.props.autoRefreshTime > 0)
this.refreshInterval = setInterval(this.onRefresh, this.props.autoRefreshTime) this.refreshInterval = setInterval(this.onRefresh, this.props.autoRefreshTime)
// if (this.scrollRef.current) // Reset scroll to top
// this.scrollRef.current.getNode().scrollToLocation({animated:false, itemIndex:0, sectionIndex:0});
} }
/** /**
* Removes any interval on un-focus * Removes any interval on un-focus
*/ */
onScreenBlur() { onScreenBlur = () => {
clearInterval(this.refreshInterval); clearInterval(this.refreshInterval);
} }
@ -173,7 +169,7 @@ class WebSectionList extends React.PureComponent<Props, State> {
duration={500} duration={500}
useNativeDriver useNativeDriver
> >
{this.props.renderSectionHeader(data)} {this.props.renderSectionHeader(data, this.state.refreshing)}
</Animatable.View> </Animatable.View>
); );
} else } else
@ -205,16 +201,12 @@ class WebSectionList extends React.PureComponent<Props, State> {
render() { render() {
let dataset = []; let dataset = [];
if (this.state.fetchedData != null || (this.state.fetchedData == null && !this.props.showError)) { if (this.state.fetchedData != null || (this.state.fetchedData == null && !this.props.showError)) {
if (this.state.fetchedData == null) dataset = this.props.createDataset(this.state.fetchedData, this.state.refreshing);
dataset = this.props.createDataset({});
else
dataset = this.props.createDataset(this.state.fetchedData);
} }
const {containerPaddingTop, scrollIndicatorInsetTop, onScrollWithListener} = this.props.collapsibleStack; const {containerPaddingTop, scrollIndicatorInsetTop, onScrollWithListener} = this.props.collapsibleStack;
return ( return (
<View> <View>
<Animated.SectionList <Animated.SectionList
ref={this.scrollRef}
sections={dataset} sections={dataset}
extraData={this.props.updateData} extraData={this.props.updateData}
refreshControl={ refreshControl={
@ -228,6 +220,9 @@ class WebSectionList extends React.PureComponent<Props, State> {
renderItem={this.renderItem} renderItem={this.renderItem}
stickySectionHeadersEnabled={this.props.stickyHeader} stickySectionHeadersEnabled={this.props.stickyHeader}
style={{minHeight: '100%'}} style={{minHeight: '100%'}}
ListHeaderComponent={this.props.renderListHeaderComponent != null
? this.props.renderListHeaderComponent(this.state.fetchedData)
: null}
ListEmptyComponent={this.state.refreshing ListEmptyComponent={this.state.refreshing
? <BasicLoadingScreen/> ? <BasicLoadingScreen/>
: <ErrorView : <ErrorView

View file

@ -5,7 +5,7 @@ import {FlatList} from 'react-native';
import i18n from "i18n-js"; import i18n from "i18n-js";
import DashboardItem from "../../components/Home/EventDashboardItem"; import DashboardItem from "../../components/Home/EventDashboardItem";
import WebSectionList from "../../components/Screens/WebSectionList"; import WebSectionList from "../../components/Screens/WebSectionList";
import {Headline, withTheme} from 'react-native-paper'; import {ActivityIndicator, Headline, withTheme} from 'react-native-paper';
import FeedItem from "../../components/Home/FeedItem"; import FeedItem from "../../components/Home/FeedItem";
import SmallDashboardItem from "../../components/Home/SmallDashboardItem"; import SmallDashboardItem from "../../components/Home/SmallDashboardItem";
import PreviewEventDashboardItem from "../../components/Home/PreviewEventDashboardItem"; import PreviewEventDashboardItem from "../../components/Home/PreviewEventDashboardItem";
@ -16,6 +16,7 @@ import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHead
import AnimatedFAB from "../../components/Animations/AnimatedFAB"; import AnimatedFAB from "../../components/Animations/AnimatedFAB";
import {StackNavigationProp} from "@react-navigation/stack"; import {StackNavigationProp} from "@react-navigation/stack";
import type {CustomTheme} from "../../managers/ThemeManager"; import type {CustomTheme} from "../../managers/ThemeManager";
import * as Animatable from "react-native-animatable";
import {View} from "react-native-animatable"; import {View} from "react-native-animatable";
import ConnectionManager from "../../managers/ConnectionManager"; import ConnectionManager from "../../managers/ConnectionManager";
import LogoutDialog from "../../components/Amicale/LogoutDialog"; import LogoutDialog from "../../components/Amicale/LogoutDialog";
@ -23,6 +24,8 @@ import AsyncStorageManager from "../../managers/AsyncStorageManager";
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";
import DashboardManager from "../../managers/DashboardManager"; import DashboardManager from "../../managers/DashboardManager";
import type {ServiceItem} from "../../managers/ServicesManager";
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
// import DATA from "../dashboard_data.json"; // import DATA from "../dashboard_data.json";
@ -61,11 +64,6 @@ export type fullDashboard = {
available_tutorials: number, available_tutorials: number,
} }
type dashboardItem = {
id: string,
content: Array<{ [key: string]: any }>
};
export type event = { export type event = {
id: number, id: number,
title: string, title: string,
@ -78,12 +76,6 @@ export type event = {
url: string, url: string,
} }
type listSection = {
title: string,
data: Array<dashboardItem> | Array<feedItem>,
id: string
};
type Props = { type Props = {
navigation: StackNavigationProp, navigation: StackNavigationProp,
route: { params: any, ... }, route: { params: any, ... },
@ -203,83 +195,40 @@ class HomeScreen extends React.Component<Props, State> {
hideDisconnectDialog = () => this.setState({dialogVisible: false}); hideDisconnectDialog = () => this.setState({dialogVisible: false});
openScanner = () => this.props.navigation.navigate("scanner");
/** /**
* Creates the dataset to be used in the FlatList * Creates the dataset to be used in the FlatList
* *
* @param fetchedData * @param fetchedData
* @param isLoading
* @return {*} * @return {*}
*/ */
createDataset = (fetchedData: rawDashboard) => { createDataset = (fetchedData: rawDashboard | null, isLoading: boolean) => {
// fetchedData = DATA; // fetchedData = DATA;
let dashboardData; if (fetchedData != null) {
if (fetchedData.news_feed != null) if (fetchedData.news_feed != null)
this.currentNewFeed = fetchedData.news_feed.data; this.currentNewFeed = fetchedData.news_feed.data;
if (fetchedData.dashboard != null) if (fetchedData.dashboard != null)
this.currentDashboard = fetchedData.dashboard; this.currentDashboard = fetchedData.dashboard;
}
if (fetchedData.dashboard != null) if (this.currentNewFeed.length > 0)
dashboardData = this.generateDashboardDataset(fetchedData.dashboard);
else
dashboardData = this.generateDashboardDataset(null);
return [ return [
{
title: '',
data: dashboardData,
id: SECTIONS_ID[0]
},
{ {
title: i18n.t("screens.home.feedTitle"), title: i18n.t("screens.home.feedTitle"),
data: this.currentNewFeed, data: this.currentNewFeed,
id: SECTIONS_ID[1] id: SECTIONS_ID[1]
} }
]; ];
};
/**
* Generates the dataset associated to the dashboard to be displayed in the FlatList as a section
*
* @param dashboardData
* @return {Array<dashboardItem>}
*/
generateDashboardDataset(dashboardData: fullDashboard | null): Array<dashboardItem> {
return [
{id: 'actions', content: []},
{
id: 'top',
content: this.dashboardManager.getCurrentDashboard(),
},
{
id: 'event',
content: dashboardData == null ? [] : dashboardData.today_events
},
];
}
/**
* Gets a dashboard item
*
* @param item The item to display
* @return {*}
*/
getDashboardItem(item: dashboardItem) {
let content = item.content;
if (item.id === 'event')
return this.getDashboardEvent(content);
else if (item.id === 'top')
return this.getDashboardRow(content);
else else
return this.getDashboardActions(); return [
} {
title: isLoading ? i18n.t("screens.home.feedLoading") : i18n.t("screens.home.feedError"),
/** data: [],
* Gets a dashboard item with action buttons id: SECTIONS_ID[1]
*
* @returns {*}
*/
getDashboardActions() {
return <ActionsDashBoardItem {...this.props} isLoggedIn={this.isLoggedIn}/>;
} }
];
};
/** /**
* Gets the time limit depending on the current day: * Gets the time limit depending on the current day:
@ -427,22 +376,13 @@ class HomeScreen extends React.Component<Props, State> {
} }
/** /**
* Gets a dashboard shortcut item * Gets a dashboard item with action buttons
* *
* @param item
* @returns {*} * @returns {*}
*/ */
dashboardRowRenderItem = ({item}: { item: DashboardItem }) => { getDashboardActions() {
return ( return <ActionsDashBoardItem {...this.props} isLoggedIn={this.isLoggedIn}/>;
<SmallDashboardItem }
image={item.image}
onPress={item.onPress}
badgeCount={this.currentDashboard != null && item.badgeFunction != null
? item.badgeFunction(this.currentDashboard)
: null}
/>
);
};
/** /**
* Gets a dashboard item with a row of shortcut buttons. * Gets a dashboard item with a row of shortcut buttons.
@ -450,7 +390,7 @@ class HomeScreen extends React.Component<Props, State> {
* @param content * @param content
* @return {*} * @return {*}
*/ */
getDashboardRow(content: Array<DashboardItem>) { getDashboardRow(content: Array<ServiceItem>) {
return ( return (
//$FlowFixMe //$FlowFixMe
<FlatList <FlatList
@ -466,6 +406,24 @@ class HomeScreen extends React.Component<Props, State> {
/>); />);
} }
/**
* Gets a dashboard shortcut item
*
* @param item
* @returns {*}
*/
dashboardRowRenderItem = ({item}: { item: ServiceItem }) => {
return (
<SmallDashboardItem
image={item.image}
onPress={item.onPress}
badgeCount={this.currentDashboard != null && item.badgeFunction != null
? item.badgeFunction(this.currentDashboard)
: null}
/>
);
};
/** /**
* Gets a render item for the given feed object * Gets a render item for the given feed object
* *
@ -491,28 +449,15 @@ class HomeScreen extends React.Component<Props, State> {
* @param section The current section * @param section The current section
* @return {*} * @return {*}
*/ */
getRenderItem = ({item, section}: { getRenderItem = ({item}: { item: feedItem, }) => this.getFeedItem(item);
item: { [key: string]: any },
section: listSection
}) => {
if (section.id === SECTIONS_ID[0]) {
const data: dashboardItem = item;
return this.getDashboardItem(data);
} else {
const data: feedItem = item;
return this.getFeedItem(data);
}
};
openScanner = () => this.props.navigation.navigate("scanner");
onScroll = (event: SyntheticEvent<EventTarget>) => { onScroll = (event: SyntheticEvent<EventTarget>) => {
if (this.fabRef.current != null) if (this.fabRef.current != null)
this.fabRef.current.onScroll(event); this.fabRef.current.onScroll(event);
}; };
renderSectionHeader = (data: { [key: string]: any }) => { renderSectionHeader = (data: { section: { [key: string]: any } }, isLoading: boolean) => {
if (data.section.title !== "") if (data.section.data.length > 0)
return ( return (
<Headline style={{ <Headline style={{
textAlign: "center", textAlign: "center",
@ -523,7 +468,59 @@ class HomeScreen extends React.Component<Props, State> {
</Headline> </Headline>
) )
else else
return null; return (
<View>
<Headline style={{
textAlign: "center",
marginTop: 50,
marginBottom: 10,
marginLeft: 20,
marginRight: 20,
color: this.props.theme.colors.textDisabled
}}>
{data.section.title}
</Headline>
{isLoading
? <ActivityIndicator
style={{
marginTop: 10
}}
/>
: <MaterialCommunityIcons
name={"access-point-network-off"}
size={100}
color={this.props.theme.colors.textDisabled}
style={{
marginLeft: "auto",
marginRight: "auto",
}}
/>}
</View>
);
}
getListHeader = (fetchedData: rawDashboard) => {
let dashboard = null;
if (fetchedData != null) {
dashboard = fetchedData.dashboard;
}
return (
<Animatable.View
animation={"fadeInDown"}
duration={500}
useNativeDriver={true}
>
{this.getDashboardActions()}
{this.getDashboardRow(this.dashboardManager.getCurrentDashboard())}
{this.getDashboardEvent(
dashboard == null
? []
: dashboard.today_events
)}
</Animatable.View>
);
} }
/** /**
@ -556,6 +553,7 @@ class HomeScreen extends React.Component<Props, State> {
onScroll={this.onScroll} onScroll={this.onScroll}
showError={false} showError={false}
renderSectionHeader={this.renderSectionHeader} renderSectionHeader={this.renderSectionHeader}
renderListHeaderComponent={this.getListHeader}
/> />
</View> </View>
<MascotPopup <MascotPopup