Update Amicale and related components to use TypeScript
This commit is contained in:
parent
18ec6e0a59
commit
f95635136e
17 changed files with 400 additions and 476 deletions
|
@ -17,37 +17,34 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {StackNavigationProp} from '@react-navigation/stack';
|
||||
import ConnectionManager from '../../managers/ConnectionManager';
|
||||
import type {ApiGenericDataType} from '../../utils/WebData';
|
||||
import {ERROR_TYPE} from '../../utils/WebData';
|
||||
import ErrorView from '../Screens/ErrorView';
|
||||
import BasicLoadingScreen from '../Screens/BasicLoadingScreen';
|
||||
|
||||
type PropsType = {
|
||||
navigation: StackNavigationProp,
|
||||
type PropsType<T> = {
|
||||
navigation: StackNavigationProp<any>;
|
||||
requests: Array<{
|
||||
link: string,
|
||||
params: {...},
|
||||
mandatory: boolean,
|
||||
}>,
|
||||
renderFunction: (Array<ApiGenericDataType | null>) => React.Node,
|
||||
link: string;
|
||||
params: object;
|
||||
mandatory: boolean;
|
||||
}>;
|
||||
renderFunction: (data: Array<T | null>) => React.ReactNode;
|
||||
errorViewOverride?: Array<{
|
||||
errorCode: number,
|
||||
message: string,
|
||||
icon: string,
|
||||
showRetryButton: boolean,
|
||||
}> | null,
|
||||
errorCode: number;
|
||||
message: string;
|
||||
icon: string;
|
||||
showRetryButton: boolean;
|
||||
}> | null;
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
loading: boolean,
|
||||
loading: boolean;
|
||||
};
|
||||
|
||||
class AuthenticatedScreen extends React.Component<PropsType, StateType> {
|
||||
class AuthenticatedScreen<T> extends React.Component<PropsType<T>, StateType> {
|
||||
static defaultProps = {
|
||||
errorViewOverride: null,
|
||||
};
|
||||
|
@ -58,13 +55,14 @@ class AuthenticatedScreen extends React.Component<PropsType, StateType> {
|
|||
|
||||
errors: Array<number>;
|
||||
|
||||
fetchedData: Array<ApiGenericDataType | null>;
|
||||
fetchedData: Array<T | null>;
|
||||
|
||||
constructor(props: PropsType) {
|
||||
constructor(props: PropsType<T>) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
};
|
||||
this.currentUserToken = null;
|
||||
this.connectionManager = ConnectionManager.getInstance();
|
||||
props.navigation.addListener('focus', this.onScreenFocus);
|
||||
this.fetchedData = new Array(props.requests.length);
|
||||
|
@ -91,20 +89,20 @@ class AuthenticatedScreen extends React.Component<PropsType, StateType> {
|
|||
* @param index The index for the data
|
||||
* @param error The error code received
|
||||
*/
|
||||
onRequestFinished(
|
||||
data: ApiGenericDataType | null,
|
||||
index: number,
|
||||
error?: number,
|
||||
) {
|
||||
onRequestFinished(data: T | null, index: number, error?: number) {
|
||||
const {props} = this;
|
||||
if (index >= 0 && index < props.requests.length) {
|
||||
this.fetchedData[index] = data;
|
||||
this.errors[index] = error != null ? error : ERROR_TYPE.SUCCESS;
|
||||
}
|
||||
// Token expired, logout user
|
||||
if (error === ERROR_TYPE.BAD_TOKEN) this.connectionManager.disconnect();
|
||||
if (error === ERROR_TYPE.BAD_TOKEN) {
|
||||
this.connectionManager.disconnect();
|
||||
}
|
||||
|
||||
if (this.allRequestsFinished()) this.setState({loading: false});
|
||||
if (this.allRequestsFinished()) {
|
||||
this.setState({loading: false});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -132,7 +130,7 @@ class AuthenticatedScreen extends React.Component<PropsType, StateType> {
|
|||
*
|
||||
* @return {*}
|
||||
*/
|
||||
getErrorRender(): React.Node {
|
||||
getErrorRender() {
|
||||
const {props} = this;
|
||||
const errorCode = this.getError();
|
||||
let shouldOverride = false;
|
||||
|
@ -169,18 +167,18 @@ class AuthenticatedScreen extends React.Component<PropsType, StateType> {
|
|||
*/
|
||||
fetchData = () => {
|
||||
const {state, props} = this;
|
||||
if (!state.loading) this.setState({loading: true});
|
||||
if (!state.loading) {
|
||||
this.setState({loading: true});
|
||||
}
|
||||
|
||||
if (this.connectionManager.isLoggedIn()) {
|
||||
for (let i = 0; i < props.requests.length; i += 1) {
|
||||
this.connectionManager
|
||||
.authenticatedRequest(
|
||||
.authenticatedRequest<T>(
|
||||
props.requests[i].link,
|
||||
props.requests[i].params,
|
||||
)
|
||||
.then((response: ApiGenericDataType): void =>
|
||||
this.onRequestFinished(response, i),
|
||||
)
|
||||
.then((response: T): void => this.onRequestFinished(response, i))
|
||||
.catch((error: number): void =>
|
||||
this.onRequestFinished(null, i, error),
|
||||
);
|
||||
|
@ -200,7 +198,9 @@ class AuthenticatedScreen extends React.Component<PropsType, StateType> {
|
|||
allRequestsFinished(): boolean {
|
||||
let finished = true;
|
||||
this.errors.forEach((error: number | null) => {
|
||||
if (error == null) finished = false;
|
||||
if (error == null) {
|
||||
finished = false;
|
||||
}
|
||||
});
|
||||
return finished;
|
||||
}
|
||||
|
@ -212,11 +212,14 @@ class AuthenticatedScreen extends React.Component<PropsType, StateType> {
|
|||
this.fetchData();
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
const {state, props} = this;
|
||||
if (state.loading) return <BasicLoadingScreen />;
|
||||
if (this.getError() === ERROR_TYPE.SUCCESS)
|
||||
if (state.loading) {
|
||||
return <BasicLoadingScreen />;
|
||||
}
|
||||
if (this.getError() === ERROR_TYPE.SUCCESS) {
|
||||
return props.renderFunction(this.fetchedData);
|
||||
}
|
||||
return this.getErrorRender();
|
||||
}
|
||||
}
|
|
@ -17,28 +17,25 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import i18n from 'i18n-js';
|
||||
import {StackNavigationProp} from '@react-navigation/stack';
|
||||
import LoadingConfirmDialog from '../Dialogs/LoadingConfirmDialog';
|
||||
import ConnectionManager from '../../managers/ConnectionManager';
|
||||
import {useNavigation} from '@react-navigation/native';
|
||||
|
||||
type PropsType = {
|
||||
navigation: StackNavigationProp,
|
||||
visible: boolean,
|
||||
onDismiss: () => void,
|
||||
visible: boolean;
|
||||
onDismiss: () => void;
|
||||
};
|
||||
|
||||
class LogoutDialog extends React.PureComponent<PropsType> {
|
||||
onClickAccept = async (): Promise<void> => {
|
||||
const {props} = this;
|
||||
function LogoutDialog(props: PropsType) {
|
||||
const navigation = useNavigation();
|
||||
const onClickAccept = async (): Promise<void> => {
|
||||
return new Promise((resolve: () => void) => {
|
||||
ConnectionManager.getInstance()
|
||||
.disconnect()
|
||||
.then(() => {
|
||||
props.navigation.reset({
|
||||
navigation.reset({
|
||||
index: 0,
|
||||
routes: [{name: 'main'}],
|
||||
});
|
||||
|
@ -48,19 +45,16 @@ class LogoutDialog extends React.PureComponent<PropsType> {
|
|||
});
|
||||
};
|
||||
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<LoadingConfirmDialog
|
||||
visible={props.visible}
|
||||
onDismiss={props.onDismiss}
|
||||
onAccept={this.onClickAccept}
|
||||
title={i18n.t('dialog.disconnect.title')}
|
||||
titleLoading={i18n.t('dialog.disconnect.titleLoading')}
|
||||
message={i18n.t('dialog.disconnect.message')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<LoadingConfirmDialog
|
||||
visible={props.visible}
|
||||
onDismiss={props.onDismiss}
|
||||
onAccept={onClickAccept}
|
||||
title={i18n.t('dialog.disconnect.title')}
|
||||
titleLoading={i18n.t('dialog.disconnect.titleLoading')}
|
||||
message={i18n.t('dialog.disconnect.message')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default LogoutDialog;
|
|
@ -17,42 +17,29 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import React from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {Headline, withTheme} from 'react-native-paper';
|
||||
import {Headline, useTheme} from 'react-native-paper';
|
||||
import i18n from 'i18n-js';
|
||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||
|
||||
type PropsType = {
|
||||
theme: CustomThemeType,
|
||||
};
|
||||
|
||||
class VoteNotAvailable extends React.Component<PropsType> {
|
||||
shouldComponentUpdate(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<View
|
||||
function VoteNotAvailable() {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
marginTop: 10,
|
||||
marginBottom: 10,
|
||||
}}>
|
||||
<Headline
|
||||
style={{
|
||||
width: '100%',
|
||||
marginTop: 10,
|
||||
marginBottom: 10,
|
||||
color: theme.colors.textDisabled,
|
||||
textAlign: 'center',
|
||||
}}>
|
||||
<Headline
|
||||
style={{
|
||||
color: props.theme.colors.textDisabled,
|
||||
textAlign: 'center',
|
||||
}}>
|
||||
{i18n.t('screens.vote.noVote')}
|
||||
</Headline>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
{i18n.t('screens.vote.noVote')}
|
||||
</Headline>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default withTheme(VoteNotAvailable);
|
||||
export default VoteNotAvailable;
|
|
@ -17,8 +17,6 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
|
@ -31,16 +29,11 @@ import {
|
|||
import {FlatList, StyleSheet} from 'react-native';
|
||||
import i18n from 'i18n-js';
|
||||
import type {VoteTeamType} from '../../../screens/Amicale/VoteScreen';
|
||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||
import type {
|
||||
CardTitleIconPropsType,
|
||||
ListIconPropsType,
|
||||
} from '../../../constants/PaperStyles';
|
||||
|
||||
type PropsType = {
|
||||
teams: Array<VoteTeamType>,
|
||||
dateEnd: string,
|
||||
theme: CustomThemeType,
|
||||
teams: Array<VoteTeamType>;
|
||||
dateEnd: string;
|
||||
theme: ReactNativePaper.Theme;
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
@ -58,10 +51,10 @@ class VoteResults extends React.Component<PropsType> {
|
|||
winnerIds: Array<number>;
|
||||
|
||||
constructor(props: PropsType) {
|
||||
super();
|
||||
super(props);
|
||||
props.teams.sort(this.sortByVotes);
|
||||
this.getTotalVotes(props.teams);
|
||||
this.getWinnerIds(props.teams);
|
||||
this.totalVotes = this.getTotalVotes(props.teams);
|
||||
this.winnerIds = this.getWinnerIds(props.teams);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(): boolean {
|
||||
|
@ -69,26 +62,31 @@ class VoteResults extends React.Component<PropsType> {
|
|||
}
|
||||
|
||||
getTotalVotes(teams: Array<VoteTeamType>) {
|
||||
this.totalVotes = 0;
|
||||
let totalVotes = 0;
|
||||
for (let i = 0; i < teams.length; i += 1) {
|
||||
this.totalVotes += teams[i].votes;
|
||||
totalVotes += teams[i].votes;
|
||||
}
|
||||
return totalVotes;
|
||||
}
|
||||
|
||||
getWinnerIds(teams: Array<VoteTeamType>) {
|
||||
const max = teams[0].votes;
|
||||
this.winnerIds = [];
|
||||
let winnerIds = [];
|
||||
for (let i = 0; i < teams.length; i += 1) {
|
||||
if (teams[i].votes === max) this.winnerIds.push(teams[i].id);
|
||||
else break;
|
||||
if (teams[i].votes === max) {
|
||||
winnerIds.push(teams[i].id);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return winnerIds;
|
||||
}
|
||||
|
||||
sortByVotes = (a: VoteTeamType, b: VoteTeamType): number => b.votes - a.votes;
|
||||
|
||||
voteKeyExtractor = (item: VoteTeamType): string => item.id.toString();
|
||||
|
||||
resultRenderItem = ({item}: {item: VoteTeamType}): React.Node => {
|
||||
resultRenderItem = ({item}: {item: VoteTeamType}) => {
|
||||
const isWinner = this.winnerIds.indexOf(item.id) !== -1;
|
||||
const isDraw = this.winnerIds.length > 1;
|
||||
const {props} = this;
|
||||
|
@ -101,7 +99,7 @@ class VoteResults extends React.Component<PropsType> {
|
|||
<List.Item
|
||||
title={item.name}
|
||||
description={`${item.votes} ${i18n.t('screens.vote.results.votes')}`}
|
||||
left={(iconProps: ListIconPropsType): React.Node =>
|
||||
left={(iconProps) =>
|
||||
isWinner ? (
|
||||
<List.Icon
|
||||
style={iconProps.style}
|
||||
|
@ -125,7 +123,7 @@ class VoteResults extends React.Component<PropsType> {
|
|||
);
|
||||
};
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
const {props} = this;
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
|
@ -134,15 +132,14 @@ class VoteResults extends React.Component<PropsType> {
|
|||
subtitle={`${i18n.t('screens.vote.results.subtitle')} ${
|
||||
props.dateEnd
|
||||
}`}
|
||||
left={(iconProps: CardTitleIconPropsType): React.Node => (
|
||||
left={(iconProps) => (
|
||||
<Avatar.Icon size={iconProps.size} icon="podium-gold" />
|
||||
)}
|
||||
/>
|
||||
<Card.Content>
|
||||
<Subheading>{`${i18n.t('screens.vote.results.totalVotes')} ${
|
||||
this.totalVotes
|
||||
}`}</Subheading>
|
||||
{/* $FlowFixMe */}
|
||||
<Subheading>
|
||||
{`${i18n.t('screens.vote.results.totalVotes')} ${this.totalVotes}`}
|
||||
</Subheading>
|
||||
<FlatList
|
||||
data={props.teams}
|
||||
keyExtractor={this.voteKeyExtractor}
|
|
@ -17,8 +17,6 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Avatar, Button, Card, RadioButton} from 'react-native-paper';
|
||||
import {FlatList, StyleSheet, View} from 'react-native';
|
||||
|
@ -27,19 +25,18 @@ import ConnectionManager from '../../../managers/ConnectionManager';
|
|||
import LoadingConfirmDialog from '../../Dialogs/LoadingConfirmDialog';
|
||||
import ErrorDialog from '../../Dialogs/ErrorDialog';
|
||||
import type {VoteTeamType} from '../../../screens/Amicale/VoteScreen';
|
||||
import type {CardTitleIconPropsType} from '../../../constants/PaperStyles';
|
||||
|
||||
type PropsType = {
|
||||
teams: Array<VoteTeamType>,
|
||||
onVoteSuccess: () => void,
|
||||
onVoteError: () => void,
|
||||
teams: Array<VoteTeamType>;
|
||||
onVoteSuccess: () => void;
|
||||
onVoteError: () => void;
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
selectedTeam: string,
|
||||
voteDialogVisible: boolean,
|
||||
errorDialogVisible: boolean,
|
||||
currentError: number,
|
||||
selectedTeam: string;
|
||||
voteDialogVisible: boolean;
|
||||
errorDialogVisible: boolean;
|
||||
currentError: number;
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
@ -53,10 +50,10 @@ const styles = StyleSheet.create({
|
|||
|
||||
export default class VoteSelect extends React.PureComponent<
|
||||
PropsType,
|
||||
StateType,
|
||||
StateType
|
||||
> {
|
||||
constructor() {
|
||||
super();
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedTeam: 'none',
|
||||
voteDialogVisible: false,
|
||||
|
@ -70,7 +67,7 @@ export default class VoteSelect extends React.PureComponent<
|
|||
|
||||
voteKeyExtractor = (item: VoteTeamType): string => item.id.toString();
|
||||
|
||||
voteRenderItem = ({item}: {item: VoteTeamType}): React.Node => (
|
||||
voteRenderItem = ({item}: {item: VoteTeamType}) => (
|
||||
<RadioButton.Item label={item.name} value={item.id.toString()} />
|
||||
);
|
||||
|
||||
|
@ -111,7 +108,7 @@ export default class VoteSelect extends React.PureComponent<
|
|||
props.onVoteError();
|
||||
};
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
const {state, props} = this;
|
||||
return (
|
||||
<View>
|
||||
|
@ -119,7 +116,7 @@ export default class VoteSelect extends React.PureComponent<
|
|||
<Card.Title
|
||||
title={i18n.t('screens.vote.select.title')}
|
||||
subtitle={i18n.t('screens.vote.select.subtitle')}
|
||||
left={(iconProps: CardTitleIconPropsType): React.Node => (
|
||||
left={(iconProps) => (
|
||||
<Avatar.Icon size={iconProps.size} icon="alert-decagram" />
|
||||
)}
|
||||
/>
|
||||
|
@ -127,7 +124,6 @@ export default class VoteSelect extends React.PureComponent<
|
|||
<RadioButton.Group
|
||||
onValueChange={this.onVoteSelectionChange}
|
||||
value={state.selectedTeam}>
|
||||
{/* $FlowFixMe */}
|
||||
<FlatList
|
||||
data={props.teams}
|
||||
keyExtractor={this.voteKeyExtractor}
|
|
@ -17,16 +17,13 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Avatar, Card, Paragraph} from 'react-native-paper';
|
||||
import {StyleSheet} from 'react-native';
|
||||
import i18n from 'i18n-js';
|
||||
import type {CardTitleIconPropsType} from '../../../constants/PaperStyles';
|
||||
|
||||
type PropsType = {
|
||||
startDate: string,
|
||||
startDate: string;
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
@ -38,28 +35,19 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
export default class VoteTease extends React.Component<PropsType> {
|
||||
shouldComponentUpdate(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
<Card.Title
|
||||
title={i18n.t('screens.vote.tease.title')}
|
||||
subtitle={i18n.t('screens.vote.tease.subtitle')}
|
||||
left={(iconProps: CardTitleIconPropsType): React.Node => (
|
||||
<Avatar.Icon size={iconProps.size} icon="vote" />
|
||||
)}
|
||||
/>
|
||||
<Card.Content>
|
||||
<Paragraph>
|
||||
{`${i18n.t('screens.vote.tease.message')} ${props.startDate}`}
|
||||
</Paragraph>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
export default function VoteTease(props: PropsType) {
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
<Card.Title
|
||||
title={i18n.t('screens.vote.tease.title')}
|
||||
subtitle={i18n.t('screens.vote.tease.subtitle')}
|
||||
left={(iconProps) => <Avatar.Icon size={iconProps.size} icon="vote" />}
|
||||
/>
|
||||
<Card.Content>
|
||||
<Paragraph>
|
||||
{`${i18n.t('screens.vote.tease.message')} ${props.startDate}`}
|
||||
</Paragraph>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
);
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Avatar, Card, Paragraph, withTheme} from 'react-native-paper';
|
||||
import {StyleSheet} from 'react-native';
|
||||
import i18n from 'i18n-js';
|
||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||
import type {CardTitleIconPropsType} from '../../../constants/PaperStyles';
|
||||
|
||||
type PropsType = {
|
||||
startDate: string | null,
|
||||
justVoted: boolean,
|
||||
hasVoted: boolean,
|
||||
isVoteRunning: boolean,
|
||||
theme: CustomThemeType,
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
margin: 10,
|
||||
},
|
||||
icon: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
});
|
||||
|
||||
class VoteWait extends React.Component<PropsType> {
|
||||
shouldComponentUpdate(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
const {startDate} = props;
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
<Card.Title
|
||||
title={
|
||||
props.isVoteRunning
|
||||
? i18n.t('screens.vote.wait.titleSubmitted')
|
||||
: i18n.t('screens.vote.wait.titleEnded')
|
||||
}
|
||||
subtitle={i18n.t('screens.vote.wait.subtitle')}
|
||||
left={(iconProps: CardTitleIconPropsType): React.Node => (
|
||||
<Avatar.Icon size={iconProps.size} icon="progress-check" />
|
||||
)}
|
||||
/>
|
||||
<Card.Content>
|
||||
{props.justVoted ? (
|
||||
<Paragraph style={{color: props.theme.colors.success}}>
|
||||
{i18n.t('screens.vote.wait.messageSubmitted')}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
{props.hasVoted ? (
|
||||
<Paragraph style={{color: props.theme.colors.success}}>
|
||||
{i18n.t('screens.vote.wait.messageVoted')}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
{startDate != null ? (
|
||||
<Paragraph>
|
||||
{`${i18n.t('screens.vote.wait.messageDate')} ${startDate}`}
|
||||
</Paragraph>
|
||||
) : (
|
||||
<Paragraph>
|
||||
{i18n.t('screens.vote.wait.messageDateUndefined')}
|
||||
</Paragraph>
|
||||
)}
|
||||
</Card.Content>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(VoteWait);
|
80
src/components/Amicale/Vote/VoteWait.tsx
Normal file
80
src/components/Amicale/Vote/VoteWait.tsx
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {Avatar, Card, Paragraph, useTheme} from 'react-native-paper';
|
||||
import {StyleSheet} from 'react-native';
|
||||
import i18n from 'i18n-js';
|
||||
|
||||
type PropsType = {
|
||||
startDate: string | null;
|
||||
justVoted: boolean;
|
||||
hasVoted: boolean;
|
||||
isVoteRunning: boolean;
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
margin: 10,
|
||||
},
|
||||
icon: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
});
|
||||
|
||||
export default function VoteWait(props: PropsType) {
|
||||
const theme = useTheme();
|
||||
const {startDate} = props;
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
<Card.Title
|
||||
title={
|
||||
props.isVoteRunning
|
||||
? i18n.t('screens.vote.wait.titleSubmitted')
|
||||
: i18n.t('screens.vote.wait.titleEnded')
|
||||
}
|
||||
subtitle={i18n.t('screens.vote.wait.subtitle')}
|
||||
left={(iconProps) => (
|
||||
<Avatar.Icon size={iconProps.size} icon="progress-check" />
|
||||
)}
|
||||
/>
|
||||
<Card.Content>
|
||||
{props.justVoted ? (
|
||||
<Paragraph style={{color: theme.colors.success}}>
|
||||
{i18n.t('screens.vote.wait.messageSubmitted')}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
{props.hasVoted ? (
|
||||
<Paragraph style={{color: theme.colors.success}}>
|
||||
{i18n.t('screens.vote.wait.messageVoted')}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
{startDate != null ? (
|
||||
<Paragraph>
|
||||
{`${i18n.t('screens.vote.wait.messageDate')} ${startDate}`}
|
||||
</Paragraph>
|
||||
) : (
|
||||
<Paragraph>
|
||||
{i18n.t('screens.vote.wait.messageDateUndefined')}
|
||||
</Paragraph>
|
||||
)}
|
||||
</Card.Content>
|
||||
</Card>
|
||||
);
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Collapsible} from 'react-navigation-collapsible';
|
||||
import withCollapsible from '../../utils/withCollapsible';
|
||||
import CustomTabBar from '../Tabbar/CustomTabBar';
|
||||
|
||||
export type CollapsibleComponentPropsType = {
|
||||
children?: React.Node,
|
||||
hasTab?: boolean,
|
||||
onScroll?: (event: SyntheticEvent<EventTarget>) => void,
|
||||
};
|
||||
|
||||
type PropsType = {
|
||||
...CollapsibleComponentPropsType,
|
||||
collapsibleStack: Collapsible,
|
||||
// eslint-disable-next-line flowtype/no-weak-types
|
||||
component: any,
|
||||
};
|
||||
|
||||
class CollapsibleComponent extends React.Component<PropsType> {
|
||||
static defaultProps = {
|
||||
children: null,
|
||||
hasTab: false,
|
||||
onScroll: null,
|
||||
};
|
||||
|
||||
onScroll = (event: SyntheticEvent<EventTarget>) => {
|
||||
const {props} = this;
|
||||
if (props.onScroll) props.onScroll(event);
|
||||
};
|
||||
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
const Comp = props.component;
|
||||
const {
|
||||
containerPaddingTop,
|
||||
scrollIndicatorInsetTop,
|
||||
onScrollWithListener,
|
||||
} = props.collapsibleStack;
|
||||
|
||||
return (
|
||||
<Comp
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
onScroll={onScrollWithListener(this.onScroll)}
|
||||
contentContainerStyle={{
|
||||
paddingTop: containerPaddingTop,
|
||||
paddingBottom: props.hasTab ? CustomTabBar.TAB_BAR_HEIGHT : 0,
|
||||
minHeight: '100%',
|
||||
}}
|
||||
scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}>
|
||||
{props.children}
|
||||
</Comp>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withCollapsible(CollapsibleComponent);
|
63
src/components/Collapsible/CollapsibleComponent.tsx
Normal file
63
src/components/Collapsible/CollapsibleComponent.tsx
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {useCollapsibleStack} from 'react-navigation-collapsible';
|
||||
import CustomTabBar from '../Tabbar/CustomTabBar';
|
||||
import {NativeScrollEvent, NativeSyntheticEvent} from 'react-native';
|
||||
|
||||
export interface CollapsibleComponentPropsType {
|
||||
children?: React.ReactNode;
|
||||
hasTab?: boolean;
|
||||
onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
|
||||
}
|
||||
|
||||
interface PropsType extends CollapsibleComponentPropsType {
|
||||
component: React.ComponentType<any>;
|
||||
}
|
||||
|
||||
function CollapsibleComponent(props: PropsType) {
|
||||
const onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
if (props.onScroll) {
|
||||
props.onScroll(event);
|
||||
}
|
||||
};
|
||||
const Comp = props.component;
|
||||
const {
|
||||
containerPaddingTop,
|
||||
scrollIndicatorInsetTop,
|
||||
onScrollWithListener,
|
||||
} = useCollapsibleStack();
|
||||
|
||||
return (
|
||||
<Comp
|
||||
{...props}
|
||||
onScroll={onScrollWithListener(onScroll)}
|
||||
contentContainerStyle={{
|
||||
paddingTop: containerPaddingTop,
|
||||
paddingBottom: props.hasTab ? CustomTabBar.TAB_BAR_HEIGHT : 0,
|
||||
minHeight: '100%',
|
||||
}}
|
||||
scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}>
|
||||
{props.children}
|
||||
</Comp>
|
||||
);
|
||||
}
|
||||
|
||||
export default CollapsibleComponent;
|
|
@ -17,29 +17,19 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Animated} from 'react-native';
|
||||
import {Animated, FlatListProps} from 'react-native';
|
||||
import type {CollapsibleComponentPropsType} from './CollapsibleComponent';
|
||||
import CollapsibleComponent from './CollapsibleComponent';
|
||||
|
||||
type PropsType = {
|
||||
...CollapsibleComponentPropsType,
|
||||
};
|
||||
type Props<T> = FlatListProps<T> & CollapsibleComponentPropsType;
|
||||
|
||||
// eslint-disable-next-line react/prefer-stateless-function
|
||||
class CollapsibleFlatList extends React.Component<PropsType> {
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<CollapsibleComponent // eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
component={Animated.FlatList}>
|
||||
{props.children}
|
||||
</CollapsibleComponent>
|
||||
);
|
||||
}
|
||||
function CollapsibleFlatList<T>(props: Props<T>) {
|
||||
return (
|
||||
<CollapsibleComponent {...props} component={Animated.FlatList}>
|
||||
{props.children}
|
||||
</CollapsibleComponent>
|
||||
);
|
||||
}
|
||||
|
||||
export default CollapsibleFlatList;
|
|
@ -17,29 +17,19 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Animated} from 'react-native';
|
||||
import {Animated, ScrollViewProps} from 'react-native';
|
||||
import type {CollapsibleComponentPropsType} from './CollapsibleComponent';
|
||||
import CollapsibleComponent from './CollapsibleComponent';
|
||||
|
||||
type PropsType = {
|
||||
...CollapsibleComponentPropsType,
|
||||
};
|
||||
type Props = ScrollViewProps & CollapsibleComponentPropsType;
|
||||
|
||||
// eslint-disable-next-line react/prefer-stateless-function
|
||||
class CollapsibleScrollView extends React.Component<PropsType> {
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<CollapsibleComponent // eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
component={Animated.ScrollView}>
|
||||
{props.children}
|
||||
</CollapsibleComponent>
|
||||
);
|
||||
}
|
||||
function CollapsibleScrollView(props: Props) {
|
||||
return (
|
||||
<CollapsibleComponent {...props} component={Animated.ScrollView}>
|
||||
{props.children}
|
||||
</CollapsibleComponent>
|
||||
);
|
||||
}
|
||||
|
||||
export default CollapsibleScrollView;
|
|
@ -17,29 +17,19 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Animated} from 'react-native';
|
||||
import {Animated, SectionListProps} from 'react-native';
|
||||
import type {CollapsibleComponentPropsType} from './CollapsibleComponent';
|
||||
import CollapsibleComponent from './CollapsibleComponent';
|
||||
|
||||
type PropsType = {
|
||||
...CollapsibleComponentPropsType,
|
||||
};
|
||||
type Props<T> = SectionListProps<T> & CollapsibleComponentPropsType;
|
||||
|
||||
// eslint-disable-next-line react/prefer-stateless-function
|
||||
class CollapsibleSectionList extends React.Component<PropsType> {
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<CollapsibleComponent // eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
component={Animated.SectionList}>
|
||||
{props.children}
|
||||
</CollapsibleComponent>
|
||||
);
|
||||
}
|
||||
function CollapsibleSectionList<T>(props: Props<T>) {
|
||||
return (
|
||||
<CollapsibleComponent {...props} component={Animated.SectionList}>
|
||||
{props.children}
|
||||
</CollapsibleComponent>
|
||||
);
|
||||
}
|
||||
|
||||
export default CollapsibleSectionList;
|
|
@ -21,8 +21,11 @@
|
|||
|
||||
import * as React from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {ActivityIndicator, withTheme} from 'react-native-paper';
|
||||
import type {CustomThemeType} from '../../managers/ThemeManager';
|
||||
import {ActivityIndicator, useTheme} from 'react-native-paper';
|
||||
|
||||
type Props = {
|
||||
isAbsolute?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component used to display a header button
|
||||
|
@ -30,29 +33,21 @@ import type {CustomThemeType} from '../../managers/ThemeManager';
|
|||
* @param props Props to pass to the component
|
||||
* @return {*}
|
||||
*/
|
||||
function BasicLoadingScreen(props: {
|
||||
theme: CustomThemeType,
|
||||
isAbsolute: boolean,
|
||||
}): React.Node {
|
||||
const {theme, isAbsolute} = props;
|
||||
const {colors} = theme;
|
||||
let position;
|
||||
if (isAbsolute != null && isAbsolute) position = 'absolute';
|
||||
|
||||
export default function BasicLoadingScreen(props: Props) {
|
||||
const theme = useTheme();
|
||||
const {isAbsolute} = props;
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: colors.background,
|
||||
position,
|
||||
backgroundColor: theme.colors.background,
|
||||
position: isAbsolute ? 'absolute' : 'relative',
|
||||
top: 0,
|
||||
right: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
<ActivityIndicator animating size="large" color={colors.primary} />
|
||||
<ActivityIndicator animating size="large" color={theme.colors.primary} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default withTheme(BasicLoadingScreen);
|
|
@ -17,8 +17,6 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Button, Subheading, withTheme} from 'react-native-paper';
|
||||
import {StyleSheet, View} from 'react-native';
|
||||
|
@ -27,17 +25,16 @@ import i18n from 'i18n-js';
|
|||
import * as Animatable from 'react-native-animatable';
|
||||
import {StackNavigationProp} from '@react-navigation/stack';
|
||||
import {ERROR_TYPE} from '../../utils/WebData';
|
||||
import type {CustomThemeType} from '../../managers/ThemeManager';
|
||||
|
||||
type PropsType = {
|
||||
navigation: StackNavigationProp,
|
||||
theme: CustomThemeType,
|
||||
route: {name: string},
|
||||
onRefresh?: () => void,
|
||||
errorCode?: number,
|
||||
icon?: string,
|
||||
message?: string,
|
||||
showRetryButton?: boolean,
|
||||
navigation?: StackNavigationProp<any>;
|
||||
theme: ReactNativePaper.Theme;
|
||||
route?: {name: string};
|
||||
onRefresh?: () => void;
|
||||
errorCode?: number;
|
||||
icon?: string;
|
||||
message?: string;
|
||||
showRetryButton?: boolean;
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
@ -82,9 +79,11 @@ class ErrorView extends React.PureComponent<PropsType> {
|
|||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.icon = '';
|
||||
this.showLoginButton = false;
|
||||
this.message = '';
|
||||
}
|
||||
|
||||
getRetryButton(): React.Node {
|
||||
getRetryButton() {
|
||||
const {props} = this;
|
||||
return (
|
||||
<Button
|
||||
|
@ -97,7 +96,7 @@ class ErrorView extends React.PureComponent<PropsType> {
|
|||
);
|
||||
}
|
||||
|
||||
getLoginButton(): React.Node {
|
||||
getLoginButton() {
|
||||
return (
|
||||
<Button
|
||||
mode="contained"
|
||||
|
@ -111,10 +110,12 @@ class ErrorView extends React.PureComponent<PropsType> {
|
|||
|
||||
goToLogin = () => {
|
||||
const {props} = this;
|
||||
props.navigation.navigate('login', {
|
||||
screen: 'login',
|
||||
params: {nextScreen: props.route.name},
|
||||
});
|
||||
if (props.navigation) {
|
||||
props.navigation.navigate('login', {
|
||||
screen: 'login',
|
||||
params: {nextScreen: props.route ? props.route.name : undefined},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
generateMessage() {
|
||||
|
@ -169,13 +170,17 @@ class ErrorView extends React.PureComponent<PropsType> {
|
|||
}
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
const {props} = this;
|
||||
this.generateMessage();
|
||||
let button;
|
||||
if (this.showLoginButton) button = this.getLoginButton();
|
||||
else if (props.showRetryButton) button = this.getRetryButton();
|
||||
else button = null;
|
||||
if (this.showLoginButton) {
|
||||
button = this.getLoginButton();
|
||||
} else if (props.showRetryButton) {
|
||||
button = this.getRetryButton();
|
||||
} else {
|
||||
button = null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Animatable.View
|
|
@ -17,8 +17,6 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {RefreshControl, View} from 'react-native';
|
||||
import {StackNavigationProp} from '@react-navigation/stack';
|
||||
|
@ -35,31 +33,30 @@ import MascotPopup from '../../components/Mascot/MascotPopup';
|
|||
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||
import VoteNotAvailable from '../../components/Amicale/Vote/VoteNotAvailable';
|
||||
import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
|
||||
import type {ApiGenericDataType} from '../../utils/WebData';
|
||||
|
||||
export type VoteTeamType = {
|
||||
id: number,
|
||||
name: string,
|
||||
votes: number,
|
||||
id: number;
|
||||
name: string;
|
||||
votes: number;
|
||||
};
|
||||
|
||||
type TeamResponseType = {
|
||||
has_voted: boolean,
|
||||
teams: Array<VoteTeamType>,
|
||||
has_voted: boolean;
|
||||
teams: Array<VoteTeamType>;
|
||||
};
|
||||
|
||||
type VoteDatesStringType = {
|
||||
date_begin: string,
|
||||
date_end: string,
|
||||
date_result_begin: string,
|
||||
date_result_end: string,
|
||||
date_begin: string;
|
||||
date_end: string;
|
||||
date_result_begin: string;
|
||||
date_result_end: string;
|
||||
};
|
||||
|
||||
type VoteDatesObjectType = {
|
||||
date_begin: Date,
|
||||
date_end: Date,
|
||||
date_result_begin: Date,
|
||||
date_result_end: Date,
|
||||
date_begin: Date;
|
||||
date_end: Date;
|
||||
date_result_begin: Date;
|
||||
date_result_end: Date;
|
||||
};
|
||||
|
||||
// const FAKE_DATE = {
|
||||
|
@ -113,12 +110,12 @@ type VoteDatesObjectType = {
|
|||
const MIN_REFRESH_TIME = 5 * 1000;
|
||||
|
||||
type PropsType = {
|
||||
navigation: StackNavigationProp,
|
||||
navigation: StackNavigationProp<any>;
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
hasVoted: boolean,
|
||||
mascotDialogVisible: boolean,
|
||||
hasVoted: boolean;
|
||||
mascotDialogVisible: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -139,10 +136,13 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
|
|||
|
||||
lastRefresh: Date | null;
|
||||
|
||||
authRef: {current: null | AuthenticatedScreen};
|
||||
authRef: {current: null | AuthenticatedScreen<any>};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.teams = [];
|
||||
this.datesString = null;
|
||||
this.dates = null;
|
||||
this.state = {
|
||||
hasVoted: false,
|
||||
mascotDialogVisible: AsyncStorageManager.getBool(
|
||||
|
@ -174,8 +174,8 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
|
|||
return dateString;
|
||||
}
|
||||
|
||||
getMainRenderItem = ({item}: {item: {key: string}}): React.Node => {
|
||||
if (item.key === 'info')
|
||||
getMainRenderItem = ({item}: {item: {key: string}}) => {
|
||||
if (item.key === 'info') {
|
||||
return (
|
||||
<View>
|
||||
<Button
|
||||
|
@ -191,21 +191,24 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
|
|||
</Button>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return this.getContent();
|
||||
};
|
||||
|
||||
getScreen = (data: Array<ApiGenericDataType | null>): React.Node => {
|
||||
getScreen = (data: Array<TeamResponseType | VoteDatesStringType | null>) => {
|
||||
const {state} = this;
|
||||
// data[0] = FAKE_TEAMS2;
|
||||
// data[1] = FAKE_DATE;
|
||||
this.lastRefresh = new Date();
|
||||
|
||||
const teams: TeamResponseType | null = data[0];
|
||||
const dateStrings: VoteDatesStringType | null = data[1];
|
||||
const teams = data[0] as TeamResponseType | null;
|
||||
const dateStrings = data[1] as VoteDatesStringType | null;
|
||||
|
||||
if (dateStrings != null && dateStrings.date_begin == null)
|
||||
if (dateStrings != null && dateStrings.date_begin == null) {
|
||||
this.datesString = null;
|
||||
else this.datesString = dateStrings;
|
||||
} else {
|
||||
this.datesString = dateStrings;
|
||||
}
|
||||
|
||||
if (teams != null) {
|
||||
this.teams = teams.teams;
|
||||
|
@ -225,13 +228,20 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
|
|||
);
|
||||
};
|
||||
|
||||
getContent(): React.Node {
|
||||
getContent() {
|
||||
const {state} = this;
|
||||
if (!this.isVoteStarted()) return this.getTeaseVoteCard();
|
||||
if (this.isVoteRunning() && !this.hasVoted && !state.hasVoted)
|
||||
if (!this.isVoteStarted()) {
|
||||
return this.getTeaseVoteCard();
|
||||
}
|
||||
if (this.isVoteRunning() && !this.hasVoted && !state.hasVoted) {
|
||||
return this.getVoteCard();
|
||||
if (!this.isResultStarted()) return this.getWaitVoteCard();
|
||||
if (this.isResultRunning()) return this.getVoteResultCard();
|
||||
}
|
||||
if (!this.isResultStarted()) {
|
||||
return this.getWaitVoteCard();
|
||||
}
|
||||
if (this.isResultRunning()) {
|
||||
return this.getVoteResultCard();
|
||||
}
|
||||
return <VoteNotAvailable />;
|
||||
}
|
||||
|
||||
|
@ -240,7 +250,7 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
|
|||
/**
|
||||
* The user has not voted yet, and the votes are open
|
||||
*/
|
||||
getVoteCard(): React.Node {
|
||||
getVoteCard() {
|
||||
return (
|
||||
<VoteSelect
|
||||
teams={this.teams}
|
||||
|
@ -253,8 +263,8 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
|
|||
/**
|
||||
* Votes have ended, results can be displayed
|
||||
*/
|
||||
getVoteResultCard(): React.Node {
|
||||
if (this.dates != null && this.datesString != null)
|
||||
getVoteResultCard() {
|
||||
if (this.dates != null && this.datesString != null) {
|
||||
return (
|
||||
<VoteResults
|
||||
teams={this.teams}
|
||||
|
@ -264,14 +274,15 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
|
|||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <VoteNotAvailable />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vote will open shortly
|
||||
*/
|
||||
getTeaseVoteCard(): React.Node {
|
||||
if (this.dates != null && this.datesString != null)
|
||||
getTeaseVoteCard() {
|
||||
if (this.dates != null && this.datesString != null) {
|
||||
return (
|
||||
<VoteTease
|
||||
startDate={this.getDateString(
|
||||
|
@ -280,24 +291,26 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
|
|||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <VoteNotAvailable />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Votes have ended, or user has voted waiting for results
|
||||
*/
|
||||
getWaitVoteCard(): React.Node {
|
||||
getWaitVoteCard() {
|
||||
const {state} = this;
|
||||
let startDate = null;
|
||||
if (
|
||||
this.dates != null &&
|
||||
this.datesString != null &&
|
||||
this.dates.date_result_begin != null
|
||||
)
|
||||
) {
|
||||
startDate = this.getDateString(
|
||||
this.dates.date_result_begin,
|
||||
this.datesString.date_result_begin,
|
||||
);
|
||||
}
|
||||
return (
|
||||
<VoteWait
|
||||
startDate={startDate}
|
||||
|
@ -314,12 +327,15 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
|
|||
reloadData = () => {
|
||||
let canRefresh;
|
||||
const {lastRefresh} = this;
|
||||
if (lastRefresh != null)
|
||||
if (lastRefresh != null) {
|
||||
canRefresh =
|
||||
new Date().getTime() - lastRefresh.getTime() > MIN_REFRESH_TIME;
|
||||
else canRefresh = true;
|
||||
if (canRefresh && this.authRef.current != null)
|
||||
} else {
|
||||
canRefresh = true;
|
||||
}
|
||||
if (canRefresh && this.authRef.current != null) {
|
||||
this.authRef.current.reload();
|
||||
}
|
||||
};
|
||||
|
||||
showMascotDialog = () => {
|
||||
|
@ -380,8 +396,12 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
|
|||
date_result_begin: dateResultBegin,
|
||||
date_result_end: dateResultEnd,
|
||||
};
|
||||
} else this.dates = null;
|
||||
} else this.dates = null;
|
||||
} else {
|
||||
this.dates = null;
|
||||
}
|
||||
} else {
|
||||
this.dates = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -391,11 +411,11 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
|
|||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
render(): React.Node {
|
||||
render() {
|
||||
const {props, state} = this;
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<AuthenticatedScreen
|
||||
<AuthenticatedScreen<TeamResponseType | VoteDatesStringType>
|
||||
navigation={props.navigation}
|
||||
ref={this.authRef}
|
||||
requests={[
|
||||
|
@ -418,7 +438,6 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
|
|||
message={i18n.t('screens.vote.mascotDialog.message')}
|
||||
icon="vote"
|
||||
buttons={{
|
||||
action: null,
|
||||
cancel: {
|
||||
message: i18n.t('screens.vote.mascotDialog.button'),
|
||||
icon: 'check',
|
|
@ -35,8 +35,6 @@ export type ApiDataLoginType = {
|
|||
token: string;
|
||||
};
|
||||
|
||||
export type ApiGenericDataType = {[key: string]: any};
|
||||
|
||||
type ApiResponseType<T> = {
|
||||
error: number;
|
||||
data: T;
|
||||
|
@ -70,7 +68,7 @@ export function isApiResponseValid<T>(response: ApiResponseType<T>): boolean {
|
|||
* @param path The API path from the API endpoint
|
||||
* @param method The HTTP method to use (GET or POST)
|
||||
* @param params The params to use for this request
|
||||
* @returns {Promise<ApiGenericDataType>}
|
||||
* @returns {Promise<T>}
|
||||
*/
|
||||
export async function apiRequest<T>(
|
||||
path: string,
|
||||
|
|
Loading…
Reference in a new issue