Compare commits

..

3 commits

Author SHA1 Message Date
fac9d8208e Improved doc 2020-03-29 15:59:25 +02:00
03549957a8 Improved documentation and fixed debug mode 2020-03-29 15:08:51 +02:00
4cdfc607e6 Improved documentation 2020-03-29 14:46:44 +02:00
37 changed files with 923 additions and 312 deletions

View file

@ -2,8 +2,14 @@ import * as React from 'react';
import {withTheme} from 'react-native-paper'; import {withTheme} from 'react-native-paper';
import {Agenda} from "react-native-calendars"; import {Agenda} from "react-native-calendars";
/**
* Abstraction layer for Agenda component, using custom configuration
*
* @param props Props to pass to the element. Must specify an onRef prop to get an Agenda ref.
* @return {*}
*/
function CustomAgenda(props) { function CustomAgenda(props) {
const { colors } = props.theme; const {colors} = props.theme;
return ( return (
<Agenda <Agenda
{...props} {...props}

View file

@ -9,47 +9,24 @@ import i18n from 'i18n-js';
import AppIntroSlider from "react-native-app-intro-slider"; import AppIntroSlider from "react-native-app-intro-slider";
import Update from "../constants/Update"; import Update from "../constants/Update";
// Content to be used int the intro slides
const styles = StyleSheet.create({
mainContent: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingBottom: 100
},
image: {
width: 300,
height: 300,
marginBottom: -50,
},
text: {
color: 'rgba(255, 255, 255, 0.8)',
backgroundColor: 'transparent',
textAlign: 'center',
paddingHorizontal: 16,
},
title: {
fontSize: 22,
color: 'white',
backgroundColor: 'transparent',
textAlign: 'center',
marginBottom: 16,
},
});
type Props = { type Props = {
onDone: Function, onDone: Function,
isUpdate: boolean, isUpdate: boolean,
isAprilFools: boolean, isAprilFools: boolean,
}; };
/**
* Class used to create intro slides
*/
export default class CustomIntroSlider extends React.Component<Props> { export default class CustomIntroSlider extends React.Component<Props> {
introSlides: Array<Object>; introSlides: Array<Object>;
updateSlides: Array<Object>; updateSlides: Array<Object>;
aprilFoolsSlides: Array<Object>; aprilFoolsSlides: Array<Object>;
/**
* Generates intro slides
*/
constructor() { constructor() {
super(); super();
this.introSlides = [ this.introSlides = [
@ -126,8 +103,9 @@ export default class CustomIntroSlider extends React.Component<Props> {
/** /**
* Render item to be used for the intro introSlides * Render item to be used for the intro introSlides
* @param item *
* @param dimensions * @param item The item to be displayed
* @param dimensions Dimensions of the item
*/ */
static getIntroRenderItem({item, dimensions}: Object) { static getIntroRenderItem({item, dimensions}: Object) {
@ -178,3 +156,29 @@ export default class CustomIntroSlider extends React.Component<Props> {
} }
const styles = StyleSheet.create({
mainContent: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingBottom: 100
},
image: {
width: 300,
height: 300,
marginBottom: -50,
},
text: {
color: 'rgba(255, 255, 255, 0.8)',
backgroundColor: 'transparent',
textAlign: 'center',
paddingHorizontal: 16,
},
title: {
fontSize: 22,
color: 'white',
backgroundColor: 'transparent',
textAlign: 'center',
marginBottom: 16,
},
});

View file

@ -4,8 +4,14 @@ import * as React from 'react';
import {withTheme} from 'react-native-paper'; import {withTheme} from 'react-native-paper';
import {Modalize} from "react-native-modalize"; import {Modalize} from "react-native-modalize";
/**
* Abstraction layer for Modalize component, using custom configuration
*
* @param props Props to pass to the element. Must specify an onRef prop to get an Modalize ref.
* @return {*}
*/
function CustomModal(props) { function CustomModal(props) {
const { colors } = props.theme; const {colors} = props.theme;
return ( return (
<Modalize <Modalize
ref={props.onRef} ref={props.onRef}

View file

@ -1,19 +1,19 @@
import * as React from 'react'; import * as React from 'react';
import {ActivityIndicator, Subheading, withTheme} from 'react-native-paper'; import {ActivityIndicator, Subheading, withTheme} from 'react-native-paper';
import {View} from "react-native"; import {StyleSheet, View} from "react-native";
import {MaterialCommunityIcons} from "@expo/vector-icons"; import {MaterialCommunityIcons} from "@expo/vector-icons";
function EmptyWebSectionListItem(props) { /**
const { colors } = props.theme; * Component used to display a message when a list is empty
*
* @param props Props to pass to the component
* @return {*}
*/
function EmptyWebSectionListItem(props: { text: string, icon: string, refreshing: boolean, theme: {} }) {
const {colors} = props.theme;
return ( return (
<View> <View>
<View style={{ <View style={styles.iconContainer}>
justifyContent: 'center',
alignItems: 'center',
width: '100%',
height: 100,
marginBottom: 20
}}>
{props.refreshing ? {props.refreshing ?
<ActivityIndicator <ActivityIndicator
animating={true} animating={true}
@ -27,9 +27,7 @@ function EmptyWebSectionListItem(props) {
</View> </View>
<Subheading style={{ <Subheading style={{
textAlign: 'center', ...styles.subheading,
marginRight: 20,
marginLeft: 20,
color: colors.textDisabled color: colors.textDisabled
}}> }}>
{props.text} {props.text}
@ -38,4 +36,19 @@ function EmptyWebSectionListItem(props) {
); );
} }
const styles = StyleSheet.create({
iconContainer: {
justifyContent: 'center',
alignItems: 'center',
width: '100%',
height: 100,
marginBottom: 20
},
subheading: {
textAlign: 'center',
marginRight: 20,
marginLeft: 20,
}
});
export default withTheme(EmptyWebSectionListItem); export default withTheme(EmptyWebSectionListItem);

View file

@ -2,7 +2,14 @@
import * as React from 'react'; import * as React from 'react';
import {Avatar, Card, withTheme} from 'react-native-paper'; import {Avatar, Card, withTheme} from 'react-native-paper';
import {StyleSheet} from "react-native";
/**
* Component used to display a dashboard item containing a preview event
*
* @param props Props to pass to the component
* @return {*}
*/
function EventDashBoardItem(props) { function EventDashBoardItem(props) {
const {colors} = props.theme; const {colors} = props.theme;
const iconColor = props.isAvailable ? const iconColor = props.isAvailable ?
@ -13,13 +20,7 @@ function EventDashBoardItem(props) {
colors.textDisabled; colors.textDisabled;
return ( return (
<Card <Card
style={{ style={styles.card}
width: 'auto',
marginLeft: 10,
marginRight: 10,
marginTop: 10,
overflow: 'hidden',
}}
onPress={props.clickAction}> onPress={props.clickAction}>
<Card.Title <Card.Title
@ -32,7 +33,7 @@ function EventDashBoardItem(props) {
icon={props.icon} icon={props.icon}
color={iconColor} color={iconColor}
size={60} size={60}
style={{backgroundColor: 'transparent'}}/>} style={styles.avatar}/>}
/> />
<Card.Content> <Card.Content>
{props.children} {props.children}
@ -41,4 +42,17 @@ function EventDashBoardItem(props) {
); );
} }
const styles = StyleSheet.create({
card: {
width: 'auto',
marginLeft: 10,
marginRight: 10,
marginTop: 10,
overflow: 'hidden',
},
avatar: {
backgroundColor: 'transparent'
}
});
export default withTheme(EventDashBoardItem); export default withTheme(EventDashBoardItem);

View file

@ -6,6 +6,11 @@ import i18n from "i18n-js";
const ICON_AMICALE = require('../assets/amicale.png'); const ICON_AMICALE = require('../assets/amicale.png');
/**
* Gets the amicale INSAT logo
*
* @return {*}
*/
function getAvatar() { function getAvatar() {
return ( return (
<Avatar.Image size={48} source={ICON_AMICALE} <Avatar.Image size={48} source={ICON_AMICALE}
@ -13,6 +18,12 @@ function getAvatar() {
); );
} }
/**
* Component used to display a feed item
*
* @param props Props to pass to the component
* @return {*}
*/
function FeedItem(props) { function FeedItem(props) {
const {colors} = props.theme; const {colors} = props.theme;
return ( return (
@ -39,7 +50,9 @@ function FeedItem(props) {
<Button <Button
color={'#57aeff'} color={'#57aeff'}
onPress={props.onOutLinkPress} onPress={props.onOutLinkPress}
icon={'facebook'}>{i18n.t('homeScreen.dashboard.seeMore')}</Button> icon={'facebook'}>
{i18n.t('homeScreen.dashboard.seeMore')}
</Button>
</Card.Actions> </Card.Actions>
</Card> </Card>
); );

View file

@ -1,8 +1,14 @@
import * as React from 'react'; import * as React from 'react';
import {IconButton, withTheme} from 'react-native-paper'; import {IconButton, withTheme} from 'react-native-paper';
/**
* Component used to display a header button
*
* @param props Props to pass to the component
* @return {*}
*/
function HeaderButton(props) { function HeaderButton(props) {
const { colors } = props.theme; const {colors} = props.theme;
return ( return (
<IconButton <IconButton
icon={props.icon} icon={props.icon}

View file

@ -1,25 +1,33 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {View} from "react-native"; import {StyleSheet, View} from "react-native";
import HTML from "react-native-render-html"; import HTML from "react-native-render-html";
import i18n from "i18n-js"; import i18n from "i18n-js";
import {Avatar, Button, Card, withTheme} from 'react-native-paper'; import {Avatar, Button, Card, withTheme} from 'react-native-paper';
import PlanningEventManager from "../utils/PlanningEventManager"; import PlanningEventManager from "../utils/PlanningEventManager";
/**
* Component used to display an event preview if an event is available
*
* @param props Props to pass to the component
* @return {*}
*/
function PreviewEventDashboardItem(props) { function PreviewEventDashboardItem(props) {
const {colors} = props.theme; const {colors} = props.theme;
const isEmpty = props.event === undefined ? true : PlanningEventManager.isDescriptionEmpty(props.event['description']); const isEmpty = props.event === undefined
? true
: PlanningEventManager.isDescriptionEmpty(props.event['description']);
if (props.event !== undefined && props.event !== null) { if (props.event !== undefined && props.event !== null) {
const hasImage = props.event['logo'] !== '' && props.event['logo'] !== null; const hasImage = props.event['logo'] !== '' && props.event['logo'] !== null;
const getImage = () => <Avatar.Image const getImage = () => <Avatar.Image
source={{uri: props.event['logo']}} source={{uri: props.event['logo']}}
size={50} size={50}
style={{backgroundColor: 'transparent'}}/>; style={styles.avatar}/>;
return ( return (
<Card <Card
style={{marginBottom: 10}} style={styles.card}
onPress={props.clickAction} onPress={props.clickAction}
elevation={3} elevation={3}
> >
@ -34,10 +42,7 @@ function PreviewEventDashboardItem(props) {
subtitle={PlanningEventManager.getFormattedEventTime(props.event['date_begin'], props.event['date_end'])} subtitle={PlanningEventManager.getFormattedEventTime(props.event['date_begin'], props.event['date_end'])}
/>} />}
{!isEmpty ? {!isEmpty ?
<Card.Content style={{ <Card.Content style={styles.content}>
maxHeight: 150,
overflow: 'hidden',
}}>
<HTML html={"<div>" + props.event['description'] + "</div>"} <HTML html={"<div>" + props.event['description'] + "</div>"}
tagsStyles={{ tagsStyles={{
p: {color: colors.text,}, p: {color: colors.text,},
@ -46,11 +51,7 @@ function PreviewEventDashboardItem(props) {
</Card.Content> : null} </Card.Content> : null}
<Card.Actions style={{ <Card.Actions style={styles.actions}>
marginLeft: 'auto',
marginTop: 'auto',
flexDirection: 'row'
}}>
<Button <Button
icon={'chevron-right'} icon={'chevron-right'}
> >
@ -63,4 +64,22 @@ function PreviewEventDashboardItem(props) {
return <View/> return <View/>
} }
const styles = StyleSheet.create({
card: {
marginBottom: 10
},
content: {
maxHeight: 150,
overflow: 'hidden',
},
actions: {
marginLeft: 'auto',
marginTop: 'auto',
flexDirection: 'row'
},
avatar: {
backgroundColor: 'transparent'
}
});
export default withTheme(PreviewEventDashboardItem); export default withTheme(PreviewEventDashboardItem);

View file

@ -1,8 +1,14 @@
import * as React from 'react'; import * as React from 'react';
import {Avatar, Card, Text, withTheme} from 'react-native-paper'; import {Avatar, Card, Text, withTheme} from 'react-native-paper';
import {View} from "react-native"; import {StyleSheet, View} from "react-native";
import ProxiwashConstants from "../constants/ProxiwashConstants"; import ProxiwashConstants from "../constants/ProxiwashConstants";
/**
* Component used to display a proxiwash item, showing machine progression and state
*
* @param props Props to pass to the component
* @return {*}
*/
function ProxiwashListItem(props) { function ProxiwashListItem(props) {
const {colors} = props.theme; const {colors} = props.theme;
let stateColors = {}; let stateColors = {};
@ -17,13 +23,13 @@ function ProxiwashListItem(props) {
icon={'bell-ring'} icon={'bell-ring'}
size={45} size={45}
color={colors.primary} color={colors.primary}
style={{backgroundColor: 'transparent'}} style={styles.icon}
/> : /> :
<Avatar.Icon <Avatar.Icon
icon={props.isDryer ? 'tumble-dryer' : 'washing-machine'} icon={props.isDryer ? 'tumble-dryer' : 'washing-machine'}
color={colors.text} color={colors.text}
size={40} size={40}
style={{backgroundColor: 'transparent'}} style={styles.icon}
/> />
); );
return ( return (
@ -35,37 +41,26 @@ function ProxiwashListItem(props) {
> >
{ProxiwashConstants.machineStates[props.state] === ProxiwashConstants.machineStates["EN COURS"] ? {ProxiwashConstants.machineStates[props.state] === ProxiwashConstants.machineStates["EN COURS"] ?
<Card style={{ <Card style={{
height: '100%', ...styles.backgroundCard,
position: 'absolute',
left: 0,
width: '100%',
backgroundColor: colors.proxiwashRunningBgColor, backgroundColor: colors.proxiwashRunningBgColor,
elevation: 0
}}/> : null }}/> : null
} }
<Card style={{ <Card style={{
height: '100%', ...styles.progressionCard,
position: 'absolute',
left: 0,
width: props.progress, width: props.progress,
backgroundColor: stateColors[ProxiwashConstants.machineStates[props.state]], backgroundColor: stateColors[ProxiwashConstants.machineStates[props.state]],
elevation: 0
}}/> }}/>
<Card.Title <Card.Title
title={props.title} title={props.title}
titleStyle={{fontSize: 17}} titleStyle={{fontSize: 17}}
subtitle={props.description} subtitle={props.description}
style={{ style={styles.title}
backgroundColor: 'transparent',
height: 64
}}
left={() => icon} left={() => icon}
right={() => ( right={() => (
<View style={{flexDirection: 'row'}}> <View style={{flexDirection: 'row'}}>
<View style={{ <View style={{justifyContent: 'center'}}>
justifyContent: 'center',
}}>
<Text style={ <Text style={
ProxiwashConstants.machineStates[props.state] === ProxiwashConstants.machineStates.TERMINE ? ProxiwashConstants.machineStates[props.state] === ProxiwashConstants.machineStates.TERMINE ?
{fontWeight: 'bold',} : {}} {fontWeight: 'bold',} : {}}
@ -73,12 +68,11 @@ function ProxiwashListItem(props) {
{props.statusText} {props.statusText}
</Text> </Text>
</View> </View>
<Avatar.Icon <Avatar.Icon
icon={props.statusIcon} icon={props.statusIcon}
color={colors.text} color={colors.text}
size={30} size={30}
style={{backgroundColor: 'transparent'}} style={styles.icon}
/> />
</View>)} </View>)}
/> />
@ -86,4 +80,27 @@ function ProxiwashListItem(props) {
); );
} }
const styles = StyleSheet.create({
icon: {
backgroundColor: 'transparent'
},
backgroundCard: {
height: '100%',
position: 'absolute',
left: 0,
width: '100%',
elevation: 0,
},
progressionCard: {
height: '100%',
position: 'absolute',
left: 0,
elevation: 0,
},
title: {
backgroundColor: 'transparent',
height: 64
}
});
export default withTheme(ProxiwashListItem); export default withTheme(ProxiwashListItem);

View file

@ -9,10 +9,12 @@ type Props = {
} }
/** /**
* FlatList implementing PureComponent for increased performance.
*
* This is a pure component, meaning it will only update if a shallow comparison of state and props is different. * This is a pure component, meaning it will only update if a shallow comparison of state and props is different.
* To force the component to update, change the value of updateData. * To force the component to update, change the value of updateData.
*/ */
export default class PureFlatList extends React.PureComponent<Props>{ export default class PureFlatList extends React.PureComponent<Props> {
static defaultProps = { static defaultProps = {
updateData: null, updateData: null,

View file

@ -20,7 +20,7 @@ type State = {
}; };
/** /**
* Class used to define a navigation drawer * Component used to render the drawer menu content
*/ */
export default class SideBar extends React.PureComponent<Props, State> { export default class SideBar extends React.PureComponent<Props, State> {
@ -33,7 +33,7 @@ export default class SideBar extends React.PureComponent<Props, State> {
getRenderItem: Function; getRenderItem: Function;
/** /**
* Generate the datasets * Generate the dataset
* *
* @param props * @param props
*/ */
@ -123,6 +123,12 @@ export default class SideBar extends React.PureComponent<Props, State> {
this.getRenderItem = this.getRenderItem.bind(this); this.getRenderItem = this.getRenderItem.bind(this);
} }
/**
* Callback when a drawer item is pressed.
* It will either navigate to the associated screen, or open the browser to the associated link
*
* @param item The item pressed
*/
onListItemPress(item: Object) { onListItemPress(item: Object) {
if (item.link === undefined) if (item.link === undefined)
this.props.navigation.navigate(item.route); this.props.navigation.navigate(item.route);
@ -130,12 +136,22 @@ export default class SideBar extends React.PureComponent<Props, State> {
WebBrowser.openBrowserAsync(item.link); WebBrowser.openBrowserAsync(item.link);
} }
/**
listKeyExtractor(item: Object) { * Key extractor for list items
*
* @param item The item to extract the key from
* @return {string} The extracted key
*/
listKeyExtractor(item: Object): string {
return item.route; return item.route;
} }
/**
* Gets the render item for the given list item
*
* @param item The item to render
* @return {*}
*/
getRenderItem({item}: Object) { getRenderItem({item}: Object) {
const onListItemPress = this.onListItemPress.bind(this, item); const onListItemPress = this.onListItemPress.bind(this, item);
if (item.icon !== undefined) { if (item.icon !== undefined) {

View file

@ -1,9 +1,15 @@
import * as React from 'react'; import * as React from 'react';
import { withTheme } from 'react-native-paper'; import {withTheme} from 'react-native-paper';
import {DrawerItem} from "@react-navigation/drawer"; import {DrawerItem} from "@react-navigation/drawer";
/**
* Component used to render a drawer menu item divider
*
* @param props Props to pass to the component
* @return {*}
*/
function SidebarDivider(props) { function SidebarDivider(props) {
const { colors } = props.theme; const {colors} = props.theme;
return ( return (
<DrawerItem <DrawerItem
label={props.title} label={props.title}

View file

@ -3,6 +3,12 @@ import {withTheme} from 'react-native-paper';
import {DrawerItem} from "@react-navigation/drawer"; import {DrawerItem} from "@react-navigation/drawer";
import {MaterialCommunityIcons} from "@expo/vector-icons"; import {MaterialCommunityIcons} from "@expo/vector-icons";
/**
* Component used to render a drawer menu item
*
* @param props Props to pass to the component
* @return {*}
*/
function SidebarItem(props) { function SidebarItem(props) {
const {colors} = props.theme; const {colors} = props.theme;
return ( return (

View file

@ -2,6 +2,12 @@ import * as React from 'react';
import {Badge, IconButton, withTheme} from 'react-native-paper'; import {Badge, IconButton, withTheme} from 'react-native-paper';
import {View} from "react-native"; import {View} from "react-native";
/**
* Component used to render a small dashboard item
*
* @param props Props to pass to the component
* @return {*}
*/
function SquareDashboardItem(props) { function SquareDashboardItem(props) {
const {colors} = props.theme; const {colors} = props.theme;
return ( return (
@ -9,9 +15,9 @@ function SquareDashboardItem(props) {
<IconButton <IconButton
icon={props.icon} icon={props.icon}
color={ color={
props.isAvailable ? props.isAvailable
props.color : ? props.color
colors.textDisabled : colors.textDisabled
} }
size={35} size={35}
onPress={props.clickAction} onPress={props.clickAction}
@ -23,9 +29,10 @@ function SquareDashboardItem(props) {
position: 'absolute', position: 'absolute',
top: 5, top: 5,
right: 5 right: 5
}}>{props.badgeNumber}</Badge> : null }}>
{props.badgeNumber}
</Badge> : null
} }
</View> </View>
); );
} }

View file

@ -29,6 +29,8 @@ type State = {
const MIN_REFRESH_TIME = 5 * 1000; const MIN_REFRESH_TIME = 5 * 1000;
/** /**
* Component used to render a SectionList with data fetched from the web
*
* This is a pure component, meaning it will only update if a shallow comparison of state and props is different. * This is a pure component, meaning it will only update if a shallow comparison of state and props is different.
* To force the component to update, change the value of updateData. * To force the component to update, change the value of updateData.
*/ */
@ -73,7 +75,7 @@ export default class WebSectionList extends React.PureComponent<Props, State> {
} }
/** /**
* Register react navigation events on first screen load. * Registers react navigation events on first screen load.
* Allows to detect when the screen is focused * Allows to detect when the screen is focused
*/ */
componentDidMount() { componentDidMount() {
@ -86,7 +88,7 @@ export default class WebSectionList extends React.PureComponent<Props, State> {
} }
/** /**
* Refresh data when focusing the screen and setup a refresh interval if asked to * Refreshes data when focusing the screen and setup a refresh interval if asked to
*/ */
onScreenFocus() { onScreenFocus() {
if (this.props.refreshOnFocus && this.lastRefresh !== undefined) if (this.props.refreshOnFocus && this.lastRefresh !== undefined)
@ -96,13 +98,19 @@ export default class WebSectionList extends React.PureComponent<Props, State> {
} }
/** /**
* Remove any interval on un-focus * Removes any interval on un-focus
*/ */
onScreenBlur() { onScreenBlur() {
clearInterval(this.refreshInterval); clearInterval(this.refreshInterval);
} }
/**
* Callback used when fetch is successful.
* It will update the displayed data and stop the refresh animation
*
* @param fetchedData The newly fetched data
*/
onFetchSuccess(fetchedData: Object) { onFetchSuccess(fetchedData: Object) {
this.setState({ this.setState({
fetchedData: fetchedData, fetchedData: fetchedData,
@ -112,6 +120,10 @@ export default class WebSectionList extends React.PureComponent<Props, State> {
this.lastRefresh = new Date(); 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({ this.setState({
fetchedData: {}, fetchedData: {},
@ -119,12 +131,10 @@ export default class WebSectionList extends React.PureComponent<Props, State> {
firstLoading: false firstLoading: false
}); });
this.showSnackBar(); this.showSnackBar();
// this.webDataManager.showUpdateToast(this.props.updateErrorText);
} }
/** /**
* Refresh data and show a toast if any error occurred * Refreshes data and shows an animations while doing it
* @private
*/ */
onRefresh() { onRefresh() {
let canRefresh; let canRefresh;
@ -140,10 +150,22 @@ export default class WebSectionList extends React.PureComponent<Props, State> {
} }
} }
/**
* Gets an empty section header
*
* @param section The current section
* @return {*}
*/
getEmptySectionHeader({section}: Object) { getEmptySectionHeader({section}: Object) {
return <View/>; return <View/>;
} }
/**
* Gets an empty render item
*
* @param item The data to display
* @return {*}
*/
getEmptyRenderItem({item}: Object) { getEmptyRenderItem({item}: Object) {
return ( return (
<EmptyWebSectionListItem <EmptyWebSectionListItem
@ -154,6 +176,11 @@ export default class WebSectionList extends React.PureComponent<Props, State> {
); );
} }
/**
* Creates an empty dataset
*
* @return {*}
*/
createEmptyDataset() { createEmptyDataset() {
return [ return [
{ {
@ -174,14 +201,26 @@ export default class WebSectionList extends React.PureComponent<Props, State> {
]; ];
} }
datasetKeyExtractor(item: Object) { /**
* Extracts a key from the given item
*
* @param item The item to extract the key from
* @return {string} The extracted key
*/
datasetKeyExtractor(item: Object): string {
return item.text return item.text
} }
/**
* Shows the error popup
*/
showSnackBar() { showSnackBar() {
this.setState({snackbarVisible: true}) this.setState({snackbarVisible: true})
} }
/**
* Hides the error popup
*/
hideSnackBar() { hideSnackBar() {
this.setState({snackbarVisible: false}) this.setState({snackbarVisible: false})
} }

View file

@ -34,8 +34,6 @@ class WebViewScreen extends React.PureComponent<Props> {
onRefreshClicked: Function; onRefreshClicked: Function;
onWebviewRef: Function; onWebviewRef: Function;
onGoBackWebview: Function;
onGoForwardWebview: Function;
getRenderLoading: Function; getRenderLoading: Function;
colors: Object; colors: Object;
@ -44,12 +42,13 @@ class WebViewScreen extends React.PureComponent<Props> {
super(props); super(props);
this.onRefreshClicked = this.onRefreshClicked.bind(this); this.onRefreshClicked = this.onRefreshClicked.bind(this);
this.onWebviewRef = this.onWebviewRef.bind(this); this.onWebviewRef = this.onWebviewRef.bind(this);
this.onGoBackWebview = this.onGoBackWebview.bind(this);
this.onGoForwardWebview = this.onGoForwardWebview.bind(this);
this.getRenderLoading = this.getRenderLoading.bind(this); this.getRenderLoading = this.getRenderLoading.bind(this);
this.colors = props.theme.colors; this.colors = props.theme.colors;
} }
/**
* Creates refresh button after mounting
*/
componentDidMount() { componentDidMount() {
const rightButton = this.getRefreshButton.bind(this); const rightButton = this.getRefreshButton.bind(this);
this.props.navigation.setOptions({ this.props.navigation.setOptions({
@ -57,42 +56,37 @@ class WebViewScreen extends React.PureComponent<Props> {
}); });
} }
getHeaderButton(clickAction: Function, icon: string) { /**
return ( * Gets a header refresh button
<HeaderButton icon={icon} onPress={clickAction}/> *
); * @return {*}
} */
getRefreshButton() { getRefreshButton() {
return ( return <HeaderButton icon={'refresh'} onPress={this.onRefreshClicked}/>
<View style={{
flexDirection: 'row',
marginRight: 10
}}>
{this.getHeaderButton(this.onRefreshClicked, 'refresh')}
</View>
);
}; };
/**
* Callback to use when refresh button is clicked. Reloads the webview.
*/
onRefreshClicked() { onRefreshClicked() {
if (this.webviewRef !== null) if (this.webviewRef !== null)
this.webviewRef.reload(); this.webviewRef.reload();
} }
onGoBackWebview() { /**
if (this.webviewRef !== null) * Callback used when receiving the webview ref. Stores the ref for later use
this.webviewRef.goBack(); *
} * @param ref
*/
onGoForwardWebview() {
if (this.webviewRef !== null)
this.webviewRef.goForward();
}
onWebviewRef(ref: Object) { onWebviewRef(ref: Object) {
this.webviewRef = ref this.webviewRef = ref
} }
/**
* Gets the loading indicator
*
* @return {*}
*/
getRenderLoading() { getRenderLoading() {
return ( return (
<View style={{ <View style={{
@ -115,7 +109,6 @@ class WebViewScreen extends React.PureComponent<Props> {
} }
render() { render() {
// console.log("rendering WebViewScreen");
return ( return (
<WebView <WebView
ref={this.onWebviewRef} ref={this.onWebviewRef}

View file

@ -1,12 +1,31 @@
import i18n from "i18n-js"; import i18n from "i18n-js";
/**
* Singleton used to manage update slides.
* Must be a singleton because it uses translations.
*
* Change values in this class to change the update slide.
* You will also need to update those translations:
* <ul>
* <li>intro.updateSlide.title</li>
* <li>intro.updateSlide.text</li>
* </ul>
*/
export default class Update { export default class Update {
// Increment the number to show the update slide
static number = 5; static number = 5;
// Change the icon to be displayed on the update slide
static icon = 'surround-sound-2-0'; static icon = 'surround-sound-2-0';
static instance: Update | null = null; static instance: Update | null = null;
title: string;
description: string;
/**
* Init translations
*/
constructor() { constructor() {
this.title = i18n.t('intro.updateSlide.title'); this.title = i18n.t('intro.updateSlide.title');
this.description = i18n.t('intro.updateSlide.text'); this.description = i18n.t('intro.updateSlide.text');
@ -14,6 +33,7 @@ export default class Update {
/** /**
* Get this class instance or create one if none is found * Get this class instance or create one if none is found
*
* @returns {Update} * @returns {Update}
*/ */
static getInstance(): Update { static getInstance(): Update {

View file

@ -10,6 +10,12 @@ type listItem = {
version: string version: string
}; };
/**
* Generates the dependencies list from the raw json
*
* @param object The raw json
* @return {Array<listItem>}
*/
function generateListFromObject(object: { [string]: string }): Array<listItem> { function generateListFromObject(object: { [string]: string }): Array<listItem> {
let list = []; let list = [];
let keys = Object.keys(object); let keys = Object.keys(object);

View file

@ -105,7 +105,7 @@ class AboutScreen extends React.Component<Props, State> {
icon: 'bug-check', icon: 'bug-check',
text: i18n.t('aboutScreen.debug'), text: i18n.t('aboutScreen.debug'),
showChevron: true, showChevron: true,
showOnlyDebug: true showOnlyInDebug: true
}, },
]; ];
/** /**
@ -171,6 +171,9 @@ class AboutScreen extends React.Component<Props, State> {
showChevron: true showChevron: true
}, },
]; ];
/**
* Order of information cards
*/
dataOrder: Array<Object> = [ dataOrder: Array<Object> = [
{ {
id: 'app', id: 'app',
@ -201,6 +204,11 @@ class AboutScreen extends React.Component<Props, State> {
this.colors = props.theme.colors; this.colors = props.theme.colors;
} }
/**
* Gets the app icon
* @param props
* @return {*}
*/
getAppIcon(props) { getAppIcon(props) {
return ( return (
<Avatar.Image <Avatar.Image
@ -211,10 +219,21 @@ class AboutScreen extends React.Component<Props, State> {
); );
} }
keyExtractor(item: Object) { /**
* Extracts a key from the given item
*
* @param item The item to extract the key from
* @return {string} The extracted key
*/
keyExtractor(item: Object): string {
return item.icon; return item.icon;
} }
/**
* Gets the app card showing information and links about the app.
*
* @return {*}
*/
getAppCard() { getAppCard() {
return ( return (
<Card style={{marginBottom: 10}}> <Card style={{marginBottom: 10}}>
@ -235,6 +254,11 @@ class AboutScreen extends React.Component<Props, State> {
); );
} }
/**
* Gets the team card showing information and links about the team
*
* @return {*}
*/
getTeamCard() { getTeamCard() {
return ( return (
<Card style={{marginBottom: 10}}> <Card style={{marginBottom: 10}}>
@ -263,6 +287,11 @@ class AboutScreen extends React.Component<Props, State> {
); );
} }
/**
* Gets the techno card showing information and links about the technologies used in the app
*
* @return {*}
*/
getTechnoCard() { getTechnoCard() {
return ( return (
<Card style={{marginBottom: 10}}> <Card style={{marginBottom: 10}}>
@ -280,12 +309,25 @@ class AboutScreen extends React.Component<Props, State> {
); );
} }
/**
* Gets a chevron icon
*
* @param props
* @return {*}
*/
getChevronIcon(props: Object) { getChevronIcon(props: Object) {
return ( return (
<List.Icon {...props} icon={'chevron-right'}/> <List.Icon {...props} icon={'chevron-right'}/>
); );
} }
/**
* Gets a custom list item icon
*
* @param item The item to show the icon for
* @param props
* @return {*}
*/
getItemIcon(item: Object, props: Object) { getItemIcon(item: Object, props: Object) {
return ( return (
<List.Icon {...props} icon={item.icon}/> <List.Icon {...props} icon={item.icon}/>
@ -295,10 +337,12 @@ class AboutScreen extends React.Component<Props, State> {
/** /**
* Get a clickable card item to be rendered inside a card. * Get a clickable card item to be rendered inside a card.
* *
* @returns {React.Node} * @returns {*}
*/ */
getCardItem({item}: Object) { getCardItem({item}: Object) {
let shouldShow = !item.showOnlyInDebug || (item.showOnlyInDebug && this.state.isDebugUnlocked); let shouldShow = item === undefined
|| !item.showOnlyInDebug
|| (item.showOnlyInDebug && this.state.isDebugUnlocked);
const getItemIcon = this.getItemIcon.bind(this, item); const getItemIcon = this.getItemIcon.bind(this, item);
if (shouldShow) { if (shouldShow) {
if (item.showChevron) { if (item.showChevron) {
@ -323,6 +367,9 @@ class AboutScreen extends React.Component<Props, State> {
return null; return null;
} }
/**
* Tries to unlock debug mode
*/
tryUnlockDebugMode() { tryUnlockDebugMode() {
this.debugTapCounter = this.debugTapCounter + 1; this.debugTapCounter = this.debugTapCounter + 1;
if (this.debugTapCounter >= 4) { if (this.debugTapCounter >= 4) {
@ -330,12 +377,20 @@ class AboutScreen extends React.Component<Props, State> {
} }
} }
/**
* Unlocks debug mode
*/
unlockDebugMode() { unlockDebugMode() {
this.setState({isDebugUnlocked: true}); this.setState({isDebugUnlocked: true});
let key = AsyncStorageManager.getInstance().preferences.debugUnlocked.key; let key = AsyncStorageManager.getInstance().preferences.debugUnlocked.key;
AsyncStorageManager.getInstance().savePref(key, '1'); AsyncStorageManager.getInstance().savePref(key, '1');
} }
/**
* Gets the bug report modal's content
*
* @return {*}
*/
getBugReportModal() { getBugReportModal() {
return ( return (
<View style={{ <View style={{
@ -376,12 +431,21 @@ class AboutScreen extends React.Component<Props, State> {
); );
} }
/**
* opens the bug report modal
*/
openBugReportModal() { openBugReportModal() {
if (this.modalRef) { if (this.modalRef) {
this.modalRef.open(); this.modalRef.open();
} }
} }
/**
* Gets a card, depending on the given item's id
*
* @param item The item to show
* @return {*}
*/
getMainCard({item}: Object) { getMainCard({item}: Object) {
switch (item.id) { switch (item.id) {
case 'app': case 'app':
@ -394,6 +458,11 @@ class AboutScreen extends React.Component<Props, State> {
return <View/>; return <View/>;
} }
/**
* Callback used when receiving the modal ref
*
* @param ref
*/
onModalRef(ref: Object) { onModalRef(ref: Object) {
this.modalRef = ref; this.modalRef = ref;
} }

View file

@ -16,7 +16,8 @@ type State = {
} }
/** /**
* Class defining the Debug screen. This screen allows the user to get detailed information on the app/device. * Class defining the Debug screen.
* This screen allows the user to get and modify information on the app/device.
*/ */
class DebugScreen extends React.Component<Props, State> { class DebugScreen extends React.Component<Props, State> {
@ -37,6 +38,15 @@ class DebugScreen extends React.Component<Props, State> {
this.colors = props.theme.colors; this.colors = props.theme.colors;
} }
/**
* Gets a clickable list item
*
* @param onPressCallback The function to call when clicking on the item
* @param icon The item's icon
* @param title The item's title
* @param subtitle The item's subtitle
* @return {*}
*/
static getGeneralItem(onPressCallback: Function, icon: ?string, title: string, subtitle: string) { static getGeneralItem(onPressCallback: Function, icon: ?string, title: string, subtitle: string) {
if (icon !== undefined) { if (icon !== undefined) {
return ( return (
@ -58,6 +68,10 @@ class DebugScreen extends React.Component<Props, State> {
} }
} }
/**
* Show the
* @param item
*/
showEditModal(item: Object) { showEditModal(item: Object) {
this.setState({ this.setState({
modalCurrentDisplayItem: item modalCurrentDisplayItem: item
@ -67,6 +81,11 @@ class DebugScreen extends React.Component<Props, State> {
} }
} }
/**
* Gets the edit modal content
*
* @return {*}
*/
getModalContent() { getModalContent() {
return ( return (
<View style={{ <View style={{
@ -104,6 +123,12 @@ class DebugScreen extends React.Component<Props, State> {
); );
} }
/**
* Saves the new value of the given preference
*
* @param key The pref key
* @param value The pref value
*/
saveNewPrefs(key: string, value: string) { saveNewPrefs(key: string, value: string) {
this.setState((prevState) => { this.setState((prevState) => {
let currentPreferences = {...prevState.currentPreferences}; let currentPreferences = {...prevState.currentPreferences};
@ -113,6 +138,11 @@ class DebugScreen extends React.Component<Props, State> {
AsyncStorageManager.getInstance().savePref(key, value); AsyncStorageManager.getInstance().savePref(key, value);
} }
/**
* Callback used when receiving the modal ref
*
* @param ref
*/
onModalRef(ref: Object) { onModalRef(ref: Object) {
this.modalRef = ref; this.modalRef = ref;
} }

View file

@ -56,6 +56,7 @@ class HomeScreen extends React.Component<Props> {
/** /**
* Converts a dateString using Unix Timestamp to a formatted date * Converts a dateString using Unix Timestamp to a formatted date
*
* @param dateString {string} The Unix Timestamp representation of a date * @param dateString {string} The Unix Timestamp representation of a date
* @return {string} The formatted output date * @return {string} The formatted output date
*/ */
@ -80,10 +81,22 @@ class HomeScreen extends React.Component<Props> {
this.props.navigation.navigate('SelfMenuScreen'); this.props.navigation.navigate('SelfMenuScreen');
} }
/**
* Extract a key for the given item
*
* @param item The item to extract the key from
* @return {*} The extracted key
*/
getKeyExtractor(item: Object) { getKeyExtractor(item: Object) {
return item !== undefined ? item.id : undefined; return item !== undefined ? item.id : undefined;
} }
/**
* Creates the dataset to be used in the FlatList
*
* @param fetchedData
* @return {*}
*/
createDataset(fetchedData: Object) { createDataset(fetchedData: Object) {
// fetchedData = DATA; // fetchedData = DATA;
let newsData = []; let newsData = [];
@ -110,6 +123,12 @@ class HomeScreen extends React.Component<Props> {
]; ];
} }
/**
* Generates the dataset associated to the dashboard to be displayed in the FlatList as a section
*
* @param dashboardData
* @return {*}
*/
generateDashboardDataset(dashboardData: Object) { generateDashboardDataset(dashboardData: Object) {
let dataset = [ let dataset = [
@ -145,6 +164,12 @@ class HomeScreen extends React.Component<Props> {
return dataset return dataset
} }
/**
* Gets a dashboard item
*
* @param item The item to display
* @return {*}
*/
getDashboardItem(item: Object) { getDashboardItem(item: Object) {
let content = item['content']; let content = item['content'];
if (item['id'] === 'event') if (item['id'] === 'event')
@ -154,7 +179,7 @@ class HomeScreen extends React.Component<Props> {
} }
/** /**
* Get the time limit depending on the current day: * Gets the time limit depending on the current day:
* 17:30 for every day of the week except for thursday 11:30 * 17:30 for every day of the week except for thursday 11:30
* 00:00 on weekends * 00:00 on weekends
*/ */
@ -170,7 +195,8 @@ class HomeScreen extends React.Component<Props> {
} }
/** /**
* Get the duration (in milliseconds) of an event * Gets the duration (in milliseconds) of an event
*
* @param event {Object} * @param event {Object}
* @return {number} The number of milliseconds * @return {number} The number of milliseconds
*/ */
@ -184,7 +210,7 @@ class HomeScreen extends React.Component<Props> {
} }
/** /**
* Get events starting after the limit * Gets events starting after the limit
* *
* @param events * @param events
* @param limit * @param limit
@ -202,8 +228,9 @@ class HomeScreen extends React.Component<Props> {
} }
/** /**
* Get the event with the longest duration in the given array. * Gets the event with the longest duration in the given array.
* If all events have the same duration, return the first in the array. * If all events have the same duration, return the first in the array.
*
* @param events * @param events
*/ */
getLongestEvent(events: Array<Object>): Object { getLongestEvent(events: Array<Object>): Object {
@ -220,7 +247,7 @@ class HomeScreen extends React.Component<Props> {
} }
/** /**
* Get events that have not yet ended/started * Gets events that have not yet ended/started
* *
* @param events * @param events
*/ */
@ -243,7 +270,7 @@ class HomeScreen extends React.Component<Props> {
} }
/** /**
* * Gets the event to display in the preview
* *
* @param events * @param events
* @return {Object} * @return {Object}
@ -266,7 +293,13 @@ class HomeScreen extends React.Component<Props> {
return displayEvent; return displayEvent;
} }
/**
* Gets the event render item.
* If a preview is available, it will be rendered inside
*
* @param content
* @return {*}
*/
getDashboardEventItem(content: Array<Object>) { getDashboardEventItem(content: Array<Object>) {
let icon = 'calendar-range'; let icon = 'calendar-range';
let title = i18n.t('homeScreen.dashboard.todayEventsTitle'); let title = i18n.t('homeScreen.dashboard.todayEventsTitle');
@ -310,7 +343,12 @@ class HomeScreen extends React.Component<Props> {
); );
} }
/**
* Gets a classic dashboard item.
*
* @param content
* @return {*}
*/
getDashboardMiddleItem(content: Array<Object>) { getDashboardMiddleItem(content: Array<Object>) {
let proxiwashData = content[0]['data']; let proxiwashData = content[0]['data'];
let tutorinsaData = content[1]['data']; let tutorinsaData = content[1]['data'];
@ -367,6 +405,12 @@ class HomeScreen extends React.Component<Props> {
WebBrowser.openBrowserAsync(link); WebBrowser.openBrowserAsync(link);
} }
/**
* Gets a render item for the given feed object
*
* @param item The feed item to display
* @return {*}
*/
getFeedItem(item: Object) { getFeedItem(item: Object) {
const onImagePress = this.openLink.bind(this, item.full_picture); const onImagePress = this.openLink.bind(this, item.full_picture);
const onOutLinkPress = this.openLink.bind(this, item.permalink_url); const onOutLinkPress = this.openLink.bind(this, item.permalink_url);
@ -382,6 +426,13 @@ class HomeScreen extends React.Component<Props> {
); );
} }
/**
* Gets a FlatList render item
*
* @param item The item to display
* @param section The current section
* @return {*}
*/
getRenderItem({item, section}: Object) { getRenderItem({item, section}: Object) {
return (section['id'] === SECTIONS_ID[0] ? return (section['id'] === SECTIONS_ID[0] ?
this.getDashboardItem(item) : this.getFeedItem(item)); this.getDashboardItem(item) : this.getFeedItem(item));

View file

@ -18,7 +18,7 @@ function openWebLink(event, link) {
} }
/** /**
* Class defining an about screen. This screen shows the user information about the app and it's author. * Class defining a planning event information page.
*/ */
class PlanningDisplayScreen extends React.Component<Props> { class PlanningDisplayScreen extends React.Component<Props> {

View file

@ -78,6 +78,9 @@ export default class PlanningScreen extends React.Component<Props, State> {
this.onBackButtonPressAndroid = this.onBackButtonPressAndroid.bind(this); this.onBackButtonPressAndroid = this.onBackButtonPressAndroid.bind(this);
} }
/**
* Captures focus and blur events to hook on android back button
*/
componentDidMount() { componentDidMount() {
this.onRefresh(); this.onRefresh();
this.didFocusSubscription = this.props.navigation.addListener( this.didFocusSubscription = this.props.navigation.addListener(
@ -98,6 +101,11 @@ export default class PlanningScreen extends React.Component<Props, State> {
); );
} }
/**
* Overrides default android back button behaviour to close the calendar if it was open.
*
* @return {boolean}
*/
onBackButtonPressAndroid() { onBackButtonPressAndroid() {
if (this.state.calendarShowing) { if (this.state.calendarShowing) {
this.agendaRef.chooseDay(this.agendaRef.state.selectedDay); this.agendaRef.chooseDay(this.agendaRef.state.selectedDay);
@ -107,6 +115,13 @@ export default class PlanningScreen extends React.Component<Props, State> {
} }
}; };
/**
* Function used to check if a row has changed
*
* @param r1
* @param r2
* @return {boolean}
*/
rowHasChanged(r1: Object, r2: Object) { rowHasChanged(r1: Object, r2: Object) {
return false; return false;
// if (r1 !== undefined && r2 !== undefined) // if (r1 !== undefined && r2 !== undefined)
@ -115,8 +130,7 @@ export default class PlanningScreen extends React.Component<Props, State> {
} }
/** /**
* Refresh data and show a toast if any error occurred * Refreshes data and shows an animation while doing it
* @private
*/ */
onRefresh = () => { onRefresh = () => {
let canRefresh; let canRefresh;
@ -143,14 +157,30 @@ export default class PlanningScreen extends React.Component<Props, State> {
} }
}; };
/**
* Callback used when receiving the agenda ref
*
* @param ref
*/
onAgendaRef(ref: Object) { onAgendaRef(ref: Object) {
this.agendaRef = ref; this.agendaRef = ref;
} }
/**
* Callback used when a button is pressed to toggle the calendar
*
* @param isCalendarOpened True is the calendar is already open, false otherwise
*/
onCalendarToggled(isCalendarOpened: boolean) { onCalendarToggled(isCalendarOpened: boolean) {
this.setState({calendarShowing: isCalendarOpened}); this.setState({calendarShowing: isCalendarOpened});
} }
/**
* Gets an event render item
*
* @param item The current event to render
* @return {*}
*/
getRenderItem(item: eventObject) { getRenderItem(item: eventObject) {
const onPress = this.props.navigation.navigate.bind(this, 'PlanningDisplayScreen', {data: item}); const onPress = this.props.navigation.navigate.bind(this, 'PlanningDisplayScreen', {data: item});
if (item.logo !== null) { if (item.logo !== null) {
@ -182,6 +212,11 @@ export default class PlanningScreen extends React.Component<Props, State> {
} }
} }
/**
* Gets an empty render item for an empty date
*
* @return {*}
*/
getRenderEmptyDate() { getRenderEmptyDate() {
return ( return (
<Divider/> <Divider/>

View file

@ -10,7 +10,7 @@ type Props = {
}; };
/** /**
* Class defining an about screen. This screen shows the user information about the app and it's author. * Class defining the proximo about screen.
*/ */
export default class ProximoAboutScreen extends React.Component<Props> { export default class ProximoAboutScreen extends React.Component<Props> {

View file

@ -1,7 +1,7 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {Platform, Image, ScrollView, View} from "react-native"; import {Image, Platform, ScrollView, View} from "react-native";
import i18n from "i18n-js"; import i18n from "i18n-js";
import CustomModal from "../../components/CustomModal"; import CustomModal from "../../components/CustomModal";
import {Avatar, IconButton, List, RadioButton, Searchbar, Subheading, Text, Title, withTheme} from "react-native-paper"; import {Avatar, IconButton, List, RadioButton, Searchbar, Subheading, Text, Title, withTheme} from "react-native-paper";
@ -77,10 +77,10 @@ class ProximoListScreen extends React.Component<Props, State> {
/** /**
* Set the sort mode from state when components are ready * Creates the header content
*/ */
componentDidMount() { componentDidMount() {
const button = this.getSortMenu.bind(this); const button = this.getSortMenuButton.bind(this);
const title = this.getSearchBar.bind(this); const title = this.getSearchBar.bind(this);
this.props.navigation.setOptions({ this.props.navigation.setOptions({
headerRight: button, headerRight: button,
@ -93,7 +93,50 @@ class ProximoListScreen extends React.Component<Props, State> {
} }
/** /**
* Set the current sort mode. * Gets the header search bar
*
* @return {*}
*/
getSearchBar() {
return (
<Searchbar
placeholder={i18n.t('proximoScreen.search')}
onChangeText={this.onSearchStringChange}
/>
);
}
/**
* Gets the sort menu header button
*
* @return {*}
*/
getSortMenuButton() {
return (
<IconButton
icon="sort"
color={this.colors.text}
size={26}
onPress={this.onSortMenuPress}
/>
);
}
/**
* Callback used when clicking on the sort menu button.
* It will open the modal to show a sort selection
*/
onSortMenuPress() {
this.setState({
modalCurrentDisplayItem: this.getModalSortMenu()
});
if (this.modalRef) {
this.modalRef.open();
}
}
/**
* Sets the current sort mode.
* *
* @param mode The number representing the mode * @param mode The number representing the mode
*/ */
@ -121,19 +164,10 @@ class ProximoListScreen extends React.Component<Props, State> {
} }
} }
getSearchBar() {
return (
<Searchbar
placeholder={i18n.t('proximoScreen.search')}
onChangeText={this.onSearchStringChange}
/>
);
}
/** /**
* get color depending on quantity available * Gets a color depending on the quantity available
* *
* @param availableStock * @param availableStock The quantity available
* @return * @return
*/ */
getStockColor(availableStock: number) { getStockColor(availableStock: number) {
@ -147,13 +181,21 @@ class ProximoListScreen extends React.Component<Props, State> {
return color; return color;
} }
sanitizeString(str: string) { /**
* Sanitizes the given string to improve search performance
*
* @param str The string to sanitize
* @return {string} The sanitized string
*/
sanitizeString(str: string): string {
return str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
} }
/** /**
* Returns only the articles whose name contains str. Case and accents insensitive. * Returns only articles whose name contains the given string.
* @param str * Case and accents insensitive.
*
* @param str The string used to filter article names
* @returns {[]} * @returns {[]}
*/ */
filterData(str: string) { filterData(str: string) {
@ -169,12 +211,23 @@ class ProximoListScreen extends React.Component<Props, State> {
return filteredData; return filteredData;
} }
/**
* Callback used when the search changes
*
* @param str The new search string
*/
onSearchStringChange(str: string) { onSearchStringChange(str: string) {
this.setState({ this.setState({
currentlyDisplayedData: this.filterData(str) currentlyDisplayedData: this.filterData(str)
}) })
} }
/**
* Gets the modal content depending on the given article
*
* @param item The article to display
* @return {*}
*/
getModalItemContent(item: Object) { getModalItemContent(item: Object) {
return ( return (
<View style={{ <View style={{
@ -206,6 +259,11 @@ class ProximoListScreen extends React.Component<Props, State> {
); );
} }
/**
* Gets the modal content to display a sort menu
*
* @return {*}
*/
getModalSortMenu() { getModalSortMenu() {
return ( return (
<View style={{ <View style={{
@ -254,6 +312,12 @@ class ProximoListScreen extends React.Component<Props, State> {
); );
} }
/**
* Callback used when clicking an article in the list.
* It opens the modal to show detailed information about the article
*
* @param item The article pressed
*/
onListItemPress(item: Object) { onListItemPress(item: Object) {
this.setState({ this.setState({
modalCurrentDisplayItem: this.getModalItemContent(item) modalCurrentDisplayItem: this.getModalItemContent(item)
@ -263,26 +327,12 @@ class ProximoListScreen extends React.Component<Props, State> {
} }
} }
onSortMenuPress() { /**
this.setState({ * Gets a render item for the given article
modalCurrentDisplayItem: this.getModalSortMenu() *
}); * @param item The article to render
if (this.modalRef) { * @return {*}
this.modalRef.open(); */
}
}
getSortMenu() {
return (
<IconButton
icon="sort"
color={this.colors.text}
size={26}
onPress={this.onSortMenuPress}
/>
);
}
renderItem({item}: Object) { renderItem({item}: Object) {
const onPress = this.onListItemPress.bind(this, item); const onPress = this.onListItemPress.bind(this, item);
return ( return (
@ -301,10 +351,21 @@ class ProximoListScreen extends React.Component<Props, State> {
); );
} }
/**
* Extracts a key for the given article
*
* @param item The article to extract the key from
* @return {*} The extracted key
*/
keyExtractor(item: Object) { keyExtractor(item: Object) {
return item.name + item.code; return item.name + item.code;
} }
/**
* Callback used when receiving the modal ref
*
* @param ref
*/
onModalRef(ref: Object) { onModalRef(ref: Object) {
this.modalRef = ref; this.modalRef = ref;
} }

View file

@ -18,8 +18,8 @@ type State = {
} }
/** /**
* Class defining the main proximo screen. This screen shows the different categories of articles * Class defining the main proximo screen.
* offered by proximo. * This screen shows the different categories of articles offered by proximo.
*/ */
class ProximoMainScreen extends React.Component<Props, State> { class ProximoMainScreen extends React.Component<Props, State> {
@ -41,6 +41,14 @@ class ProximoMainScreen extends React.Component<Props, State> {
this.colors = props.theme.colors; this.colors = props.theme.colors;
} }
/**
* Function used to sort items in the list.
* Makes the All category stick to the top and sorts the others by name ascending
*
* @param a
* @param b
* @return {number}
*/
static sortFinalData(a: Object, b: Object) { static sortFinalData(a: Object, b: Object) {
let str1 = a.type.name.toLowerCase(); let str1 = a.type.name.toLowerCase();
let str2 = b.type.name.toLowerCase(); let str2 = b.type.name.toLowerCase();
@ -59,17 +67,76 @@ class ProximoMainScreen extends React.Component<Props, State> {
return 0; return 0;
} }
/**
* Creates header button
*/
componentDidMount() { componentDidMount() {
const rightButton = this.getRightButton.bind(this); const rightButton = this.getHeaderButtons.bind(this);
this.props.navigation.setOptions({ this.props.navigation.setOptions({
headerRight: rightButton, headerRight: rightButton,
}); });
} }
/**
* Callback used when the search button is pressed.
* This will open a new ProximoListScreen with all items displayed
*/
onPressSearchBtn() {
let searchScreenData = {
shouldFocusSearchBar: true,
data: {
type: {
id: "0",
name: i18n.t('proximoScreen.all'),
icon: 'star'
},
data: this.articles !== undefined ?
this.getAvailableArticles(this.articles, undefined) : []
},
};
this.props.navigation.navigate('ProximoListScreen', searchScreenData);
}
/**
* Callback used when the about button is pressed.
* This will open the ProximoAboutScreen
*/
onPressAboutBtn() {
this.props.navigation.navigate('ProximoAboutScreen');
}
/**
* Gets the header buttons
* @return {*}
*/
getHeaderButtons() {
return (
<View
style={{
flexDirection: 'row',
}}>
<HeaderButton icon={'magnify'} onPress={this.onPressSearchBtn}/>
<HeaderButton icon={'information'} onPress={this.onPressAboutBtn}/>
</View>
);
}
/**
* Extracts a key for the given category
*
* @param item The category to extract the key from
* @return {*} The extracted key
*/
getKeyExtractor(item: Object) { getKeyExtractor(item: Object) {
return item !== undefined ? item.type['id'] : undefined; return item !== undefined ? item.type['id'] : undefined;
} }
/**
* Creates the dataset to be used in the FlatList
*
* @param fetchedData
* @return {*}
* */
createDataset(fetchedData: Object) { createDataset(fetchedData: Object) {
return [ return [
{ {
@ -133,39 +200,12 @@ class ProximoMainScreen extends React.Component<Props, State> {
return availableArticles; return availableArticles;
} }
onPressSearchBtn() { /**
let searchScreenData = { * Gets the given category render item
shouldFocusSearchBar: true, *
data: { * @param item The category to render
type: { * @return {*}
id: "0", */
name: i18n.t('proximoScreen.all'),
icon: 'star'
},
data: this.articles !== undefined ?
this.getAvailableArticles(this.articles, undefined) : []
},
};
this.props.navigation.navigate('ProximoListScreen', searchScreenData);
}
onPressAboutBtn() {
this.props.navigation.navigate('ProximoAboutScreen');
}
getRightButton() {
return (
<View
style={{
flexDirection: 'row',
}}>
<HeaderButton icon={'magnify'} onPress={this.onPressSearchBtn}/>
<HeaderButton icon={'information'} onPress={this.onPressAboutBtn}/>
</View>
);
}
getRenderItem({item}: Object) { getRenderItem({item}: Object) {
let dataToSend = { let dataToSend = {
shouldFocusSearchBar: false, shouldFocusSearchBar: false,

View file

@ -10,7 +10,7 @@ type Props = {
}; };
/** /**
* Class defining an about screen. This screen shows the user information about the app and it's author. * Class defining the proxiwash about screen.
*/ */
export default class ProxiwashAboutScreen extends React.Component<Props> { export default class ProxiwashAboutScreen extends React.Component<Props> {

View file

@ -58,7 +58,6 @@ class ProxiwashScreen extends React.Component<Props, State> {
refreshing: false, refreshing: false,
firstLoading: true, firstLoading: true,
fetchedData: {}, fetchedData: {},
// machinesWatched: JSON.parse(dataString),
machinesWatched: [], machinesWatched: [],
modalCurrentDisplayItem: null, modalCurrentDisplayItem: null,
bannerVisible: AsyncStorageManager.getInstance().preferences.proxiwashShowBanner.current === '1', bannerVisible: AsyncStorageManager.getInstance().preferences.proxiwashShowBanner.current === '1',
@ -97,6 +96,10 @@ class ProxiwashScreen extends React.Component<Props, State> {
this.colors = props.theme.colors; this.colors = props.theme.colors;
} }
/**
* Callback used when closing the banner.
* This hides the banner and saves to preferences to prevent it from reopening
*/
onHideBanner() { onHideBanner() {
this.setState({bannerVisible: false}); this.setState({bannerVisible: false});
AsyncStorageManager.getInstance().savePref( AsyncStorageManager.getInstance().savePref(
@ -109,7 +112,7 @@ class ProxiwashScreen extends React.Component<Props, State> {
* Setup notification channel for android and add listeners to detect notifications fired * Setup notification channel for android and add listeners to detect notifications fired
*/ */
componentDidMount() { componentDidMount() {
const rightButton = this.getRightButton.bind(this); const rightButton = this.getAboutButton.bind(this);
this.props.navigation.setOptions({ this.props.navigation.setOptions({
headerRight: rightButton, headerRight: rightButton,
}); });
@ -134,16 +137,35 @@ class ProxiwashScreen extends React.Component<Props, State> {
} }
} }
getDryersKeyExtractor(item: Object) { /**
return item !== undefined ? "dryer" + item.number : undefined; * Callback used when pressing the about button.
} * This will open the ProxiwashAboutScreen.
*/
getWashersKeyExtractor(item: Object) { onAboutPress() {
return item !== undefined ? "washer" + item.number : undefined; this.props.navigation.navigate('ProxiwashAboutScreen');
} }
/** /**
* Setup notifications for the machine with the given ID. * Gets the about header button
*
* @return {*}
*/
getAboutButton() {
return <HeaderButton icon={'information'} onPress={this.onAboutPress}/>;
}
/**
* Extracts the key for the given item
*
* @param item The item to extract the key from
* @return {*} The extracted key
*/
getKeyExtractor(item: Object) {
return item !== undefined ? item.number : undefined;
}
/**
* Setups notifications for the machine with the given ID.
* One notification will be sent at the end of the program. * One notification will be sent at the end of the program.
* Another will be send a few minutes before the end, based on the value of reminderNotifTime * Another will be send a few minutes before the end, based on the value of reminderNotifTime
* *
@ -162,6 +184,9 @@ class ProxiwashScreen extends React.Component<Props, State> {
} }
} }
/**
* Shows a warning telling the user notifications are disabled for the app
*/
showNotificationsDisabledWarning() { showNotificationsDisabledWarning() {
Alert.alert( Alert.alert(
i18n.t("proxiwashScreen.modal.notificationErrorTitle"), i18n.t("proxiwashScreen.modal.notificationErrorTitle"),
@ -170,7 +195,7 @@ class ProxiwashScreen extends React.Component<Props, State> {
} }
/** /**
* Stop scheduled notifications for the machine of the given ID. * Stops scheduled notifications for the machine of the given ID.
* This will also remove the notification if it was already shown. * This will also remove the notification if it was already shown.
* *
* @param machineId The machine's ID * @param machineId The machine's ID
@ -187,7 +212,7 @@ class ProxiwashScreen extends React.Component<Props, State> {
} }
/** /**
* Add the given notifications associated to a machine ID to the watchlist, and save the array to the preferences * Adds the given notifications associated to a machine ID to the watchlist, and saves the array to the preferences
* *
* @param machineId * @param machineId
*/ */
@ -198,7 +223,7 @@ class ProxiwashScreen extends React.Component<Props, State> {
} }
/** /**
* remove the given index from the watchlist array and save it to preferences * Removes the given index from the watchlist array and saves it to preferences
* *
* @param index * @param index
*/ */
@ -209,14 +234,12 @@ class ProxiwashScreen extends React.Component<Props, State> {
} }
/** /**
* Set the given data as the watchlist and save it to preferences * Sets the given data as the watchlist
* *
* @param data * @param data
*/ */
updateNotificationState(data: Array<Object>) { updateNotificationState(data: Array<Object>) {
this.setState({machinesWatched: data}); this.setState({machinesWatched: data});
// let prefKey = AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.key;
// AsyncStorageManager.getInstance().savePref(prefKey, JSON.stringify(data));
} }
/** /**
@ -229,6 +252,12 @@ class ProxiwashScreen extends React.Component<Props, State> {
return this.state.machinesWatched.indexOf(machineID) !== -1; return this.state.machinesWatched.indexOf(machineID) !== -1;
} }
/**
* Creates the dataset to be used by the flatlist
*
* @param fetchedData
* @return {*}
*/
createDataset(fetchedData: Object) { createDataset(fetchedData: Object) {
let data = fetchedData; let data = fetchedData;
if (AprilFoolsManager.getInstance().isAprilFoolsEnabled()) { if (AprilFoolsManager.getInstance().isAprilFoolsEnabled()) {
@ -244,18 +273,25 @@ class ProxiwashScreen extends React.Component<Props, State> {
icon: 'tumble-dryer', icon: 'tumble-dryer',
data: data.dryers === undefined ? [] : data.dryers, data: data.dryers === undefined ? [] : data.dryers,
extraData: this.state, extraData: this.state,
keyExtractor: this.getDryersKeyExtractor keyExtractor: this.getKeyExtractor
}, },
{ {
title: i18n.t('proxiwashScreen.washers'), title: i18n.t('proxiwashScreen.washers'),
icon: 'washing-machine', icon: 'washing-machine',
data: data.washers === undefined ? [] : data.washers, data: data.washers === undefined ? [] : data.washers,
extraData: this.state, extraData: this.state,
keyExtractor: this.getWashersKeyExtractor keyExtractor: this.getKeyExtractor
}, },
]; ];
} }
/**
* Shows a modal for the given item
*
* @param title The title to use
* @param item The item to display information for in the modal
* @param isDryer True if the given item is a dryer
*/
showModal(title: string, item: Object, isDryer: boolean) { showModal(title: string, item: Object, isDryer: boolean) {
this.setState({ this.setState({
modalCurrentDisplayItem: this.getModalContent(title, item, isDryer) modalCurrentDisplayItem: this.getModalContent(title, item, isDryer)
@ -265,6 +301,11 @@ class ProxiwashScreen extends React.Component<Props, State> {
} }
} }
/**
* Callback used when the user clicks on enable notifications for a machine
*
* @param machineId The machine's id to set notifications for
*/
onSetupNotificationsPress(machineId: string) { onSetupNotificationsPress(machineId: string) {
if (this.modalRef) { if (this.modalRef) {
this.modalRef.close(); this.modalRef.close();
@ -272,6 +313,15 @@ class ProxiwashScreen extends React.Component<Props, State> {
this.setupNotifications(machineId) this.setupNotifications(machineId)
} }
/**
* Generates the modal content.
* This shows information for the given machine.
*
* @param title The title to use
* @param item The item to display information for in the modal
* @param isDryer True if the given item is a dryer
* @return {*}
*/
getModalContent(title: string, item: Object, isDryer: boolean) { getModalContent(title: string, item: Object, isDryer: boolean) {
let button = { let button = {
text: i18n.t("proxiwashScreen.modal.ok"), text: i18n.t("proxiwashScreen.modal.ok"),
@ -334,20 +384,21 @@ class ProxiwashScreen extends React.Component<Props, State> {
); );
} }
onAboutPress() { /**
this.props.navigation.navigate('ProxiwashAboutScreen'); * Callback used when receiving modal ref
} *
* @param ref
getRightButton() { */
return (
<HeaderButton icon={'information'} onPress={this.onAboutPress}/>
);
}
onModalRef(ref: Object) { onModalRef(ref: Object) {
this.modalRef = ref; this.modalRef = ref;
} }
/**
* Gets the number of machines available
*
* @param isDryer True if we are only checking for dryer, false for washers
* @return {number} The number of machines available
*/
getMachineAvailableNumber(isDryer: boolean) { getMachineAvailableNumber(isDryer: boolean) {
let data; let data;
if (isDryer) if (isDryer)
@ -362,6 +413,12 @@ class ProxiwashScreen extends React.Component<Props, State> {
return count; return count;
} }
/**
* Gets the section render item
*
* @param section The section to render
* @return {*}
*/
getRenderSectionHeader({section}: Object) { getRenderSectionHeader({section}: Object) {
const isDryer = section.title === i18n.t('proxiwashScreen.dryers'); const isDryer = section.title === i18n.t('proxiwashScreen.dryers');
const nbAvailable = this.getMachineAvailableNumber(isDryer); const nbAvailable = this.getMachineAvailableNumber(isDryer);
@ -401,7 +458,7 @@ class ProxiwashScreen extends React.Component<Props, State> {
} }
/** /**
* Get list item to be rendered * Gets the list item to be rendered
* *
* @param item The object containing the item's FetchedData * @param item The object containing the item's FetchedData
* @param section The object describing the current SectionList section * @param section The object describing the current SectionList section
@ -467,7 +524,6 @@ class ProxiwashScreen extends React.Component<Props, State> {
refreshOnFocus={true} refreshOnFocus={true}
updateData={this.state.machinesWatched.length}/> updateData={this.state.machinesWatched.length}/>
</View> </View>
); );
} }
} }

View file

@ -15,7 +15,6 @@ type Props = {
/** /**
* Class defining the app's menu screen. * Class defining the app's menu screen.
* This screen fetches data from etud to render the RU menu
*/ */
class SelfMenuScreen extends React.Component<Props> { class SelfMenuScreen extends React.Component<Props> {
@ -33,10 +32,22 @@ class SelfMenuScreen extends React.Component<Props> {
this.colors = props.theme.colors; this.colors = props.theme.colors;
} }
/**
* Extract a key for the given item
*
* @param item The item to extract the key from
* @return {*} The extracted key
*/
getKeyExtractor(item: Object) { getKeyExtractor(item: Object) {
return item !== undefined ? item['name'] : undefined; return item !== undefined ? item['name'] : undefined;
} }
/**
* Creates the dataset to be used in the FlatList
*
* @param fetchedData
* @return {[]}
*/
createDataset(fetchedData: Object) { createDataset(fetchedData: Object) {
let result = []; let result = [];
// Prevent crash by giving a default value when fetchedData is empty (not yet available) // Prevent crash by giving a default value when fetchedData is empty (not yet available)
@ -66,6 +77,12 @@ class SelfMenuScreen extends React.Component<Props> {
return result return result
} }
/**
* Gets the render section header
*
* @param section The section to render the header from
* @return {*}
*/
getRenderSectionHeader({section}: Object) { getRenderSectionHeader({section}: Object) {
return ( return (
<Card style={{ <Card style={{
@ -92,6 +109,12 @@ class SelfMenuScreen extends React.Component<Props> {
); );
} }
/**
* Gets a FlatList render item
*
* @param item The item to render
* @return {*}
*/
getRenderItem({item}: Object) { getRenderItem({item}: Object) {
return ( return (
<Card style={{ <Card style={{
@ -128,6 +151,12 @@ class SelfMenuScreen extends React.Component<Props> {
); );
} }
/**
* Formats the given string to make sure it starts with a capital letter
*
* @param name The string to format
* @return {string} The formatted string
*/
formatName(name: String) { formatName(name: String) {
return name.charAt(0) + name.substr(1).toLowerCase(); return name.charAt(0) + name.substr(1).toLowerCase();
} }

View file

@ -46,7 +46,7 @@ export default class SettingsScreen extends React.Component<Props, State> {
} }
/** /**
* Save the value for the proxiwash reminder notification time * Saves the value for the proxiwash reminder notification time
* *
* @param value The value to store * @param value The value to store
*/ */
@ -65,7 +65,7 @@ export default class SettingsScreen extends React.Component<Props, State> {
} }
/** /**
* Save the value for the proxiwash reminder notification time * Saves the value for the proxiwash reminder notification time
* *
* @param value The value to store * @param value The value to store
*/ */
@ -118,7 +118,7 @@ export default class SettingsScreen extends React.Component<Props, State> {
} }
/** /**
* Toggle night mode and save it to preferences * Toggles night mode and saves it to preferences
*/ */
onToggleNightMode() { onToggleNightMode() {
ThemeManager.getInstance().setNightMode(!this.state.nightMode); ThemeManager.getInstance().setNightMode(!this.state.nightMode);
@ -138,7 +138,7 @@ export default class SettingsScreen extends React.Component<Props, State> {
} }
/** /**
* Get a list item using a checkbox control * Gets a list item using a checkbox control
* *
* @param onPressCallback The callback when the checkbox state changes * @param onPressCallback The callback when the checkbox state changes
* @param icon The icon name to display on the list item * @param icon The icon name to display on the list item
@ -209,7 +209,6 @@ export default class SettingsScreen extends React.Component<Props, State> {
</List.Accordion> </List.Accordion>
</List.Section> </List.Section>
</Card> </Card>
</ScrollView> </ScrollView>
); );
} }

View file

@ -14,14 +14,16 @@ const PC_URL = 'http://planex.insa-toulouse.fr/sallesInfo.php';
const CUSTOM_CSS_GENERAL = 'https://etud.insa-toulouse.fr/~amicale_app/custom_css/rooms/customMobile.css'; const CUSTOM_CSS_GENERAL = 'https://etud.insa-toulouse.fr/~amicale_app/custom_css/rooms/customMobile.css';
/** /**
* Class defining the app's planex screen. * Class defining the app's available rooms screen.
* This screen uses a webview to render the planex page * This screen uses a webview to render the page
*/ */
export default class AvailableRoomScreen extends React.Component<Props> { export default class AvailableRoomScreen extends React.Component<Props> {
customInjectedJS: string; customInjectedJS: string;
customBibInjectedJS: string;
/**
* Defines custom injected JavaScript to improve the page display on mobile
*/
constructor() { constructor() {
super(); super();
this.customInjectedJS = this.customInjectedJS =

View file

@ -13,14 +13,17 @@ const CUSTOM_CSS_GENERAL = 'https://etud.insa-toulouse.fr/~amicale_app/custom_cs
const CUSTOM_CSS_Bib = 'https://etud.insa-toulouse.fr/~amicale_app/custom_css/rooms/customBibMobile.css'; const CUSTOM_CSS_Bib = 'https://etud.insa-toulouse.fr/~amicale_app/custom_css/rooms/customBibMobile.css';
/** /**
* Class defining the app's planex screen. * Class defining the app's Bib screen.
* This screen uses a webview to render the planex page * This screen uses a webview to render the page
*/ */
export default class AvailableRoomScreen extends React.Component<Props> { export default class AvailableRoomScreen extends React.Component<Props> {
customInjectedJS: string; customInjectedJS: string;
customBibInjectedJS: string; customBibInjectedJS: string;
/**
* Defines custom injected JavaScript to improve the page display on mobile
*/
constructor() { constructor() {
super(); super();
this.customInjectedJS = this.customInjectedJS =

View file

@ -80,15 +80,23 @@ const OBSERVE_MUTATIONS_INJECTED =
' removeAlpha($(this));\n' + ' removeAlpha($(this));\n' +
'});'; '});';
/** /**
* Class defining the app's planex screen. * Class defining the app's Planex screen.
* This screen uses a webview to render the planex page * This screen uses a webview to render the page
*/ */
export default class PlanexScreen extends React.Component<Props, State> { export default class PlanexScreen extends React.Component<Props, State> {
customInjectedJS: string; customInjectedJS: string;
onHideBanner: Function; onHideBanner: Function;
onGoToSettings: Function; onGoToSettings: Function;
state = {
bannerVisible:
AsyncStorageManager.getInstance().preferences.planexShowBanner.current === '1' &&
AsyncStorageManager.getInstance().preferences.defaultStartScreen.current !== 'Planex',
};
/**
* Defines custom injected JavaScript to improve the page display on mobile
*/
constructor() { constructor() {
super(); super();
this.customInjectedJS = this.customInjectedJS =
@ -102,17 +110,15 @@ export default class PlanexScreen extends React.Component<Props, State> {
this.customInjectedJS += this.customInjectedJS +=
'removeAlpha();' + 'removeAlpha();' +
'});true;'; // Prevent crash on ios '});true;'; // Prevents crash on ios
this.onHideBanner = this.onHideBanner.bind(this); this.onHideBanner = this.onHideBanner.bind(this);
this.onGoToSettings = this.onGoToSettings.bind(this); this.onGoToSettings = this.onGoToSettings.bind(this);
} }
state = { /**
bannerVisible: * Callback used when closing the banner.
AsyncStorageManager.getInstance().preferences.planexShowBanner.current === '1' && * This hides the banner and saves to preferences to prevent it from reopening
AsyncStorageManager.getInstance().preferences.defaultStartScreen.current !== 'Planex', */
};
onHideBanner() { onHideBanner() {
this.setState({bannerVisible: false}); this.setState({bannerVisible: false});
AsyncStorageManager.getInstance().savePref( AsyncStorageManager.getInstance().savePref(
@ -121,6 +127,11 @@ export default class PlanexScreen extends React.Component<Props, State> {
); );
} }
/**
* Callback used when the used click on the navigate to settings button.
* This will hide the banner and open the SettingsScreen
*
*/
onGoToSettings() { onGoToSettings() {
this.onHideBanner(); this.onHideBanner();
this.props.navigation.navigate('SettingsScreen'); this.props.navigation.navigate('SettingsScreen');

View file

@ -3,7 +3,7 @@
import {AsyncStorage} from "react-native"; import {AsyncStorage} from "react-native";
/** /**
* Static class used to manage preferences. * Singleton used to manage preferences.
* Preferences are fetched at the start of the app and saved in an instance object. * Preferences are fetched at the start of the app and saved in an instance object.
* This allows for a synchronous access to saved data. * This allows for a synchronous access to saved data.
*/ */
@ -14,7 +14,7 @@ export default class AsyncStorageManager {
/** /**
* Get this class instance or create one if none is found * Get this class instance or create one if none is found
* @returns {ThemeManager} * @returns {AsyncStorageManager}
*/ */
static getInstance(): AsyncStorageManager { static getInstance(): AsyncStorageManager {
return AsyncStorageManager.instance === null ? return AsyncStorageManager.instance === null ?
@ -39,11 +39,6 @@ export default class AsyncStorageManager {
default: '5', default: '5',
current: '', current: '',
}, },
proxiwashWatchedMachines: {
key: 'proxiwashWatchedMachines',
default: '[]',
current: '',
},
nightModeFollowSystem: { nightModeFollowSystem: {
key: 'nightModeFollowSystem', key: 'nightModeFollowSystem',
default: '1', default: '1',
@ -113,7 +108,7 @@ export default class AsyncStorageManager {
/** /**
* Save the value associated to the given key to preferences. * Save the value associated to the given key to preferences.
* This updates the preferences object and saves it to AsynStorage. * This updates the preferences object and saves it to AsyncStorage.
* *
* @param key * @param key
* @param val * @param val

View file

@ -2,10 +2,13 @@
import i18n from 'i18n-js'; import i18n from 'i18n-js';
/**
* Singleton used to manage date translations.
* Translations are hardcoded as toLocaleDateString does not work on current android JS engine
*/
export default class DateManager { export default class DateManager {
static instance: DateManager | null = null; static instance: DateManager | null = null;
// Hard code strings as toLocaleDateString does not work on current android JS engine
daysOfWeek = []; daysOfWeek = [];
monthsOfYear = []; monthsOfYear = [];
@ -42,6 +45,12 @@ export default class DateManager {
DateManager.instance; DateManager.instance;
} }
/**
* Gets a translated string representing the given date.
*
* @param dateString The date with the format YYYY-MM-DD
* @return {string} The translated string
*/
getTranslatedDate(dateString: string) { getTranslatedDate(dateString: string) {
let dateArray = dateString.split('-'); let dateArray = dateString.split('-');
let date = new Date(); let date = new Date();

View file

@ -49,6 +49,11 @@ export default class NotificationsManager {
} }
} }
/**
* Gets the machines watched from the server
*
* @param callback Function to execute with the fetched data
*/
static getMachineNotificationWatchlist(callback: Function) { static getMachineNotificationWatchlist(callback: Function) {
let token = AsyncStorageManager.getInstance().preferences.expoToken.current; let token = AsyncStorageManager.getInstance().preferences.expoToken.current;
if (token !== '') { if (token !== '') {
@ -72,10 +77,10 @@ export default class NotificationsManager {
} }
/** /**
* Ask the server to enable/disable notifications for the specified machine * Asks the server to enable/disable notifications for the specified machine
* *
* @param machineID * @param machineID The machine ID
* @param isEnabled * @param isEnabled True to enable notifications, false to disable
*/ */
static setupMachineNotification(machineID: string, isEnabled: boolean) { static setupMachineNotification(machineID: string, isEnabled: boolean) {
let token = AsyncStorageManager.getInstance().preferences.expoToken.current; let token = AsyncStorageManager.getInstance().preferences.expoToken.current;
@ -100,8 +105,9 @@ export default class NotificationsManager {
} }
/** /**
* Send the selected reminder time for notifications to the server * Sends the selected reminder time for notifications to the server
* @param time *
* @param time The reminder time to use
*/ */
static setMachineReminderNotificationTime(time: number) { static setMachineReminderNotificationTime(time: number) {
let token = AsyncStorageManager.getInstance().preferences.expoToken.current; let token = AsyncStorageManager.getInstance().preferences.expoToken.current;

View file

@ -19,7 +19,12 @@ export default class ThemeManager {
this.updateThemeCallback = null; this.updateThemeCallback = null;
} }
static getWhiteTheme() { /**
* Gets the light theme
*
* @return {Object} Object containing theme variables
* */
static getWhiteTheme(): Object {
return { return {
...DefaultTheme, ...DefaultTheme,
colors: { colors: {
@ -70,7 +75,12 @@ export default class ThemeManager {
}; };
} }
static getDarkTheme() { /**
* Gets the dark theme
*
* @return {Object} Object containing theme variables
* */
static getDarkTheme(): Object {
return { return {
...DarkTheme, ...DarkTheme,
colors: { colors: {
@ -124,6 +134,7 @@ export default class ThemeManager {
/** /**
* Get this class instance or create one if none is found * Get this class instance or create one if none is found
*
* @returns {ThemeManager} * @returns {ThemeManager}
*/ */
static getInstance(): ThemeManager { static getInstance(): ThemeManager {
@ -133,6 +144,10 @@ export default class ThemeManager {
} }
/** /**
* Gets night mode status.
* If Follow System Preferences is enabled, will first use system theme.
* If disabled or not available, will use value stored din preferences
*
* @returns {boolean} Night mode state * @returns {boolean} Night mode state
*/ */
static getNightMode(): boolean { static getNightMode(): boolean {
@ -143,8 +158,9 @@ export default class ThemeManager {
} }
/** /**
* Get the current theme based on night mode * Get the current theme based on night mode and events
* @returns {Object} *
* @returns {Object} The current theme
*/ */
static getCurrentTheme(): Object { static getCurrentTheme(): Object {
if (AprilFoolsManager.getInstance().isAprilFoolsEnabled()) if (AprilFoolsManager.getInstance().isAprilFoolsEnabled())
@ -153,6 +169,11 @@ export default class ThemeManager {
return ThemeManager.getBaseTheme() return ThemeManager.getBaseTheme()
} }
/**
* Get the theme based on night mode
*
* @return {Object} The theme
*/
static getBaseTheme() { static getBaseTheme() {
if (ThemeManager.getNightMode()) if (ThemeManager.getNightMode())
return ThemeManager.getDarkTheme(); return ThemeManager.getDarkTheme();
@ -161,7 +182,8 @@ export default class ThemeManager {
} }
/** /**
* Set the function to be called when the theme is changed (allows for general reload of the app) * Sets the function to be called when the theme is changed (allows for general reload of the app)
*
* @param callback Function to call after theme change * @param callback Function to call after theme change
*/ */
setUpdateThemeCallback(callback: ?Function) { setUpdateThemeCallback(callback: ?Function) {
@ -171,7 +193,7 @@ export default class ThemeManager {
/** /**
* Set night mode and save it to preferences * Set night mode and save it to preferences
* *
* @param isNightMode Whether to enable night mode * @param isNightMode True to enable night mode, false to disable
*/ */
setNightMode(isNightMode: boolean) { setNightMode(isNightMode: boolean) {
let nightModeKey = AsyncStorageManager.getInstance().preferences.nightMode.key; let nightModeKey = AsyncStorageManager.getInstance().preferences.nightMode.key;