Compare commits
10 commits
bdeae6933a
...
7f24eb77ac
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f24eb77ac | |||
| 581ea516ae | |||
| b64b68dc8a | |||
| 75b7412eb2 | |||
| 6dbce2cc3e | |||
| b85dab627a | |||
| 8c7ceb84fc | |||
| 759c369c93 | |||
| 9eb925d1a1 | |||
| 433306e2a7 |
32 changed files with 892 additions and 691 deletions
140
App.js
140
App.js
|
|
@ -1,11 +1,12 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Platform, StatusBar, YellowBox} from 'react-native';
|
||||
import {Platform, StatusBar, View, YellowBox} from 'react-native';
|
||||
import LocaleManager from './src/managers/LocaleManager';
|
||||
import AsyncStorageManager from "./src/managers/AsyncStorageManager";
|
||||
import CustomIntroSlider from "./src/components/Overrides/CustomIntroSlider";
|
||||
import {SplashScreen} from 'expo';
|
||||
import type {CustomTheme} from "./src/managers/ThemeManager";
|
||||
import ThemeManager from './src/managers/ThemeManager';
|
||||
import {NavigationContainer} from '@react-navigation/native';
|
||||
import {createStackNavigator} from '@react-navigation/stack';
|
||||
|
|
@ -17,6 +18,7 @@ import Update from "./src/constants/Update";
|
|||
import ConnectionManager from "./src/managers/ConnectionManager";
|
||||
import URLHandler from "./src/utils/URLHandler";
|
||||
import {setSafeBounceHeight} from "react-navigation-collapsible";
|
||||
import * as Animatable from 'react-native-animatable';
|
||||
|
||||
YellowBox.ignoreWarnings([ // collapsible headers cause this warning, just ignore as it is not an issue
|
||||
'Non-serializable values were found in the navigation state',
|
||||
|
|
@ -29,7 +31,7 @@ type State = {
|
|||
showIntro: boolean,
|
||||
showUpdate: boolean,
|
||||
showAprilFools: boolean,
|
||||
currentTheme: ?Object,
|
||||
currentTheme: CustomTheme | null,
|
||||
};
|
||||
|
||||
const Stack = createStackNavigator();
|
||||
|
|
@ -44,42 +46,58 @@ export default class App extends React.Component<Props, State> {
|
|||
currentTheme: null,
|
||||
};
|
||||
|
||||
navigatorRef: Object;
|
||||
navigatorRef: { current: null | NavigationContainer };
|
||||
|
||||
defaultRoute: string | null;
|
||||
defaultData: Object;
|
||||
defaultHomeRoute: string | null;
|
||||
defaultHomeData: { [key: string]: any }
|
||||
|
||||
createDrawerNavigator: Function;
|
||||
createDrawerNavigator: () => React.Node;
|
||||
|
||||
urlHandler: URLHandler;
|
||||
storageManager: AsyncStorageManager;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
LocaleManager.initTranslations();
|
||||
SplashScreen.preventAutoHide();
|
||||
this.navigatorRef = React.createRef();
|
||||
this.defaultRoute = null;
|
||||
this.defaultData = {};
|
||||
this.defaultHomeRoute = null;
|
||||
this.defaultHomeData = {};
|
||||
this.storageManager = AsyncStorageManager.getInstance();
|
||||
this.urlHandler = new URLHandler(this.onInitialURLParsed, this.onDetectURL);
|
||||
this.urlHandler.listen();
|
||||
setSafeBounceHeight(Platform.OS === 'ios' ? 100 : 20);
|
||||
}
|
||||
|
||||
onInitialURLParsed = ({route, data}: Object) => {
|
||||
this.defaultRoute = route;
|
||||
this.defaultData = data;
|
||||
};
|
||||
|
||||
onDetectURL = ({route, data}: Object) => {
|
||||
// Navigate to nested navigator and pass data to the index screen
|
||||
this.navigatorRef.current.navigate('home', {
|
||||
screen: 'index',
|
||||
params: {nextScreen: route, data: data}
|
||||
});
|
||||
/**
|
||||
* THe app has been started by an url, and it has been parsed.
|
||||
* Set a new default start route based on the data parsed.
|
||||
*
|
||||
* @param parsedData The data parsed from the url
|
||||
*/
|
||||
onInitialURLParsed = (parsedData: { route: string, data: { [key: string]: any } }) => {
|
||||
this.defaultHomeRoute = parsedData.route;
|
||||
this.defaultHomeData = parsedData.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the theme
|
||||
* An url has been opened and parsed while the app was active.
|
||||
* Redirect the user to the screen according to parsed data.
|
||||
*
|
||||
* @param parsedData The data parsed from the url
|
||||
*/
|
||||
onDetectURL = (parsedData: { route: string, data: { [key: string]: any } }) => {
|
||||
// Navigate to nested navigator and pass data to the index screen
|
||||
if (this.navigatorRef.current != null) {
|
||||
this.navigatorRef.current.navigate('home', {
|
||||
screen: 'index',
|
||||
params: {nextScreen: parsedData.route, data: parsedData.data}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the current theme
|
||||
*/
|
||||
onUpdateTheme = () => {
|
||||
this.setState({
|
||||
|
|
@ -88,6 +106,10 @@ export default class App extends React.Component<Props, State> {
|
|||
this.setupStatusBar();
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates status bar content color if on iOS only,
|
||||
* as the android status bar is always set to black.
|
||||
*/
|
||||
setupStatusBar() {
|
||||
if (Platform.OS === 'ios') {
|
||||
if (ThemeManager.getNightMode()) {
|
||||
|
|
@ -96,12 +118,10 @@ export default class App extends React.Component<Props, State> {
|
|||
StatusBar.setBarStyle('dark-content', true);
|
||||
}
|
||||
}
|
||||
// StatusBar.setTranslucent(false);
|
||||
// StatusBar.setBackgroundColor(ThemeManager.getCurrentTheme().colors.surface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback when user ends the intro. Save in preferences to avaoid showing back the introSlides
|
||||
* Callback when user ends the intro. Save in preferences to avoid showing back the introSlides
|
||||
*/
|
||||
onIntroDone = () => {
|
||||
this.setState({
|
||||
|
|
@ -109,46 +129,52 @@ export default class App extends React.Component<Props, State> {
|
|||
showUpdate: false,
|
||||
showAprilFools: false,
|
||||
});
|
||||
AsyncStorageManager.getInstance().savePref(AsyncStorageManager.getInstance().preferences.showIntro.key, '0');
|
||||
AsyncStorageManager.getInstance().savePref(AsyncStorageManager.getInstance().preferences.updateNumber.key, Update.number.toString());
|
||||
AsyncStorageManager.getInstance().savePref(AsyncStorageManager.getInstance().preferences.showAprilFoolsStart.key, '0');
|
||||
this.storageManager.savePref(this.storageManager.preferences.showIntro.key, '0');
|
||||
this.storageManager.savePref(this.storageManager.preferences.updateNumber.key, Update.number.toString());
|
||||
this.storageManager.savePref(this.storageManager.preferences.showAprilFoolsStart.key, '0');
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
await this.loadAssetsAsync();
|
||||
componentDidMount() {
|
||||
this.loadAssetsAsync().then(() => {
|
||||
this.onLoadFinished();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads every async data
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async loadAssetsAsync() {
|
||||
// Wait for custom fonts to be loaded before showing the app
|
||||
await AsyncStorageManager.getInstance().loadPreferences();
|
||||
ThemeManager.getInstance().setUpdateThemeCallback(this.onUpdateTheme);
|
||||
await this.storageManager.loadPreferences();
|
||||
await initExpoToken();
|
||||
try {
|
||||
await ConnectionManager.getInstance().recoverLogin();
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
this.createDrawerNavigator = () => <DrawerNavigator defaultRoute={this.defaultRoute}
|
||||
defaultData={this.defaultData}/>;
|
||||
this.onLoadFinished();
|
||||
}
|
||||
|
||||
/**
|
||||
* Async loading is done, finish processing startup data
|
||||
*/
|
||||
onLoadFinished() {
|
||||
// console.log("finished");
|
||||
// Only show intro if this is the first time starting the app
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
currentTheme: ThemeManager.getCurrentTheme(),
|
||||
showIntro: AsyncStorageManager.getInstance().preferences.showIntro.current === '1',
|
||||
showUpdate: AsyncStorageManager.getInstance().preferences.updateNumber.current !== Update.number.toString(),
|
||||
showAprilFools: AprilFoolsManager.getInstance().isAprilFoolsEnabled() && AsyncStorageManager.getInstance().preferences.showAprilFoolsStart.current === '1',
|
||||
});
|
||||
this.createDrawerNavigator = () => <DrawerNavigator
|
||||
defaultHomeRoute={this.defaultHomeRoute}
|
||||
defaultHomeData={this.defaultHomeData}/>;
|
||||
ThemeManager.getInstance().setUpdateThemeCallback(this.onUpdateTheme);
|
||||
// Status bar goes dark if set too fast on ios
|
||||
if (Platform.OS === 'ios')
|
||||
setTimeout(this.setupStatusBar, 1000);
|
||||
else
|
||||
this.setupStatusBar();
|
||||
SplashScreen.hide();
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
currentTheme: ThemeManager.getCurrentTheme(),
|
||||
showIntro: this.storageManager.preferences.showIntro.current === '1',
|
||||
showUpdate: this.storageManager.preferences.updateNumber.current !== Update.number.toString(),
|
||||
showAprilFools: AprilFoolsManager.getInstance().isAprilFoolsEnabled() && this.storageManager.preferences.showAprilFoolsStart.current === '1',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -166,11 +192,27 @@ export default class App extends React.Component<Props, State> {
|
|||
} else {
|
||||
return (
|
||||
<PaperProvider theme={this.state.currentTheme}>
|
||||
<NavigationContainer theme={this.state.currentTheme} ref={this.navigatorRef}>
|
||||
<Stack.Navigator headerMode="none">
|
||||
<Stack.Screen name="Root" component={this.createDrawerNavigator}/>
|
||||
</Stack.Navigator>
|
||||
</NavigationContainer>
|
||||
<View style={{
|
||||
flex: 1,
|
||||
backgroundColor: ThemeManager.getCurrentTheme().colors.background
|
||||
}}>
|
||||
<Animatable.View
|
||||
style={{flex: 1,}}
|
||||
animation={"fadeIn"}
|
||||
duration={1000}
|
||||
useNativeDriver
|
||||
onAnimationBegin={() => {
|
||||
// delay the hiding even 1ms is enough to prevent flickering
|
||||
setTimeout(() => SplashScreen.hide(), 1);
|
||||
}}
|
||||
>
|
||||
<NavigationContainer theme={this.state.currentTheme} ref={this.navigatorRef}>
|
||||
<Stack.Navigator headerMode="none">
|
||||
<Stack.Screen name="Root" component={this.createDrawerNavigator}/>
|
||||
</Stack.Navigator>
|
||||
</NavigationContainer>
|
||||
</Animatable.View>
|
||||
</View>
|
||||
</PaperProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,15 +5,16 @@ import ConnectionManager from "../../managers/ConnectionManager";
|
|||
import {ERROR_TYPE} from "../../utils/WebData";
|
||||
import ErrorView from "../Screens/ErrorView";
|
||||
import BasicLoadingScreen from "../Screens/BasicLoadingScreen";
|
||||
import {StackNavigationProp} from "@react-navigation/stack";
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
navigation: StackNavigationProp,
|
||||
requests: Array<{
|
||||
link: string,
|
||||
params: Object,
|
||||
mandatory: boolean
|
||||
}>,
|
||||
renderFunction: Function,
|
||||
renderFunction: (Array<{ [key: string]: any } | null>) => React.Node,
|
||||
}
|
||||
|
||||
type State = {
|
||||
|
|
@ -29,7 +30,7 @@ class AuthenticatedScreen extends React.Component<Props, State> {
|
|||
currentUserToken: string | null;
|
||||
connectionManager: ConnectionManager;
|
||||
errors: Array<number>;
|
||||
fetchedData: Array<Object>;
|
||||
fetchedData: Array<{ [key: string]: any } | null>;
|
||||
|
||||
constructor(props: Object) {
|
||||
super(props);
|
||||
|
|
@ -88,7 +89,7 @@ class AuthenticatedScreen extends React.Component<Props, State> {
|
|||
* @param index The index for the data
|
||||
* @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) {
|
||||
this.fetchedData[index] = data;
|
||||
this.errors[index] = error;
|
||||
|
|
|
|||
|
|
@ -4,17 +4,18 @@ import * as React from 'react';
|
|||
import i18n from 'i18n-js';
|
||||
import LoadingConfirmDialog from "../Dialogs/LoadingConfirmDialog";
|
||||
import ConnectionManager from "../../managers/ConnectionManager";
|
||||
import {StackNavigationProp} from "@react-navigation/stack";
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
navigation: StackNavigationProp,
|
||||
visible: boolean,
|
||||
onDismiss: Function,
|
||||
onDismiss: () => void,
|
||||
}
|
||||
|
||||
class LogoutDialog extends React.PureComponent<Props> {
|
||||
|
||||
onClickAccept = async () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((resolve) => {
|
||||
ConnectionManager.getInstance().disconnect()
|
||||
.then(() => {
|
||||
this.props.navigation.reset({
|
||||
|
|
|
|||
|
|
@ -4,21 +4,23 @@ import * as React from 'react';
|
|||
import {Avatar, Card, List, ProgressBar, Subheading, withTheme} from "react-native-paper";
|
||||
import {FlatList, StyleSheet} from "react-native";
|
||||
import i18n from 'i18n-js';
|
||||
import type {team} from "../../../screens/Amicale/VoteScreen";
|
||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
||||
|
||||
|
||||
type Props = {
|
||||
teams: Array<Object>,
|
||||
teams: Array<team>,
|
||||
dateEnd: string,
|
||||
theme: CustomTheme,
|
||||
}
|
||||
|
||||
class VoteResults extends React.Component<Props> {
|
||||
|
||||
totalVotes: number;
|
||||
winnerIds: Array<number>;
|
||||
colors: Object;
|
||||
|
||||
constructor(props) {
|
||||
super();
|
||||
this.colors = props.theme.colors;
|
||||
props.teams.sort(this.sortByVotes);
|
||||
this.getTotalVotes(props.teams);
|
||||
this.getWinnerIds(props.teams);
|
||||
|
|
@ -28,18 +30,18 @@ class VoteResults extends React.Component<Props> {
|
|||
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;
|
||||
for (let i = 0; i < teams.length; i++) {
|
||||
this.totalVotes += teams[i].votes;
|
||||
}
|
||||
}
|
||||
|
||||
getWinnerIds(teams: Array<Object>){
|
||||
getWinnerIds(teams: Array<team>) {
|
||||
let max = teams[0].votes;
|
||||
this.winnerIds= [];
|
||||
this.winnerIds = [];
|
||||
for (let i = 0; i < teams.length; i++) {
|
||||
if (teams[i].votes === max)
|
||||
this.winnerIds.push(teams[i].id);
|
||||
|
|
@ -48,11 +50,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 isDraw = this.winnerIds.length > 1;
|
||||
const colors = this.props.theme.colors;
|
||||
return (
|
||||
<Card style={{
|
||||
marginTop: 10,
|
||||
|
|
@ -62,16 +65,16 @@ class VoteResults extends React.Component<Props> {
|
|||
title={item.name}
|
||||
description={item.votes + ' ' + i18n.t('voteScreen.results.votes')}
|
||||
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}
|
||||
titleStyle={{
|
||||
color: isWinner
|
||||
? this.colors.primary
|
||||
: this.colors.text
|
||||
? colors.primary
|
||||
: colors.text
|
||||
}}
|
||||
style={{padding: 0}}
|
||||
/>
|
||||
<ProgressBar progress={item.votes / this.totalVotes} color={this.colors.primary}/>
|
||||
<ProgressBar progress={item.votes / this.totalVotes} color={colors.primary}/>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
|
@ -88,7 +91,7 @@ class VoteResults extends React.Component<Props> {
|
|||
/>}
|
||||
/>
|
||||
<Card.Content>
|
||||
<Subheading>{i18n.t('voteScreen.results.totalVotes') + ' ' +this.totalVotes}</Subheading>
|
||||
<Subheading>{i18n.t('voteScreen.results.totalVotes') + ' ' + this.totalVotes}</Subheading>
|
||||
{/*$FlowFixMe*/}
|
||||
<FlatList
|
||||
data={this.props.teams}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,12 @@ import ConnectionManager from "../../../managers/ConnectionManager";
|
|||
import LoadingConfirmDialog from "../../Dialogs/LoadingConfirmDialog";
|
||||
import ErrorDialog from "../../Dialogs/ErrorDialog";
|
||||
import i18n from 'i18n-js';
|
||||
import type {team} from "../../../screens/Amicale/VoteScreen";
|
||||
|
||||
type Props = {
|
||||
teams: Array<Object>,
|
||||
onVoteSuccess: Function,
|
||||
onVoteError: Function,
|
||||
teams: Array<team>,
|
||||
onVoteSuccess: () => void,
|
||||
onVoteError: () => void,
|
||||
}
|
||||
|
||||
type State = {
|
||||
|
|
@ -33,16 +34,16 @@ export default class VoteSelect extends React.PureComponent<Props, State> {
|
|||
|
||||
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});
|
||||
|
||||
onVoteDialogDismiss = () => this.setState({voteDialogVisible: false,});
|
||||
|
||||
onVoteDialogAccept = async () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((resolve) => {
|
||||
ConnectionManager.getInstance().authenticatedRequest(
|
||||
"elections/vote",
|
||||
{"team": parseInt(this.state.selectedTeam)})
|
||||
|
|
@ -76,10 +77,11 @@ export default class VoteSelect extends React.PureComponent<Props, State> {
|
|||
<Card.Title
|
||||
title={i18n.t('voteScreen.select.title')}
|
||||
subtitle={i18n.t('voteScreen.select.subtitle')}
|
||||
left={(props) => <Avatar.Icon
|
||||
{...props}
|
||||
icon={"alert-decagram"}
|
||||
/>}
|
||||
left={(props) =>
|
||||
<Avatar.Icon
|
||||
{...props}
|
||||
icon={"alert-decagram"}
|
||||
/>}
|
||||
/>
|
||||
<Card.Content>
|
||||
<RadioButton.Group
|
||||
|
|
|
|||
|
|
@ -7,9 +7,7 @@ import i18n from 'i18n-js';
|
|||
|
||||
const ICON_AMICALE = require('../../../../assets/amicale.png');
|
||||
|
||||
type Props = {}
|
||||
|
||||
export default class VoteTitle extends React.Component<Props> {
|
||||
export default class VoteTitle extends React.Component<{}> {
|
||||
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -4,28 +4,25 @@ import * as React from 'react';
|
|||
import {ActivityIndicator, Card, Paragraph, withTheme} from "react-native-paper";
|
||||
import {StyleSheet} from "react-native";
|
||||
import i18n from 'i18n-js';
|
||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
||||
|
||||
type Props = {
|
||||
startDate: string | null,
|
||||
justVoted: boolean,
|
||||
hasVoted: boolean,
|
||||
isVoteRunning: boolean,
|
||||
theme: CustomTheme,
|
||||
}
|
||||
|
||||
class VoteWait extends React.Component<Props> {
|
||||
|
||||
colors: Object;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.colors = props.theme.colors;
|
||||
}
|
||||
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
render() {
|
||||
const colors = this.props.theme.colors;
|
||||
const startDate = this.props.startDate;
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
<Card.Title
|
||||
|
|
@ -38,22 +35,22 @@ class VoteWait extends React.Component<Props> {
|
|||
<Card.Content>
|
||||
{
|
||||
this.props.justVoted
|
||||
? <Paragraph style={{color: this.colors.success}}>
|
||||
? <Paragraph style={{color: colors.success}}>
|
||||
{i18n.t('voteScreen.wait.messageSubmitted')}
|
||||
</Paragraph>
|
||||
: null
|
||||
}
|
||||
{
|
||||
this.props.hasVoted
|
||||
? <Paragraph style={{color: this.colors.success}}>
|
||||
? <Paragraph style={{color: colors.success}}>
|
||||
{i18n.t('voteScreen.wait.messageVoted')}
|
||||
</Paragraph>
|
||||
: null
|
||||
}
|
||||
{
|
||||
this.props.startDate !== null
|
||||
startDate != null
|
||||
? <Paragraph>
|
||||
{i18n.t('voteScreen.wait.messageDate') + ' ' + this.props.startDate}
|
||||
{i18n.t('voteScreen.wait.messageDate') + ' ' + startDate}
|
||||
</Paragraph>
|
||||
: <Paragraph>{i18n.t('voteScreen.wait.messageDateUndefined')}</Paragraph>
|
||||
}
|
||||
|
|
|
|||
87
src/components/Animations/AnimatedAccordion.js
Normal file
87
src/components/Animations/AnimatedAccordion.js
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {View} from "react-native";
|
||||
import {List, withTheme} from 'react-native-paper';
|
||||
import Collapsible from "react-native-collapsible";
|
||||
import * as Animatable from "react-native-animatable";
|
||||
import type {CustomTheme} from "../../managers/ThemeManager";
|
||||
|
||||
type Props = {
|
||||
theme: CustomTheme,
|
||||
title: string,
|
||||
subtitle?: string,
|
||||
left?: (props: { [keys: string]: any }) => React.Node,
|
||||
startOpen: boolean,
|
||||
keepOpen: boolean,
|
||||
unmountWhenCollapsed: boolean,
|
||||
children?: React.Node,
|
||||
}
|
||||
|
||||
type State = {
|
||||
expanded: boolean
|
||||
}
|
||||
|
||||
const AnimatedListIcon = Animatable.createAnimatableComponent(List.Icon);
|
||||
|
||||
class AnimatedAccordion extends React.PureComponent<Props, State> {
|
||||
|
||||
static defaultProps = {
|
||||
startOpen: false,
|
||||
keepOpen: false,
|
||||
unmountWhenCollapsed: false,
|
||||
}
|
||||
chevronRef: { current: null | AnimatedListIcon };
|
||||
state = {
|
||||
expanded: false,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.chevronRef = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.startOpen)
|
||||
this.toggleAccordion();
|
||||
}
|
||||
|
||||
toggleAccordion = () => {
|
||||
if (!this.props.keepOpen) {
|
||||
if (this.chevronRef.current != null)
|
||||
this.chevronRef.current.transitionTo({rotate: this.state.expanded ? '0deg' : '180deg'});
|
||||
this.setState({expanded: !this.state.expanded})
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const colors = this.props.theme.colors;
|
||||
return (
|
||||
<View>
|
||||
<List.Item
|
||||
{...this.props}
|
||||
title={this.props.title}
|
||||
subtitle={this.props.subtitle}
|
||||
titleStyle={this.state.expanded ? {color: colors.primary} : undefined}
|
||||
onPress={this.toggleAccordion}
|
||||
right={(props) => <AnimatedListIcon
|
||||
ref={this.chevronRef}
|
||||
{...props}
|
||||
icon={"chevron-down"}
|
||||
color={this.state.expanded ? colors.primary : undefined}
|
||||
useNativeDriver
|
||||
/>}
|
||||
left={this.props.left}
|
||||
/>
|
||||
<Collapsible collapsed={!this.props.keepOpen && !this.state.expanded}>
|
||||
{!this.props.unmountWhenCollapsed || (this.props.unmountWhenCollapsed && this.state.expanded)
|
||||
? this.props.children
|
||||
: null}
|
||||
</Collapsible>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default withTheme(AnimatedAccordion);
|
||||
|
|
@ -6,13 +6,15 @@ import {FAB, IconButton, Surface, withTheme} from "react-native-paper";
|
|||
import AutoHideHandler from "../../utils/AutoHideHandler";
|
||||
import * as Animatable from 'react-native-animatable';
|
||||
import CustomTabBar from "../Tabbar/CustomTabBar";
|
||||
import {StackNavigationProp} from "@react-navigation/stack";
|
||||
import type {CustomTheme} from "../../managers/ThemeManager";
|
||||
|
||||
const AnimatedFAB = Animatable.createAnimatableComponent(FAB);
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
theme: Object,
|
||||
onPress: Function,
|
||||
navigation: StackNavigationProp,
|
||||
theme: CustomTheme,
|
||||
onPress: (action: string, data: any) => void,
|
||||
seekAttention: boolean,
|
||||
}
|
||||
|
||||
|
|
@ -28,10 +30,10 @@ const DISPLAY_MODES = {
|
|||
|
||||
class AnimatedBottomBar extends React.Component<Props, State> {
|
||||
|
||||
ref: Object;
|
||||
ref: { current: null | Animatable.View };
|
||||
hideHandler: AutoHideHandler;
|
||||
|
||||
displayModeIcons: Object;
|
||||
displayModeIcons: { [key: string]: string };
|
||||
|
||||
state = {
|
||||
currentMode: DISPLAY_MODES.WEEK,
|
||||
|
|
@ -55,7 +57,7 @@ class AnimatedBottomBar extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
onHideChange = (shouldHide: boolean) => {
|
||||
if (this.ref.current) {
|
||||
if (this.ref.current != null) {
|
||||
if (shouldHide)
|
||||
this.ref.current.bounceOutDown(1000);
|
||||
else
|
||||
|
|
@ -63,7 +65,7 @@ class AnimatedBottomBar extends React.Component<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
onScroll = (event: Object) => {
|
||||
onScroll = (event: SyntheticEvent<EventTarget>) => {
|
||||
this.hideHandler.onScroll(event);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -6,18 +6,19 @@ import {FAB} from "react-native-paper";
|
|||
import AutoHideHandler from "../../utils/AutoHideHandler";
|
||||
import * as Animatable from 'react-native-animatable';
|
||||
import CustomTabBar from "../Tabbar/CustomTabBar";
|
||||
import {StackNavigationProp} from "@react-navigation/stack";
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
navigation: StackNavigationProp,
|
||||
icon: string,
|
||||
onPress: Function,
|
||||
onPress: () => void,
|
||||
}
|
||||
|
||||
const AnimatedFab = Animatable.createAnimatableComponent(FAB);
|
||||
|
||||
export default class AnimatedFAB extends React.Component<Props> {
|
||||
|
||||
ref: Object;
|
||||
ref: { current: null | Animatable.View };
|
||||
hideHandler: AutoHideHandler;
|
||||
|
||||
constructor() {
|
||||
|
|
@ -27,12 +28,12 @@ export default class AnimatedFAB extends React.Component<Props> {
|
|||
this.hideHandler.addListener(this.onHideChange);
|
||||
}
|
||||
|
||||
onScroll = (event: Object) => {
|
||||
onScroll = (event: SyntheticEvent<EventTarget>) => {
|
||||
this.hideHandler.onScroll(event);
|
||||
};
|
||||
|
||||
onHideChange = (shouldHide: boolean) => {
|
||||
if (this.ref.current) {
|
||||
if (this.ref.current != null) {
|
||||
if (shouldHide)
|
||||
this.ref.current.bounceOutDown(1000);
|
||||
else
|
||||
|
|
|
|||
|
|
@ -3,16 +3,17 @@
|
|||
import * as React from 'react';
|
||||
import * as Animatable from "react-native-animatable";
|
||||
import {CommonActions} from "@react-navigation/native";
|
||||
import {StackNavigationProp} from "@react-navigation/stack";
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
route: Object,
|
||||
children: React$Node
|
||||
navigation: StackNavigationProp,
|
||||
route: { params?: any, ... },
|
||||
children: React.Node
|
||||
}
|
||||
|
||||
export default class AnimatedFocusView extends React.Component<Props> {
|
||||
|
||||
ref: Object;
|
||||
ref: { current: null | Animatable.View };
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
|
@ -24,7 +25,7 @@ export default class AnimatedFocusView extends React.Component<Props> {
|
|||
}
|
||||
|
||||
onScreenFocus = () => {
|
||||
if (this.props.route.params !== undefined) {
|
||||
if (this.props.route.params != null) {
|
||||
if (this.props.route.params.animationDir && this.ref.current) {
|
||||
if (this.props.route.params.animationDir === "right")
|
||||
this.ref.current.fadeInRight(300);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Button, Dialog, Paragraph, Portal} from 'react-native-paper';
|
||||
|
||||
type Props = {
|
||||
visible: boolean,
|
||||
onDismiss: Function,
|
||||
onDismiss: () => void,
|
||||
title: string,
|
||||
message: string,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import i18n from "i18n-js";
|
||||
import {ERROR_TYPE} from "../../utils/WebData";
|
||||
|
|
@ -5,7 +7,7 @@ import AlertDialog from "./AlertDialog";
|
|||
|
||||
type Props = {
|
||||
visible: boolean,
|
||||
onDismiss: Function,
|
||||
onDismiss: () => void,
|
||||
errorCode: number,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {ActivityIndicator, Button, Dialog, Paragraph, Portal} from 'react-native-paper';
|
||||
import i18n from "i18n-js";
|
||||
|
||||
type Props = {
|
||||
visible: boolean,
|
||||
onDismiss: Function,
|
||||
onAccept: Function, // async function to be executed
|
||||
onDismiss: () => void,
|
||||
onAccept: () => Promise<void>, // async function to be executed
|
||||
title: string,
|
||||
titleLoading: string,
|
||||
message: string,
|
||||
|
|
@ -21,18 +23,25 @@ class LoadingConfirmDialog extends React.PureComponent<Props, State> {
|
|||
loading: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the dialog into loading state and closes it when operation finishes
|
||||
*/
|
||||
onClickAccept = () => {
|
||||
this.setState({loading: true});
|
||||
this.props.onAccept()
|
||||
.then(() => {
|
||||
//Wait for fade out animations to finish before hiding loading
|
||||
setTimeout(() => {
|
||||
this.setState({loading: false})
|
||||
}, 200);
|
||||
|
||||
});
|
||||
this.props.onAccept().then(this.hideLoading);
|
||||
};
|
||||
|
||||
/**
|
||||
* Waits for fade out animations to finish before hiding loading
|
||||
* @returns {TimeoutID}
|
||||
*/
|
||||
hideLoading = () => setTimeout(() => {
|
||||
this.setState({loading: false})
|
||||
}, 200);
|
||||
|
||||
/**
|
||||
* Hide the dialog if it is not loading
|
||||
*/
|
||||
onDismiss = () => {
|
||||
if (!this.state.loading)
|
||||
this.props.onDismiss();
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@ import * as React from 'react';
|
|||
import {Button, Card, withTheme} from 'react-native-paper';
|
||||
import {Platform, StyleSheet} from "react-native";
|
||||
import i18n from 'i18n-js';
|
||||
import {DrawerNavigationProp} from "@react-navigation/drawer";
|
||||
import type {CustomTheme} from "../../managers/ThemeManager";
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
theme: Object,
|
||||
navigation: DrawerNavigationProp,
|
||||
theme: CustomTheme,
|
||||
}
|
||||
|
||||
class ActionsDashBoardItem extends React.Component<Props> {
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@ import * as React from 'react';
|
|||
import {Avatar, Card, Text, withTheme} from 'react-native-paper';
|
||||
import {StyleSheet} from "react-native";
|
||||
import i18n from "i18n-js";
|
||||
import type {CustomTheme} from "../../managers/ThemeManager";
|
||||
|
||||
type Props = {
|
||||
eventNumber: number;
|
||||
clickAction: Function,
|
||||
theme: Object,
|
||||
clickAction: () => void,
|
||||
theme: CustomTheme,
|
||||
children?: React.Node
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,15 +1,21 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Avatar, Button, Card, Text} from 'react-native-paper';
|
||||
import {View} from "react-native";
|
||||
import Autolink from "react-native-autolink";
|
||||
import i18n from "i18n-js";
|
||||
import ImageModal from 'react-native-image-modal';
|
||||
import {StackNavigationProp} from "@react-navigation/stack";
|
||||
import type {CustomTheme} from "../../managers/ThemeManager";
|
||||
import type {feedItem} from "../../screens/Home/HomeScreen";
|
||||
|
||||
const ICON_AMICALE = require('../../../assets/amicale.png');
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
theme: Object,
|
||||
navigation: StackNavigationProp,
|
||||
theme: CustomTheme,
|
||||
item: feedItem,
|
||||
title: string,
|
||||
subtitle: string,
|
||||
height: number,
|
||||
|
|
@ -32,17 +38,19 @@ class FeedItem extends React.Component<Props> {
|
|||
*/
|
||||
getAvatar() {
|
||||
return (
|
||||
<Avatar.Image size={48} source={ICON_AMICALE}
|
||||
style={{backgroundColor: 'transparent'}}/>
|
||||
<Avatar.Image
|
||||
size={48} source={ICON_AMICALE}
|
||||
style={{backgroundColor: 'transparent'}}/>
|
||||
);
|
||||
}
|
||||
|
||||
onPress = () => {
|
||||
this.props.navigation.navigate('feed-information',
|
||||
this.props.navigation.navigate(
|
||||
'feed-information',
|
||||
{
|
||||
data: this.props.item,
|
||||
date: this.props.subtitle
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
|
|
|
|||
|
|
@ -6,10 +6,13 @@ import i18n from "i18n-js";
|
|||
import {Avatar, Button, Card} from 'react-native-paper';
|
||||
import {getFormattedEventTime, isDescriptionEmpty} from "../../utils/Planning";
|
||||
import CustomHTML from "../Overrides/CustomHTML";
|
||||
import type {CustomTheme} from "../../managers/ThemeManager";
|
||||
import type {event} from "../../screens/Home/HomeScreen";
|
||||
|
||||
type Props = {
|
||||
event: Object,
|
||||
clickAction: Function,
|
||||
event?: event,
|
||||
clickAction: () => void,
|
||||
theme?: CustomTheme,
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -19,14 +22,15 @@ class PreviewEventDashboardItem extends React.Component<Props> {
|
|||
|
||||
render() {
|
||||
const props = this.props;
|
||||
const isEmpty = props.event === undefined
|
||||
const isEmpty = props.event == null
|
||||
? true
|
||||
: isDescriptionEmpty(props.event['description']);
|
||||
: isDescriptionEmpty(props.event.description);
|
||||
|
||||
if (props.event !== undefined && props.event !== null) {
|
||||
const hasImage = props.event['logo'] !== '' && props.event['logo'] !== null;
|
||||
if (props.event != null) {
|
||||
const event = props.event;
|
||||
const hasImage = event.logo !== '' && event.logo != null;
|
||||
const getImage = () => <Avatar.Image
|
||||
source={{uri: props.event['logo']}}
|
||||
source={{uri: event.logo}}
|
||||
size={50}
|
||||
style={styles.avatar}/>;
|
||||
return (
|
||||
|
|
@ -37,17 +41,17 @@ class PreviewEventDashboardItem extends React.Component<Props> {
|
|||
>
|
||||
{hasImage ?
|
||||
<Card.Title
|
||||
title={props.event['title']}
|
||||
subtitle={getFormattedEventTime(props.event['date_begin'], props.event['date_end'])}
|
||||
title={event.title}
|
||||
subtitle={getFormattedEventTime(event.date_begin, event.date_end)}
|
||||
left={getImage}
|
||||
/> :
|
||||
<Card.Title
|
||||
title={props.event['title']}
|
||||
subtitle={getFormattedEventTime(props.event['date_begin'], props.event['date_end'])}
|
||||
title={event.title}
|
||||
subtitle={getFormattedEventTime(event.date_begin, event.date_end)}
|
||||
/>}
|
||||
{!isEmpty ?
|
||||
<Card.Content style={styles.content}>
|
||||
<CustomHTML html={props.event['description']}/>
|
||||
<CustomHTML html={event.description}/>
|
||||
</Card.Content> : null}
|
||||
|
||||
<Card.Actions style={styles.actions}>
|
||||
|
|
|
|||
|
|
@ -3,15 +3,16 @@
|
|||
import * as React from 'react';
|
||||
import {Badge, IconButton, withTheme} from 'react-native-paper';
|
||||
import {View} from "react-native";
|
||||
import type {CustomTheme} from "../../managers/ThemeManager";
|
||||
|
||||
|
||||
type Props = {
|
||||
color: string,
|
||||
icon: string,
|
||||
clickAction: Function,
|
||||
clickAction: () => void,
|
||||
isAvailable: boolean,
|
||||
badgeNumber: number,
|
||||
theme: Object,
|
||||
theme: CustomTheme,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -4,21 +4,15 @@ import * as React from 'react';
|
|||
import {Card, List, Text, withTheme} from 'react-native-paper';
|
||||
import {StyleSheet, View} from "react-native";
|
||||
import i18n from 'i18n-js';
|
||||
import AnimatedAccordion from "../../Animations/AnimatedAccordion";
|
||||
|
||||
type Props = {
|
||||
categoryRender: Function,
|
||||
categories: Array<Object>,
|
||||
}
|
||||
|
||||
type State = {
|
||||
expanded: boolean,
|
||||
}
|
||||
class ClubListHeader extends React.Component<Props> {
|
||||
|
||||
class ClubListHeader extends React.Component<Props, State> {
|
||||
|
||||
state = {
|
||||
expanded: true
|
||||
};
|
||||
|
||||
colors: Object;
|
||||
|
||||
|
|
@ -35,22 +29,19 @@ class ClubListHeader extends React.Component<Props, State> {
|
|||
return final;
|
||||
}
|
||||
|
||||
onPress = () => this.setState({expanded: !this.state.expanded});
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
<List.Accordion
|
||||
<AnimatedAccordion
|
||||
title={i18n.t("clubs.categories")}
|
||||
left={props => <List.Icon {...props} icon="star"/>}
|
||||
expanded={this.state.expanded}
|
||||
onPress={this.onPress}
|
||||
startOpen={true}
|
||||
>
|
||||
<Text style={styles.text}>{i18n.t("clubs.categoriesFilterMessage")}</Text>
|
||||
<View style={styles.chipContainer}>
|
||||
{this.getCategoriesRender()}
|
||||
</View>
|
||||
</List.Accordion>
|
||||
</AnimatedAccordion>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ import * as React from 'react';
|
|||
import {List, withTheme} from 'react-native-paper';
|
||||
import {FlatList, View} from "react-native";
|
||||
import {stringMatchQuery} from "../../../utils/Search";
|
||||
import Collapsible from "react-native-collapsible";
|
||||
import * as Animatable from "react-native-animatable";
|
||||
import GroupListItem from "./GroupListItem";
|
||||
import AnimatedAccordion from "../../Animations/AnimatedAccordion";
|
||||
|
||||
type Props = {
|
||||
item: Object,
|
||||
|
|
@ -47,11 +47,6 @@ class GroupListAccordion extends React.Component<Props, State> {
|
|||
|| (nextProps.item.content.length !== this.props.item.content.length);
|
||||
}
|
||||
|
||||
onPress = () => {
|
||||
this.chevronRef.current.transitionTo({rotate: this.state.expanded ? '0deg' : '180deg'});
|
||||
this.setState({expanded: !this.state.expanded})
|
||||
};
|
||||
|
||||
keyExtractor = (item: Object) => item.id.toString();
|
||||
|
||||
renderItem = ({item}: Object) => {
|
||||
|
|
@ -73,20 +68,14 @@ class GroupListAccordion extends React.Component<Props, State> {
|
|||
|
||||
render() {
|
||||
const item = this.props.item;
|
||||
const accordionColor = this.state.expanded
|
||||
? this.props.theme.colors.primary
|
||||
: this.props.theme.colors.text;
|
||||
// console.log(item.id);
|
||||
return (
|
||||
<View>
|
||||
<List.Item
|
||||
<AnimatedAccordion
|
||||
title={item.name}
|
||||
onPress={this.onPress}
|
||||
style={{
|
||||
height: this.props.height,
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
titleStyle={{color: accordionColor}}
|
||||
left={props =>
|
||||
item.id === "0"
|
||||
? <List.Icon
|
||||
|
|
@ -95,35 +84,19 @@ class GroupListAccordion extends React.Component<Props, State> {
|
|||
color={this.props.theme.colors.tetrisScore}
|
||||
/>
|
||||
: null}
|
||||
right={(props) => <AnimatedListIcon
|
||||
ref={this.chevronRef}
|
||||
{...props}
|
||||
icon={"chevron-down"}
|
||||
color={this.state.expanded
|
||||
? this.props.theme.colors.primary
|
||||
: props.color
|
||||
}
|
||||
useNativeDriver
|
||||
/>}
|
||||
/>
|
||||
<Collapsible
|
||||
collapsed={!this.state.expanded}
|
||||
ease={"easeInOut"}
|
||||
unmountWhenCollapsed={true}// Only render list if expanded for increased performance
|
||||
>
|
||||
{this.state.expanded // Only render list if expanded for increased performance
|
||||
? <FlatList
|
||||
data={item.content}
|
||||
extraData={this.props.currentSearchString}
|
||||
renderItem={this.renderItem}
|
||||
keyExtractor={this.keyExtractor}
|
||||
listKey={item.id}
|
||||
// Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
|
||||
getItemLayout={this.itemLayout} // Broken with search
|
||||
removeClippedSubviews={true}
|
||||
/>
|
||||
: null}
|
||||
|
||||
</Collapsible>
|
||||
<FlatList
|
||||
data={item.content}
|
||||
extraData={this.props.currentSearchString}
|
||||
renderItem={this.renderItem}
|
||||
keyExtractor={this.keyExtractor}
|
||||
listKey={item.id}
|
||||
// Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
|
||||
getItemLayout={this.itemLayout} // Broken with search
|
||||
removeClippedSubviews={true}
|
||||
/>
|
||||
</AnimatedAccordion>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,26 +10,28 @@ import BasicLoadingScreen from "./BasicLoadingScreen";
|
|||
import {withCollapsible} from "../../utils/withCollapsible";
|
||||
import * as Animatable from 'react-native-animatable';
|
||||
import CustomTabBar from "../Tabbar/CustomTabBar";
|
||||
import {Collapsible} from "react-navigation-collapsible";
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
navigation: { [key: string]: any },
|
||||
fetchUrl: string,
|
||||
autoRefreshTime: number,
|
||||
refreshOnFocus: boolean,
|
||||
renderItem: React.Node,
|
||||
renderSectionHeader: React.Node,
|
||||
stickyHeader: boolean,
|
||||
createDataset: Function,
|
||||
updateData: number,
|
||||
itemHeight: number | null,
|
||||
onScroll: Function,
|
||||
collapsibleStack: Object,
|
||||
renderItem: (data: { [key: string]: any }) => React.Node,
|
||||
createDataset: (data: { [key: string]: any }) => Array<Object>,
|
||||
onScroll: (event: SyntheticEvent<EventTarget>) => void,
|
||||
collapsibleStack: Collapsible,
|
||||
|
||||
itemHeight?: number,
|
||||
updateData?: number,
|
||||
renderSectionHeader?: (data: { [key: string]: any }) => React.Node,
|
||||
stickyHeader?: boolean,
|
||||
}
|
||||
|
||||
type State = {
|
||||
refreshing: boolean,
|
||||
firstLoading: boolean,
|
||||
fetchedData: ?Object,
|
||||
fetchedData: { [key: string]: any } | null,
|
||||
snackbarVisible: boolean
|
||||
};
|
||||
|
||||
|
|
@ -45,37 +47,21 @@ const MIN_REFRESH_TIME = 5 * 1000;
|
|||
class WebSectionList extends React.PureComponent<Props, State> {
|
||||
|
||||
static defaultProps = {
|
||||
renderSectionHeader: null,
|
||||
stickyHeader: false,
|
||||
updateData: 0,
|
||||
itemHeight: null,
|
||||
};
|
||||
|
||||
scrollRef: Object;
|
||||
scrollRef: { current: null | Animated.SectionList };
|
||||
refreshInterval: IntervalID;
|
||||
lastRefresh: Date;
|
||||
lastRefresh: Date | null;
|
||||
|
||||
state = {
|
||||
refreshing: false,
|
||||
firstLoading: true,
|
||||
fetchedData: undefined,
|
||||
fetchedData: null,
|
||||
snackbarVisible: false
|
||||
};
|
||||
|
||||
onRefresh: Function;
|
||||
onFetchSuccess: Function;
|
||||
onFetchError: Function;
|
||||
getEmptySectionHeader: Function;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
// creating references to functions used in render()
|
||||
this.onRefresh = this.onRefresh.bind(this);
|
||||
this.onFetchSuccess = this.onFetchSuccess.bind(this);
|
||||
this.onFetchError = this.onFetchError.bind(this);
|
||||
this.getEmptySectionHeader = this.getEmptySectionHeader.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers react navigation events on first screen load.
|
||||
* Allows to detect when the screen is focused
|
||||
|
|
@ -87,13 +73,14 @@ class WebSectionList extends React.PureComponent<Props, State> {
|
|||
this.props.navigation.addListener('blur', onScreenBlur);
|
||||
this.scrollRef = React.createRef();
|
||||
this.onRefresh();
|
||||
this.lastRefresh = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes data when focusing the screen and setup a refresh interval if asked to
|
||||
*/
|
||||
onScreenFocus() {
|
||||
if (this.props.refreshOnFocus && this.lastRefresh !== undefined)
|
||||
if (this.props.refreshOnFocus && this.lastRefresh)
|
||||
this.onRefresh();
|
||||
if (this.props.autoRefreshTime > 0)
|
||||
this.refreshInterval = setInterval(this.onRefresh, this.props.autoRefreshTime)
|
||||
|
|
@ -115,36 +102,37 @@ class WebSectionList extends React.PureComponent<Props, State> {
|
|||
*
|
||||
* @param fetchedData The newly fetched data
|
||||
*/
|
||||
onFetchSuccess(fetchedData: Object) {
|
||||
onFetchSuccess = (fetchedData: { [key: string]: any }) => {
|
||||
this.setState({
|
||||
fetchedData: fetchedData,
|
||||
refreshing: false,
|
||||
firstLoading: false
|
||||
});
|
||||
this.lastRefresh = new Date();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback used when fetch encountered an error.
|
||||
* It will reset the displayed data and show an error.
|
||||
*/
|
||||
onFetchError() {
|
||||
onFetchError = () => {
|
||||
this.setState({
|
||||
fetchedData: undefined,
|
||||
fetchedData: null,
|
||||
refreshing: false,
|
||||
firstLoading: false
|
||||
});
|
||||
this.showSnackBar();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Refreshes data and shows an animations while doing it
|
||||
*/
|
||||
onRefresh() {
|
||||
onRefresh = () => {
|
||||
let canRefresh;
|
||||
if (this.lastRefresh !== undefined)
|
||||
canRefresh = (new Date().getTime() - this.lastRefresh.getTime()) > MIN_REFRESH_TIME;
|
||||
else
|
||||
if (this.lastRefresh != null) {
|
||||
const last = this.lastRefresh;
|
||||
canRefresh = (new Date().getTime() - last.getTime()) > MIN_REFRESH_TIME;
|
||||
} else
|
||||
canRefresh = true;
|
||||
if (canRefresh) {
|
||||
this.setState({refreshing: true});
|
||||
|
|
@ -152,17 +140,7 @@ class WebSectionList extends React.PureComponent<Props, State> {
|
|||
.then(this.onFetchSuccess)
|
||||
.catch(this.onFetchError);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an empty section header
|
||||
*
|
||||
* @param section The current section
|
||||
* @return {*}
|
||||
*/
|
||||
getEmptySectionHeader({section}: Object) {
|
||||
return <View/>;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows the error popup
|
||||
|
|
@ -174,25 +152,38 @@ class WebSectionList extends React.PureComponent<Props, State> {
|
|||
*/
|
||||
hideSnackBar = () => this.setState({snackbarVisible: false});
|
||||
|
||||
itemLayout = (data: Object, index: number) => ({
|
||||
length: this.props.itemHeight,
|
||||
offset: this.props.itemHeight * index,
|
||||
index
|
||||
});
|
||||
itemLayout = (data: { [key: string]: any }, index: number) => {
|
||||
const height = this.props.itemHeight;
|
||||
if (height == null)
|
||||
return undefined;
|
||||
return {
|
||||
length: height,
|
||||
offset: height * index,
|
||||
index
|
||||
}
|
||||
};
|
||||
|
||||
renderSectionHeader = (data: Object) => {
|
||||
return (
|
||||
<Animatable.View
|
||||
animation={"fadeInUp"}
|
||||
duration={500}
|
||||
useNativeDriver
|
||||
>
|
||||
{this.props.renderSectionHeader(data)}
|
||||
</Animatable.View>
|
||||
);
|
||||
renderSectionHeader = (data: { section: { [key: string]: any } }) => {
|
||||
if (this.props.renderSectionHeader != null) {
|
||||
return (
|
||||
<Animatable.View
|
||||
animation={"fadeInUp"}
|
||||
duration={500}
|
||||
useNativeDriver
|
||||
>
|
||||
{this.props.renderSectionHeader(data)}
|
||||
</Animatable.View>
|
||||
);
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
||||
renderItem = (data: Object) => {
|
||||
renderItem = (data: {
|
||||
item: { [key: string]: any },
|
||||
index: number,
|
||||
section: { [key: string]: any },
|
||||
separators: { [key: string]: any },
|
||||
}) => {
|
||||
return (
|
||||
<Animatable.View
|
||||
animation={"fadeInUp"}
|
||||
|
|
@ -204,20 +195,18 @@ class WebSectionList extends React.PureComponent<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
onScroll = (event: Object) => {
|
||||
onScroll = (event: SyntheticEvent<EventTarget>) => {
|
||||
if (this.props.onScroll)
|
||||
this.props.onScroll(event);
|
||||
}
|
||||
|
||||
render() {
|
||||
let dataset = [];
|
||||
if (this.state.fetchedData !== undefined)
|
||||
if (this.state.fetchedData != null)
|
||||
dataset = this.props.createDataset(this.state.fetchedData);
|
||||
const shouldRenderHeader = this.props.renderSectionHeader !== null;
|
||||
const {containerPaddingTop, scrollIndicatorInsetTop, onScrollWithListener} = this.props.collapsibleStack;
|
||||
return (
|
||||
<View>
|
||||
{/*$FlowFixMe*/}
|
||||
<Animated.SectionList
|
||||
ref={this.scrollRef}
|
||||
sections={dataset}
|
||||
|
|
@ -229,9 +218,7 @@ class WebSectionList extends React.PureComponent<Props, State> {
|
|||
onRefresh={this.onRefresh}
|
||||
/>
|
||||
}
|
||||
//$FlowFixMe
|
||||
renderSectionHeader={shouldRenderHeader ? this.renderSectionHeader : this.getEmptySectionHeader}
|
||||
//$FlowFixMe
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
renderItem={this.renderItem}
|
||||
stickySectionHeadersEnabled={this.props.stickyHeader}
|
||||
style={{minHeight: '100%'}}
|
||||
|
|
@ -242,7 +229,7 @@ class WebSectionList extends React.PureComponent<Props, State> {
|
|||
errorCode={ERROR_TYPE.CONNECTION_ERROR}
|
||||
onRefresh={this.onRefresh}/>
|
||||
}
|
||||
getItemLayout={this.props.itemHeight !== null ? this.itemLayout : undefined}
|
||||
getItemLayout={this.props.itemHeight != null ? this.itemLayout : undefined}
|
||||
// Animations
|
||||
onScroll={onScrollWithListener(this.onScroll)}
|
||||
contentContainerStyle={{
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {FlatList, View} from "react-native";
|
||||
import {Drawer, List, withTheme} from 'react-native-paper';
|
||||
import {FlatList} from "react-native";
|
||||
import {Drawer, withTheme} from 'react-native-paper';
|
||||
import {Linking} from "expo";
|
||||
import Collapsible from "react-native-collapsible";
|
||||
import * as Animatable from "react-native-animatable";
|
||||
import AnimatedAccordion from "../Animations/AnimatedAccordion";
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
|
|
@ -17,28 +16,17 @@ type Props = {
|
|||
listData: Array<Object>,
|
||||
}
|
||||
|
||||
type State = {
|
||||
expanded: boolean
|
||||
}
|
||||
|
||||
const AnimatedListIcon = Animatable.createAnimatableComponent(List.Icon);
|
||||
|
||||
const LIST_ITEM_HEIGHT = 48;
|
||||
|
||||
class SideBarSection extends React.PureComponent<Props, State> {
|
||||
|
||||
state = {
|
||||
expanded: this.props.startOpen,
|
||||
};
|
||||
class SideBarSection extends React.PureComponent<Props> {
|
||||
|
||||
colors: Object;
|
||||
shouldExpand: boolean;
|
||||
chevronRef: Object;
|
||||
accordionRef: {current: null | AnimatedAccordion};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.colors = props.theme.colors;
|
||||
this.chevronRef = React.createRef();
|
||||
this.accordionRef = React.createRef();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -50,7 +38,7 @@ class SideBarSection extends React.PureComponent<Props, State> {
|
|||
shouldExpandList() {
|
||||
for (let i = 0; i < this.props.listData.length; i++) {
|
||||
if (this.props.listData[i].route === this.props.activeRoute) {
|
||||
return this.state.expanded === false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
|
@ -109,13 +97,6 @@ class SideBarSection extends React.PureComponent<Props, State> {
|
|||
);
|
||||
};
|
||||
|
||||
toggleAccordion = () => {
|
||||
if ((!this.state.expanded && this.shouldExpand) || !this.shouldExpand) {
|
||||
this.chevronRef.current.transitionTo({ rotate: this.state.expanded ? '0deg' : '180deg' });
|
||||
this.setState({expanded: !this.state.expanded})
|
||||
}
|
||||
};
|
||||
|
||||
shouldRenderAccordion() {
|
||||
let itemsToRender = 0;
|
||||
for (let i = 0; i < this.props.listData.length; i++) {
|
||||
|
|
@ -144,26 +125,13 @@ class SideBarSection extends React.PureComponent<Props, State> {
|
|||
|
||||
render() {
|
||||
if (this.shouldRenderAccordion()) {
|
||||
this.shouldExpand = this.shouldExpandList();
|
||||
if (this.shouldExpand)
|
||||
this.toggleAccordion();
|
||||
return (
|
||||
<View>
|
||||
<List.Item
|
||||
title={this.props.sectionName}
|
||||
// expanded={this.state.expanded}
|
||||
onPress={this.toggleAccordion}
|
||||
right={(props) => <AnimatedListIcon
|
||||
ref={this.chevronRef}
|
||||
{...props}
|
||||
icon={"chevron-down"}
|
||||
useNativeDriver
|
||||
/>}
|
||||
/>
|
||||
<Collapsible collapsed={!this.state.expanded}>
|
||||
{this.getFlatList()}
|
||||
</Collapsible>
|
||||
</View>
|
||||
<AnimatedAccordion
|
||||
title={this.props.sectionName}
|
||||
keepOpen={this.shouldExpandList()}
|
||||
>
|
||||
{this.getFlatList()}
|
||||
</AnimatedAccordion>
|
||||
);
|
||||
} else
|
||||
return this.getFlatList();
|
||||
|
|
|
|||
|
|
@ -12,8 +12,7 @@ const deviceWidth = Dimensions.get("window").width;
|
|||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
state: Object,
|
||||
theme: Object,
|
||||
theme?: Object,
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import * as React from 'react';
|
|||
import {withTheme} from 'react-native-paper';
|
||||
import TabIcon from "./TabIcon";
|
||||
import TabHomeIcon from "./TabHomeIcon";
|
||||
import {AnimatedValue} from "react-native-reanimated";
|
||||
import {Animated} from 'react-native';
|
||||
|
||||
type Props = {
|
||||
|
|
@ -17,6 +16,13 @@ type State = {
|
|||
translateY: AnimatedValue,
|
||||
}
|
||||
|
||||
const TAB_ICONS = {
|
||||
planning: 'calendar-range',
|
||||
proxiwash: 'tshirt-crew',
|
||||
proximo: 'cart',
|
||||
planex: 'clock',
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstraction layer for Agenda component, using custom configuration
|
||||
*/
|
||||
|
|
@ -25,6 +31,8 @@ class CustomTabBar extends React.Component<Props, State> {
|
|||
static TAB_BAR_HEIGHT = 48;
|
||||
|
||||
barSynced: boolean; // Is the bar synced with the header for animations?
|
||||
activeColor: string;
|
||||
inactiveColor: string;
|
||||
|
||||
state = {
|
||||
translateY: new Animated.Value(0),
|
||||
|
|
@ -37,10 +45,12 @@ class CustomTabBar extends React.Component<Props, State> {
|
|||
|
||||
tabRef: Object;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.tabRef = React.createRef();
|
||||
this.barSynced = false;
|
||||
this.activeColor = props.theme.colors.primary;
|
||||
this.inactiveColor = props.theme.colors.tabIcon;
|
||||
}
|
||||
|
||||
onItemPress(route: Object, currentIndex: number, destIndex: number) {
|
||||
|
|
@ -58,14 +68,73 @@ class CustomTabBar extends React.Component<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
tabBarIcon = (route, focused) => {
|
||||
let icon = TAB_ICONS[route.name];
|
||||
icon = focused ? icon : icon + ('-outline');
|
||||
if (route.name !== "home")
|
||||
return icon;
|
||||
else
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
onRouteChange = () => {
|
||||
this.barSynced = false;
|
||||
}
|
||||
|
||||
render() {
|
||||
renderIcon = (route, index) => {
|
||||
const state = this.props.state;
|
||||
const descriptors = this.props.descriptors;
|
||||
const navigation = this.props.navigation;
|
||||
const {options} = this.props.descriptors[route.key];
|
||||
const label =
|
||||
options.tabBarLabel !== undefined
|
||||
? options.tabBarLabel
|
||||
: options.title !== undefined
|
||||
? options.title
|
||||
: route.name;
|
||||
|
||||
const isFocused = state.index === index;
|
||||
|
||||
const onPress = () => this.onItemPress(route, state.index, index);
|
||||
|
||||
const onLongPress = () => {
|
||||
this.props.navigation.emit({
|
||||
type: 'tabLongPress',
|
||||
target: route.key,
|
||||
});
|
||||
};
|
||||
if (isFocused) {
|
||||
const stackState = route.state;
|
||||
const stackRoute = route.state ? stackState.routes[stackState.index] : undefined;
|
||||
const params = stackRoute ? stackRoute.params : undefined;
|
||||
const collapsible = params ? params.collapsible : undefined;
|
||||
if (collapsible && !this.barSynced) {
|
||||
this.barSynced = true;
|
||||
this.setState({translateY: Animated.multiply(-1.5, collapsible.translateY)});
|
||||
}
|
||||
}
|
||||
|
||||
const color = isFocused ? this.activeColor : this.inactiveColor;
|
||||
if (route.name !== "home") {
|
||||
return <TabIcon
|
||||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
icon={this.tabBarIcon(route, isFocused)}
|
||||
color={color}
|
||||
label={label}
|
||||
focused={isFocused}
|
||||
extraData={state.index > index}
|
||||
key={route.key}
|
||||
/>
|
||||
} else
|
||||
return <TabHomeIcon
|
||||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
focused={isFocused}
|
||||
key={route.key}
|
||||
/>
|
||||
};
|
||||
|
||||
render() {
|
||||
this.props.navigation.addListener('state', this.onRouteChange);
|
||||
return (
|
||||
<Animated.View
|
||||
|
|
@ -84,57 +153,7 @@ class CustomTabBar extends React.Component<Props, State> {
|
|||
transform: [{translateY: this.state.translateY}]
|
||||
}}
|
||||
>
|
||||
{state.routes.map((route, index) => {
|
||||
const {options} = descriptors[route.key];
|
||||
const label =
|
||||
options.tabBarLabel !== undefined
|
||||
? options.tabBarLabel
|
||||
: options.title !== undefined
|
||||
? options.title
|
||||
: route.name;
|
||||
|
||||
const isFocused = state.index === index;
|
||||
|
||||
const onPress = () => this.onItemPress(route, state.index, index);
|
||||
|
||||
const onLongPress = () => {
|
||||
navigation.emit({
|
||||
type: 'tabLongPress',
|
||||
target: route.key,
|
||||
});
|
||||
};
|
||||
if (isFocused) {
|
||||
const stackState = route.state;
|
||||
const stackRoute = route.state ? stackState.routes[stackState.index] : undefined;
|
||||
const params = stackRoute ? stackRoute.params : undefined;
|
||||
const collapsible = params ? params.collapsible : undefined;
|
||||
if (collapsible && !this.barSynced) {
|
||||
this.barSynced = true;
|
||||
this.setState({translateY: Animated.multiply(-1.5, collapsible.translateY)});
|
||||
}
|
||||
}
|
||||
|
||||
const color = isFocused ? options.activeColor : options.inactiveColor;
|
||||
const iconData = {focused: isFocused, color: color};
|
||||
if (route.name !== "home") {
|
||||
return <TabIcon
|
||||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
icon={options.tabBarIcon(iconData)}
|
||||
color={color}
|
||||
label={label}
|
||||
focused={isFocused}
|
||||
extraData={state.index > index}
|
||||
key={route.key}
|
||||
/>
|
||||
} else
|
||||
return <TabHomeIcon
|
||||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
focused={isFocused}
|
||||
key={route.key}
|
||||
/>
|
||||
})}
|
||||
{this.props.state.routes.map(this.renderIcon)}
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,61 @@
|
|||
// @flow
|
||||
|
||||
import AsyncStorageManager from "./AsyncStorageManager";
|
||||
import {DarkTheme, DefaultTheme} from 'react-native-paper';
|
||||
import {DarkTheme, DefaultTheme, Theme} from 'react-native-paper';
|
||||
import AprilFoolsManager from "./AprilFoolsManager";
|
||||
import {Appearance} from 'react-native-appearance';
|
||||
|
||||
const colorScheme = Appearance.getColorScheme();
|
||||
|
||||
export type CustomTheme = {
|
||||
...Theme,
|
||||
colors: {
|
||||
primary: string,
|
||||
accent: string,
|
||||
tabIcon: string,
|
||||
card: string,
|
||||
dividerBackground: string,
|
||||
ripple: string,
|
||||
textDisabled: string,
|
||||
icon: string,
|
||||
subtitle: string,
|
||||
success: string,
|
||||
warning: string,
|
||||
danger: string,
|
||||
|
||||
// Calendar/Agenda
|
||||
agendaBackgroundColor: string,
|
||||
agendaDayTextColor: string,
|
||||
|
||||
// PROXIWASH
|
||||
proxiwashFinishedColor: string,
|
||||
proxiwashReadyColor: string,
|
||||
proxiwashRunningColor: string,
|
||||
proxiwashRunningBgColor: string,
|
||||
proxiwashBrokenColor: string,
|
||||
proxiwashErrorColor: string,
|
||||
|
||||
// Screens
|
||||
planningColor: string,
|
||||
proximoColor: string,
|
||||
proxiwashColor: string,
|
||||
menuColor: string,
|
||||
tutorinsaColor: string,
|
||||
|
||||
// Tetris
|
||||
tetrisBackground: string,
|
||||
tetrisBorder:string,
|
||||
tetrisScore:string,
|
||||
tetrisI : string,
|
||||
tetrisO : string,
|
||||
tetrisT : string,
|
||||
tetrisS : string,
|
||||
tetrisZ : string,
|
||||
tetrisJ : string,
|
||||
tetrisL : string,
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Singleton class used to manage themes
|
||||
*/
|
||||
|
|
@ -22,9 +71,9 @@ export default class ThemeManager {
|
|||
/**
|
||||
* Gets the light theme
|
||||
*
|
||||
* @return {Object} Object containing theme variables
|
||||
* @return {CustomTheme} Object containing theme variables
|
||||
* */
|
||||
static getWhiteTheme(): Object {
|
||||
static getWhiteTheme(): CustomTheme {
|
||||
return {
|
||||
...DefaultTheme,
|
||||
colors: {
|
||||
|
|
@ -41,6 +90,7 @@ export default class ThemeManager {
|
|||
success: "#5cb85c",
|
||||
warning: "#f0ad4e",
|
||||
danger: "#d9534f",
|
||||
cc: 'dst',
|
||||
|
||||
// Calendar/Agenda
|
||||
agendaBackgroundColor: '#f3f3f4',
|
||||
|
|
@ -79,9 +129,9 @@ export default class ThemeManager {
|
|||
/**
|
||||
* Gets the dark theme
|
||||
*
|
||||
* @return {Object} Object containing theme variables
|
||||
* @return {CustomTheme} Object containing theme variables
|
||||
* */
|
||||
static getDarkTheme(): Object {
|
||||
static getDarkTheme(): CustomTheme {
|
||||
return {
|
||||
...DarkTheme,
|
||||
colors: {
|
||||
|
|
@ -162,9 +212,9 @@ export default class ThemeManager {
|
|||
/**
|
||||
* Get the current theme based on night mode and events
|
||||
*
|
||||
* @returns {Object} The current theme
|
||||
* @returns {CustomTheme} The current theme
|
||||
*/
|
||||
static getCurrentTheme(): Object {
|
||||
static getCurrentTheme(): CustomTheme {
|
||||
if (AprilFoolsManager.getInstance().isAprilFoolsEnabled())
|
||||
return AprilFoolsManager.getAprilFoolsTheme(ThemeManager.getWhiteTheme());
|
||||
else
|
||||
|
|
@ -174,9 +224,9 @@ export default class ThemeManager {
|
|||
/**
|
||||
* Get the theme based on night mode
|
||||
*
|
||||
* @return {Object} The theme
|
||||
* @return {CustomTheme} The theme
|
||||
*/
|
||||
static getBaseTheme() {
|
||||
static getBaseTheme(): CustomTheme {
|
||||
if (ThemeManager.getNightMode())
|
||||
return ThemeManager.getDarkTheme();
|
||||
else
|
||||
|
|
@ -188,7 +238,7 @@ export default class ThemeManager {
|
|||
*
|
||||
* @param callback Function to call after theme change
|
||||
*/
|
||||
setUpdateThemeCallback(callback: ?Function) {
|
||||
setUpdateThemeCallback(callback: () => void) {
|
||||
this.updateThemeCallback = callback;
|
||||
}
|
||||
|
||||
|
|
@ -200,7 +250,7 @@ export default class ThemeManager {
|
|||
setNightMode(isNightMode: boolean) {
|
||||
let nightModeKey = AsyncStorageManager.getInstance().preferences.nightMode.key;
|
||||
AsyncStorageManager.getInstance().savePref(nightModeKey, isNightMode ? '1' : '0');
|
||||
if (this.updateThemeCallback !== null)
|
||||
if (this.updateThemeCallback != null)
|
||||
this.updateThemeCallback();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {createDrawerNavigator} from '@react-navigation/drawer';
|
||||
import {createDrawerNavigator, DrawerNavigationProp} from '@react-navigation/drawer';
|
||||
import TabNavigator from './MainTabNavigator';
|
||||
import SettingsScreen from '../screens/Other/SettingsScreen';
|
||||
import AboutScreen from '../screens/About/AboutScreen';
|
||||
|
|
@ -35,7 +35,7 @@ const defaultScreenOptions = {
|
|||
...TransitionPresets.SlideFromRightIOS,
|
||||
};
|
||||
|
||||
function getDrawerButton(navigation: Object) {
|
||||
function getDrawerButton(navigation: DrawerNavigationProp) {
|
||||
return (
|
||||
<MaterialHeaderButtons left={true}>
|
||||
<Item title="menu" iconName="menu" onPress={navigation.openDrawer}/>
|
||||
|
|
@ -415,11 +415,9 @@ function ProfileStackComponent() {
|
|||
<ClubStack.Screen
|
||||
name="club-information"
|
||||
component={ClubDisplayScreen}
|
||||
options={({navigation}) => {
|
||||
return {
|
||||
title: i18n.t('screens.clubDisplayScreen'),
|
||||
...TransitionPresets.ModalSlideFromBottomIOS,
|
||||
};
|
||||
options={{
|
||||
title: i18n.t('screens.clubDisplayScreen'),
|
||||
...TransitionPresets.ModalSlideFromBottomIOS,
|
||||
}}
|
||||
/>
|
||||
</ProfileStack.Navigator>
|
||||
|
|
@ -509,21 +507,17 @@ function ClubStackComponent() {
|
|||
<ClubStack.Screen
|
||||
name="club-information"
|
||||
component={ClubDisplayScreen}
|
||||
options={({navigation}) => {
|
||||
return {
|
||||
title: i18n.t('screens.clubDisplayScreen'),
|
||||
...TransitionPresets.ModalSlideFromBottomIOS,
|
||||
};
|
||||
options={{
|
||||
title: i18n.t('screens.clubDisplayScreen'),
|
||||
...TransitionPresets.ModalSlideFromBottomIOS,
|
||||
}}
|
||||
/>
|
||||
<ClubStack.Screen
|
||||
name="club-about"
|
||||
component={ClubAboutScreen}
|
||||
options={({navigation}) => {
|
||||
return {
|
||||
title: i18n.t('screens.clubsAbout'),
|
||||
...TransitionPresets.ModalSlideFromBottomIOS,
|
||||
};
|
||||
options={{
|
||||
title: i18n.t('screens.clubsAbout'),
|
||||
...TransitionPresets.ModalSlideFromBottomIOS,
|
||||
}}
|
||||
/>
|
||||
</ClubStack.Navigator>
|
||||
|
|
@ -533,27 +527,22 @@ function ClubStackComponent() {
|
|||
|
||||
const Drawer = createDrawerNavigator();
|
||||
|
||||
function getDrawerContent(props) {
|
||||
return <Sidebar {...props}/>
|
||||
}
|
||||
|
||||
type Props = {
|
||||
defaultRoute: string | null,
|
||||
defaultData: Object
|
||||
defaultHomeRoute: string | null,
|
||||
defaultHomeData: { [key: string]: any }
|
||||
}
|
||||
|
||||
|
||||
export default class DrawerNavigator extends React.Component<Props> {
|
||||
|
||||
createTabNavigator: Object;
|
||||
createTabNavigator: () => React.Element<TabNavigator>;
|
||||
|
||||
constructor(props: Object) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.createTabNavigator = () => <TabNavigator defaultRoute={props.defaultRoute}
|
||||
defaultData={props.defaultData}/>
|
||||
this.createTabNavigator = () => <TabNavigator {...props}/>
|
||||
}
|
||||
|
||||
getDrawerContent = (props: { navigation: DrawerNavigationProp }) => <Sidebar {...props}/>
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Drawer.Navigator
|
||||
|
|
@ -561,7 +550,7 @@ export default class DrawerNavigator extends React.Component<Props> {
|
|||
headerMode={'float'}
|
||||
backBehavior={'initialRoute'}
|
||||
drawerType={'front'}
|
||||
drawerContent={(props) => getDrawerContent(props)}
|
||||
drawerContent={this.getDrawerContent}
|
||||
screenOptions={defaultScreenOptions}
|
||||
>
|
||||
<Drawer.Screen
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import ProximoListScreen from "../screens/Proximo/ProximoListScreen";
|
|||
import ProximoAboutScreen from "../screens/Proximo/ProximoAboutScreen";
|
||||
import PlanexScreen from '../screens/Planex/PlanexScreen';
|
||||
import AsyncStorageManager from "../managers/AsyncStorageManager";
|
||||
import {useTheme, withTheme} from 'react-native-paper';
|
||||
import {useTheme} from 'react-native-paper';
|
||||
import i18n from "i18n-js";
|
||||
import ClubDisplayScreen from "../screens/Amicale/Clubs/ClubDisplayScreen";
|
||||
import ScannerScreen from "../screens/Home/ScannerScreen";
|
||||
|
|
@ -21,14 +21,7 @@ import FeedItemScreen from "../screens/Home/FeedItemScreen";
|
|||
import {createCollapsibleStack} from "react-navigation-collapsible";
|
||||
import GroupSelectionScreen from "../screens/Planex/GroupSelectionScreen";
|
||||
import CustomTabBar from "../components/Tabbar/CustomTabBar";
|
||||
|
||||
const TAB_ICONS = {
|
||||
home: 'triangle',
|
||||
planning: 'calendar-range',
|
||||
proxiwash: 'tshirt-crew',
|
||||
proximo: 'cart',
|
||||
planex: 'clock',
|
||||
};
|
||||
import {DrawerNavigationProp} from "@react-navigation/drawer";
|
||||
|
||||
const defaultScreenOptions = {
|
||||
gestureEnabled: true,
|
||||
|
|
@ -36,7 +29,7 @@ const defaultScreenOptions = {
|
|||
...TransitionPresets.SlideFromRightIOS,
|
||||
};
|
||||
|
||||
function getDrawerButton(navigation: Object) {
|
||||
function getDrawerButton(navigation: DrawerNavigationProp) {
|
||||
return (
|
||||
<MaterialHeaderButtons left={true}>
|
||||
<Item title="menu" iconName="menu" onPress={navigation.openDrawer}/>
|
||||
|
|
@ -147,7 +140,6 @@ function ProxiwashStackComponent() {
|
|||
const PlanningStack = createStackNavigator();
|
||||
|
||||
function PlanningStackComponent() {
|
||||
const {colors} = useTheme();
|
||||
return (
|
||||
<PlanningStack.Navigator
|
||||
initialRouteName="index"
|
||||
|
|
@ -179,10 +171,10 @@ function PlanningStackComponent() {
|
|||
|
||||
const HomeStack = createStackNavigator();
|
||||
|
||||
function HomeStackComponent(initialRoute: string | null, defaultData: Object) {
|
||||
let data;
|
||||
if (initialRoute !== null)
|
||||
data = {data: defaultData, nextScreen: initialRoute, shouldOpen: true};
|
||||
function HomeStackComponent(initialRoute: string | null, defaultData: { [key: string]: any }) {
|
||||
let params = undefined;
|
||||
if (initialRoute != null)
|
||||
params = {data: defaultData, nextScreen: initialRoute, shouldOpen: true};
|
||||
const {colors} = useTheme();
|
||||
return (
|
||||
<HomeStack.Navigator
|
||||
|
|
@ -204,7 +196,7 @@ function HomeStackComponent(initialRoute: string | null, defaultData: Object) {
|
|||
},
|
||||
};
|
||||
}}
|
||||
initialParams={data}
|
||||
initialParams={params}
|
||||
/>,
|
||||
{
|
||||
collapsedColor: 'transparent',
|
||||
|
|
@ -222,31 +214,25 @@ function HomeStackComponent(initialRoute: string | null, defaultData: Object) {
|
|||
<HomeStack.Screen
|
||||
name="home-club-information"
|
||||
component={ClubDisplayScreen}
|
||||
options={({navigation}) => {
|
||||
return {
|
||||
title: i18n.t('screens.clubDisplayScreen'),
|
||||
...TransitionPresets.ModalSlideFromBottomIOS,
|
||||
};
|
||||
options={{
|
||||
title: i18n.t('screens.clubDisplayScreen'),
|
||||
...TransitionPresets.ModalSlideFromBottomIOS,
|
||||
}}
|
||||
/>
|
||||
<HomeStack.Screen
|
||||
name="feed-information"
|
||||
component={FeedItemScreen}
|
||||
options={({navigation}) => {
|
||||
return {
|
||||
title: i18n.t('screens.feedDisplayScreen'),
|
||||
...TransitionPresets.ModalSlideFromBottomIOS,
|
||||
};
|
||||
options={{
|
||||
title: i18n.t('screens.feedDisplayScreen'),
|
||||
...TransitionPresets.ModalSlideFromBottomIOS,
|
||||
}}
|
||||
/>
|
||||
<HomeStack.Screen
|
||||
name="scanner"
|
||||
component={ScannerScreen}
|
||||
options={({navigation}) => {
|
||||
return {
|
||||
title: i18n.t('screens.scanner'),
|
||||
...TransitionPresets.ModalSlideFromBottomIOS,
|
||||
};
|
||||
options={{
|
||||
title: i18n.t('screens.scanner'),
|
||||
...TransitionPresets.ModalSlideFromBottomIOS,
|
||||
}}
|
||||
/>
|
||||
</HomeStack.Navigator>
|
||||
|
|
@ -287,14 +273,12 @@ function PlanexStackComponent() {
|
|||
<PlanexStack.Screen
|
||||
name="group-select"
|
||||
component={GroupSelectionScreen}
|
||||
options={({navigation}) => {
|
||||
return {
|
||||
title: 'GroupSelectionScreen',
|
||||
headerStyle: {
|
||||
backgroundColor: colors.surface,
|
||||
},
|
||||
...TransitionPresets.ModalSlideFromBottomIOS,
|
||||
};
|
||||
options={{
|
||||
title: 'GroupSelectionScreen',
|
||||
headerStyle: {
|
||||
backgroundColor: colors.surface,
|
||||
},
|
||||
...TransitionPresets.ModalSlideFromBottomIOS,
|
||||
}}
|
||||
/>,
|
||||
{
|
||||
|
|
@ -309,44 +293,28 @@ function PlanexStackComponent() {
|
|||
const Tab = createBottomTabNavigator();
|
||||
|
||||
type Props = {
|
||||
defaultRoute: string | null,
|
||||
defaultData: Object
|
||||
defaultHomeRoute: string | null,
|
||||
defaultHomeData: { [key: string]: any }
|
||||
}
|
||||
|
||||
class TabNavigator extends React.Component<Props> {
|
||||
|
||||
createHomeStackComponent: Object;
|
||||
export default class TabNavigator extends React.Component<Props> {
|
||||
|
||||
createHomeStackComponent: () => HomeStackComponent;
|
||||
defaultRoute: string;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.defaultRoute = AsyncStorageManager.getInstance().preferences.defaultStartScreen.current.toLowerCase();
|
||||
|
||||
if (props.defaultRoute !== null)
|
||||
if (props.defaultHomeRoute != null)
|
||||
this.defaultRoute = 'home';
|
||||
|
||||
this.createHomeStackComponent = () => HomeStackComponent(props.defaultRoute, props.defaultData);
|
||||
else
|
||||
this.defaultRoute = AsyncStorageManager.getInstance().preferences.defaultStartScreen.current.toLowerCase();
|
||||
this.createHomeStackComponent = () => HomeStackComponent(props.defaultHomeRoute, props.defaultHomeData);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Tab.Navigator
|
||||
initialRouteName={this.defaultRoute}
|
||||
barStyle={{backgroundColor: this.props.theme.colors.surface, overflow: 'visible'}}
|
||||
screenOptions={({route}) => ({
|
||||
tabBarIcon: ({focused, color}) => {
|
||||
let icon = TAB_ICONS[route.name];
|
||||
icon = focused ? icon : icon + ('-outline');
|
||||
if (route.name !== "home")
|
||||
return icon;
|
||||
else
|
||||
return null;
|
||||
},
|
||||
tabBarLabel: route.name !== 'home' ? undefined : '',
|
||||
activeColor: this.props.theme.colors.primary,
|
||||
inactiveColor: this.props.theme.colors.tabIcon,
|
||||
})}
|
||||
tabBar={props => <CustomTabBar {...props} />}
|
||||
>
|
||||
<Tab.Screen
|
||||
|
|
@ -379,5 +347,3 @@ class TabNavigator extends React.Component<Props> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(TabNavigator);
|
||||
|
|
|
|||
|
|
@ -10,53 +10,78 @@ import VoteSelect from "../../components/Amicale/Vote/VoteSelect";
|
|||
import VoteResults from "../../components/Amicale/Vote/VoteResults";
|
||||
import VoteWait from "../../components/Amicale/Vote/VoteWait";
|
||||
|
||||
const FAKE_DATE = {
|
||||
"date_begin": "2020-04-09 15:50",
|
||||
"date_end": "2020-04-09 15:50",
|
||||
"date_result_begin": "2020-04-09 15:50",
|
||||
"date_result_end": "2020-04-09 22:50",
|
||||
export type team = {
|
||||
id: number,
|
||||
name: string,
|
||||
votes: number,
|
||||
}
|
||||
|
||||
type teamResponse = {
|
||||
has_voted: boolean,
|
||||
teams: Array<team>,
|
||||
};
|
||||
|
||||
const FAKE_DATE2 = {
|
||||
"date_begin": null,
|
||||
"date_end": null,
|
||||
"date_result_begin": null,
|
||||
"date_result_end": null,
|
||||
};
|
||||
type stringVoteDates = {
|
||||
date_begin: string,
|
||||
date_end: string,
|
||||
date_result_begin: string,
|
||||
date_result_end: string,
|
||||
}
|
||||
|
||||
const FAKE_TEAMS = {
|
||||
has_voted: false,
|
||||
teams: [
|
||||
{
|
||||
id: 1,
|
||||
name: "TEST TEAM1",
|
||||
},
|
||||
{
|
||||
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,
|
||||
},
|
||||
],
|
||||
};
|
||||
type objectVoteDates = {
|
||||
date_begin: Date,
|
||||
date_end: Date,
|
||||
date_result_begin: Date,
|
||||
date_result_end: Date,
|
||||
}
|
||||
|
||||
// const FAKE_DATE = {
|
||||
// "date_begin": "2020-04-19 15:50",
|
||||
// "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_DATE2 = {
|
||||
// "date_begin": null,
|
||||
// "date_end": null,
|
||||
// "date_result_begin": null,
|
||||
// "date_result_end": null,
|
||||
// };
|
||||
//
|
||||
// const FAKE_TEAMS = {
|
||||
// has_voted: false,
|
||||
// teams: [
|
||||
// {
|
||||
// id: 1,
|
||||
// name: "TEST TEAM1",
|
||||
// },
|
||||
// {
|
||||
// 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;
|
||||
|
||||
|
|
@ -74,17 +99,17 @@ export default class VoteScreen extends React.Component<Props, State> {
|
|||
hasVoted: false,
|
||||
};
|
||||
|
||||
teams: Array<Object>;
|
||||
teams: Array<team>;
|
||||
hasVoted: boolean;
|
||||
datesString: Object;
|
||||
dates: Object;
|
||||
datesString: null | stringVoteDates;
|
||||
dates: null | objectVoteDates;
|
||||
|
||||
today: Date;
|
||||
|
||||
mainFlatListData: Array<Object>;
|
||||
mainFlatListData: Array<{ key: string }>;
|
||||
lastRefresh: Date;
|
||||
|
||||
authRef: Object;
|
||||
authRef: { current: null | AuthenticatedScreen };
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
|
@ -103,69 +128,81 @@ export default class VoteScreen extends React.Component<Props, State> {
|
|||
canRefresh = (new Date().getTime() - this.lastRefresh.getTime()) > MIN_REFRESH_TIME;
|
||||
else
|
||||
canRefresh = true;
|
||||
if (canRefresh)
|
||||
if (canRefresh && this.authRef.current != null)
|
||||
this.authRef.current.reload()
|
||||
};
|
||||
|
||||
generateDateObject() {
|
||||
this.dates = {
|
||||
date_begin: stringToDate(this.datesString.date_begin),
|
||||
date_end: stringToDate(this.datesString.date_end),
|
||||
date_result_begin: stringToDate(this.datesString.date_result_begin),
|
||||
date_result_end: stringToDate(this.datesString.date_result_end),
|
||||
};
|
||||
const strings = this.datesString;
|
||||
if (strings != null) {
|
||||
const dateBegin = stringToDate(strings.date_begin);
|
||||
const dateEnd = stringToDate(strings.date_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 {
|
||||
if (this.today.getDate() === date.getDate()) {
|
||||
const str = getTimeOnlyString(dateString);
|
||||
return str !== null ? str : "";
|
||||
return str != null ? str : "";
|
||||
} else
|
||||
return dateString;
|
||||
}
|
||||
|
||||
isVoteAvailable() {
|
||||
return this.dates.date_begin !== null;
|
||||
}
|
||||
|
||||
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() {
|
||||
return this.today > this.dates.date_begin;
|
||||
return this.dates != null && this.today > this.dates.date_begin;
|
||||
}
|
||||
|
||||
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() {
|
||||
return this.today > this.dates.date_result_begin;
|
||||
return this.dates != null && this.today > this.dates.date_result_begin;
|
||||
}
|
||||
|
||||
mainRenderItem = ({item}: Object) => {
|
||||
if (item.key === 'info')
|
||||
return <VoteTitle/>;
|
||||
else if (item.key === 'main' && this.isVoteAvailable())
|
||||
else if (item.key === 'main' && this.dates != null)
|
||||
return this.getContent();
|
||||
else
|
||||
return null;
|
||||
};
|
||||
|
||||
getScreen = (data: Array<Object | null>) => {
|
||||
getScreen = (data: Array<{ [key: string]: any } | null>) => {
|
||||
// data[0] = FAKE_TEAMS2;
|
||||
// data[1] = FAKE_DATE;
|
||||
this.lastRefresh = new Date();
|
||||
|
||||
if (data[1] === null)
|
||||
data[1] = {date_begin: null};
|
||||
const teams : teamResponse | null = data[0];
|
||||
const dateStrings : stringVoteDates | null = data[1];
|
||||
|
||||
if (data[0] !== null) {
|
||||
this.teams = data[0].teams;
|
||||
this.hasVoted = data[0].has_voted;
|
||||
if (dateStrings != null && dateStrings.date_begin == null)
|
||||
this.datesString = null;
|
||||
else
|
||||
this.datesString = dateStrings;
|
||||
|
||||
if (teams != null) {
|
||||
this.teams = teams.teams;
|
||||
this.hasVoted = teams.has_voted;
|
||||
}
|
||||
this.datesString = data[1];
|
||||
|
||||
this.generateDateObject();
|
||||
return (
|
||||
<View>
|
||||
|
|
@ -211,15 +248,26 @@ export default class VoteScreen extends React.Component<Props, State> {
|
|||
* Votes have ended, results can be displayed
|
||||
*/
|
||||
getVoteResultCard() {
|
||||
return <VoteResults teams={this.teams}
|
||||
dateEnd={this.getDateString(this.dates.date_result_end, this.datesString.date_result_end)}/>;
|
||||
if (this.dates != null && this.datesString != null)
|
||||
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
|
||||
*/
|
||||
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() {
|
||||
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);
|
||||
return <VoteWait startDate={startDate} hasVoted={this.hasVoted || this.state.hasVoted}
|
||||
justVoted={this.state.hasVoted}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Animated, FlatList} from 'react-native';
|
||||
import {FlatList} from 'react-native';
|
||||
import i18n from "i18n-js";
|
||||
import DashboardItem from "../../components/Home/EventDashboardItem";
|
||||
import WebSectionList from "../../components/Screens/WebSectionList";
|
||||
|
|
@ -14,9 +14,10 @@ import ActionsDashBoardItem from "../../components/Home/ActionsDashboardItem";
|
|||
import ConnectionManager from "../../managers/ConnectionManager";
|
||||
import {CommonActions} from '@react-navigation/native';
|
||||
import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton";
|
||||
import {AnimatedValue} from "react-native-reanimated";
|
||||
import AnimatedFAB from "../../components/Animations/AnimatedFAB";
|
||||
import AnimatedFocusView from "../../components/Animations/AnimatedFocusView";
|
||||
import {StackNavigationProp} from "@react-navigation/stack";
|
||||
import type {CustomTheme} from "../../managers/ThemeManager";
|
||||
// import DATA from "../dashboard_data.json";
|
||||
|
||||
|
||||
|
|
@ -31,30 +32,80 @@ const SECTIONS_ID = [
|
|||
|
||||
const REFRESH_TIME = 1000 * 20; // Refresh every 20 seconds
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
route: Object,
|
||||
theme: Object,
|
||||
type rawDashboard = {
|
||||
news_feed: {
|
||||
data: Array<feedItem>,
|
||||
},
|
||||
dashboard: fullDashboard,
|
||||
}
|
||||
|
||||
type State = {
|
||||
fabPosition: AnimatedValue
|
||||
export type feedItem = {
|
||||
full_picture: string,
|
||||
message: string,
|
||||
permalink_url: string,
|
||||
created_time: number,
|
||||
id: string,
|
||||
};
|
||||
|
||||
type fullDashboard = {
|
||||
today_menu: Array<{ [key: string]: any }>,
|
||||
proximo_articles: number,
|
||||
available_machines: {
|
||||
dryers: number,
|
||||
washers: number,
|
||||
},
|
||||
today_events: Array<{ [key: string]: any }>,
|
||||
available_tutorials: number,
|
||||
}
|
||||
|
||||
type dashboardItem = {
|
||||
id: string,
|
||||
content: Array<{ [key: string]: any }>
|
||||
};
|
||||
|
||||
type dashboardSmallItem = {
|
||||
id: string,
|
||||
data: number,
|
||||
icon: string,
|
||||
color: string,
|
||||
onPress: () => void,
|
||||
isAvailable: boolean
|
||||
};
|
||||
|
||||
export type event = {
|
||||
id: number,
|
||||
title: string,
|
||||
logo: string | null,
|
||||
date_begin: string,
|
||||
date_end: string,
|
||||
description: string,
|
||||
club: string,
|
||||
category_id: number,
|
||||
url: string,
|
||||
}
|
||||
|
||||
type listSection = {
|
||||
title: string,
|
||||
data: Array<dashboardItem> | Array<feedItem>,
|
||||
id: string
|
||||
};
|
||||
|
||||
type Props = {
|
||||
navigation: StackNavigationProp,
|
||||
route: { params: any, ... },
|
||||
theme: CustomTheme,
|
||||
}
|
||||
|
||||
/**
|
||||
* Class defining the app's home screen
|
||||
*/
|
||||
class HomeScreen extends React.Component<Props, State> {
|
||||
class HomeScreen extends React.Component<Props> {
|
||||
|
||||
colors: Object;
|
||||
|
||||
isLoggedIn: boolean | null;
|
||||
|
||||
fabRef: Object;
|
||||
|
||||
state = {
|
||||
fabPosition: new Animated.Value(0),
|
||||
};
|
||||
fabRef: { current: null | AnimatedFAB };
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -69,8 +120,8 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
* @param dateString {string} The Unix Timestamp representation of a date
|
||||
* @return {string} The formatted output date
|
||||
*/
|
||||
static getFormattedDate(dateString: string) {
|
||||
let date = new Date(Number.parseInt(dateString) * 1000);
|
||||
static getFormattedDate(dateString: number) {
|
||||
let date = new Date(dateString * 1000);
|
||||
return date.toLocaleString();
|
||||
}
|
||||
|
||||
|
|
@ -92,8 +143,8 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
};
|
||||
|
||||
handleNavigationParams = () => {
|
||||
if (this.props.route.params !== undefined) {
|
||||
if (this.props.route.params.nextScreen !== undefined && this.props.route.params.nextScreen !== null) {
|
||||
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}));
|
||||
|
|
@ -138,14 +189,14 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
* @param fetchedData
|
||||
* @return {*}
|
||||
*/
|
||||
createDataset = (fetchedData: Object) => {
|
||||
createDataset = (fetchedData: rawDashboard) => {
|
||||
// fetchedData = DATA;
|
||||
let newsData = [];
|
||||
let dashboardData = [];
|
||||
if (fetchedData['news_feed'] !== undefined)
|
||||
newsData = fetchedData['news_feed']['data'];
|
||||
if (fetchedData['dashboard'] !== undefined)
|
||||
dashboardData = this.generateDashboardDataset(fetchedData['dashboard']);
|
||||
if (fetchedData.news_feed != null)
|
||||
newsData = fetchedData.news_feed.data;
|
||||
if (fetchedData.dashboard != null)
|
||||
dashboardData = this.generateDashboardDataset(fetchedData.dashboard);
|
||||
return [
|
||||
{
|
||||
title: '',
|
||||
|
|
@ -164,79 +215,61 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
* Generates the dataset associated to the dashboard to be displayed in the FlatList as a section
|
||||
*
|
||||
* @param dashboardData
|
||||
* @return {*}
|
||||
* @return {Array<dashboardItem>}
|
||||
*/
|
||||
generateDashboardDataset(dashboardData: Object) {
|
||||
let dataset = [
|
||||
|
||||
generateDashboardDataset(dashboardData: fullDashboard): Array<dashboardItem> {
|
||||
return [
|
||||
{
|
||||
id: 'top',
|
||||
content: []
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
content: undefined
|
||||
},
|
||||
{
|
||||
id: 'event',
|
||||
content: undefined
|
||||
},
|
||||
];
|
||||
for (let [key, value: number | Object | Array<string>] of Object.entries(dashboardData)) {
|
||||
switch (key) {
|
||||
case 'available_machines':
|
||||
dataset[0]['content'][0] = {
|
||||
content: [
|
||||
{
|
||||
id: 'washers',
|
||||
data: value.washers,
|
||||
data: dashboardData.available_machines.washers,
|
||||
icon: 'washing-machine',
|
||||
color: this.colors.proxiwashColor,
|
||||
onPress: this.onProxiwashClick,
|
||||
isAvailable: value.washers > 0
|
||||
};
|
||||
dataset[0]['content'][1] = {
|
||||
...dataset[0]['content'][0],
|
||||
isAvailable: dashboardData.available_machines.washers > 0
|
||||
},
|
||||
{
|
||||
id: 'dryers',
|
||||
data: value.dryers,
|
||||
data: dashboardData.available_machines.dryers,
|
||||
icon: 'tumble-dryer',
|
||||
isAvailable: value.dryers > 0
|
||||
};
|
||||
break;
|
||||
case 'available_tutorials':
|
||||
dataset[0]['content'][2] = {
|
||||
id: key,
|
||||
data: value,
|
||||
color: this.colors.proxiwashColor,
|
||||
onPress: this.onProxiwashClick,
|
||||
isAvailable: dashboardData.available_machines.dryers > 0
|
||||
},
|
||||
{
|
||||
id: 'available_tutorials',
|
||||
data: dashboardData.available_tutorials,
|
||||
icon: 'school',
|
||||
color: this.colors.tutorinsaColor,
|
||||
onPress: this.onTutorInsaClick,
|
||||
isAvailable: parseInt(value) > 0
|
||||
};
|
||||
break;
|
||||
case 'proximo_articles':
|
||||
dataset[0]['content'][3] = {
|
||||
id: key,
|
||||
data: value,
|
||||
isAvailable: dashboardData.available_tutorials > 0
|
||||
},
|
||||
{
|
||||
id: 'proximo_articles',
|
||||
data: dashboardData.proximo_articles,
|
||||
icon: 'shopping',
|
||||
color: this.colors.proximoColor,
|
||||
onPress: this.onProximoClick,
|
||||
isAvailable: parseInt(value) > 0
|
||||
};
|
||||
break;
|
||||
case 'today_menu':
|
||||
dataset[0]['content'][4] = {
|
||||
id: key,
|
||||
data: 0,
|
||||
icon: 'silverware-fork-knife',
|
||||
isAvailable: dashboardData.proximo_articles > 0
|
||||
},
|
||||
{
|
||||
id: 'silverware-fork-knife',
|
||||
data: dashboardData.today_menu,
|
||||
icon: 'shopping',
|
||||
color: this.colors.menuColor,
|
||||
onPress: this.onMenuClick,
|
||||
isAvailable: value.length > 0
|
||||
};
|
||||
break;
|
||||
case 'today_events':
|
||||
dataset[2]['content'] = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return dataset
|
||||
isAvailable: dashboardData.today_menu.length > 0
|
||||
},
|
||||
]
|
||||
},
|
||||
{id: 'actions', content: []},
|
||||
{
|
||||
id: 'event',
|
||||
content: dashboardData.today_events
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -245,11 +278,11 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
* @param item The item to display
|
||||
* @return {*}
|
||||
*/
|
||||
getDashboardItem(item: Object) {
|
||||
let content = item['content'];
|
||||
if (item['id'] === 'event')
|
||||
getDashboardItem(item: dashboardItem) {
|
||||
let content = item.content;
|
||||
if (item.id === 'event')
|
||||
return this.getDashboardEvent(content);
|
||||
else if (item['id'] === 'top')
|
||||
else if (item.id === 'top')
|
||||
return this.getDashboardRow(content);
|
||||
else
|
||||
return this.getDashboardActions();
|
||||
|
|
@ -278,14 +311,14 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
/**
|
||||
* Gets the duration (in milliseconds) of an event
|
||||
*
|
||||
* @param event {Object}
|
||||
* @param event {event}
|
||||
* @return {number} The number of milliseconds
|
||||
*/
|
||||
getEventDuration(event: Object): number {
|
||||
let start = stringToDate(event['date_begin']);
|
||||
let end = stringToDate(event['date_end']);
|
||||
getEventDuration(event: event): number {
|
||||
let start = stringToDate(event.date_begin);
|
||||
let end = stringToDate(event.date_end);
|
||||
let duration = 0;
|
||||
if (start !== undefined && start !== null && end !== undefined && end !== null)
|
||||
if (start != null && end != null)
|
||||
duration = end - start;
|
||||
return duration;
|
||||
}
|
||||
|
|
@ -297,11 +330,11 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
* @param limit
|
||||
* @return {Array<Object>}
|
||||
*/
|
||||
getEventsAfterLimit(events: Object, limit: Date): 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 !== undefined && startDate !== null && startDate >= limit) {
|
||||
let startDate = stringToDate(event.date_begin);
|
||||
if (startDate != null && startDate >= limit) {
|
||||
validEvents.push(event);
|
||||
}
|
||||
}
|
||||
|
|
@ -314,7 +347,7 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
*
|
||||
* @param events
|
||||
*/
|
||||
getLongestEvent(events: Array<Object>): Object {
|
||||
getLongestEvent(events: Array<event>): event {
|
||||
let longestEvent = events[0];
|
||||
let longestTime = 0;
|
||||
for (let event of events) {
|
||||
|
|
@ -332,16 +365,16 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
*
|
||||
* @param events
|
||||
*/
|
||||
getFutureEvents(events: Array<Object>): Array<Object> {
|
||||
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 !== undefined && startDate !== null) {
|
||||
let startDate = stringToDate(event.date_begin);
|
||||
let endDate = stringToDate(event.date_end);
|
||||
if (startDate != null) {
|
||||
if (startDate > now)
|
||||
validEvents.push(event);
|
||||
else if (endDate !== undefined && endDate !== null) {
|
||||
else if (endDate != null) {
|
||||
if (endDate > now || endDate < startDate) // Display event if it ends the following day
|
||||
validEvents.push(event);
|
||||
}
|
||||
|
|
@ -356,8 +389,8 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
* @param events
|
||||
* @return {Object}
|
||||
*/
|
||||
getDisplayEvent(events: Array<Object>): Object {
|
||||
let displayEvent = undefined;
|
||||
getDisplayEvent(events: Array<event>): event | null {
|
||||
let displayEvent = null;
|
||||
if (events.length > 1) {
|
||||
let eventsAfterLimit = this.getEventsAfterLimit(events, this.getTodayEventTimeLimit());
|
||||
if (eventsAfterLimit.length > 0) {
|
||||
|
|
@ -383,7 +416,7 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
* @param content
|
||||
* @return {*}
|
||||
*/
|
||||
getDashboardEvent(content: Array<Object>) {
|
||||
getDashboardEvent(content: Array<event>) {
|
||||
let futureEvents = this.getFutureEvents(content);
|
||||
let displayEvent = this.getDisplayEvent(futureEvents);
|
||||
const clickPreviewAction = () =>
|
||||
|
|
@ -394,14 +427,14 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
clickAction={this.onEventContainerClick}
|
||||
>
|
||||
<PreviewEventDashboardItem
|
||||
event={displayEvent}
|
||||
event={displayEvent != null ? displayEvent : undefined}
|
||||
clickAction={clickPreviewAction}
|
||||
/>
|
||||
</DashboardItem>
|
||||
);
|
||||
}
|
||||
|
||||
dashboardRowRenderItem = ({item}: Object) => {
|
||||
dashboardRowRenderItem = ({item}: { item: dashboardSmallItem }) => {
|
||||
return (
|
||||
<SquareDashboardItem
|
||||
color={item.color}
|
||||
|
|
@ -419,16 +452,18 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
* @param content
|
||||
* @return {*}
|
||||
*/
|
||||
getDashboardRow(content: Array<Object>) {
|
||||
return <FlatList
|
||||
data={content}
|
||||
renderItem={this.dashboardRowRenderItem}
|
||||
horizontal={true}
|
||||
contentContainerStyle={{
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
}}
|
||||
/>;
|
||||
getDashboardRow(content: Array<dashboardSmallItem>) {
|
||||
return (
|
||||
//$FlowFixMe
|
||||
<FlatList
|
||||
data={content}
|
||||
renderItem={this.dashboardRowRenderItem}
|
||||
horizontal={true}
|
||||
contentContainerStyle={{
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
}}
|
||||
/>);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -437,7 +472,7 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
* @param item The feed item to display
|
||||
* @return {*}
|
||||
*/
|
||||
getFeedItem(item: Object) {
|
||||
getFeedItem(item: feedItem) {
|
||||
return (
|
||||
<FeedItem
|
||||
{...this.props}
|
||||
|
|
@ -456,27 +491,34 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
* @param section The current section
|
||||
* @return {*}
|
||||
*/
|
||||
getRenderItem = ({item, section}: Object) => {
|
||||
return (section['id'] === SECTIONS_ID[0]
|
||||
? this.getDashboardItem(item)
|
||||
: this.getFeedItem(item));
|
||||
getRenderItem = ({item, section}: {
|
||||
item: { [key: string]: any },
|
||||
section: listSection
|
||||
}) => {
|
||||
if (section.id === SECTIONS_ID[0]) {
|
||||
const data: dashboardItem = item;
|
||||
return this.getDashboardItem(data);
|
||||
} else {
|
||||
const data: feedItem = item;
|
||||
return this.getFeedItem(data);
|
||||
}
|
||||
};
|
||||
|
||||
openScanner = () => this.props.navigation.navigate("scanner");
|
||||
|
||||
onScroll = (event: Object) => {
|
||||
this.fabRef.current.onScroll(event);
|
||||
onScroll = (event: SyntheticEvent<EventTarget>) => {
|
||||
if (this.fabRef.current != null)
|
||||
this.fabRef.current.onScroll(event);
|
||||
};
|
||||
|
||||
render() {
|
||||
const nav = this.props.navigation;
|
||||
return (
|
||||
<AnimatedFocusView
|
||||
{...this.props}
|
||||
>
|
||||
<WebSectionList
|
||||
{...this.props}
|
||||
createDataset={this.createDataset}
|
||||
navigation={nav}
|
||||
autoRefreshTime={REFRESH_TIME}
|
||||
refreshOnFocus={true}
|
||||
fetchUrl={DATA_URL}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import AsyncStorageManager from "../../managers/AsyncStorageManager";
|
|||
import {setMachineReminderNotificationTime} from "../../utils/Notifications";
|
||||
import {Card, List, Switch, ToggleButton} from 'react-native-paper';
|
||||
import {Appearance} from "react-native-appearance";
|
||||
import AnimatedAccordion from "../../components/Animations/AnimatedAccordion";
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
|
|
@ -89,6 +90,7 @@ export default class SettingsScreen extends React.Component<Props, State> {
|
|||
<ToggleButton.Row
|
||||
onValueChange={this.onProxiwashNotifPickerValueChange}
|
||||
value={this.state.proxiwashNotifPickerSelected}
|
||||
style={{marginLeft: 'auto', marginRight: 'auto'}}
|
||||
>
|
||||
<ToggleButton icon="close" value="never"/>
|
||||
<ToggleButton icon="numeric-2" value="2"/>
|
||||
|
|
@ -107,6 +109,7 @@ export default class SettingsScreen extends React.Component<Props, State> {
|
|||
<ToggleButton.Row
|
||||
onValueChange={this.onStartScreenPickerValueChange}
|
||||
value={this.state.startScreenPickerSelected}
|
||||
style={{marginLeft: 'auto', marginRight: 'auto'}}
|
||||
>
|
||||
<ToggleButton icon="shopping" value="proximo"/>
|
||||
<ToggleButton icon="calendar-range" value="planning"/>
|
||||
|
|
@ -188,25 +191,25 @@ export default class SettingsScreen extends React.Component<Props, State> {
|
|||
this.state.nightMode
|
||||
) : null
|
||||
}
|
||||
<List.Accordion
|
||||
<AnimatedAccordion
|
||||
title={i18n.t('settingsScreen.startScreen')}
|
||||
description={i18n.t('settingsScreen.startScreenSub')}
|
||||
subtitle={i18n.t('settingsScreen.startScreenSub')}
|
||||
left={props => <List.Icon {...props} icon="power"/>}
|
||||
>
|
||||
{this.getStartScreenPicker()}
|
||||
</List.Accordion>
|
||||
</AnimatedAccordion>
|
||||
</List.Section>
|
||||
</Card>
|
||||
<Card style={{margin: 5}}>
|
||||
<Card.Title title="Proxiwash"/>
|
||||
<List.Section>
|
||||
<List.Accordion
|
||||
<AnimatedAccordion
|
||||
title={i18n.t('settingsScreen.proxiwashNotifReminder')}
|
||||
description={i18n.t('settingsScreen.proxiwashNotifReminderSub')}
|
||||
left={props => <List.Icon {...props} icon="washing-machine"/>}
|
||||
>
|
||||
{this.getProxiwashNotifPicker()}
|
||||
</List.Accordion>
|
||||
</AnimatedAccordion>
|
||||
</List.Section>
|
||||
</Card>
|
||||
</ScrollView>
|
||||
|
|
|
|||
|
|
@ -14,12 +14,13 @@ export const withCollapsible = (Component: any) => {
|
|||
progress,
|
||||
opacity,
|
||||
} = useCollapsibleStack();
|
||||
const statusbarHeight = StatusBar.currentHeight != null ? StatusBar.currentHeight : 0;
|
||||
return <Component
|
||||
collapsibleStack={{
|
||||
onScroll,
|
||||
onScrollWithListener,
|
||||
containerPaddingTop: containerPaddingTop - StatusBar.currentHeight,
|
||||
scrollIndicatorInsetTop: scrollIndicatorInsetTop - StatusBar.currentHeight,
|
||||
containerPaddingTop: containerPaddingTop - statusbarHeight,
|
||||
scrollIndicatorInsetTop: scrollIndicatorInsetTop - statusbarHeight,
|
||||
translateY,
|
||||
progress,
|
||||
opacity,
|
||||
|
|
|
|||
Loading…
Reference in a new issue