Improved flow typing for the vote screen

This commit is contained in:
Arnaud Vergnet 2020-04-19 18:49:24 +02:00
parent b85dab627a
commit 6dbce2cc3e
7 changed files with 170 additions and 122 deletions

View file

@ -5,15 +5,16 @@ import ConnectionManager from "../../managers/ConnectionManager";
import {ERROR_TYPE} from "../../utils/WebData"; import {ERROR_TYPE} from "../../utils/WebData";
import ErrorView from "../Screens/ErrorView"; import ErrorView from "../Screens/ErrorView";
import BasicLoadingScreen from "../Screens/BasicLoadingScreen"; import BasicLoadingScreen from "../Screens/BasicLoadingScreen";
import {StackNavigationProp} from "@react-navigation/stack";
type Props = { type Props = {
navigation: Object, navigation: StackNavigationProp,
requests: Array<{ requests: Array<{
link: string, link: string,
params: Object, params: Object,
mandatory: boolean mandatory: boolean
}>, }>,
renderFunction: Function, renderFunction: (Array<{ [key: string]: any } | null>) => React.Node,
} }
type State = { type State = {
@ -29,7 +30,7 @@ class AuthenticatedScreen extends React.Component<Props, State> {
currentUserToken: string | null; currentUserToken: string | null;
connectionManager: ConnectionManager; connectionManager: ConnectionManager;
errors: Array<number>; errors: Array<number>;
fetchedData: Array<Object>; fetchedData: Array<{ [key: string]: any } | null>;
constructor(props: Object) { constructor(props: Object) {
super(props); super(props);
@ -88,7 +89,7 @@ class AuthenticatedScreen extends React.Component<Props, State> {
* @param index The index for the data * @param index The index for the data
* @param error The error code received * @param error The error code received
*/ */
onRequestFinished(data: Object | null, index: number, error: number) { onRequestFinished(data: { [key: string]: any } | null, index: number, error: number) {
if (index >= 0 && index < this.props.requests.length) { if (index >= 0 && index < this.props.requests.length) {
this.fetchedData[index] = data; this.fetchedData[index] = data;
this.errors[index] = error; this.errors[index] = error;

View file

@ -4,17 +4,18 @@ import * as React from 'react';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import LoadingConfirmDialog from "../Dialogs/LoadingConfirmDialog"; import LoadingConfirmDialog from "../Dialogs/LoadingConfirmDialog";
import ConnectionManager from "../../managers/ConnectionManager"; import ConnectionManager from "../../managers/ConnectionManager";
import {StackNavigationProp} from "@react-navigation/stack";
type Props = { type Props = {
navigation: Object, navigation: StackNavigationProp,
visible: boolean, visible: boolean,
onDismiss: Function, onDismiss: () => void,
} }
class LogoutDialog extends React.PureComponent<Props> { class LogoutDialog extends React.PureComponent<Props> {
onClickAccept = async () => { onClickAccept = async () => {
return new Promise((resolve, reject) => { return new Promise((resolve) => {
ConnectionManager.getInstance().disconnect() ConnectionManager.getInstance().disconnect()
.then(() => { .then(() => {
this.props.navigation.reset({ this.props.navigation.reset({

View file

@ -1,24 +1,25 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {Avatar, Card, List, ProgressBar, Subheading, withTheme} from "react-native-paper"; import {Avatar, Card, List, ProgressBar, Subheading, Theme, withTheme} from "react-native-paper";
import {FlatList, StyleSheet} from "react-native"; import {FlatList, StyleSheet} from "react-native";
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import type {team} from "../../../screens/Amicale/VoteScreen";
type Props = { type Props = {
teams: Array<Object>, teams: Array<team>,
dateEnd: string, dateEnd: string,
theme: Theme,
} }
class VoteResults extends React.Component<Props> { class VoteResults extends React.Component<Props> {
totalVotes: number; totalVotes: number;
winnerIds: Array<number>; winnerIds: Array<number>;
colors: Object;
constructor(props) { constructor(props) {
super(); super();
this.colors = props.theme.colors;
props.teams.sort(this.sortByVotes); props.teams.sort(this.sortByVotes);
this.getTotalVotes(props.teams); this.getTotalVotes(props.teams);
this.getWinnerIds(props.teams); this.getWinnerIds(props.teams);
@ -28,16 +29,16 @@ class VoteResults extends React.Component<Props> {
return false; return false;
} }
sortByVotes = (a: Object, b: Object) => b.votes - a.votes; sortByVotes = (a: team, b: team) => b.votes - a.votes;
getTotalVotes(teams: Array<Object>) { getTotalVotes(teams: Array<team>) {
this.totalVotes = 0; this.totalVotes = 0;
for (let i = 0; i < teams.length; i++) { for (let i = 0; i < teams.length; i++) {
this.totalVotes += teams[i].votes; this.totalVotes += teams[i].votes;
} }
} }
getWinnerIds(teams: Array<Object>){ getWinnerIds(teams: Array<team>){
let max = teams[0].votes; let max = teams[0].votes;
this.winnerIds= []; this.winnerIds= [];
for (let i = 0; i < teams.length; i++) { for (let i = 0; i < teams.length; i++) {
@ -48,11 +49,12 @@ class VoteResults extends React.Component<Props> {
} }
} }
voteKeyExtractor = (item: Object) => item.id.toString(); voteKeyExtractor = (item: team) => item.id.toString();
resultRenderItem = ({item}: Object) => { resultRenderItem = ({item}: {item: team}) => {
const isWinner = this.winnerIds.indexOf(item.id) !== -1; const isWinner = this.winnerIds.indexOf(item.id) !== -1;
const isDraw = this.winnerIds.length > 1; const isDraw = this.winnerIds.length > 1;
const colors = this.props.theme.colors;
return ( return (
<Card style={{ <Card style={{
marginTop: 10, marginTop: 10,
@ -62,16 +64,16 @@ class VoteResults extends React.Component<Props> {
title={item.name} title={item.name}
description={item.votes + ' ' + i18n.t('voteScreen.results.votes')} description={item.votes + ' ' + i18n.t('voteScreen.results.votes')}
left={props => isWinner left={props => isWinner
? <List.Icon {...props} icon={isDraw ? "trophy-outline" : "trophy"} color={this.colors.primary}/> ? <List.Icon {...props} icon={isDraw ? "trophy-outline" : "trophy"} color={colors.primary}/>
: null} : null}
titleStyle={{ titleStyle={{
color: isWinner color: isWinner
? this.colors.primary ? colors.primary
: this.colors.text : colors.text
}} }}
style={{padding: 0}} style={{padding: 0}}
/> />
<ProgressBar progress={item.votes / this.totalVotes} color={this.colors.primary}/> <ProgressBar progress={item.votes / this.totalVotes} color={colors.primary}/>
</Card> </Card>
); );
}; };

View file

@ -7,11 +7,12 @@ import ConnectionManager from "../../../managers/ConnectionManager";
import LoadingConfirmDialog from "../../Dialogs/LoadingConfirmDialog"; import LoadingConfirmDialog from "../../Dialogs/LoadingConfirmDialog";
import ErrorDialog from "../../Dialogs/ErrorDialog"; import ErrorDialog from "../../Dialogs/ErrorDialog";
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import type {team} from "../../../screens/Amicale/VoteScreen";
type Props = { type Props = {
teams: Array<Object>, teams: Array<team>,
onVoteSuccess: Function, onVoteSuccess: () => void,
onVoteError: Function, onVoteError: () => void,
} }
type State = { type State = {
@ -33,16 +34,16 @@ export default class VoteSelect extends React.PureComponent<Props, State> {
onVoteSelectionChange = (team: string) => this.setState({selectedTeam: team}); onVoteSelectionChange = (team: string) => this.setState({selectedTeam: team});
voteKeyExtractor = (item: Object) => item.id.toString(); voteKeyExtractor = (item: team) => item.id.toString();
voteRenderItem = ({item}: Object) => <RadioButton.Item label={item.name} value={item.id.toString()}/>; voteRenderItem = ({item}: { item: team }) => <RadioButton.Item label={item.name} value={item.id.toString()}/>;
showVoteDialog = () => this.setState({voteDialogVisible: true}); showVoteDialog = () => this.setState({voteDialogVisible: true});
onVoteDialogDismiss = () => this.setState({voteDialogVisible: false,}); onVoteDialogDismiss = () => this.setState({voteDialogVisible: false,});
onVoteDialogAccept = async () => { onVoteDialogAccept = async () => {
return new Promise((resolve, reject) => { return new Promise((resolve) => {
ConnectionManager.getInstance().authenticatedRequest( ConnectionManager.getInstance().authenticatedRequest(
"elections/vote", "elections/vote",
{"team": parseInt(this.state.selectedTeam)}) {"team": parseInt(this.state.selectedTeam)})
@ -76,10 +77,11 @@ export default class VoteSelect extends React.PureComponent<Props, State> {
<Card.Title <Card.Title
title={i18n.t('voteScreen.select.title')} title={i18n.t('voteScreen.select.title')}
subtitle={i18n.t('voteScreen.select.subtitle')} subtitle={i18n.t('voteScreen.select.subtitle')}
left={(props) => <Avatar.Icon left={(props) =>
{...props} <Avatar.Icon
icon={"alert-decagram"} {...props}
/>} icon={"alert-decagram"}
/>}
/> />
<Card.Content> <Card.Content>
<RadioButton.Group <RadioButton.Group

View file

@ -7,9 +7,7 @@ import i18n from 'i18n-js';
const ICON_AMICALE = require('../../../../assets/amicale.png'); const ICON_AMICALE = require('../../../../assets/amicale.png');
type Props = {} export default class VoteTitle extends React.Component<{}> {
export default class VoteTitle extends React.Component<Props> {
shouldComponentUpdate() { shouldComponentUpdate() {
return false; return false;

View file

@ -1,7 +1,7 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {ActivityIndicator, Card, Paragraph, withTheme} from "react-native-paper"; import {ActivityIndicator, Card, Paragraph, Theme, withTheme} from "react-native-paper";
import {StyleSheet} from "react-native"; import {StyleSheet} from "react-native";
import i18n from 'i18n-js'; import i18n from 'i18n-js';
@ -10,22 +10,18 @@ type Props = {
justVoted: boolean, justVoted: boolean,
hasVoted: boolean, hasVoted: boolean,
isVoteRunning: boolean, isVoteRunning: boolean,
theme: Theme,
} }
class VoteWait extends React.Component<Props> { class VoteWait extends React.Component<Props> {
colors: Object;
constructor(props) {
super(props);
this.colors = props.theme.colors;
}
shouldComponentUpdate() { shouldComponentUpdate() {
return false; return false;
} }
render() { render() {
const colors = this.props.theme.colors;
const startDate = this.props.startDate;
return ( return (
<Card style={styles.card}> <Card style={styles.card}>
<Card.Title <Card.Title
@ -38,22 +34,22 @@ class VoteWait extends React.Component<Props> {
<Card.Content> <Card.Content>
{ {
this.props.justVoted this.props.justVoted
? <Paragraph style={{color: this.colors.success}}> ? <Paragraph style={{color: colors.success}}>
{i18n.t('voteScreen.wait.messageSubmitted')} {i18n.t('voteScreen.wait.messageSubmitted')}
</Paragraph> </Paragraph>
: null : null
} }
{ {
this.props.hasVoted this.props.hasVoted
? <Paragraph style={{color: this.colors.success}}> ? <Paragraph style={{color: colors.success}}>
{i18n.t('voteScreen.wait.messageVoted')} {i18n.t('voteScreen.wait.messageVoted')}
</Paragraph> </Paragraph>
: null : null
} }
{ {
this.props.startDate !== null startDate != null
? <Paragraph> ? <Paragraph>
{i18n.t('voteScreen.wait.messageDate') + ' ' + this.props.startDate} {i18n.t('voteScreen.wait.messageDate') + ' ' + startDate}
</Paragraph> </Paragraph>
: <Paragraph>{i18n.t('voteScreen.wait.messageDateUndefined')}</Paragraph> : <Paragraph>{i18n.t('voteScreen.wait.messageDateUndefined')}</Paragraph>
} }

View file

@ -10,53 +10,78 @@ import VoteSelect from "../../components/Amicale/Vote/VoteSelect";
import VoteResults from "../../components/Amicale/Vote/VoteResults"; import VoteResults from "../../components/Amicale/Vote/VoteResults";
import VoteWait from "../../components/Amicale/Vote/VoteWait"; import VoteWait from "../../components/Amicale/Vote/VoteWait";
const FAKE_DATE = { export type team = {
"date_begin": "2020-04-09 15:50", id: number,
"date_end": "2020-04-09 15:50", name: string,
"date_result_begin": "2020-04-09 15:50", votes: number,
"date_result_end": "2020-04-09 22:50", }
type teamResponse = {
has_voted: boolean,
teams: Array<team>,
}; };
const FAKE_DATE2 = { type stringVoteDates = {
"date_begin": null, date_begin: string,
"date_end": null, date_end: string,
"date_result_begin": null, date_result_begin: string,
"date_result_end": null, date_result_end: string,
}; }
const FAKE_TEAMS = { type objectVoteDates = {
has_voted: false, date_begin: Date,
teams: [ date_end: Date,
{ date_result_begin: Date,
id: 1, date_result_end: Date,
name: "TEST TEAM1", }
},
{ // const FAKE_DATE = {
id: 2, // "date_begin": "2020-04-19 15:50",
name: "TEST TEAM2", // "date_end": "2020-04-19 15:50",
}, // "date_result_begin": "2020-04-19 19:50",
], // "date_result_end": "2020-04-19 22:50",
}; // };
const FAKE_TEAMS2 = { //
has_voted: false, // const FAKE_DATE2 = {
teams: [ // "date_begin": null,
{ // "date_end": null,
id: 1, // "date_result_begin": null,
name: "TEST TEAM1", // "date_result_end": null,
votes: 9, // };
}, //
{ // const FAKE_TEAMS = {
id: 2, // has_voted: false,
name: "TEST TEAM2", // teams: [
votes: 9, // {
}, // id: 1,
{ // name: "TEST TEAM1",
id: 3, // },
name: "TEST TEAM3", // {
votes: 5, // id: 2,
}, // name: "TEST TEAM2",
], // },
}; // ],
// };
// const FAKE_TEAMS2 = {
// has_voted: false,
// teams: [
// {
// id: 1,
// name: "TEST TEAM1",
// votes: 9,
// },
// {
// id: 2,
// name: "TEST TEAM2",
// votes: 9,
// },
// {
// id: 3,
// name: "TEST TEAM3",
// votes: 5,
// },
// ],
// };
const MIN_REFRESH_TIME = 5 * 1000; const MIN_REFRESH_TIME = 5 * 1000;
@ -74,17 +99,17 @@ export default class VoteScreen extends React.Component<Props, State> {
hasVoted: false, hasVoted: false,
}; };
teams: Array<Object>; teams: Array<team>;
hasVoted: boolean; hasVoted: boolean;
datesString: Object; datesString: null | stringVoteDates;
dates: Object; dates: null | objectVoteDates;
today: Date; today: Date;
mainFlatListData: Array<Object>; mainFlatListData: Array<{ key: string }>;
lastRefresh: Date; lastRefresh: Date;
authRef: Object; authRef: { current: null | AuthenticatedScreen };
constructor() { constructor() {
super(); super();
@ -103,69 +128,81 @@ export default class VoteScreen extends React.Component<Props, State> {
canRefresh = (new Date().getTime() - this.lastRefresh.getTime()) > MIN_REFRESH_TIME; canRefresh = (new Date().getTime() - this.lastRefresh.getTime()) > MIN_REFRESH_TIME;
else else
canRefresh = true; canRefresh = true;
if (canRefresh) if (canRefresh && this.authRef.current != null)
this.authRef.current.reload() this.authRef.current.reload()
}; };
generateDateObject() { generateDateObject() {
this.dates = { const strings = this.datesString;
date_begin: stringToDate(this.datesString.date_begin), if (strings != null) {
date_end: stringToDate(this.datesString.date_end), const dateBegin = stringToDate(strings.date_begin);
date_result_begin: stringToDate(this.datesString.date_result_begin), const dateEnd = stringToDate(strings.date_end);
date_result_end: stringToDate(this.datesString.date_result_end), const dateResultBegin = stringToDate(strings.date_result_begin);
}; const dateResultEnd = stringToDate(strings.date_result_end);
if (dateBegin != null && dateEnd != null && dateResultBegin != null && dateResultEnd != null) {
this.dates = {
date_begin: dateBegin,
date_end: dateEnd,
date_result_begin: dateResultBegin,
date_result_end: dateResultEnd,
};
} else
this.dates = null;
} else
this.dates = null;
} }
getDateString(date: Date, dateString: string): string { getDateString(date: Date, dateString: string): string {
if (this.today.getDate() === date.getDate()) { if (this.today.getDate() === date.getDate()) {
const str = getTimeOnlyString(dateString); const str = getTimeOnlyString(dateString);
return str !== null ? str : ""; return str != null ? str : "";
} else } else
return dateString; return dateString;
} }
isVoteAvailable() {
return this.dates.date_begin !== null;
}
isVoteRunning() { isVoteRunning() {
return this.today > this.dates.date_begin && this.today < this.dates.date_end; return this.dates != null && this.today > this.dates.date_begin && this.today < this.dates.date_end;
} }
isVoteStarted() { isVoteStarted() {
return this.today > this.dates.date_begin; return this.dates != null && this.today > this.dates.date_begin;
} }
isResultRunning() { isResultRunning() {
return this.today > this.dates.date_result_begin && this.today < this.dates.date_result_end; return this.dates != null && this.today > this.dates.date_result_begin && this.today < this.dates.date_result_end;
} }
isResultStarted() { isResultStarted() {
return this.today > this.dates.date_result_begin; return this.dates != null && this.today > this.dates.date_result_begin;
} }
mainRenderItem = ({item}: Object) => { mainRenderItem = ({item}: Object) => {
if (item.key === 'info') if (item.key === 'info')
return <VoteTitle/>; return <VoteTitle/>;
else if (item.key === 'main' && this.isVoteAvailable()) else if (item.key === 'main' && this.dates != null)
return this.getContent(); return this.getContent();
else else
return null; return null;
}; };
getScreen = (data: Array<Object | null>) => { getScreen = (data: Array<{ [key: string]: any } | null>) => {
// data[0] = FAKE_TEAMS2; // data[0] = FAKE_TEAMS2;
// data[1] = FAKE_DATE; // data[1] = FAKE_DATE;
this.lastRefresh = new Date(); this.lastRefresh = new Date();
if (data[1] === null) const teams : teamResponse | null = data[0];
data[1] = {date_begin: null}; const dateStrings : stringVoteDates | null = data[1];
if (data[0] !== null) { if (dateStrings != null && dateStrings.date_begin == null)
this.teams = data[0].teams; this.datesString = null;
this.hasVoted = data[0].has_voted; else
this.datesString = dateStrings;
if (teams != null) {
this.teams = teams.teams;
this.hasVoted = teams.has_voted;
} }
this.datesString = data[1];
this.generateDateObject(); this.generateDateObject();
return ( return (
<View> <View>
@ -211,15 +248,26 @@ export default class VoteScreen extends React.Component<Props, State> {
* Votes have ended, results can be displayed * Votes have ended, results can be displayed
*/ */
getVoteResultCard() { getVoteResultCard() {
return <VoteResults teams={this.teams} if (this.dates != null && this.datesString != null)
dateEnd={this.getDateString(this.dates.date_result_end, this.datesString.date_result_end)}/>; return <VoteResults
teams={this.teams}
dateEnd={this.getDateString(
this.dates.date_result_end,
this.datesString.date_result_end)}
/>;
else
return null;
} }
/** /**
* Vote will open shortly * Vote will open shortly
*/ */
getTeaseVoteCard() { getTeaseVoteCard() {
return <VoteTease startDate={this.getDateString(this.dates.date_begin, this.datesString.date_begin)}/>; if (this.dates != null && this.datesString != null)
return <VoteTease
startDate={this.getDateString(this.dates.date_begin, this.datesString.date_begin)}/>;
else
return null;
} }
/** /**
@ -227,7 +275,7 @@ export default class VoteScreen extends React.Component<Props, State> {
*/ */
getWaitVoteCard() { getWaitVoteCard() {
let startDate = null; let startDate = null;
if (this.dates.date_result_begin !== 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); startDate = this.getDateString(this.dates.date_result_begin, this.datesString.date_result_begin);
return <VoteWait startDate={startDate} hasVoted={this.hasVoted || this.state.hasVoted} return <VoteWait startDate={startDate} hasVoted={this.hasVoted || this.state.hasVoted}
justVoted={this.state.hasVoted} justVoted={this.state.hasVoted}