Compare commits

...

10 commits

32 changed files with 892 additions and 691 deletions

140
App.js
View file

@ -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>
);
}

View file

@ -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;

View file

@ -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({

View file

@ -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}

View file

@ -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

View file

@ -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;

View file

@ -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>
}

View 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);

View file

@ -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);
};

View file

@ -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

View file

@ -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);

View file

@ -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,
}

View file

@ -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,
}

View file

@ -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();

View file

@ -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> {

View file

@ -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
}
/**

View file

@ -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() {

View file

@ -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}>

View file

@ -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,
};
/**

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

@ -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={{

View file

@ -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();

View file

@ -12,8 +12,7 @@ const deviceWidth = Dimensions.get("window").width;
type Props = {
navigation: Object,
state: Object,
theme: Object,
theme?: Object,
};
type State = {

View file

@ -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>
);
}

View file

@ -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();
}

View file

@ -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

View file

@ -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);

View file

@ -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}

View file

@ -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}

View file

@ -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>

View file

@ -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,