Added documentation

This commit is contained in:
keplyx 2019-06-29 15:43:57 +02:00
parent 0385bbb882
commit 0bdcf5a4ba
13 changed files with 260 additions and 27 deletions

View file

@ -13,14 +13,19 @@ type Props = {
navigation: Object, navigation: Object,
}; };
/**
* Custom component defining a header using native base
*
* @prop backButton {boolean} Whether to show a back button or a burger menu. Use burger if unspecified
* @prop rightMenu {React.Node} Element to place at the right of the header. Use nothing if unspecified
* @prop title {string} This header title
* @prop navigation {Object} The navigation object from react navigation
*/
export default class CustomHeader extends React.Component<Props> { export default class CustomHeader extends React.Component<Props> {
static defaultProps = { static defaultProps = {
backButton: false, backButton: false,
rightMenu: <Right/>, rightMenu: <Right/>,
fontSize: 26,
width: 30,
}; };
render() { render() {

View file

@ -12,6 +12,15 @@ type Props = {
width: number, width: number,
} }
/**
* Custom component defining a material icon using native base
*
* @prop active {boolean} Whether to set the icon color to active
* @prop icon {string} The icon string to use from MaterialCommunityIcons
* @prop color {string} The icon color. Use default theme color if unspecified
* @prop fontSize {number} The icon size. Use 26 if unspecified
* @prop width {number} The icon width. Use 30 if unspecified
*/
export default class CustomMaterialIcon extends React.Component<Props> { export default class CustomMaterialIcon extends React.Component<Props> {
static defaultProps = { static defaultProps = {

View file

@ -20,15 +20,24 @@ type State = {
active: string, active: string,
}; };
/**
* Class used to define a navigation drawer
*/
export default class SideBar extends React.Component<Props, State> { export default class SideBar extends React.Component<Props, State> {
dataSet: Array<Object>; dataSet: Array<Object>;
constructor(props: Props) { state = {
super(props);
this.state = {
active: 'Home', active: 'Home',
}; };
/**
* Generate the datasets
*
* @param props
*/
constructor(props: Props) {
super(props);
this.dataSet = [ this.dataSet = [
{ {
name: i18n.t('screens.home'), name: i18n.t('screens.home'),
@ -82,6 +91,10 @@ export default class SideBar extends React.Component<Props, State> {
]; ];
} }
/**
* Navigate to the selected route, close the drawer, and mark the correct item as selected
* @param route {string} The route name to navigate to
*/
navigateToScreen(route: string) { navigateToScreen(route: string) {
this.props.navigation.navigate(route); this.props.navigation.navigate(route);
this.props.navigation.closeDrawer(); this.props.navigation.closeDrawer();

View file

@ -6,6 +6,9 @@ import MainDrawerNavigator from './MainDrawerNavigator';
import ProximoListScreen from '../screens/Proximo/ProximoListScreen'; import ProximoListScreen from '../screens/Proximo/ProximoListScreen';
import AboutDependenciesScreen from '../screens/About/AboutDependenciesScreen'; import AboutDependenciesScreen from '../screens/About/AboutDependenciesScreen';
/**
* Create a stack navigator using the drawer to handle navigation between screens
*/
export default createAppContainer( export default createAppContainer(
createStackNavigator({ createStackNavigator({
Main: MainDrawerNavigator, Main: MainDrawerNavigator,

View file

@ -11,7 +11,9 @@ import SettingsScreen from '../screens/SettingsScreen';
import AboutScreen from '../screens/About/AboutScreen'; import AboutScreen from '../screens/About/AboutScreen';
import SideMenu from "../components/SideMenu"; import SideMenu from "../components/SideMenu";
/**
* Creates the drawer navigation stack
*/
export default createDrawerNavigator({ export default createDrawerNavigator({
Home: {screen: HomeScreen}, Home: {screen: HomeScreen},
Planning: {screen: PlanningScreen,}, Planning: {screen: PlanningScreen,},

View file

@ -20,6 +20,9 @@ type Props = {
navigation: Object navigation: Object
} }
/**
* Class defining a screen showing the list of libraries used by the app, taken from package.json
*/
export default class AboutDependenciesScreen extends React.Component<Props> { export default class AboutDependenciesScreen extends React.Component<Props> {
render() { render() {

View file

@ -26,13 +26,22 @@ type Props = {
navigation: Object, navigation: Object,
}; };
/**
* Opens a link in the device's browser
* @param link The link to open
*/
function openWebLink(link) { function openWebLink(link) {
Linking.openURL(link).catch((err) => console.error('Error opening link', err)); Linking.openURL(link).catch((err) => console.error('Error opening link', err));
} }
/**
* Class defining an about screen. This screen shows the user information about the app and it's author.
*/
export default class AboutScreen extends React.Component<Props> { export default class AboutScreen extends React.Component<Props> {
/**
* Data to be displayed in the app card
*/
appData: Array<Object> = [ appData: Array<Object> = [
{ {
onPressCallback: () => openWebLink(Platform.OS === "ios" ? links.appstore : links.playstore), onPressCallback: () => openWebLink(Platform.OS === "ios" ? links.appstore : links.playstore),
@ -66,6 +75,9 @@ export default class AboutScreen extends React.Component<Props> {
}, },
]; ];
/**
* Data to be displayed in the author card
*/
authorData: Array<Object> = [ authorData: Array<Object> = [
{ {
onPressCallback: () => Alert.alert('Coucou', 'Whaou'), onPressCallback: () => Alert.alert('Coucou', 'Whaou'),
@ -93,6 +105,9 @@ export default class AboutScreen extends React.Component<Props> {
}, },
]; ];
/**
* Data to be displayed in the technologies card
*/
technoData: Array<Object> = [ technoData: Array<Object> = [
{ {
onPressCallback: () => openWebLink(links.react), onPressCallback: () => openWebLink(links.react),
@ -108,7 +123,16 @@ export default class AboutScreen extends React.Component<Props> {
}, },
]; ];
getCardItem(onPressCallback: Function, icon: string, text: string, showChevron: boolean) { /**
* Get a clickable card item to be rendered inside a card.
*
* @param onPressCallback The callback to use when the item is clicked
* @param icon The icon name to use from MaterialCommunityIcons
* @param text The text to show
* @param showChevron Whether to show a chevron indicating this button will change screen
* @returns {React.Node}
*/
static getCardItem(onPressCallback: Function, icon: string, text: string, showChevron: boolean) {
return ( return (
<CardItem button <CardItem button
onPress={onPressCallback}> onPress={onPressCallback}>
@ -150,7 +174,7 @@ export default class AboutScreen extends React.Component<Props> {
data={this.appData} data={this.appData}
keyExtractor={(item) => item.icon} keyExtractor={(item) => item.icon}
renderItem={({item}) => renderItem={({item}) =>
this.getCardItem(item.onPressCallback, item.icon, item.text, item.showChevron) AboutScreen.getCardItem(item.onPressCallback, item.icon, item.text, item.showChevron)
} }
/> />
</Card> </Card>
@ -163,7 +187,7 @@ export default class AboutScreen extends React.Component<Props> {
data={this.authorData} data={this.authorData}
keyExtractor={(item) => item.icon} keyExtractor={(item) => item.icon}
renderItem={({item}) => renderItem={({item}) =>
this.getCardItem(item.onPressCallback, item.icon, item.text, item.showChevron) AboutScreen.getCardItem(item.onPressCallback, item.icon, item.text, item.showChevron)
} }
/> />
</Card> </Card>
@ -176,7 +200,7 @@ export default class AboutScreen extends React.Component<Props> {
data={this.technoData} data={this.technoData}
keyExtractor={(item) => item.icon} keyExtractor={(item) => item.icon}
renderItem={({item}) => renderItem={({item}) =>
this.getCardItem(item.onPressCallback, item.icon, item.text, item.showChevron) AboutScreen.getCardItem(item.onPressCallback, item.icon, item.text, item.showChevron)
} }
/> />
</Card> </Card>

View file

@ -10,6 +10,9 @@ type Props = {
navigation: Object, navigation: Object,
} }
/**
* Class defining the app's home screen
*/
export default class HomeScreen extends React.Component<Props> { export default class HomeScreen extends React.Component<Props> {
render() { render() {
const nav = this.props.navigation; const nav = this.props.navigation;

View file

@ -9,6 +9,9 @@ type Props = {
navigation: Object, navigation: Object,
} }
/**
* Class defining the app's planning screen
*/
export default class PlanningScreen extends React.Component<Props> { export default class PlanningScreen extends React.Component<Props> {
render() { render() {
const nav = this.props.navigation; const nav = this.props.navigation;

View file

@ -16,7 +16,6 @@ const sortMode = {
name: '1', name: '1',
}; };
function sortPrice(a, b) { function sortPrice(a, b) {
return a.price - b.price; return a.price - b.price;
} }
@ -53,7 +52,10 @@ type State = {
sortNameIcon: React.Node, sortNameIcon: React.Node,
}; };
export default class ProximoMainScreen extends React.Component<Props, State> { /**
* Class defining proximo's article list of a certain category.
*/
export default class ProximoListScreen extends React.Component<Props, State> {
state = { state = {
navData: this.props.navigation.getParam('data', []).sort(sortPrice), navData: this.props.navigation.getParam('data', []).sort(sortPrice),
@ -65,11 +67,22 @@ export default class ProximoMainScreen extends React.Component<Props, State> {
_menu: Menu; _menu: Menu;
/**
* Saves the reference to the sort menu for later use
*
* @param ref The menu reference
*/
setMenuRef = (ref: Menu) => { setMenuRef = (ref: Menu) => {
this._menu = ref; this._menu = ref;
}; };
toggleSortMode(mode: string) { /**
* Sets the sort mode based on the one selected.
* If the selected mode is the current one, reverse it.
*
* @param mode The string representing the mode
*/
sortModeSelected(mode: string) {
let isReverse = this.state.isSortReversed; let isReverse = this.state.isSortReversed;
if (mode === this.state.currentSortMode) // reverse mode if (mode === this.state.currentSortMode) // reverse mode
isReverse = !isReverse; // this.state not updating on this function cycle isReverse = !isReverse; // this.state not updating on this function cycle
@ -78,6 +91,12 @@ export default class ProximoMainScreen extends React.Component<Props, State> {
this.setSortMode(mode, isReverse); this.setSortMode(mode, isReverse);
} }
/**
* Set the current sort mode.
*
* @param mode The string representing the mode
* @param isReverse Whether to use a reverse sort
*/
setSortMode(mode: string, isReverse: boolean) { setSortMode(mode: string, isReverse: boolean) {
this.setState({ this.setState({
currentSortMode: mode, currentSortMode: mode,
@ -107,10 +126,19 @@ export default class ProximoMainScreen extends React.Component<Props, State> {
this._menu.hide(); this._menu.hide();
} }
/**
* Set the sort mode from state when components are ready
*/
componentDidMount() { componentDidMount() {
this.setSortMode(this.state.currentSortMode, this.state.isSortReversed); this.setSortMode(this.state.currentSortMode, this.state.isSortReversed);
} }
/**
* Set the sort menu icon based on the given mode.
*
* @param mode The string representing the mode
* @param isReverse Whether to use a reversed icon
*/
setupSortIcons(mode: string, isReverse: boolean) { setupSortIcons(mode: string, isReverse: boolean) {
const downSortIcon = const downSortIcon =
<Icon <Icon
@ -166,12 +194,12 @@ export default class ProximoMainScreen extends React.Component<Props, State> {
} }
> >
<MenuItem <MenuItem
onPress={() => this.toggleSortMode(sortMode.name)}> onPress={() => this.sortModeSelected(sortMode.name)}>
{this.state.sortNameIcon} {this.state.sortNameIcon}
{i18n.t('proximoScreen.sortName')} {i18n.t('proximoScreen.sortName')}
</MenuItem> </MenuItem>
<MenuItem <MenuItem
onPress={() => this.toggleSortMode(sortMode.price)}> onPress={() => this.sortModeSelected(sortMode.price)}>
{this.state.sortPriceIcon} {this.state.sortPriceIcon}
{i18n.t('proximoScreen.sortPrice')} {i18n.t('proximoScreen.sortPrice')}
</MenuItem> </MenuItem>

View file

@ -27,6 +27,10 @@ type State = {
data: Object, data: Object,
}; };
/**
* Class defining the main proximo screen. This screen shows the different categories of articles
* offered by proximo.
*/
export default class ProximoMainScreen extends React.Component<Props, State> { export default class ProximoMainScreen extends React.Component<Props, State> {
state = { state = {
@ -35,7 +39,15 @@ export default class ProximoMainScreen extends React.Component<Props, State> {
data: {}, data: {},
}; };
static generateDataset(types: Array<string>, data: Object) { /**
* Generate the dataset using types and data.
* This will group items under the same type.
*
* @param types An array containing the types available (categories)
* @param data The array of articles represented by objects
* @returns {Array} The formatted dataset
*/
static generateDataset(types: Array<string>, data: Array<Object>) {
let finalData = []; let finalData = [];
for (let i = 0; i < types.length; i++) { for (let i = 0; i < types.length; i++) {
finalData.push({ finalData.push({
@ -51,6 +63,11 @@ export default class ProximoMainScreen extends React.Component<Props, State> {
return finalData; return finalData;
} }
/**
* Async function reading data from the proximo website and setting the state to rerender the list
*
* @returns {Promise<void>}
*/
async readData() { async readData() {
try { try {
let response = await fetch(DATA_URL); let response = await fetch(DATA_URL);
@ -68,10 +85,18 @@ export default class ProximoMainScreen extends React.Component<Props, State> {
} }
} }
/**
* Refresh the list on first screen load
*/
componentDidMount() { componentDidMount() {
this._onRefresh(); this._onRefresh();
} }
/**
* Display a loading indicator and fetch data from the internet
*
* @private
*/
_onRefresh = () => { _onRefresh = () => {
this.setState({refreshing: true}); this.setState({refreshing: true});
this.readData().then(() => { this.readData().then(() => {
@ -88,7 +113,12 @@ export default class ProximoMainScreen extends React.Component<Props, State> {
}); });
}; };
/**
* Renders the proximo categories list.
* If we are loading for the first time, change the data for the SectionList to display a loading message.
*
* @returns {react.Node}
*/
render() { render() {
const nav = this.props.navigation; const nav = this.props.navigation;
const data = [ const data = [

View file

@ -38,6 +38,10 @@ type State = {
machinesWatched: Array<Object> machinesWatched: Array<Object>
}; };
/**
* Class defining the app's proxiwash screen. This screen shows information about washing machines and
* dryers, taken from a scrapper reading proxiwash website
*/
export default class ProxiwashScreen extends React.Component<Props, State> { export default class ProxiwashScreen extends React.Component<Props, State> {
state = { state = {
@ -47,6 +51,11 @@ export default class ProxiwashScreen extends React.Component<Props, State> {
machinesWatched: [], machinesWatched: [],
}; };
/**
* Creates machine state parameters using current theme and translations
*
* @param props
*/
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
let colors = ThemeManager.getInstance().getCurrentThemeVariables(); let colors = ThemeManager.getInstance().getCurrentThemeVariables();
@ -63,6 +72,11 @@ export default class ProxiwashScreen extends React.Component<Props, State> {
stateStrings[MACHINE_STATES.ERREUR] = i18n.t('proxiwashScreen.states.error'); stateStrings[MACHINE_STATES.ERREUR] = i18n.t('proxiwashScreen.states.error');
} }
/**
* Read the data from the proxiwash scrapper and set it to current state to reload the screen
*
* @returns {Promise<void>}
*/
async readData() { async readData() {
try { try {
let response = await fetch(DATA_URL); let response = await fetch(DATA_URL);
@ -75,6 +89,11 @@ export default class ProxiwashScreen extends React.Component<Props, State> {
} }
} }
/**
* Get which machines have notifications enabled before loading the screen
*
* @returns {Promise<void>}
*/
async componentWillMount() { async componentWillMount() {
let dataString = await AsyncStorage.getItem(WATCHED_MACHINES_PREFKEY); let dataString = await AsyncStorage.getItem(WATCHED_MACHINES_PREFKEY);
if (dataString === null) if (dataString === null)
@ -84,11 +103,18 @@ export default class ProxiwashScreen extends React.Component<Props, State> {
}); });
} }
/**
* Refresh the data on first screen load
*/
componentDidMount() { componentDidMount() {
this._onRefresh(); this._onRefresh();
} }
/**
* Show the refresh inddicator and wait for data to be fetched from the scrapper
*
* @private
*/
_onRefresh = () => { _onRefresh = () => {
this.setState({refreshing: true}); this.setState({refreshing: true});
this.readData().then(() => { this.readData().then(() => {
@ -105,6 +131,14 @@ export default class ProxiwashScreen extends React.Component<Props, State> {
}); });
}; };
/**
* Get the time remaining based on start/end time and done percent
*
* @param startString The string representing the start time. Format: hh:mm
* @param endString The string representing the end time. Format: hh:mm
* @param percentDone The percentage done
* @returns {number} How many minutes are remaining for this machine
*/
static getRemainingTime(startString: string, endString: string, percentDone: string): number { static getRemainingTime(startString: string, endString: string, percentDone: string): number {
let startArray = startString.split(':'); let startArray = startString.split(':');
let endArray = endString.split(':'); let endArray = endString.split(':');
@ -117,6 +151,15 @@ export default class ProxiwashScreen extends React.Component<Props, State> {
return parseInt(time); return parseInt(time);
} }
/**
* Setup notifications for the machine with the given ID.
* 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
*
* @param machineId The machine's ID
* @param remainingTime The time remaining for this machine
* @returns {Promise<void>}
*/
async setupNotifications(machineId: string, remainingTime: number) { async setupNotifications(machineId: string, remainingTime: number) {
if (!this.isMachineWatched(machineId)) { if (!this.isMachineWatched(machineId)) {
let endNotifID = await NotificationsManager.scheduleNotification( let endNotifID = await NotificationsManager.scheduleNotification(
@ -148,6 +191,12 @@ export default class ProxiwashScreen extends React.Component<Props, State> {
this.disableNotification(machineId); this.disableNotification(machineId);
} }
/**
* Stop scheduled notifications for the machine of the given ID.
* This will also remove the notification if it was already shown.
*
* @param machineId The machine's ID
*/
disableNotification(machineId: string) { disableNotification(machineId: string) {
let data: Object = this.state.machinesWatched; let data: Object = this.state.machinesWatched;
if (data.length > 0) { if (data.length > 0) {
@ -164,12 +213,26 @@ export default class ProxiwashScreen extends React.Component<Props, State> {
} }
} }
isMachineWatched(number: string) { /**
* Checks whether the machine of the given ID has scheduled notifications
*
* @param machineID The machine's ID
* @returns {boolean}
*/
isMachineWatched(machineID: string) {
return this.state.machinesWatched.find(function (elem) { return this.state.machinesWatched.find(function (elem) {
return elem.machineNumber === number return elem.machineNumber === machineID
}) !== undefined; }) !== undefined;
} }
/**
* Get list item to be rendered
*
* @param item The object containing the item's data
* @param section The object describing the current SectionList section
* @param data The full data used by the SectionList
* @returns {React.Node}
*/
renderItem(item: Object, section: Object, data: Object) { renderItem(item: Object, section: Object, data: Object) {
return ( return (
<ListItem <ListItem
@ -226,6 +289,12 @@ export default class ProxiwashScreen extends React.Component<Props, State> {
</ListItem>); </ListItem>);
} }
/**
* Renders the machines list.
* If we are loading for the first time, change the data for the SectionList to display a loading message.
*
* @returns {react.Node}
*/
render() { render() {
const nav = this.props.navigation; const nav = this.props.navigation;
const data = [ const data = [

View file

@ -33,12 +33,20 @@ type State = {
proxiwashNotifPickerSelected: string, proxiwashNotifPickerSelected: string,
}; };
/**
* Class defining the Settings screen. This screen shows controls to modify app preferences.
*/
export default class SettingsScreen extends React.Component<Props, State> { export default class SettingsScreen extends React.Component<Props, State> {
state = { state = {
nightMode: ThemeManager.getInstance().getNightMode(), nightMode: ThemeManager.getInstance().getNightMode(),
proxiwashNotifPickerSelected: "5" proxiwashNotifPickerSelected: "5"
}; };
/**
* Gets data from preferences before rendering components
*
* @returns {Promise<void>}
*/
async componentWillMount() { async componentWillMount() {
let val = await AsyncStorage.getItem(proxiwashNotifKey); let val = await AsyncStorage.getItem(proxiwashNotifKey);
if (val === null) if (val === null)
@ -48,7 +56,11 @@ export default class SettingsScreen extends React.Component<Props, State> {
}); });
} }
/**
* Save the value for the proxiwash reminder notification time
*
* @param value The value to store
*/
onProxiwashNotifPickerValueChange(value: string) { onProxiwashNotifPickerValueChange(value: string) {
AsyncStorage.setItem(proxiwashNotifKey, value); AsyncStorage.setItem(proxiwashNotifKey, value);
this.setState({ this.setState({
@ -56,6 +68,11 @@ export default class SettingsScreen extends React.Component<Props, State> {
}); });
} }
/**
* Returns a picker allowing the user to select the proxiwash reminder notification time
*
* @returns {React.Node}
*/
getProxiwashNotifPicker() { getProxiwashNotifPicker() {
return ( return (
<Picker <Picker
@ -77,6 +94,9 @@ export default class SettingsScreen extends React.Component<Props, State> {
); );
} }
/**
* Toggle night mode and save it to preferences
*/
toggleNightMode() { toggleNightMode() {
ThemeManager.getInstance().setNightMode(!this.state.nightMode); ThemeManager.getInstance().setNightMode(!this.state.nightMode);
this.setState({nightMode: !this.state.nightMode}); this.setState({nightMode: !this.state.nightMode});
@ -84,6 +104,9 @@ export default class SettingsScreen extends React.Component<Props, State> {
this.resetStack(); this.resetStack();
} }
/**
* Reset react navigation stack to allow for a theme reset
*/
resetStack() { resetStack() {
const resetAction = StackActions.reset({ const resetAction = StackActions.reset({
index: 0, index: 0,
@ -94,7 +117,16 @@ export default class SettingsScreen extends React.Component<Props, State> {
this.props.navigation.navigate('Settings'); this.props.navigation.navigate('Settings');
} }
getToggleItem(onPressCallback: Function, icon: string, text: string, subtitle: string) { /**
* Get a list item using a checkbox control
*
* @param onPressCallback The callback when the checkbox state changes
* @param icon The icon name to display on the list item
* @param title The text to display as this list item title
* @param subtitle The text to display as this list item subtitle
* @returns {React.Node}
*/
getToggleItem(onPressCallback: Function, icon: string, title: string, subtitle: string) {
return ( return (
<ListItem <ListItem
button button
@ -106,7 +138,7 @@ export default class SettingsScreen extends React.Component<Props, State> {
</Left> </Left>
<Body> <Body>
<Text> <Text>
{text} {title}
</Text> </Text>
<Text note> <Text note>
{subtitle} {subtitle}
@ -120,7 +152,16 @@ export default class SettingsScreen extends React.Component<Props, State> {
); );
} }
static getGeneralItem(control: React.Node, icon: string, text: string, subtitle: string) { /**
* Get a list item using the specified control
*
* @param control The custom control to use
* @param icon The icon name to display on the list item
* @param title The text to display as this list item title
* @param subtitle The text to display as this list item subtitle
* @returns {React.Node}
*/
static getGeneralItem(control: React.Node, icon: string, title: string, subtitle: string) {
return ( return (
<ListItem <ListItem
thumbnail thumbnail
@ -130,7 +171,7 @@ export default class SettingsScreen extends React.Component<Props, State> {
</Left> </Left>
<Body> <Body>
<Text> <Text>
{text} {title}
</Text> </Text>
<Text note> <Text note>
{subtitle} {subtitle}