123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597 |
- // @flow
-
- import * as React from 'react';
- import {FlatList} from 'react-native';
- import i18n from "i18n-js";
- import DashboardItem from "../../components/Home/EventDashboardItem";
- import WebSectionList from "../../components/Screens/WebSectionList";
- import {Headline, withTheme} from 'react-native-paper';
- import FeedItem from "../../components/Home/FeedItem";
- import SmallDashboardItem from "../../components/Home/SmallDashboardItem";
- import PreviewEventDashboardItem from "../../components/Home/PreviewEventDashboardItem";
- import {stringToDate} from "../../utils/Planning";
- import ActionsDashBoardItem from "../../components/Home/ActionsDashboardItem";
- import {CommonActions} from '@react-navigation/native';
- import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton";
- import AnimatedFAB from "../../components/Animations/AnimatedFAB";
- import {StackNavigationProp} from "@react-navigation/stack";
- import type {CustomTheme} from "../../managers/ThemeManager";
- import {View} from "react-native-animatable";
- import ConnectionManager from "../../managers/ConnectionManager";
- import LogoutDialog from "../../components/Amicale/LogoutDialog";
- import AsyncStorageManager from "../../managers/AsyncStorageManager";
- import {MASCOT_STYLE} from "../../components/Mascot/Mascot";
- import MascotPopup from "../../components/Mascot/MascotPopup";
- import DashboardManager from "../../managers/DashboardManager";
- // import DATA from "../dashboard_data.json";
-
-
- const NAME_AMICALE = 'Amicale INSA Toulouse';
- const DATA_URL = "https://etud.insa-toulouse.fr/~amicale_app/v2/dashboard/dashboard_data.json";
- const FEED_ITEM_HEIGHT = 500;
-
- const SECTIONS_ID = [
- 'dashboard',
- 'news_feed'
- ];
-
- const REFRESH_TIME = 1000 * 20; // Refresh every 20 seconds
-
- type rawDashboard = {
- news_feed: {
- data: Array<feedItem>,
- },
- dashboard: fullDashboard,
- }
-
- export type feedItem = {
- full_picture: string,
- message: string,
- permalink_url: string,
- created_time: number,
- id: string,
- };
-
- export type fullDashboard = {
- today_menu: Array<{ [key: string]: any }>,
- proximo_articles: number,
- available_dryers: number,
- available_washers: number,
- today_events: Array<{ [key: string]: any }>,
- available_tutorials: number,
- }
-
- type dashboardItem = {
- id: string,
- content: Array<{ [key: string]: any }>
- };
-
- export type event = {
- id: number,
- title: string,
- logo: string | null,
- date_begin: string,
- date_end: string,
- description: string,
- club: string,
- category_id: number,
- url: string,
- }
-
- type listSection = {
- title: string,
- data: Array<dashboardItem> | Array<feedItem>,
- id: string
- };
-
- type Props = {
- navigation: StackNavigationProp,
- route: { params: any, ... },
- theme: CustomTheme,
- }
-
- type State = {
- dialogVisible: boolean,
- mascotDialogVisible: boolean,
- }
-
- /**
- * Class defining the app's home screen
- */
- class HomeScreen extends React.Component<Props, State> {
-
- isLoggedIn: boolean | null;
-
- fabRef: { current: null | AnimatedFAB };
- currentNewFeed: Array<feedItem>;
- currentDashboard: fullDashboard | null;
-
- dashboardManager: DashboardManager;
-
- constructor(props) {
- super(props);
- this.fabRef = React.createRef();
- this.dashboardManager = new DashboardManager(this.props.navigation);
- this.currentNewFeed = [];
- this.currentDashboard = null;
- this.isLoggedIn = ConnectionManager.getInstance().isLoggedIn();
- this.props.navigation.setOptions({
- headerRight: this.getHeaderButton,
- });
- this.state = {
- dialogVisible: false,
- mascotDialogVisible: AsyncStorageManager.getInstance().preferences.homeShowBanner.current === "1"
- && !this.isLoggedIn,
- }
- }
-
- /**
- * Converts a dateString using Unix Timestamp to a formatted date
- *
- * @param dateString {string} The Unix Timestamp representation of a date
- * @return {string} The formatted output date
- */
- static getFormattedDate(dateString: number) {
- let date = new Date(dateString * 1000);
- return date.toLocaleString();
- }
-
- componentDidMount() {
- this.props.navigation.addListener('focus', this.onScreenFocus);
- // Handle link open when home is focused
- this.props.navigation.addListener('state', this.handleNavigationParams);
- }
-
- /**
- * Updates login state and navigation parameters on screen focus
- */
- onScreenFocus = () => {
- if (ConnectionManager.getInstance().isLoggedIn() !== this.isLoggedIn) {
- this.isLoggedIn = ConnectionManager.getInstance().isLoggedIn();
- this.props.navigation.setOptions({
- headerRight: this.getHeaderButton,
- });
- }
- // handle link open when home is not focused or created
- this.handleNavigationParams();
- };
-
- /**
- * Navigates to the a new screen if navigation parameters specify one
- */
- handleNavigationParams = () => {
- if (this.props.route.params != null) {
- if (this.props.route.params.nextScreen != null) {
- this.props.navigation.navigate(this.props.route.params.nextScreen, this.props.route.params.data);
- // reset params to prevent infinite loop
- this.props.navigation.dispatch(CommonActions.setParams({nextScreen: null}));
- }
- }
- };
-
- /**
- * Gets header buttons based on login state
- *
- * @returns {*}
- */
- getHeaderButton = () => {
- let onPressLog = () => this.props.navigation.navigate("login", {nextScreen: "profile"});
- let logIcon = "login";
- let logColor = this.props.theme.colors.primary;
- if (this.isLoggedIn) {
- onPressLog = () => this.showDisconnectDialog();
- logIcon = "logout";
- logColor = this.props.theme.colors.text;
- }
-
- const onPressSettings = () => this.props.navigation.navigate("settings");
- return <MaterialHeaderButtons>
- <Item title="log" iconName={logIcon} color={logColor} onPress={onPressLog}/>
- <Item title={i18n.t("screens.settings.title")} iconName={"cog"} onPress={onPressSettings}/>
- </MaterialHeaderButtons>;
- };
-
- hideMascotDialog = () => {
- AsyncStorageManager.getInstance().savePref(
- AsyncStorageManager.getInstance().preferences.homeShowBanner.key,
- '0'
- );
- this.setState({mascotDialogVisible: false})
- };
-
- showDisconnectDialog = () => this.setState({dialogVisible: true});
-
- hideDisconnectDialog = () => this.setState({dialogVisible: false});
-
- /**
- * Creates the dataset to be used in the FlatList
- *
- * @param fetchedData
- * @return {*}
- */
- createDataset = (fetchedData: rawDashboard) => {
- // fetchedData = DATA;
- let dashboardData;
- if (fetchedData.news_feed != null)
- this.currentNewFeed = fetchedData.news_feed.data;
- if (fetchedData.dashboard != null)
- this.currentDashboard = fetchedData.dashboard;
-
- if (fetchedData.dashboard != null)
- dashboardData = this.generateDashboardDataset(fetchedData.dashboard);
- else
- dashboardData = this.generateDashboardDataset(null);
- return [
- {
- title: '',
- data: dashboardData,
- id: SECTIONS_ID[0]
- },
- {
- title: i18n.t("screens.home.feedTitle"),
- data: this.currentNewFeed,
- 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
- return this.getDashboardActions();
- }
-
- /**
- * Gets a dashboard item with action buttons
- *
- * @returns {*}
- */
- getDashboardActions() {
- return <ActionsDashBoardItem {...this.props} isLoggedIn={this.isLoggedIn}/>;
- }
-
- /**
- * Gets the time limit depending on the current day:
- * 17:30 for every day of the week except for thursday 11:30
- * 00:00 on weekends
- */
- getTodayEventTimeLimit() {
- let now = new Date();
- if (now.getDay() === 4) // Thursday
- now.setHours(11, 30, 0);
- else if (now.getDay() === 6 || now.getDay() === 0) // Weekend
- now.setHours(0, 0, 0);
- else
- now.setHours(17, 30, 0);
- return now;
- }
-
- /**
- * Gets the duration (in milliseconds) of an event
- *
- * @param event {event}
- * @return {number} The number of milliseconds
- */
- getEventDuration(event: event): number {
- let start = stringToDate(event.date_begin);
- let end = stringToDate(event.date_end);
- let duration = 0;
- if (start != null && end != null)
- duration = end - start;
- return duration;
- }
-
- /**
- * Gets events starting after the limit
- *
- * @param events
- * @param limit
- * @return {Array<Object>}
- */
- getEventsAfterLimit(events: Array<event>, limit: Date): Array<event> {
- let validEvents = [];
- for (let event of events) {
- let startDate = stringToDate(event.date_begin);
- if (startDate != null && startDate >= limit) {
- validEvents.push(event);
- }
- }
- return validEvents;
- }
-
- /**
- * Gets the event with the longest duration in the given array.
- * If all events have the same duration, return the first in the array.
- *
- * @param events
- */
- getLongestEvent(events: Array<event>): event {
- let longestEvent = events[0];
- let longestTime = 0;
- for (let event of events) {
- let time = this.getEventDuration(event);
- if (time > longestTime) {
- longestTime = time;
- longestEvent = event;
- }
- }
- return longestEvent;
- }
-
- /**
- * Gets events that have not yet ended/started
- *
- * @param events
- */
- getFutureEvents(events: Array<event>): Array<event> {
- let validEvents = [];
- let now = new Date();
- for (let event of events) {
- let startDate = stringToDate(event.date_begin);
- let endDate = stringToDate(event.date_end);
- if (startDate != null) {
- if (startDate > now)
- validEvents.push(event);
- else if (endDate != null) {
- if (endDate > now || endDate < startDate) // Display event if it ends the following day
- validEvents.push(event);
- }
- }
- }
- return validEvents;
- }
-
- /**
- * Gets the event to display in the preview
- *
- * @param events
- * @return {Object}
- */
- getDisplayEvent(events: Array<event>): event | null {
- let displayEvent = null;
- if (events.length > 1) {
- let eventsAfterLimit = this.getEventsAfterLimit(events, this.getTodayEventTimeLimit());
- if (eventsAfterLimit.length > 0) {
- if (eventsAfterLimit.length === 1)
- displayEvent = eventsAfterLimit[0];
- else
- displayEvent = this.getLongestEvent(events);
- } else {
- displayEvent = this.getLongestEvent(events);
- }
- } else if (events.length === 1) {
- displayEvent = events[0];
- }
- return displayEvent;
- }
-
- onEventContainerClick = () => this.props.navigation.navigate('planning');
-
- /**
- * Gets the event dashboard render item.
- * If a preview is available, it will be rendered inside
- *
- * @param content
- * @return {*}
- */
- getDashboardEvent(content: Array<event>) {
- let futureEvents = this.getFutureEvents(content);
- let displayEvent = this.getDisplayEvent(futureEvents);
- // const clickPreviewAction = () =>
- // this.props.navigation.navigate('students', {
- // screen: 'planning-information',
- // params: {data: displayEvent}
- // });
- return (
- <DashboardItem
- eventNumber={futureEvents.length}
- clickAction={this.onEventContainerClick}
- >
- <PreviewEventDashboardItem
- event={displayEvent != null ? displayEvent : undefined}
- clickAction={this.onEventContainerClick}
- />
- </DashboardItem>
- );
- }
-
- /**
- * Gets a dashboard shortcut item
- *
- * @param item
- * @returns {*}
- */
- dashboardRowRenderItem = ({item}: { item: DashboardItem }) => {
- return (
- <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.
- *
- * @param content
- * @return {*}
- */
- getDashboardRow(content: Array<DashboardItem>) {
- return (
- //$FlowFixMe
- <FlatList
- data={content}
- renderItem={this.dashboardRowRenderItem}
- horizontal={true}
- contentContainerStyle={{
- marginLeft: 'auto',
- marginRight: 'auto',
- marginTop: 10,
- marginBottom: 10,
- }}
- />);
- }
-
- /**
- * Gets a render item for the given feed object
- *
- * @param item The feed item to display
- * @return {*}
- */
- getFeedItem(item: feedItem) {
- return (
- <FeedItem
- {...this.props}
- item={item}
- title={NAME_AMICALE}
- subtitle={HomeScreen.getFormattedDate(item.created_time)}
- height={FEED_ITEM_HEIGHT}
- />
- );
- }
-
- /**
- * Gets a FlatList render item
- *
- * @param item The item to display
- * @param section The current section
- * @return {*}
- */
- getRenderItem = ({item, section}: {
- 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>) => {
- if (this.fabRef.current != null)
- this.fabRef.current.onScroll(event);
- };
-
- renderSectionHeader = (data: { [key: string]: any }) => {
- if (data.section.title !== "")
- return (
- <Headline style={{
- textAlign: "center",
- marginTop: 50,
- marginBottom: 10,
- }}>
- {data.section.title}
- </Headline>
- )
- else
- return null;
- }
-
- /**
- * Callback when pressing the login button on the banner.
- * This hides the banner and takes the user to the login page.
- */
- onLogin = () => {
- this.hideMascotDialog();
- this.props.navigation.navigate("login", {nextScreen: "profile"});
- }
-
- render() {
- return (
- <View
- style={{flex: 1}}
- >
- <View style={{
- position: "absolute",
- width: "100%",
- height: "100%",
- }}>
- <WebSectionList
- {...this.props}
- createDataset={this.createDataset}
- autoRefreshTime={REFRESH_TIME}
- refreshOnFocus={true}
- fetchUrl={DATA_URL}
- renderItem={this.getRenderItem}
- itemHeight={FEED_ITEM_HEIGHT}
- onScroll={this.onScroll}
- showError={false}
- renderSectionHeader={this.renderSectionHeader}
- />
- </View>
- <MascotPopup
- visible={this.state.mascotDialogVisible}
- title={i18n.t("screens.home.mascotDialog.title")}
- message={i18n.t("screens.home.mascotDialog.message")}
- icon={"human-greeting"}
- buttons={{
- action: {
- message: i18n.t("screens.home.mascotDialog.login"),
- icon: "login",
- onPress: this.onLogin,
- },
- cancel: {
- message: i18n.t("screens.home.mascotDialog.later"),
- icon: "close",
- color: this.props.theme.colors.warning,
- onPress: this.hideMascotDialog,
- }
- }}
- emotion={MASCOT_STYLE.CUTE}
- />
- <AnimatedFAB
- {...this.props}
- ref={this.fabRef}
- icon="qrcode-scan"
- onPress={this.openScanner}
- />
- <LogoutDialog
- {...this.props}
- visible={this.state.dialogVisible}
- onDismiss={this.hideDisconnectDialog}
- />
- </View>
- );
- }
- }
-
- export default withTheme(HomeScreen);
|