forked from vergnet/application-amicale
Improve Home components to match linter
This commit is contained in:
parent
34ccf9c4c9
commit
6b12b4cde2
9 changed files with 1273 additions and 1198 deletions
|
@ -2,35 +2,44 @@
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {List, withTheme} from 'react-native-paper';
|
import {List, withTheme} from 'react-native-paper';
|
||||||
import {View} from "react-native";
|
import {View} from 'react-native';
|
||||||
import type {CustomTheme} from "../../managers/ThemeManager";
|
|
||||||
import i18n from 'i18n-js';
|
import i18n from 'i18n-js';
|
||||||
import {StackNavigationProp} from "@react-navigation/stack";
|
import {StackNavigationProp} from '@react-navigation/stack';
|
||||||
|
import type {CustomTheme} from '../../managers/ThemeManager';
|
||||||
|
|
||||||
type Props = {
|
type PropsType = {
|
||||||
navigation: StackNavigationProp,
|
navigation: StackNavigationProp,
|
||||||
theme: CustomTheme,
|
theme: CustomTheme,
|
||||||
|
};
|
||||||
|
|
||||||
|
class ActionsDashBoardItem extends React.Component<PropsType> {
|
||||||
|
shouldComponentUpdate(nextProps: PropsType): boolean {
|
||||||
|
const {props} = this;
|
||||||
|
return nextProps.theme.dark !== props.theme.dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ActionsDashBoardItem extends React.Component<Props> {
|
render(): React.Node {
|
||||||
|
const {props} = this;
|
||||||
shouldComponentUpdate(nextProps: Props): boolean {
|
|
||||||
return (nextProps.theme.dark !== this.props.theme.dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<List.Item
|
<List.Item
|
||||||
title={i18n.t("screens.feedback.homeButtonTitle")}
|
title={i18n.t('screens.feedback.homeButtonTitle')}
|
||||||
description={i18n.t("screens.feedback.homeButtonSubtitle")}
|
description={i18n.t('screens.feedback.homeButtonSubtitle')}
|
||||||
left={props => <List.Icon {...props} icon={"comment-quote"}/>}
|
left={({size}: {size: number}): React.Node => (
|
||||||
right={props => <List.Icon {...props} icon={"chevron-right"}/>}
|
<List.Icon size={size} icon="comment-quote" />
|
||||||
onPress={() => this.props.navigation.navigate("feedback")}
|
)}
|
||||||
style={{paddingTop: 0, paddingBottom: 0, marginLeft: 10, marginRight: 10}}
|
right={({size}: {size: number}): React.Node => (
|
||||||
|
<List.Icon size={size} icon="chevron-right" />
|
||||||
|
)}
|
||||||
|
onPress={(): void => props.navigation.navigate('feedback')}
|
||||||
|
style={{
|
||||||
|
paddingTop: 0,
|
||||||
|
paddingBottom: 0,
|
||||||
|
marginLeft: 10,
|
||||||
|
marginRight: 10,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,79 +1,23 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {Avatar, Card, Text, TouchableRipple, withTheme} from 'react-native-paper';
|
import {
|
||||||
import {StyleSheet, View} from "react-native";
|
Avatar,
|
||||||
import i18n from "i18n-js";
|
Card,
|
||||||
import type {CustomTheme} from "../../managers/ThemeManager";
|
Text,
|
||||||
|
TouchableRipple,
|
||||||
|
withTheme,
|
||||||
|
} from 'react-native-paper';
|
||||||
|
import {StyleSheet, View} from 'react-native';
|
||||||
|
import i18n from 'i18n-js';
|
||||||
|
import type {CustomTheme} from '../../managers/ThemeManager';
|
||||||
|
|
||||||
type Props = {
|
type PropsType = {
|
||||||
eventNumber: number;
|
eventNumber: number,
|
||||||
clickAction: () => void,
|
clickAction: () => void,
|
||||||
theme: CustomTheme,
|
theme: CustomTheme,
|
||||||
children?: React.Node
|
children?: React.Node,
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Component used to display a dashboard item containing a preview event
|
|
||||||
*/
|
|
||||||
class EventDashBoardItem extends React.Component<Props> {
|
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: Props) {
|
|
||||||
return (nextProps.theme.dark !== this.props.theme.dark)
|
|
||||||
|| (nextProps.eventNumber !== this.props.eventNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const props = this.props;
|
|
||||||
const colors = props.theme.colors;
|
|
||||||
const isAvailable = props.eventNumber > 0;
|
|
||||||
const iconColor = isAvailable ?
|
|
||||||
colors.planningColor :
|
|
||||||
colors.textDisabled;
|
|
||||||
const textColor = isAvailable ?
|
|
||||||
colors.text :
|
|
||||||
colors.textDisabled;
|
|
||||||
let subtitle;
|
|
||||||
if (isAvailable) {
|
|
||||||
subtitle =
|
|
||||||
<Text>
|
|
||||||
<Text style={{fontWeight: "bold"}}>{props.eventNumber}</Text>
|
|
||||||
<Text>
|
|
||||||
{props.eventNumber > 1
|
|
||||||
? i18n.t('screens.home.dashboard.todayEventsSubtitlePlural')
|
|
||||||
: i18n.t('screens.home.dashboard.todayEventsSubtitle')}
|
|
||||||
</Text>
|
|
||||||
</Text>;
|
|
||||||
} else
|
|
||||||
subtitle = i18n.t('screens.home.dashboard.todayEventsSubtitleNA');
|
|
||||||
return (
|
|
||||||
<Card style={styles.card}>
|
|
||||||
<TouchableRipple
|
|
||||||
style={{flex: 1}}
|
|
||||||
onPress={props.clickAction}>
|
|
||||||
<View>
|
|
||||||
<Card.Title
|
|
||||||
title={i18n.t('screens.home.dashboard.todayEventsTitle')}
|
|
||||||
titleStyle={{color: textColor}}
|
|
||||||
subtitle={subtitle}
|
|
||||||
subtitleStyle={{color: textColor}}
|
|
||||||
left={() =>
|
|
||||||
<Avatar.Icon
|
|
||||||
icon={'calendar-range'}
|
|
||||||
color={iconColor}
|
|
||||||
size={60}
|
|
||||||
style={styles.avatar}/>}
|
|
||||||
/>
|
|
||||||
<Card.Content>
|
|
||||||
{props.children}
|
|
||||||
</Card.Content>
|
|
||||||
</View>
|
|
||||||
</TouchableRipple>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
card: {
|
card: {
|
||||||
|
@ -84,8 +28,69 @@ const styles = StyleSheet.create({
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
},
|
},
|
||||||
avatar: {
|
avatar: {
|
||||||
backgroundColor: 'transparent'
|
backgroundColor: 'transparent',
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component used to display a dashboard item containing a preview event
|
||||||
|
*/
|
||||||
|
class EventDashBoardItem extends React.Component<PropsType> {
|
||||||
|
static defaultProps = {
|
||||||
|
children: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
shouldComponentUpdate(nextProps: PropsType): boolean {
|
||||||
|
const {props} = this;
|
||||||
|
return (
|
||||||
|
nextProps.theme.dark !== props.theme.dark ||
|
||||||
|
nextProps.eventNumber !== props.eventNumber
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): React.Node {
|
||||||
|
const {props} = this;
|
||||||
|
const {colors} = props.theme;
|
||||||
|
const isAvailable = props.eventNumber > 0;
|
||||||
|
const iconColor = isAvailable ? colors.planningColor : colors.textDisabled;
|
||||||
|
const textColor = isAvailable ? colors.text : colors.textDisabled;
|
||||||
|
let subtitle;
|
||||||
|
if (isAvailable) {
|
||||||
|
subtitle = (
|
||||||
|
<Text>
|
||||||
|
<Text style={{fontWeight: 'bold'}}>{props.eventNumber}</Text>
|
||||||
|
<Text>
|
||||||
|
{props.eventNumber > 1
|
||||||
|
? i18n.t('screens.home.dashboard.todayEventsSubtitlePlural')
|
||||||
|
: i18n.t('screens.home.dashboard.todayEventsSubtitle')}
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
} else subtitle = i18n.t('screens.home.dashboard.todayEventsSubtitleNA');
|
||||||
|
return (
|
||||||
|
<Card style={styles.card}>
|
||||||
|
<TouchableRipple style={{flex: 1}} onPress={props.clickAction}>
|
||||||
|
<View>
|
||||||
|
<Card.Title
|
||||||
|
title={i18n.t('screens.home.dashboard.todayEventsTitle')}
|
||||||
|
titleStyle={{color: textColor}}
|
||||||
|
subtitle={subtitle}
|
||||||
|
subtitleStyle={{color: textColor}}
|
||||||
|
left={(): React.Node => (
|
||||||
|
<Avatar.Icon
|
||||||
|
icon="calendar-range"
|
||||||
|
color={iconColor}
|
||||||
|
size={60}
|
||||||
|
style={styles.avatar}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Card.Content>{props.children}</Card.Content>
|
||||||
|
</View>
|
||||||
|
</TouchableRipple>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default withTheme(EventDashBoardItem);
|
export default withTheme(EventDashBoardItem);
|
||||||
|
|
|
@ -2,67 +2,47 @@
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {Button, Card, Text, TouchableRipple} from 'react-native-paper';
|
import {Button, Card, Text, TouchableRipple} from 'react-native-paper';
|
||||||
import {Image, View} from "react-native";
|
import {Image, View} from 'react-native';
|
||||||
import Autolink from "react-native-autolink";
|
import Autolink from 'react-native-autolink';
|
||||||
import i18n from "i18n-js";
|
import i18n from 'i18n-js';
|
||||||
import ImageModal from 'react-native-image-modal';
|
import ImageModal from 'react-native-image-modal';
|
||||||
import {StackNavigationProp} from "@react-navigation/stack";
|
import {StackNavigationProp} from '@react-navigation/stack';
|
||||||
import type {CustomTheme} from "../../managers/ThemeManager";
|
import type {FeedItemType} from '../../screens/Home/HomeScreen';
|
||||||
import type {feedItem} from "../../screens/Home/HomeScreen";
|
|
||||||
|
|
||||||
const ICON_AMICALE = require('../../../assets/amicale.png');
|
const ICON_AMICALE = require('../../../assets/amicale.png');
|
||||||
|
|
||||||
type Props = {
|
type PropsType = {
|
||||||
navigation: StackNavigationProp,
|
navigation: StackNavigationProp,
|
||||||
theme: CustomTheme,
|
item: FeedItemType,
|
||||||
item: feedItem,
|
|
||||||
title: string,
|
title: string,
|
||||||
subtitle: string,
|
subtitle: string,
|
||||||
height: number,
|
height: number,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component used to display a feed item
|
* Component used to display a feed item
|
||||||
*/
|
*/
|
||||||
class FeedItem extends React.Component<Props> {
|
class FeedItem extends React.Component<PropsType> {
|
||||||
|
shouldComponentUpdate(): boolean {
|
||||||
shouldComponentUpdate() {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the amicale INSAT logo
|
|
||||||
*
|
|
||||||
* @return {*}
|
|
||||||
*/
|
|
||||||
getAvatar() {
|
|
||||||
return (
|
|
||||||
<Image
|
|
||||||
size={48}
|
|
||||||
source={ICON_AMICALE}
|
|
||||||
style={{
|
|
||||||
width: 48,
|
|
||||||
height: 48,
|
|
||||||
}}/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onPress = () => {
|
onPress = () => {
|
||||||
this.props.navigation.navigate(
|
const {props} = this;
|
||||||
'feed-information',
|
props.navigation.navigate('feed-information', {
|
||||||
{
|
data: props.item,
|
||||||
data: this.props.item,
|
date: props.subtitle,
|
||||||
date: this.props.subtitle
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render(): React.Node {
|
||||||
const item = this.props.item;
|
const {props} = this;
|
||||||
const hasImage = item.full_picture !== '' && item.full_picture !== undefined;
|
const {item} = props;
|
||||||
|
const hasImage =
|
||||||
|
item.full_picture !== '' && item.full_picture !== undefined;
|
||||||
|
|
||||||
const cardMargin = 10;
|
const cardMargin = 10;
|
||||||
const cardHeight = this.props.height - 2 * cardMargin;
|
const cardHeight = props.height - 2 * cardMargin;
|
||||||
const imageSize = 250;
|
const imageSize = 250;
|
||||||
const titleHeight = 80;
|
const titleHeight = 80;
|
||||||
const actionsHeight = 60;
|
const actionsHeight = 60;
|
||||||
|
@ -74,23 +54,29 @@ class FeedItem extends React.Component<Props> {
|
||||||
style={{
|
style={{
|
||||||
margin: cardMargin,
|
margin: cardMargin,
|
||||||
height: cardHeight,
|
height: cardHeight,
|
||||||
}}
|
}}>
|
||||||
>
|
<TouchableRipple style={{flex: 1}} onPress={this.onPress}>
|
||||||
<TouchableRipple
|
|
||||||
style={{flex: 1}}
|
|
||||||
onPress={this.onPress}>
|
|
||||||
<View>
|
<View>
|
||||||
<Card.Title
|
<Card.Title
|
||||||
title={this.props.title}
|
title={props.title}
|
||||||
subtitle={this.props.subtitle}
|
subtitle={props.subtitle}
|
||||||
left={this.getAvatar}
|
left={(): React.Node => (
|
||||||
|
<Image
|
||||||
|
size={48}
|
||||||
|
source={ICON_AMICALE}
|
||||||
|
style={{
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
style={{height: titleHeight}}
|
style={{height: titleHeight}}
|
||||||
/>
|
/>
|
||||||
{hasImage ?
|
{hasImage ? (
|
||||||
<View style={{marginLeft: 'auto', marginRight: 'auto'}}>
|
<View style={{marginLeft: 'auto', marginRight: 'auto'}}>
|
||||||
<ImageModal
|
<ImageModal
|
||||||
resizeMode="contain"
|
resizeMode="contain"
|
||||||
imageBackgroundColor={"#000"}
|
imageBackgroundColor="#000"
|
||||||
style={{
|
style={{
|
||||||
width: imageSize,
|
width: imageSize,
|
||||||
height: imageSize,
|
height: imageSize,
|
||||||
|
@ -98,21 +84,23 @@ class FeedItem extends React.Component<Props> {
|
||||||
source={{
|
source={{
|
||||||
uri: item.full_picture,
|
uri: item.full_picture,
|
||||||
}}
|
}}
|
||||||
/></View> : null}
|
/>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
<Card.Content>
|
<Card.Content>
|
||||||
{item.message !== undefined ?
|
{item.message !== undefined ? (
|
||||||
<Autolink
|
<Autolink
|
||||||
text={item.message}
|
text={item.message}
|
||||||
hashtag="facebook"
|
hashtag="facebook"
|
||||||
component={Text}
|
component={Text}
|
||||||
style={{height: textHeight}}
|
style={{height: textHeight}}
|
||||||
/> : null
|
/>
|
||||||
}
|
) : null}
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
<Card.Actions style={{height: actionsHeight}}>
|
<Card.Actions style={{height: actionsHeight}}>
|
||||||
<Button
|
<Button
|
||||||
onPress={this.onPress}
|
onPress={this.onPress}
|
||||||
icon={'plus'}
|
icon="plus"
|
||||||
style={{marginLeft: 'auto'}}>
|
style={{marginLeft: 'auto'}}>
|
||||||
{i18n.t('screens.home.dashboard.seeMore')}
|
{i18n.t('screens.home.dashboard.seeMore')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -1,81 +1,21 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {StyleSheet, View} from "react-native";
|
import {StyleSheet, View} from 'react-native';
|
||||||
import i18n from "i18n-js";
|
import i18n from 'i18n-js';
|
||||||
import {Avatar, Button, Card, TouchableRipple} from 'react-native-paper';
|
import {Avatar, Button, Card, TouchableRipple} from 'react-native-paper';
|
||||||
import {getFormattedEventTime, isDescriptionEmpty} from "../../utils/Planning";
|
import {getFormattedEventTime, isDescriptionEmpty} from '../../utils/Planning';
|
||||||
import CustomHTML from "../Overrides/CustomHTML";
|
import CustomHTML from '../Overrides/CustomHTML';
|
||||||
import type {CustomTheme} from "../../managers/ThemeManager";
|
import type {EventType} from '../../screens/Home/HomeScreen';
|
||||||
import type {event} from "../../screens/Home/HomeScreen";
|
|
||||||
|
|
||||||
type Props = {
|
type PropsType = {
|
||||||
event?: event,
|
event?: EventType | null,
|
||||||
clickAction: () => void,
|
clickAction: () => void,
|
||||||
theme?: CustomTheme,
|
};
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Component used to display an event preview if an event is available
|
|
||||||
*/
|
|
||||||
class PreviewEventDashboardItem extends React.Component<Props> {
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const props = this.props;
|
|
||||||
const isEmpty = props.event == null
|
|
||||||
? true
|
|
||||||
: isDescriptionEmpty(props.event.description);
|
|
||||||
|
|
||||||
if (props.event != null) {
|
|
||||||
const event = props.event;
|
|
||||||
const hasImage = event.logo !== '' && event.logo != null;
|
|
||||||
const getImage = () => <Avatar.Image
|
|
||||||
source={{uri: event.logo}}
|
|
||||||
size={50}
|
|
||||||
style={styles.avatar}/>;
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
style={styles.card}
|
|
||||||
elevation={3}
|
|
||||||
>
|
|
||||||
<TouchableRipple
|
|
||||||
style={{flex: 1}}
|
|
||||||
onPress={props.clickAction}>
|
|
||||||
<View>
|
|
||||||
{hasImage ?
|
|
||||||
<Card.Title
|
|
||||||
title={event.title}
|
|
||||||
subtitle={getFormattedEventTime(event.date_begin, event.date_end)}
|
|
||||||
left={getImage}
|
|
||||||
/> :
|
|
||||||
<Card.Title
|
|
||||||
title={event.title}
|
|
||||||
subtitle={getFormattedEventTime(event.date_begin, event.date_end)}
|
|
||||||
/>}
|
|
||||||
{!isEmpty ?
|
|
||||||
<Card.Content style={styles.content}>
|
|
||||||
<CustomHTML html={event.description}/>
|
|
||||||
</Card.Content> : null}
|
|
||||||
|
|
||||||
<Card.Actions style={styles.actions}>
|
|
||||||
<Button
|
|
||||||
icon={'chevron-right'}
|
|
||||||
>
|
|
||||||
{i18n.t("screens.home.dashboard.seeMore")}
|
|
||||||
</Button>
|
|
||||||
</Card.Actions>
|
|
||||||
</View>
|
|
||||||
</TouchableRipple>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
} else
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
card: {
|
card: {
|
||||||
marginBottom: 10
|
marginBottom: 10,
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
maxHeight: 150,
|
maxHeight: 150,
|
||||||
|
@ -84,11 +24,77 @@ const styles = StyleSheet.create({
|
||||||
actions: {
|
actions: {
|
||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
marginTop: 'auto',
|
marginTop: 'auto',
|
||||||
flexDirection: 'row'
|
flexDirection: 'row',
|
||||||
},
|
},
|
||||||
avatar: {
|
avatar: {
|
||||||
backgroundColor: 'transparent'
|
backgroundColor: 'transparent',
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component used to display an event preview if an event is available
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line react/prefer-stateless-function
|
||||||
|
class PreviewEventDashboardItem extends React.Component<PropsType> {
|
||||||
|
static defaultProps = {
|
||||||
|
event: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
render(): React.Node {
|
||||||
|
const {props} = this;
|
||||||
|
const {event} = props;
|
||||||
|
const isEmpty =
|
||||||
|
event == null ? true : isDescriptionEmpty(event.description);
|
||||||
|
|
||||||
|
if (event != null) {
|
||||||
|
const hasImage = event.logo !== '' && event.logo != null;
|
||||||
|
const getImage = (): React.Node => (
|
||||||
|
<Avatar.Image
|
||||||
|
source={{uri: event.logo}}
|
||||||
|
size={50}
|
||||||
|
style={styles.avatar}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Card style={styles.card} elevation={3}>
|
||||||
|
<TouchableRipple style={{flex: 1}} onPress={props.clickAction}>
|
||||||
|
<View>
|
||||||
|
{hasImage ? (
|
||||||
|
<Card.Title
|
||||||
|
title={event.title}
|
||||||
|
subtitle={getFormattedEventTime(
|
||||||
|
event.date_begin,
|
||||||
|
event.date_end,
|
||||||
|
)}
|
||||||
|
left={getImage}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Card.Title
|
||||||
|
title={event.title}
|
||||||
|
subtitle={getFormattedEventTime(
|
||||||
|
event.date_begin,
|
||||||
|
event.date_end,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!isEmpty ? (
|
||||||
|
<Card.Content style={styles.content}>
|
||||||
|
<CustomHTML html={event.description} />
|
||||||
|
</Card.Content>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<Card.Actions style={styles.actions}>
|
||||||
|
<Button icon="chevron-right">
|
||||||
|
{i18n.t('screens.home.dashboard.seeMore')}
|
||||||
|
</Button>
|
||||||
|
</Card.Actions>
|
||||||
|
</View>
|
||||||
|
</TouchableRipple>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default PreviewEventDashboardItem;
|
export default PreviewEventDashboardItem;
|
||||||
|
|
|
@ -2,13 +2,13 @@
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {Badge, TouchableRipple, withTheme} from 'react-native-paper';
|
import {Badge, TouchableRipple, withTheme} from 'react-native-paper';
|
||||||
import {Dimensions, Image, View} from "react-native";
|
import {Dimensions, Image, View} from 'react-native';
|
||||||
import type {CustomTheme} from "../../managers/ThemeManager";
|
import * as Animatable from 'react-native-animatable';
|
||||||
import * as Animatable from "react-native-animatable";
|
import type {CustomTheme} from '../../managers/ThemeManager';
|
||||||
|
|
||||||
type Props = {
|
type PropsType = {
|
||||||
image: string,
|
image: string | null,
|
||||||
onPress: () => void,
|
onPress: () => void | null,
|
||||||
badgeCount: number | null,
|
badgeCount: number | null,
|
||||||
theme: CustomTheme,
|
theme: CustomTheme,
|
||||||
};
|
};
|
||||||
|
@ -18,50 +18,51 @@ const AnimatableBadge = Animatable.createAnimatableComponent(Badge);
|
||||||
/**
|
/**
|
||||||
* Component used to render a small dashboard item
|
* Component used to render a small dashboard item
|
||||||
*/
|
*/
|
||||||
class SmallDashboardItem extends React.Component<Props> {
|
class SmallDashboardItem extends React.Component<PropsType> {
|
||||||
|
|
||||||
itemSize: number;
|
itemSize: number;
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: PropsType) {
|
||||||
super(props);
|
super(props);
|
||||||
this.itemSize = Dimensions.get('window').width / 8;
|
this.itemSize = Dimensions.get('window').width / 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: Props) {
|
shouldComponentUpdate(nextProps: PropsType): boolean {
|
||||||
return (nextProps.theme.dark !== this.props.theme.dark)
|
const {props} = this;
|
||||||
|| (nextProps.badgeCount !== this.props.badgeCount);
|
return (
|
||||||
|
nextProps.theme.dark !== props.theme.dark ||
|
||||||
|
nextProps.badgeCount !== props.badgeCount
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render(): React.Node {
|
||||||
const props = this.props;
|
const {props} = this;
|
||||||
return (
|
return (
|
||||||
<TouchableRipple
|
<TouchableRipple
|
||||||
onPress={this.props.onPress}
|
onPress={props.onPress}
|
||||||
borderless={true}
|
borderless
|
||||||
style={{
|
style={{
|
||||||
marginLeft: this.itemSize / 6,
|
marginLeft: this.itemSize / 6,
|
||||||
marginRight: this.itemSize / 6,
|
marginRight: this.itemSize / 6,
|
||||||
}}
|
}}>
|
||||||
>
|
<View
|
||||||
<View style={{
|
style={{
|
||||||
width: this.itemSize,
|
width: this.itemSize,
|
||||||
height: this.itemSize,
|
height: this.itemSize,
|
||||||
}}>
|
}}>
|
||||||
<Image
|
<Image
|
||||||
source={{uri: props.image}}
|
source={{uri: props.image}}
|
||||||
style={{
|
style={{
|
||||||
width: "80%",
|
width: '80%',
|
||||||
height: "80%",
|
height: '80%',
|
||||||
marginLeft: "auto",
|
marginLeft: 'auto',
|
||||||
marginRight: "auto",
|
marginRight: 'auto',
|
||||||
marginTop: "auto",
|
marginTop: 'auto',
|
||||||
marginBottom: "auto",
|
marginBottom: 'auto',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{
|
{props.badgeCount != null && props.badgeCount > 0 ? (
|
||||||
props.badgeCount != null && props.badgeCount > 0 ?
|
|
||||||
<AnimatableBadge
|
<AnimatableBadge
|
||||||
animation={"zoomIn"}
|
animation="zoomIn"
|
||||||
duration={300}
|
duration={300}
|
||||||
useNativeDriver
|
useNativeDriver
|
||||||
style={{
|
style={{
|
||||||
|
@ -73,14 +74,12 @@ class SmallDashboardItem extends React.Component<Props> {
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
}}>
|
}}>
|
||||||
{props.badgeCount}
|
{props.badgeCount}
|
||||||
</AnimatableBadge> : null
|
</AnimatableBadge>
|
||||||
}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
</TouchableRipple>
|
</TouchableRipple>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withTheme(SmallDashboardItem);
|
export default withTheme(SmallDashboardItem);
|
||||||
|
|
|
@ -4,37 +4,41 @@ import * as React from 'react';
|
||||||
import {Linking, View} from 'react-native';
|
import {Linking, View} from 'react-native';
|
||||||
import {Avatar, Card, Text, withTheme} from 'react-native-paper';
|
import {Avatar, Card, Text, withTheme} from 'react-native-paper';
|
||||||
import ImageModal from 'react-native-image-modal';
|
import ImageModal from 'react-native-image-modal';
|
||||||
import Autolink from "react-native-autolink";
|
import Autolink from 'react-native-autolink';
|
||||||
import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton";
|
import {StackNavigationProp} from '@react-navigation/stack';
|
||||||
import CustomTabBar from "../../components/Tabbar/CustomTabBar";
|
import MaterialHeaderButtons, {
|
||||||
import {StackNavigationProp} from "@react-navigation/stack";
|
Item,
|
||||||
import type {feedItem} from "./HomeScreen";
|
} from '../../components/Overrides/CustomHeaderButton';
|
||||||
import CollapsibleScrollView from "../../components/Collapsible/CollapsibleScrollView";
|
import CustomTabBar from '../../components/Tabbar/CustomTabBar';
|
||||||
|
import type {FeedItemType} from './HomeScreen';
|
||||||
|
import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView';
|
||||||
|
|
||||||
type Props = {
|
type PropsType = {
|
||||||
navigation: StackNavigationProp,
|
navigation: StackNavigationProp,
|
||||||
route: { params: { data: feedItem, date: string } }
|
route: {params: {data: FeedItemType, date: string}},
|
||||||
};
|
};
|
||||||
|
|
||||||
const ICON_AMICALE = require('../../../assets/amicale.png');
|
const ICON_AMICALE = require('../../../assets/amicale.png');
|
||||||
|
|
||||||
const NAME_AMICALE = 'Amicale INSA Toulouse';
|
const NAME_AMICALE = 'Amicale INSA Toulouse';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class defining a feed item page.
|
* Class defining a feed item page.
|
||||||
*/
|
*/
|
||||||
class FeedItemScreen extends React.Component<Props> {
|
class FeedItemScreen extends React.Component<PropsType> {
|
||||||
|
displayData: FeedItemType;
|
||||||
|
|
||||||
displayData: feedItem;
|
|
||||||
date: string;
|
date: string;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props: PropsType) {
|
||||||
super(props);
|
super(props);
|
||||||
this.displayData = props.route.params.data;
|
this.displayData = props.route.params.data;
|
||||||
this.date = props.route.params.date;
|
this.date = props.route.params.date;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.navigation.setOptions({
|
const {props} = this;
|
||||||
|
props.navigation.setOptions({
|
||||||
headerRight: this.getHeaderButton,
|
headerRight: this.getHeaderButton,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -51,41 +55,41 @@ class FeedItemScreen extends React.Component<Props> {
|
||||||
*
|
*
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
getHeaderButton = () => {
|
getHeaderButton = (): React.Node => {
|
||||||
return <MaterialHeaderButtons>
|
return (
|
||||||
<Item title="main" iconName={'facebook'} color={"#2e88fe"} onPress={this.onOutLinkPress}/>
|
<MaterialHeaderButtons>
|
||||||
</MaterialHeaderButtons>;
|
<Item
|
||||||
|
title="main"
|
||||||
|
iconName="facebook"
|
||||||
|
color="#2e88fe"
|
||||||
|
onPress={this.onOutLinkPress}
|
||||||
|
/>
|
||||||
|
</MaterialHeaderButtons>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
render(): React.Node {
|
||||||
* Gets the Amicale INSA avatar
|
const hasImage =
|
||||||
*
|
this.displayData.full_picture !== '' &&
|
||||||
* @returns {*}
|
this.displayData.full_picture != null;
|
||||||
*/
|
|
||||||
getAvatar() {
|
|
||||||
return (
|
return (
|
||||||
<Avatar.Image size={48} source={ICON_AMICALE}
|
<CollapsibleScrollView style={{margin: 5}} hasTab>
|
||||||
style={{backgroundColor: 'transparent'}}/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const hasImage = this.displayData.full_picture !== '' && this.displayData.full_picture != null;
|
|
||||||
return (
|
|
||||||
<CollapsibleScrollView
|
|
||||||
style={{margin: 5,}}
|
|
||||||
hasTab={true}
|
|
||||||
>
|
|
||||||
<Card.Title
|
<Card.Title
|
||||||
title={NAME_AMICALE}
|
title={NAME_AMICALE}
|
||||||
subtitle={this.date}
|
subtitle={this.date}
|
||||||
left={this.getAvatar}
|
left={(): React.Node => (
|
||||||
|
<Avatar.Image
|
||||||
|
size={48}
|
||||||
|
source={ICON_AMICALE}
|
||||||
|
style={{backgroundColor: 'transparent'}}
|
||||||
/>
|
/>
|
||||||
{hasImage ?
|
)}
|
||||||
|
/>
|
||||||
|
{hasImage ? (
|
||||||
<View style={{marginLeft: 'auto', marginRight: 'auto'}}>
|
<View style={{marginLeft: 'auto', marginRight: 'auto'}}>
|
||||||
<ImageModal
|
<ImageModal
|
||||||
resizeMode="contain"
|
resizeMode="contain"
|
||||||
imageBackgroundColor={"#000"}
|
imageBackgroundColor="#000"
|
||||||
style={{
|
style={{
|
||||||
width: 250,
|
width: 250,
|
||||||
height: 250,
|
height: 250,
|
||||||
|
@ -93,15 +97,17 @@ class FeedItemScreen extends React.Component<Props> {
|
||||||
source={{
|
source={{
|
||||||
uri: this.displayData.full_picture,
|
uri: this.displayData.full_picture,
|
||||||
}}
|
}}
|
||||||
/></View> : null}
|
/>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
<Card.Content style={{paddingBottom: CustomTabBar.TAB_BAR_HEIGHT + 20}}>
|
<Card.Content 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}
|
||||||
hashtag="facebook"
|
hashtag="facebook"
|
||||||
component={Text}
|
component={Text}
|
||||||
/> : null
|
/>
|
||||||
}
|
) : null}
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
</CollapsibleScrollView>
|
</CollapsibleScrollView>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,52 +2,44 @@
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {FlatList} from 'react-native';
|
import {FlatList} from 'react-native';
|
||||||
import i18n from "i18n-js";
|
import i18n from 'i18n-js';
|
||||||
import DashboardItem from "../../components/Home/EventDashboardItem";
|
|
||||||
import WebSectionList from "../../components/Screens/WebSectionList";
|
|
||||||
import {ActivityIndicator, Headline, withTheme} from 'react-native-paper';
|
import {ActivityIndicator, 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 {CommonActions} from '@react-navigation/native';
|
||||||
import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton";
|
import {StackNavigationProp} from '@react-navigation/stack';
|
||||||
import AnimatedFAB from "../../components/Animations/AnimatedFAB";
|
import * as Animatable from 'react-native-animatable';
|
||||||
import {StackNavigationProp} from "@react-navigation/stack";
|
import {View} from 'react-native-animatable';
|
||||||
import type {CustomTheme} from "../../managers/ThemeManager";
|
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
import * as Animatable from "react-native-animatable";
|
import DashboardItem from '../../components/Home/EventDashboardItem';
|
||||||
import {View} from "react-native-animatable";
|
import WebSectionList from '../../components/Screens/WebSectionList';
|
||||||
import ConnectionManager from "../../managers/ConnectionManager";
|
import FeedItem from '../../components/Home/FeedItem';
|
||||||
import LogoutDialog from "../../components/Amicale/LogoutDialog";
|
import SmallDashboardItem from '../../components/Home/SmallDashboardItem';
|
||||||
import AsyncStorageManager from "../../managers/AsyncStorageManager";
|
import PreviewEventDashboardItem from '../../components/Home/PreviewEventDashboardItem';
|
||||||
import {MASCOT_STYLE} from "../../components/Mascot/Mascot";
|
import ActionsDashBoardItem from '../../components/Home/ActionsDashboardItem';
|
||||||
import MascotPopup from "../../components/Mascot/MascotPopup";
|
import MaterialHeaderButtons, {
|
||||||
import DashboardManager from "../../managers/DashboardManager";
|
Item,
|
||||||
import type {ServiceItem} from "../../managers/ServicesManager";
|
} from '../../components/Overrides/CustomHeaderButton';
|
||||||
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
|
import AnimatedFAB from '../../components/Animations/AnimatedFAB';
|
||||||
|
import type {CustomTheme} from '../../managers/ThemeManager';
|
||||||
|
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 type {ServiceItem} from '../../managers/ServicesManager';
|
||||||
|
import {getDisplayEvent, getFutureEvents} from '../../utils/Home';
|
||||||
// import DATA from "../dashboard_data.json";
|
// import DATA from "../dashboard_data.json";
|
||||||
|
|
||||||
|
|
||||||
const NAME_AMICALE = 'Amicale INSA Toulouse';
|
const NAME_AMICALE = 'Amicale INSA Toulouse';
|
||||||
const DATA_URL = "https://etud.insa-toulouse.fr/~amicale_app/v2/dashboard/dashboard_data.json";
|
const DATA_URL =
|
||||||
|
'https://etud.insa-toulouse.fr/~amicale_app/v2/dashboard/dashboard_data.json';
|
||||||
const FEED_ITEM_HEIGHT = 500;
|
const FEED_ITEM_HEIGHT = 500;
|
||||||
|
|
||||||
const SECTIONS_ID = [
|
const SECTIONS_ID = ['dashboard', 'news_feed'];
|
||||||
'dashboard',
|
|
||||||
'news_feed'
|
|
||||||
];
|
|
||||||
|
|
||||||
const REFRESH_TIME = 1000 * 20; // Refresh every 20 seconds
|
const REFRESH_TIME = 1000 * 20; // Refresh every 20 seconds
|
||||||
|
|
||||||
type rawDashboard = {
|
export type FeedItemType = {
|
||||||
news_feed: {
|
|
||||||
data: Array<feedItem>,
|
|
||||||
},
|
|
||||||
dashboard: fullDashboard,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type feedItem = {
|
|
||||||
full_picture: string,
|
full_picture: string,
|
||||||
message: string,
|
message: string,
|
||||||
permalink_url: string,
|
permalink_url: string,
|
||||||
|
@ -55,16 +47,7 @@ export type feedItem = {
|
||||||
id: string,
|
id: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type fullDashboard = {
|
export type EventType = {
|
||||||
today_menu: Array<{ [key: string]: any }>,
|
|
||||||
proximo_articles: number,
|
|
||||||
available_dryers: number,
|
|
||||||
available_washers: number,
|
|
||||||
today_events: Array<{ [key: string]: any }>,
|
|
||||||
available_tutorials: number,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type event = {
|
|
||||||
id: number,
|
id: number,
|
||||||
title: string,
|
title: string,
|
||||||
logo: string | null,
|
logo: string | null,
|
||||||
|
@ -74,44 +57,68 @@ export type event = {
|
||||||
club: string,
|
club: string,
|
||||||
category_id: number,
|
category_id: number,
|
||||||
url: string,
|
url: string,
|
||||||
}
|
};
|
||||||
|
|
||||||
type Props = {
|
export type FullDashboardType = {
|
||||||
|
today_menu: Array<{[key: string]: {...}}>,
|
||||||
|
proximo_articles: number,
|
||||||
|
available_dryers: number,
|
||||||
|
available_washers: number,
|
||||||
|
today_events: Array<EventType>,
|
||||||
|
available_tutorials: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
type RawDashboardType = {
|
||||||
|
news_feed: {
|
||||||
|
data: Array<FeedItemType>,
|
||||||
|
},
|
||||||
|
dashboard: FullDashboardType,
|
||||||
|
};
|
||||||
|
|
||||||
|
type PropsType = {
|
||||||
navigation: StackNavigationProp,
|
navigation: StackNavigationProp,
|
||||||
route: { params: any, ... },
|
route: {params: {nextScreen: string, data: {...}}},
|
||||||
theme: CustomTheme,
|
theme: CustomTheme,
|
||||||
}
|
};
|
||||||
|
|
||||||
type State = {
|
type StateType = {
|
||||||
dialogVisible: boolean,
|
dialogVisible: boolean,
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class defining the app's home screen
|
* Class defining the app's home screen
|
||||||
*/
|
*/
|
||||||
class HomeScreen extends React.Component<Props, State> {
|
class HomeScreen extends React.Component<PropsType, StateType> {
|
||||||
|
|
||||||
isLoggedIn: boolean | null;
|
isLoggedIn: boolean | null;
|
||||||
|
|
||||||
fabRef: {current: null | AnimatedFAB};
|
fabRef: {current: null | AnimatedFAB};
|
||||||
currentNewFeed: Array<feedItem>;
|
|
||||||
currentDashboard: fullDashboard | null;
|
currentNewFeed: Array<FeedItemType>;
|
||||||
|
|
||||||
|
currentDashboard: FullDashboardType | null;
|
||||||
|
|
||||||
dashboardManager: DashboardManager;
|
dashboardManager: DashboardManager;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props: PropsType) {
|
||||||
super(props);
|
super(props);
|
||||||
this.fabRef = React.createRef();
|
this.fabRef = React.createRef();
|
||||||
this.dashboardManager = new DashboardManager(this.props.navigation);
|
this.dashboardManager = new DashboardManager(props.navigation);
|
||||||
this.currentNewFeed = [];
|
this.currentNewFeed = [];
|
||||||
this.currentDashboard = null;
|
this.currentDashboard = null;
|
||||||
this.isLoggedIn = ConnectionManager.getInstance().isLoggedIn();
|
this.isLoggedIn = ConnectionManager.getInstance().isLoggedIn();
|
||||||
this.props.navigation.setOptions({
|
props.navigation.setOptions({
|
||||||
headerRight: this.getHeaderButton,
|
headerRight: this.getHeaderButton,
|
||||||
});
|
});
|
||||||
this.state = {
|
this.state = {
|
||||||
dialogVisible: false,
|
dialogVisible: false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const {props} = this;
|
||||||
|
props.navigation.addListener('focus', this.onScreenFocus);
|
||||||
|
// Handle link open when home is focused
|
||||||
|
props.navigation.addListener('state', this.handleNavigationParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -120,24 +127,19 @@ class HomeScreen extends React.Component<Props, State> {
|
||||||
* @param dateString {string} The Unix Timestamp representation of a date
|
* @param dateString {string} The Unix Timestamp representation of a date
|
||||||
* @return {string} The formatted output date
|
* @return {string} The formatted output date
|
||||||
*/
|
*/
|
||||||
static getFormattedDate(dateString: number) {
|
static getFormattedDate(dateString: number): string {
|
||||||
let date = new Date(dateString * 1000);
|
const date = new Date(dateString * 1000);
|
||||||
return date.toLocaleString();
|
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
|
* Updates login state and navigation parameters on screen focus
|
||||||
*/
|
*/
|
||||||
onScreenFocus = () => {
|
onScreenFocus = () => {
|
||||||
|
const {props} = this;
|
||||||
if (ConnectionManager.getInstance().isLoggedIn() !== this.isLoggedIn) {
|
if (ConnectionManager.getInstance().isLoggedIn() !== this.isLoggedIn) {
|
||||||
this.isLoggedIn = ConnectionManager.getInstance().isLoggedIn();
|
this.isLoggedIn = ConnectionManager.getInstance().isLoggedIn();
|
||||||
this.props.navigation.setOptions({
|
props.navigation.setOptions({
|
||||||
headerRight: this.getHeaderButton,
|
headerRight: this.getHeaderButton,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -145,197 +147,41 @@ class HomeScreen extends React.Component<Props, State> {
|
||||||
this.handleNavigationParams();
|
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
|
* Gets header buttons based on login state
|
||||||
*
|
*
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
getHeaderButton = () => {
|
getHeaderButton = (): React.Node => {
|
||||||
let onPressLog = () => this.props.navigation.navigate("login", {nextScreen: "profile"});
|
const {props} = this;
|
||||||
let logIcon = "login";
|
let onPressLog = (): void =>
|
||||||
let logColor = this.props.theme.colors.primary;
|
props.navigation.navigate('login', {nextScreen: 'profile'});
|
||||||
|
let logIcon = 'login';
|
||||||
|
let logColor = props.theme.colors.primary;
|
||||||
if (this.isLoggedIn) {
|
if (this.isLoggedIn) {
|
||||||
onPressLog = () => this.showDisconnectDialog();
|
onPressLog = (): void => this.showDisconnectDialog();
|
||||||
logIcon = "logout";
|
logIcon = 'logout';
|
||||||
logColor = this.props.theme.colors.text;
|
logColor = props.theme.colors.text;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onPressSettings = () => this.props.navigation.navigate("settings");
|
const onPressSettings = (): void => props.navigation.navigate('settings');
|
||||||
return <MaterialHeaderButtons>
|
return (
|
||||||
<Item title="log" iconName={logIcon} color={logColor} onPress={onPressLog}/>
|
<MaterialHeaderButtons>
|
||||||
<Item title={i18n.t("screens.settings.title")} iconName={"cog"} onPress={onPressSettings}/>
|
<Item
|
||||||
</MaterialHeaderButtons>;
|
title="log"
|
||||||
|
iconName={logIcon}
|
||||||
|
color={logColor}
|
||||||
|
onPress={onPressLog}
|
||||||
|
/>
|
||||||
|
<Item
|
||||||
|
title={i18n.t('screens.settings.title')}
|
||||||
|
iconName="cog"
|
||||||
|
onPress={onPressSettings}
|
||||||
|
/>
|
||||||
|
</MaterialHeaderButtons>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
showDisconnectDialog = () => this.setState({dialogVisible: true});
|
|
||||||
|
|
||||||
hideDisconnectDialog = () => this.setState({dialogVisible: false});
|
|
||||||
|
|
||||||
openScanner = () => this.props.navigation.navigate("scanner");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the dataset to be used in the FlatList
|
|
||||||
*
|
|
||||||
* @param fetchedData
|
|
||||||
* @param isLoading
|
|
||||||
* @return {*}
|
|
||||||
*/
|
|
||||||
createDataset = (fetchedData: rawDashboard | null, isLoading: boolean) => {
|
|
||||||
// fetchedData = DATA;
|
|
||||||
if (fetchedData != null) {
|
|
||||||
if (fetchedData.news_feed != null)
|
|
||||||
this.currentNewFeed = fetchedData.news_feed.data;
|
|
||||||
if (fetchedData.dashboard != null)
|
|
||||||
this.currentDashboard = fetchedData.dashboard;
|
|
||||||
}
|
|
||||||
if (this.currentNewFeed.length > 0)
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
title: i18n.t("screens.home.feedTitle"),
|
|
||||||
data: this.currentNewFeed,
|
|
||||||
id: SECTIONS_ID[1]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
else
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
title: isLoading ? i18n.t("screens.home.feedLoading") : i18n.t("screens.home.feedError"),
|
|
||||||
data: [],
|
|
||||||
id: SECTIONS_ID[1]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
* Gets the event dashboard render item.
|
||||||
* If a preview is available, it will be rendered inside
|
* If a preview is available, it will be rendered inside
|
||||||
|
@ -343,9 +189,9 @@ class HomeScreen extends React.Component<Props, State> {
|
||||||
* @param content
|
* @param content
|
||||||
* @return {*}
|
* @return {*}
|
||||||
*/
|
*/
|
||||||
getDashboardEvent(content: Array<event>) {
|
getDashboardEvent(content: Array<EventType>): React.Node {
|
||||||
let futureEvents = this.getFutureEvents(content);
|
const futureEvents = getFutureEvents(content);
|
||||||
let displayEvent = this.getDisplayEvent(futureEvents);
|
const displayEvent = getDisplayEvent(futureEvents);
|
||||||
// const clickPreviewAction = () =>
|
// const clickPreviewAction = () =>
|
||||||
// this.props.navigation.navigate('students', {
|
// this.props.navigation.navigate('students', {
|
||||||
// screen: 'planning-information',
|
// screen: 'planning-information',
|
||||||
|
@ -354,10 +200,9 @@ class HomeScreen extends React.Component<Props, State> {
|
||||||
return (
|
return (
|
||||||
<DashboardItem
|
<DashboardItem
|
||||||
eventNumber={futureEvents.length}
|
eventNumber={futureEvents.length}
|
||||||
clickAction={this.onEventContainerClick}
|
clickAction={this.onEventContainerClick}>
|
||||||
>
|
|
||||||
<PreviewEventDashboardItem
|
<PreviewEventDashboardItem
|
||||||
event={displayEvent != null ? displayEvent : undefined}
|
event={displayEvent}
|
||||||
clickAction={this.onEventContainerClick}
|
clickAction={this.onEventContainerClick}
|
||||||
/>
|
/>
|
||||||
</DashboardItem>
|
</DashboardItem>
|
||||||
|
@ -369,8 +214,14 @@ class HomeScreen extends React.Component<Props, State> {
|
||||||
*
|
*
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
getDashboardActions() {
|
getDashboardActions(): React.Node {
|
||||||
return <ActionsDashBoardItem {...this.props} isLoggedIn={this.isLoggedIn}/>;
|
const {props} = this;
|
||||||
|
return (
|
||||||
|
<ActionsDashBoardItem
|
||||||
|
navigation={props.navigation}
|
||||||
|
isLoggedIn={this.isLoggedIn}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -379,20 +230,21 @@ class HomeScreen extends React.Component<Props, State> {
|
||||||
* @param content
|
* @param content
|
||||||
* @return {*}
|
* @return {*}
|
||||||
*/
|
*/
|
||||||
getDashboardRow(content: Array<ServiceItem>) {
|
getDashboardRow(content: Array<ServiceItem | null>): React.Node {
|
||||||
return (
|
return (
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
<FlatList
|
<FlatList
|
||||||
data={content}
|
data={content}
|
||||||
renderItem={this.dashboardRowRenderItem}
|
renderItem={this.getDashboardRowRenderItem}
|
||||||
horizontal={true}
|
horizontal
|
||||||
contentContainerStyle={{
|
contentContainerStyle={{
|
||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
marginRight: 'auto',
|
marginRight: 'auto',
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
}}
|
}}
|
||||||
/>);
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -401,16 +253,24 @@ class HomeScreen extends React.Component<Props, State> {
|
||||||
* @param item
|
* @param item
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
dashboardRowRenderItem = ({item}: { item: ServiceItem }) => {
|
getDashboardRowRenderItem = ({
|
||||||
|
item,
|
||||||
|
}: {
|
||||||
|
item: ServiceItem | null,
|
||||||
|
}): React.Node => {
|
||||||
|
if (item != null)
|
||||||
return (
|
return (
|
||||||
<SmallDashboardItem
|
<SmallDashboardItem
|
||||||
image={item.image}
|
image={item.image}
|
||||||
onPress={item.onPress}
|
onPress={item.onPress}
|
||||||
badgeCount={this.currentDashboard != null && item.badgeFunction != null
|
badgeCount={
|
||||||
|
this.currentDashboard != null && item.badgeFunction != null
|
||||||
? item.badgeFunction(this.currentDashboard)
|
? item.badgeFunction(this.currentDashboard)
|
||||||
: null}
|
: null
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
return <SmallDashboardItem image={null} onPress={null} badgeCount={null} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -419,10 +279,11 @@ class HomeScreen extends React.Component<Props, State> {
|
||||||
* @param item The feed item to display
|
* @param item The feed item to display
|
||||||
* @return {*}
|
* @return {*}
|
||||||
*/
|
*/
|
||||||
getFeedItem(item: feedItem) {
|
getFeedItem(item: FeedItemType): React.Node {
|
||||||
|
const {props} = this;
|
||||||
return (
|
return (
|
||||||
<FeedItem
|
<FeedItem
|
||||||
{...this.props}
|
navigation={props.navigation}
|
||||||
item={item}
|
item={item}
|
||||||
title={NAME_AMICALE}
|
title={NAME_AMICALE}
|
||||||
subtitle={HomeScreen.getFormattedDate(item.created_time)}
|
subtitle={HomeScreen.getFormattedDate(item.created_time)}
|
||||||
|
@ -438,139 +299,218 @@ class HomeScreen extends React.Component<Props, State> {
|
||||||
* @param section The current section
|
* @param section The current section
|
||||||
* @return {*}
|
* @return {*}
|
||||||
*/
|
*/
|
||||||
getRenderItem = ({item}: { item: feedItem, }) => this.getFeedItem(item);
|
getRenderItem = ({item}: {item: FeedItemType}): React.Node =>
|
||||||
|
this.getFeedItem(item);
|
||||||
|
|
||||||
onScroll = (event: SyntheticEvent<EventTarget>) => {
|
getRenderSectionHeader = (
|
||||||
if (this.fabRef.current != null)
|
data: {
|
||||||
this.fabRef.current.onScroll(event);
|
section: {
|
||||||
};
|
data: Array<{...}>,
|
||||||
|
title: string,
|
||||||
renderSectionHeader = (data: { section: { [key: string]: any } }, isLoading: boolean) => {
|
},
|
||||||
|
},
|
||||||
|
isLoading: boolean,
|
||||||
|
): React.Node => {
|
||||||
|
const {props} = this;
|
||||||
if (data.section.data.length > 0)
|
if (data.section.data.length > 0)
|
||||||
return (
|
return (
|
||||||
<Headline style={{
|
<Headline
|
||||||
textAlign: "center",
|
style={{
|
||||||
|
textAlign: 'center',
|
||||||
marginTop: 50,
|
marginTop: 50,
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
}}>
|
}}>
|
||||||
{data.section.title}
|
{data.section.title}
|
||||||
</Headline>
|
</Headline>
|
||||||
)
|
);
|
||||||
else
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<Headline style={{
|
<Headline
|
||||||
textAlign: "center",
|
style={{
|
||||||
|
textAlign: 'center',
|
||||||
marginTop: 50,
|
marginTop: 50,
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
marginLeft: 20,
|
marginLeft: 20,
|
||||||
marginRight: 20,
|
marginRight: 20,
|
||||||
color: this.props.theme.colors.textDisabled
|
color: props.theme.colors.textDisabled,
|
||||||
}}>
|
}}>
|
||||||
{data.section.title}
|
{data.section.title}
|
||||||
</Headline>
|
</Headline>
|
||||||
{isLoading
|
{isLoading ? (
|
||||||
? <ActivityIndicator
|
<ActivityIndicator
|
||||||
style={{
|
style={{
|
||||||
marginTop: 10
|
marginTop: 10,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
: <MaterialCommunityIcons
|
) : (
|
||||||
name={"access-point-network-off"}
|
<MaterialCommunityIcons
|
||||||
|
name="access-point-network-off"
|
||||||
size={100}
|
size={100}
|
||||||
color={this.props.theme.colors.textDisabled}
|
color={props.theme.colors.textDisabled}
|
||||||
style={{
|
style={{
|
||||||
marginLeft: "auto",
|
marginLeft: 'auto',
|
||||||
marginRight: "auto",
|
marginRight: 'auto',
|
||||||
}}
|
}}
|
||||||
/>}
|
/>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
getListHeader = (fetchedData: rawDashboard) => {
|
getListHeader = (fetchedData: RawDashboardType): React.Node => {
|
||||||
let dashboard = null;
|
let dashboard = null;
|
||||||
if (fetchedData != null) {
|
if (fetchedData != null) dashboard = fetchedData.dashboard;
|
||||||
dashboard = fetchedData.dashboard;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animatable.View
|
<Animatable.View animation="fadeInDown" duration={500} useNativeDriver>
|
||||||
animation={"fadeInDown"}
|
|
||||||
duration={500}
|
|
||||||
useNativeDriver={true}
|
|
||||||
>
|
|
||||||
{this.getDashboardActions()}
|
{this.getDashboardActions()}
|
||||||
{this.getDashboardRow(this.dashboardManager.getCurrentDashboard())}
|
{this.getDashboardRow(this.dashboardManager.getCurrentDashboard())}
|
||||||
{this.getDashboardEvent(
|
{this.getDashboardEvent(
|
||||||
dashboard == null
|
dashboard == null ? [] : dashboard.today_events,
|
||||||
? []
|
|
||||||
: dashboard.today_events
|
|
||||||
)}
|
)}
|
||||||
</Animatable.View>
|
</Animatable.View>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the a new screen if navigation parameters specify one
|
||||||
|
*/
|
||||||
|
handleNavigationParams = () => {
|
||||||
|
const {props} = this;
|
||||||
|
if (props.route.params != null) {
|
||||||
|
if (props.route.params.nextScreen != null) {
|
||||||
|
props.navigation.navigate(
|
||||||
|
props.route.params.nextScreen,
|
||||||
|
props.route.params.data,
|
||||||
|
);
|
||||||
|
// reset params to prevent infinite loop
|
||||||
|
props.navigation.dispatch(CommonActions.setParams({nextScreen: null}));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
showDisconnectDialog = (): void => this.setState({dialogVisible: true});
|
||||||
|
|
||||||
|
hideDisconnectDialog = (): void => this.setState({dialogVisible: false});
|
||||||
|
|
||||||
|
openScanner = () => {
|
||||||
|
const {props} = this;
|
||||||
|
props.navigation.navigate('scanner');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the dataset to be used in the FlatList
|
||||||
|
*
|
||||||
|
* @param fetchedData
|
||||||
|
* @param isLoading
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
createDataset = (
|
||||||
|
fetchedData: RawDashboardType | null,
|
||||||
|
isLoading: boolean,
|
||||||
|
): Array<{
|
||||||
|
title: string,
|
||||||
|
data: [] | Array<FeedItemType>,
|
||||||
|
id: string,
|
||||||
|
}> => {
|
||||||
|
// fetchedData = DATA;
|
||||||
|
if (fetchedData != null) {
|
||||||
|
if (fetchedData.news_feed != null)
|
||||||
|
this.currentNewFeed = fetchedData.news_feed.data;
|
||||||
|
if (fetchedData.dashboard != null)
|
||||||
|
this.currentDashboard = fetchedData.dashboard;
|
||||||
|
}
|
||||||
|
if (this.currentNewFeed.length > 0)
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: i18n.t('screens.home.feedTitle'),
|
||||||
|
data: this.currentNewFeed,
|
||||||
|
id: SECTIONS_ID[1],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: isLoading
|
||||||
|
? i18n.t('screens.home.feedLoading')
|
||||||
|
: i18n.t('screens.home.feedError'),
|
||||||
|
data: [],
|
||||||
|
id: SECTIONS_ID[1],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
onEventContainerClick = () => {
|
||||||
|
const {props} = this;
|
||||||
|
props.navigation.navigate('planning');
|
||||||
|
};
|
||||||
|
|
||||||
|
onScroll = (event: SyntheticEvent<EventTarget>) => {
|
||||||
|
if (this.fabRef.current != null) this.fabRef.current.onScroll(event);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback when pressing the login button on the banner.
|
* Callback when pressing the login button on the banner.
|
||||||
* This hides the banner and takes the user to the login page.
|
* This hides the banner and takes the user to the login page.
|
||||||
*/
|
*/
|
||||||
onLogin = () => this.props.navigation.navigate("login", {nextScreen: "profile"});
|
onLogin = () => {
|
||||||
|
const {props} = this;
|
||||||
|
props.navigation.navigate('login', {
|
||||||
|
nextScreen: 'profile',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render(): React.Node {
|
||||||
|
const {props, state} = this;
|
||||||
return (
|
return (
|
||||||
|
<View style={{flex: 1}}>
|
||||||
<View
|
<View
|
||||||
style={{flex: 1}}
|
style={{
|
||||||
>
|
position: 'absolute',
|
||||||
<View style={{
|
width: '100%',
|
||||||
position: "absolute",
|
height: '100%',
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
}}>
|
}}>
|
||||||
<WebSectionList
|
<WebSectionList
|
||||||
{...this.props}
|
navigation={props.navigation}
|
||||||
createDataset={this.createDataset}
|
createDataset={this.createDataset}
|
||||||
autoRefreshTime={REFRESH_TIME}
|
autoRefreshTime={REFRESH_TIME}
|
||||||
refreshOnFocus={true}
|
refreshOnFocus
|
||||||
fetchUrl={DATA_URL}
|
fetchUrl={DATA_URL}
|
||||||
renderItem={this.getRenderItem}
|
renderItem={this.getRenderItem}
|
||||||
itemHeight={FEED_ITEM_HEIGHT}
|
itemHeight={FEED_ITEM_HEIGHT}
|
||||||
onScroll={this.onScroll}
|
onScroll={this.onScroll}
|
||||||
showError={false}
|
showError={false}
|
||||||
renderSectionHeader={this.renderSectionHeader}
|
renderSectionHeader={this.getRenderSectionHeader}
|
||||||
renderListHeaderComponent={this.getListHeader}
|
renderListHeaderComponent={this.getListHeader}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
{!this.isLoggedIn
|
{!this.isLoggedIn ? (
|
||||||
? <MascotPopup
|
<MascotPopup
|
||||||
prefKey={AsyncStorageManager.PREFERENCES.homeShowBanner.key}
|
prefKey={AsyncStorageManager.PREFERENCES.homeShowBanner.key}
|
||||||
title={i18n.t("screens.home.mascotDialog.title")}
|
title={i18n.t('screens.home.mascotDialog.title')}
|
||||||
message={i18n.t("screens.home.mascotDialog.message")}
|
message={i18n.t('screens.home.mascotDialog.message')}
|
||||||
icon={"human-greeting"}
|
icon="human-greeting"
|
||||||
buttons={{
|
buttons={{
|
||||||
action: {
|
action: {
|
||||||
message: i18n.t("screens.home.mascotDialog.login"),
|
message: i18n.t('screens.home.mascotDialog.login'),
|
||||||
icon: "login",
|
icon: 'login',
|
||||||
onPress: this.onLogin,
|
onPress: this.onLogin,
|
||||||
},
|
},
|
||||||
cancel: {
|
cancel: {
|
||||||
message: i18n.t("screens.home.mascotDialog.later"),
|
message: i18n.t('screens.home.mascotDialog.later'),
|
||||||
icon: "close",
|
icon: 'close',
|
||||||
color: this.props.theme.colors.warning,
|
color: props.theme.colors.warning,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
emotion={MASCOT_STYLE.CUTE}
|
emotion={MASCOT_STYLE.CUTE}
|
||||||
/> : null}
|
/>
|
||||||
|
) : null}
|
||||||
<AnimatedFAB
|
<AnimatedFAB
|
||||||
{...this.props}
|
|
||||||
ref={this.fabRef}
|
ref={this.fabRef}
|
||||||
icon="qrcode-scan"
|
icon="qrcode-scan"
|
||||||
onPress={this.openScanner}
|
onPress={this.openScanner}
|
||||||
/>
|
/>
|
||||||
<LogoutDialog
|
<LogoutDialog
|
||||||
{...this.props}
|
navigation={props.navigation}
|
||||||
visible={this.state.dialogVisible}
|
visible={state.dialogVisible}
|
||||||
onDismiss={this.hideDisconnectDialog}
|
onDismiss={this.hideDisconnectDialog}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -1,90 +1,65 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {Linking, Platform, StyleSheet, View} from "react-native";
|
import {Linking, Platform, StyleSheet, View} from 'react-native';
|
||||||
import {Button, Text, withTheme} from 'react-native-paper';
|
import {Button, Text, withTheme} from 'react-native-paper';
|
||||||
import {RNCamera} from 'react-native-camera';
|
import {RNCamera} from 'react-native-camera';
|
||||||
import {BarcodeMask} from '@nartc/react-native-barcode-mask';
|
import {BarcodeMask} from '@nartc/react-native-barcode-mask';
|
||||||
import URLHandler from "../../utils/URLHandler";
|
|
||||||
import AlertDialog from "../../components/Dialogs/AlertDialog";
|
|
||||||
import i18n from 'i18n-js';
|
import i18n from 'i18n-js';
|
||||||
import CustomTabBar from "../../components/Tabbar/CustomTabBar";
|
|
||||||
import LoadingConfirmDialog from "../../components/Dialogs/LoadingConfirmDialog";
|
|
||||||
import {PERMISSIONS, request, RESULTS} from 'react-native-permissions';
|
import {PERMISSIONS, request, RESULTS} from 'react-native-permissions';
|
||||||
import {MASCOT_STYLE} from "../../components/Mascot/Mascot";
|
import URLHandler from '../../utils/URLHandler';
|
||||||
import MascotPopup from "../../components/Mascot/MascotPopup";
|
import AlertDialog from '../../components/Dialogs/AlertDialog';
|
||||||
|
import CustomTabBar from '../../components/Tabbar/CustomTabBar';
|
||||||
|
import LoadingConfirmDialog from '../../components/Dialogs/LoadingConfirmDialog';
|
||||||
|
import {MASCOT_STYLE} from '../../components/Mascot/Mascot';
|
||||||
|
import MascotPopup from '../../components/Mascot/MascotPopup';
|
||||||
|
|
||||||
type Props = {};
|
type StateType = {
|
||||||
type State = {
|
|
||||||
hasPermission: boolean,
|
hasPermission: boolean,
|
||||||
scanned: boolean,
|
scanned: boolean,
|
||||||
dialogVisible: boolean,
|
dialogVisible: boolean,
|
||||||
mascotDialogVisible: boolean,
|
mascotDialogVisible: boolean,
|
||||||
dialogTitle: string,
|
|
||||||
dialogMessage: string,
|
|
||||||
loading: boolean,
|
loading: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
class ScannerScreen extends React.Component<Props, State> {
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 20,
|
||||||
|
width: '80%',
|
||||||
|
left: '10%',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
state = {
|
class ScannerScreen extends React.Component<null, StateType> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.state = {
|
||||||
hasPermission: false,
|
hasPermission: false,
|
||||||
scanned: false,
|
scanned: false,
|
||||||
mascotDialogVisible: false,
|
mascotDialogVisible: false,
|
||||||
dialogVisible: false,
|
dialogVisible: false,
|
||||||
dialogTitle: "",
|
|
||||||
dialogMessage: "",
|
|
||||||
loading: false,
|
loading: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.requestPermissions();
|
this.requestPermissions();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Requests permission to use the camera
|
|
||||||
*/
|
|
||||||
requestPermissions = () => {
|
|
||||||
if (Platform.OS === 'android')
|
|
||||||
request(PERMISSIONS.ANDROID.CAMERA).then(this.updatePermissionStatus)
|
|
||||||
else
|
|
||||||
request(PERMISSIONS.IOS.CAMERA).then(this.updatePermissionStatus)
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the state permission status
|
|
||||||
*
|
|
||||||
* @param result
|
|
||||||
*/
|
|
||||||
updatePermissionStatus = (result) => this.setState({hasPermission: result === RESULTS.GRANTED});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens scanned link if it is a valid app link or shows and error dialog
|
|
||||||
*
|
|
||||||
* @param type The barcode type
|
|
||||||
* @param data The scanned value
|
|
||||||
*/
|
|
||||||
handleCodeScanned = ({type, data}) => {
|
|
||||||
if (!URLHandler.isUrlValid(data))
|
|
||||||
this.showErrorDialog();
|
|
||||||
else {
|
|
||||||
this.showOpeningDialog();
|
|
||||||
Linking.openURL(data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a view asking user for permission to use the camera
|
* Gets a view asking user for permission to use the camera
|
||||||
*
|
*
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
getPermissionScreen() {
|
getPermissionScreen(): React.Node {
|
||||||
return <View style={{marginLeft: 10, marginRight: 10}}>
|
return (
|
||||||
<Text>{i18n.t("screens.scanner.permissions.error")}</Text>
|
<View style={{marginLeft: 10, marginRight: 10}}>
|
||||||
|
<Text>{i18n.t('screens.scanner.permissions.error')}</Text>
|
||||||
<Button
|
<Button
|
||||||
icon="camera"
|
icon="camera"
|
||||||
mode="contained"
|
mode="contained"
|
||||||
|
@ -93,11 +68,72 @@ class ScannerScreen extends React.Component<Props, State> {
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
marginRight: 'auto',
|
marginRight: 'auto',
|
||||||
}}
|
}}>
|
||||||
>
|
{i18n.t('screens.scanner.permissions.button')}
|
||||||
{i18n.t("screens.scanner.permissions.button")}
|
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a view with the scanner.
|
||||||
|
* This scanner uses the back camera, can only scan qr codes and has a square mask on the center.
|
||||||
|
* The mask is only for design purposes as a code is scanned as soon as it enters the camera view
|
||||||
|
*
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
getScanner(): React.Node {
|
||||||
|
const {state} = this;
|
||||||
|
return (
|
||||||
|
<RNCamera
|
||||||
|
onBarCodeRead={state.scanned ? null : this.onCodeScanned}
|
||||||
|
type={RNCamera.Constants.Type.back}
|
||||||
|
barCodeScannerSettings={{
|
||||||
|
barCodeTypes: [RNCamera.Constants.BarCodeType.qr],
|
||||||
|
}}
|
||||||
|
style={StyleSheet.absoluteFill}
|
||||||
|
captureAudio={false}>
|
||||||
|
<BarcodeMask
|
||||||
|
backgroundColor="#000"
|
||||||
|
maskOpacity={0.5}
|
||||||
|
animatedLineThickness={1}
|
||||||
|
animationDuration={1000}
|
||||||
|
width={250}
|
||||||
|
height={250}
|
||||||
|
/>
|
||||||
|
</RNCamera>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests permission to use the camera
|
||||||
|
*/
|
||||||
|
requestPermissions = () => {
|
||||||
|
if (Platform.OS === 'android')
|
||||||
|
request(PERMISSIONS.ANDROID.CAMERA).then(this.updatePermissionStatus);
|
||||||
|
else request(PERMISSIONS.IOS.CAMERA).then(this.updatePermissionStatus);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the state permission status
|
||||||
|
*
|
||||||
|
* @param result
|
||||||
|
*/
|
||||||
|
updatePermissionStatus = (result: RESULTS) => {
|
||||||
|
this.setState({
|
||||||
|
hasPermission: result === RESULTS.GRANTED,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a dialog indicating the user the scanned code was invalid
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line react/sort-comp
|
||||||
|
showErrorDialog() {
|
||||||
|
this.setState({
|
||||||
|
dialogVisible: true,
|
||||||
|
scanned: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -120,119 +156,82 @@ class ScannerScreen extends React.Component<Props, State> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows a dialog indicating the user the scanned code was invalid
|
|
||||||
*/
|
|
||||||
showErrorDialog() {
|
|
||||||
this.setState({
|
|
||||||
dialogVisible: true,
|
|
||||||
scanned: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hide any dialog
|
* Hide any dialog
|
||||||
*/
|
*/
|
||||||
onDialogDismiss = () => this.setState({
|
onDialogDismiss = () => {
|
||||||
|
this.setState({
|
||||||
dialogVisible: false,
|
dialogVisible: false,
|
||||||
scanned: false,
|
scanned: false,
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
onMascotDialogDismiss = () => this.setState({
|
onMascotDialogDismiss = () => {
|
||||||
|
this.setState({
|
||||||
mascotDialogVisible: false,
|
mascotDialogVisible: false,
|
||||||
scanned: false,
|
scanned: false,
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a view with the scanner.
|
* Opens scanned link if it is a valid app link or shows and error dialog
|
||||||
* This scanner uses the back camera, can only scan qr codes and has a square mask on the center.
|
|
||||||
* The mask is only for design purposes as a code is scanned as soon as it enters the camera view
|
|
||||||
*
|
*
|
||||||
* @returns {*}
|
* @param type The barcode type
|
||||||
|
* @param data The scanned value
|
||||||
*/
|
*/
|
||||||
getScanner() {
|
onCodeScanned = ({data}: {data: string}) => {
|
||||||
return (
|
if (!URLHandler.isUrlValid(data)) this.showErrorDialog();
|
||||||
<RNCamera
|
else {
|
||||||
onBarCodeRead={this.state.scanned ? undefined : this.handleCodeScanned}
|
this.showOpeningDialog();
|
||||||
type={RNCamera.Constants.Type.back}
|
Linking.openURL(data);
|
||||||
barCodeScannerSettings={{
|
|
||||||
barCodeTypes: [RNCamera.Constants.BarCodeType.qr],
|
|
||||||
}}
|
|
||||||
style={StyleSheet.absoluteFill}
|
|
||||||
captureAudio={false}
|
|
||||||
>
|
|
||||||
<BarcodeMask
|
|
||||||
backgroundColor={"#000"}
|
|
||||||
maskOpacity={0.5}
|
|
||||||
animatedLineThickness={1}
|
|
||||||
animationDuration={1000}
|
|
||||||
width={250}
|
|
||||||
height={250}
|
|
||||||
/>
|
|
||||||
</RNCamera>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render(): React.Node {
|
||||||
|
const {state} = this;
|
||||||
return (
|
return (
|
||||||
<View style={{
|
<View
|
||||||
|
style={{
|
||||||
...styles.container,
|
...styles.container,
|
||||||
marginBottom: CustomTabBar.TAB_BAR_HEIGHT
|
marginBottom: CustomTabBar.TAB_BAR_HEIGHT,
|
||||||
}}>
|
}}>
|
||||||
{this.state.hasPermission
|
{state.hasPermission ? this.getScanner() : this.getPermissionScreen()}
|
||||||
? this.getScanner()
|
|
||||||
: this.getPermissionScreen()
|
|
||||||
}
|
|
||||||
<Button
|
<Button
|
||||||
icon="information"
|
icon="information"
|
||||||
mode="contained"
|
mode="contained"
|
||||||
onPress={this.showHelpDialog}
|
onPress={this.showHelpDialog}
|
||||||
style={styles.button}
|
style={styles.button}>
|
||||||
>
|
{i18n.t('screens.scanner.help.button')}
|
||||||
{i18n.t("screens.scanner.help.button")}
|
|
||||||
</Button>
|
</Button>
|
||||||
<MascotPopup
|
<MascotPopup
|
||||||
visible={this.state.mascotDialogVisible}
|
visible={state.mascotDialogVisible}
|
||||||
title={i18n.t("screens.scanner.mascotDialog.title")}
|
title={i18n.t('screens.scanner.mascotDialog.title')}
|
||||||
message={i18n.t("screens.scanner.mascotDialog.message")}
|
message={i18n.t('screens.scanner.mascotDialog.message')}
|
||||||
icon={"camera-iris"}
|
icon="camera-iris"
|
||||||
buttons={{
|
buttons={{
|
||||||
action: null,
|
action: null,
|
||||||
cancel: {
|
cancel: {
|
||||||
message: i18n.t("screens.scanner.mascotDialog.button"),
|
message: i18n.t('screens.scanner.mascotDialog.button'),
|
||||||
icon: "check",
|
icon: 'check',
|
||||||
onPress: this.onMascotDialogDismiss,
|
onPress: this.onMascotDialogDismiss,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
emotion={MASCOT_STYLE.NORMAL}
|
emotion={MASCOT_STYLE.NORMAL}
|
||||||
/>
|
/>
|
||||||
<AlertDialog
|
<AlertDialog
|
||||||
visible={this.state.dialogVisible}
|
visible={state.dialogVisible}
|
||||||
onDismiss={this.onDialogDismiss}
|
onDismiss={this.onDialogDismiss}
|
||||||
title={i18n.t("screens.scanner.error.title")}
|
title={i18n.t('screens.scanner.error.title')}
|
||||||
message={i18n.t("screens.scanner.error.message")}
|
message={i18n.t('screens.scanner.error.message')}
|
||||||
/>
|
/>
|
||||||
<LoadingConfirmDialog
|
<LoadingConfirmDialog
|
||||||
visible={this.state.loading}
|
visible={state.loading}
|
||||||
titleLoading={i18n.t("general.loading")}
|
titleLoading={i18n.t('general.loading')}
|
||||||
startLoading={true}
|
startLoading
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
justifyContent: 'center',
|
|
||||||
},
|
|
||||||
button: {
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: 20,
|
|
||||||
width: '80%',
|
|
||||||
left: '10%'
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default withTheme(ScannerScreen);
|
export default withTheme(ScannerScreen);
|
||||||
|
|
123
src/utils/Home.js
Normal file
123
src/utils/Home.js
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import {stringToDate} from './Planning';
|
||||||
|
import type {EventType} from '../screens/Home/HomeScreen';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
export function getTodayEventTimeLimit(): Date {
|
||||||
|
const 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 {EventType}
|
||||||
|
* @return {number} The number of milliseconds
|
||||||
|
*/
|
||||||
|
export function getEventDuration(event: EventType): number {
|
||||||
|
const start = stringToDate(event.date_begin);
|
||||||
|
const 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>}
|
||||||
|
*/
|
||||||
|
export function getEventsAfterLimit(
|
||||||
|
events: Array<EventType>,
|
||||||
|
limit: Date,
|
||||||
|
): Array<EventType> {
|
||||||
|
const validEvents = [];
|
||||||
|
events.forEach((event: EventType) => {
|
||||||
|
const 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
|
||||||
|
*/
|
||||||
|
export function getLongestEvent(events: Array<EventType>): EventType {
|
||||||
|
let longestEvent = events[0];
|
||||||
|
let longestTime = 0;
|
||||||
|
events.forEach((event: EventType) => {
|
||||||
|
const time = getEventDuration(event);
|
||||||
|
if (time > longestTime) {
|
||||||
|
longestTime = time;
|
||||||
|
longestEvent = event;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return longestEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets events that have not yet ended/started
|
||||||
|
*
|
||||||
|
* @param events
|
||||||
|
*/
|
||||||
|
export function getFutureEvents(events: Array<EventType>): Array<EventType> {
|
||||||
|
const validEvents = [];
|
||||||
|
const now = new Date();
|
||||||
|
events.forEach((event: EventType) => {
|
||||||
|
const startDate = stringToDate(event.date_begin);
|
||||||
|
const 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 {EventType | null}
|
||||||
|
*/
|
||||||
|
export function getDisplayEvent(events: Array<EventType>): EventType | null {
|
||||||
|
let displayEvent = null;
|
||||||
|
if (events.length > 1) {
|
||||||
|
const eventsAfterLimit = getEventsAfterLimit(
|
||||||
|
events,
|
||||||
|
getTodayEventTimeLimit(),
|
||||||
|
);
|
||||||
|
if (eventsAfterLimit.length > 0) {
|
||||||
|
if (eventsAfterLimit.length === 1) [displayEvent] = eventsAfterLimit;
|
||||||
|
else displayEvent = getLongestEvent(events);
|
||||||
|
} else {
|
||||||
|
displayEvent = getLongestEvent(events);
|
||||||
|
}
|
||||||
|
} else if (events.length === 1) {
|
||||||
|
[displayEvent] = events;
|
||||||
|
}
|
||||||
|
return displayEvent;
|
||||||
|
}
|
Loading…
Reference in a new issue