diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 0000000..1fed445 --- /dev/null +++ b/.flowconfig @@ -0,0 +1,11 @@ +[ignore] + +[include] + +[libs] + +[lints] + +[options] + +[strict] diff --git a/App.js b/App.js index bfc5ba5..ac00160 100644 --- a/App.js +++ b/App.js @@ -1,3 +1,5 @@ +// @flow + import React from 'react'; import {StyleProvider, Root, View} from 'native-base'; import AppNavigator from './navigation/AppNavigator'; @@ -7,26 +9,37 @@ import * as Font from 'expo-font'; // edited native-base-shoutem-theme according to // https://github.com/GeekyAnts/theme/pull/5/files/91f67c55ca6e65fe3af779586b506950c9f331be#diff-4cfc2dd4d5dae7954012899f2268a422 // to allow for dynamic theme switching -import { clearThemeCache } from 'native-base-shoutem-theme'; +import {clearThemeCache} from 'native-base-shoutem-theme'; -export default class App extends React.Component { +type Props = {}; - constructor(props) { +type State = { + isLoading: boolean, + currentTheme: ?Object, +}; + +export default class App extends React.Component { + + state = { + isLoading: true, + currentTheme: null, + }; + + constructor(props: Object) { super(props); - LocaleManager.getInstance().initTranslations(); - this.updateTheme = this.updateTheme.bind(this); - this.state = { - isLoading: true, - currentTheme: undefined, - }; + LocaleManager.initTranslations(); } + /** + * Loads data before components are mounted, like fonts and themes + * @returns {Promise} + */ async componentWillMount() { await Font.loadAsync({ 'Roboto': require('native-base/Fonts/Roboto.ttf'), 'Roboto_medium': require('native-base/Fonts/Roboto_medium.ttf'), }); - ThemeManager.getInstance().setUpdateThemeCallback(this.updateTheme); + ThemeManager.getInstance().setUpdateThemeCallback(() => this.updateTheme()); await ThemeManager.getInstance().getDataFromPreferences(); this.setState({ isLoading: false, @@ -34,6 +47,9 @@ export default class App extends React.Component { }); } + /** + * Updates the theme and clears the cache to force reloading the app colors + */ updateTheme() { // console.log('update theme called'); this.setState({ @@ -42,6 +58,11 @@ export default class App extends React.Component { clearThemeCache(); } + /** + * Renders the app based on loading state + * + * @returns {*} + */ render() { if (this.state.isLoading) { return ; diff --git a/components/CustomHeader.js b/components/CustomHeader.js index 2c6c1ed..d701046 100644 --- a/components/CustomHeader.js +++ b/components/CustomHeader.js @@ -1,15 +1,31 @@ -import React from "react"; -import {Body, Button, Header, Icon, Left, Right, Title} from "native-base"; +// @flow + +import * as React from "react"; +import {Body, Header, Icon, Left, Right, Title} from "native-base"; import {StyleSheet} from "react-native"; import {getStatusBarHeight} from "react-native-status-bar-height"; import Touchable from 'react-native-platform-touchable'; +type Props = { + backButton: boolean, + rightMenu: React.Node, + title: string, + navigation: Object, +}; + + +export default class CustomHeader extends React.Component { + + static defaultProps = { + backButton: false, + rightMenu: , + fontSize: 26, + width: 30, + }; -export default class CustomHeader extends React.Component { render() { let button; - let rightMenu; - if (this.props.backButton !== undefined && this.props.backButton === true) + if (this.props.backButton) button = ; - if (this.props.rightMenu) - rightMenu = this.props.rightMenu; - else - rightMenu = ; - return (
@@ -43,7 +54,7 @@ export default class CustomHeader extends React.Component { {this.props.title} - {rightMenu} + {this.props.rightMenu}
); } }; diff --git a/components/CustomMaterialIcon.js b/components/CustomMaterialIcon.js index 146d2fa..656c31f 100644 --- a/components/CustomMaterialIcon.js +++ b/components/CustomMaterialIcon.js @@ -1,12 +1,25 @@ -import React from 'react'; +// @flow + +import * as React from 'react'; import {Icon} from "native-base"; import ThemeManager from '../utils/ThemeManager'; -export default class CustomMaterialIcon extends React.Component { +type Props = { + active: boolean, + icon: string, + color: ?string, + fontSize: number, + width: number, +} - constructor(props) { - super(props); - } +export default class CustomMaterialIcon extends React.Component { + + static defaultProps = { + active: false, + color: undefined, + fontSize: 26, + width: 30, + }; render() { return ( @@ -21,8 +34,8 @@ export default class CustomMaterialIcon extends React.Component { this.props.active ? ThemeManager.getInstance().getCurrentThemeVariables().brandPrimary : ThemeManager.getInstance().getCurrentThemeVariables().customMaterialIconColor, - fontSize: this.props.fontSize !== undefined ? this.props.fontSize : 26, - width: this.props.width !== undefined ? this.props.width : 30 + fontSize: this.props.fontSize, + width: this.props.width }} /> ); diff --git a/components/SideMenu.js b/components/SideMenu.js index 386b263..af745e4 100644 --- a/components/SideMenu.js +++ b/components/SideMenu.js @@ -1,6 +1,8 @@ -import React from 'react'; +// @flow + +import * as React from 'react'; import {Platform, Dimensions, StyleSheet, Image, FlatList, Linking} from 'react-native'; -import {Badge, Text, Container, Content, Icon, Left, ListItem, Right} from "native-base"; +import {Badge, Text, Container, Content, Left, ListItem, Right} from "native-base"; import i18n from "i18n-js"; import CustomMaterialIcon from '../components/CustomMaterialIcon'; @@ -10,9 +12,19 @@ const drawerCover = require("../assets/drawer-cover.png"); const WIKETUD_LINK = "https://www.etud.insa-toulouse.fr/wiketud/index.php/Accueil"; -export default class SideBar extends React.Component { +type Props = { + navigation: Object, +}; - constructor(props) { +type State = { + active: string, +}; + +export default class SideBar extends React.Component { + + dataSet: Array; + + constructor(props: Props) { super(props); this.state = { active: 'Home', @@ -70,7 +82,7 @@ export default class SideBar extends React.Component { ]; } - navigateToScreen(route) { + navigateToScreen(route: string) { this.props.navigation.navigate(route); this.props.navigation.closeDrawer(); this.setState({active: route}); @@ -88,7 +100,7 @@ export default class SideBar extends React.Component { item.route} + keyExtractor={(item) => item.route} renderItem={({item}) => - ); - } - -} diff --git a/navigation/AppNavigator.js b/navigation/AppNavigator.js index faed221..34a800e 100644 --- a/navigation/AppNavigator.js +++ b/navigation/AppNavigator.js @@ -1,3 +1,5 @@ +// @flow + import {createAppContainer, createStackNavigator} from 'react-navigation'; import MainDrawerNavigator from './MainDrawerNavigator'; diff --git a/navigation/MainDrawerNavigator.js b/navigation/MainDrawerNavigator.js index 6e422e3..5781202 100644 --- a/navigation/MainDrawerNavigator.js +++ b/navigation/MainDrawerNavigator.js @@ -1,4 +1,6 @@ -import React from 'react'; +// @flow + +import * as React from 'react'; import {createDrawerNavigator} from 'react-navigation'; import HomeScreen from '../screens/HomeScreen'; diff --git a/navigation/MainTabNavigator.js b/navigation/MainTabNavigator.js deleted file mode 100644 index bc422c7..0000000 --- a/navigation/MainTabNavigator.js +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react'; -import {Platform} from 'react-native'; -import {createStackNavigator} from 'react-navigation'; -import {createMaterialBottomTabNavigator} from "react-navigation-material-bottom-tabs"; -import TabBarIcon from '../components/TabBarIcon'; - -import HomeScreen from '../screens/HomeScreen'; -import PlanningScreen from '../screens/PlanningScreen'; - -const HomeStack = createStackNavigator({ - Home: HomeScreen, -}); - -HomeStack.navigationOptions = { - tabBarLabel: 'Home', - tabBarIcon: ({focused}) => ( - - ), -}; - -const ProfileStack = createStackNavigator({ - Profile: PlanningScreen, -}); - -ProfileStack.navigationOptions = { - tabBarLabel: 'Profile', - tabBarIcon: ({focused}) => ( - - ), -}; - - -export default createMaterialBottomTabNavigator( - { - Home: HomeStack, - Profile: ProfileStack - }, { - initialRouteName: 'Home', - shifting: true, - activeColor: Colors.tabIconSelected, - inactiveColor: Colors.tabIconDefault, - barStyle: {backgroundColor: Colors.mainColor}, - } -); diff --git a/screens/About/AboutDependenciesScreen.js b/screens/About/AboutDependenciesScreen.js index 688eb18..fb173bd 100644 --- a/screens/About/AboutDependenciesScreen.js +++ b/screens/About/AboutDependenciesScreen.js @@ -1,5 +1,7 @@ -import React from 'react'; -import {Container, Text, Content, ListItem, Body, Left, Thumbnail, Right, Button, Icon} from 'native-base'; +// @flow + +import * as React from 'react'; +import {Container, Text, Content, ListItem, Body} from 'native-base'; import CustomHeader from "../../components/CustomHeader"; import {FlatList} from "react-native"; import i18n from "i18n-js"; @@ -14,8 +16,11 @@ function generateListFromObject(object) { return list; } +type Props = { + navigation: Object +} -export default class AboutDependenciesScreen extends React.Component { +export default class AboutDependenciesScreen extends React.Component { render() { const nav = this.props.navigation; @@ -26,7 +31,7 @@ export default class AboutDependenciesScreen extends React.Component { item.name} + keyExtractor={(item) => item.name} style={{minHeight: 300, width: '100%'}} renderItem={({item}) => diff --git a/screens/About/AboutScreen.js b/screens/About/AboutScreen.js index 13124c4..38255ce 100644 --- a/screens/About/AboutScreen.js +++ b/screens/About/AboutScreen.js @@ -1,6 +1,8 @@ -import React from 'react'; +// @flow + +import * as React from 'react'; import {Platform, StyleSheet, Linking, Alert, FlatList} from 'react-native'; -import {Container, Content, Text, Card, CardItem, Body, Icon, Left, Right, Thumbnail, H1, ListItem} from 'native-base'; +import {Container, Content, Text, Card, CardItem, Body, Left, Right, Thumbnail, H1} from 'native-base'; import CustomHeader from "../../components/CustomHeader"; import i18n from "i18n-js"; import appJson from '../../app'; @@ -20,13 +22,18 @@ const links = { react: 'https://facebook.github.io/react-native/', }; +type Props = { + navigation: Object, +}; + + function openWebLink(link) { Linking.openURL(link).catch((err) => console.error('Error opening link', err)); } -export default class AboutScreen extends React.Component { +export default class AboutScreen extends React.Component { - appData = [ + appData: Array = [ { onPressCallback: () => openWebLink(Platform.OS === "ios" ? links.appstore : links.playstore), icon: Platform.OS === "ios" ? 'apple' : 'google-play', @@ -59,7 +66,7 @@ export default class AboutScreen extends React.Component { }, ]; - authorData = [ + authorData: Array = [ { onPressCallback: () => Alert.alert('Coucou', 'Whaou'), icon: 'account-circle', @@ -86,7 +93,7 @@ export default class AboutScreen extends React.Component { }, ]; - technoData = [ + technoData: Array = [ { onPressCallback: () => openWebLink(links.react), icon: 'react', @@ -101,7 +108,7 @@ export default class AboutScreen extends React.Component { }, ]; - getCardItem(onPressCallback, icon, text, showChevron) { + getCardItem(onPressCallback: Function, icon: string, text: string, showChevron: boolean) { return ( @@ -178,12 +185,3 @@ export default class AboutScreen extends React.Component { ); } } - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#fff', - alignItems: 'center', - justifyContent: 'center', - }, -}); diff --git a/screens/HomeScreen.js b/screens/HomeScreen.js index b320465..1cd12eb 100644 --- a/screens/HomeScreen.js +++ b/screens/HomeScreen.js @@ -1,12 +1,16 @@ -import React from 'react'; +// @flow + +import * as React from 'react'; import {Container, Content, Text, Button, Icon} from 'native-base'; import CustomHeader from '../components/CustomHeader'; import i18n from "i18n-js"; import NotificationsManager from '../utils/NotificationsManager' -import { Notifications } from 'expo'; +type Props = { + navigation: Object, +} -export default class HomeScreen extends React.Component { +export default class HomeScreen extends React.Component { render() { const nav = this.props.navigation; return ( @@ -19,7 +23,7 @@ export default class HomeScreen extends React.Component { name={'bell-ring'} type={'MaterialCommunityIcons'} /> - Notif + Instant Notification diff --git a/screens/PlanningScreen.js b/screens/PlanningScreen.js index c74290a..c6ce3b5 100644 --- a/screens/PlanningScreen.js +++ b/screens/PlanningScreen.js @@ -1,10 +1,15 @@ -import React from 'react'; -import { StyleSheet, View } from 'react-native'; +// @flow + +import * as React from 'react'; import {Container, Text} from 'native-base'; import CustomHeader from "../components/CustomHeader"; import i18n from "i18n-js"; -export default class PlanningScreen extends React.Component { +type Props = { + navigation: Object, +} + +export default class PlanningScreen extends React.Component { render() { const nav = this.props.navigation; return ( @@ -14,11 +19,4 @@ export default class PlanningScreen extends React.Component { ); } } -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#fff', - alignItems: 'center', - justifyContent: 'center', - }, -}); + diff --git a/screens/Proximo/ProximoListScreen.js b/screens/Proximo/ProximoListScreen.js index 6616579..ee69d55 100644 --- a/screens/Proximo/ProximoListScreen.js +++ b/screens/Proximo/ProximoListScreen.js @@ -1,9 +1,11 @@ -import React from 'react'; +// @flow + +import * as React from 'react'; import {Container, Text, Content, ListItem, Left, Thumbnail, Right, Body, Icon} from 'native-base'; import CustomHeader from "../../components/CustomHeader"; -import {AsyncStorage, FlatList, View} from "react-native"; +import {FlatList} from "react-native"; import Touchable from 'react-native-platform-touchable'; -import Menu, {MenuItem, MenuDivider} from 'react-native-material-menu'; +import Menu, {MenuItem} from 'react-native-material-menu'; import i18n from "i18n-js"; const IMG_URL = "https://etud.insa-toulouse.fr/~vergnet/appli-amicale/img/"; @@ -39,24 +41,35 @@ function sortNameReverse(a, b) { return 0; } +type Props = { + navigation: Object +} -export default class ProximoMainScreen extends React.Component { - constructor(props) { - super(props); - this.state = { - navData: this.props.navigation.getParam('data', []).sort(sortPrice), - currentSortMode: sortMode.price, - isSortReversed: false, - sortPriceIcon: '', - sortNameIcon: '', - }; - } +type State = { + navData: Array, + currentSortMode: string, + isSortReversed: boolean, + sortPriceIcon: React.Node, + sortNameIcon: React.Node, +}; - setMenuRef = ref => { +export default class ProximoMainScreen extends React.Component { + + state = { + navData: this.props.navigation.getParam('data', []).sort(sortPrice), + currentSortMode: sortMode.price, + isSortReversed: false, + sortPriceIcon: '', + sortNameIcon: '', + }; + + _menu: Menu; + + setMenuRef = (ref: Menu) => { this._menu = ref; }; - toggleSortMode(mode) { + toggleSortMode(mode: string) { let isReverse = this.state.isSortReversed; if (mode === this.state.currentSortMode) // reverse mode isReverse = !isReverse; // this.state not updating on this function cycle @@ -65,7 +78,7 @@ export default class ProximoMainScreen extends React.Component { this.setSortMode(mode, isReverse); } - setSortMode(mode, isReverse) { + setSortMode(mode: string, isReverse: boolean) { this.setState({ currentSortMode: mode, isSortReversed: isReverse @@ -98,7 +111,7 @@ export default class ProximoMainScreen extends React.Component { this.setSortMode(this.state.currentSortMode, this.state.isSortReversed); } - setupSortIcons(mode, isReverse) { + setupSortIcons(mode: string, isReverse: boolean) { const downSortIcon = { + + state = { + refreshing: false, + firstLoading: true, + data: {}, + }; + + static generateDataset(types: Array, data: Object) { let finalData = []; for (let i = 0; i < types.length; i++) { finalData.push({ diff --git a/screens/ProxiwashScreen.js b/screens/ProxiwashScreen.js index fe2546d..0df7953 100644 --- a/screens/ProxiwashScreen.js +++ b/screens/ProxiwashScreen.js @@ -1,4 +1,6 @@ -import React from 'react'; +// @flow + +import * as React from 'react'; import {SectionList, RefreshControl, View} from 'react-native'; import {Body, Container, Icon, Left, ListItem, Right, Text, Toast, H2, Button} from 'native-base'; import CustomHeader from "../components/CustomHeader"; @@ -25,10 +27,27 @@ let stateStrings = {}; let stateColors = {}; +type Props = { + navigation: Object, +}; -export default class ProxiwashScreen extends React.Component { +type State = { + refreshing: boolean, + firstLoading: boolean, + data: Object, + machinesWatched: Array +}; - constructor(props) { +export default class ProxiwashScreen extends React.Component { + + state = { + refreshing: false, + firstLoading: true, + data: {}, + machinesWatched: [], + }; + + constructor(props: Props) { super(props); let colors = ThemeManager.getInstance().getCurrentThemeVariables(); stateColors[MACHINE_STATES.TERMINE] = colors.proxiwashFinishedColor; @@ -42,24 +61,12 @@ export default class ProxiwashScreen extends React.Component { stateStrings[MACHINE_STATES.FONCTIONNE] = i18n.t('proxiwashScreen.states.running'); stateStrings[MACHINE_STATES.HS] = i18n.t('proxiwashScreen.states.broken'); stateStrings[MACHINE_STATES.ERREUR] = i18n.t('proxiwashScreen.states.error'); - this.state = { - refreshing: false, - firstLoading: true, - data: {}, - machinesWatched: [], - }; } async readData() { try { let response = await fetch(DATA_URL); let responseJson = await response.json(); - // This prevents end notifications from showing - // let watchList = this.state.machinesWatched; - // for (let i = 0; i < watchList.length; i++) { - // if (responseJson[MACHINE_STATES[watchList[i].machineNumber.state]] !== MACHINE_STATES.FONCTIONNE) - // this.disableNotification(watchList[i].machineNumber); - // } this.setState({ data: responseJson }); @@ -98,21 +105,23 @@ export default class ProxiwashScreen extends React.Component { }); }; - static getRemainingTime(startString, endString, percentDone) { + static getRemainingTime(startString: string, endString: string, percentDone: string): number { let startArray = startString.split(':'); let endArray = endString.split(':'); let startDate = new Date(); startDate.setHours(parseInt(startArray[0]), parseInt(startArray[1]), 0, 0); let endDate = new Date(); endDate.setHours(parseInt(endArray[0]), parseInt(endArray[1]), 0, 0); - return (((100 - percentDone) / 100) * (endDate - startDate) / (60 * 1000)).toFixed(0); // Convert milliseconds into minutes + // Convert milliseconds into minutes + let time: string = (((100 - parseFloat(percentDone)) / 100) * (endDate - startDate) / (60 * 1000)).toFixed(0); + return parseInt(time); } - async setupNotifications(number, remainingTime) { - if (!this.isMachineWatched(number)) { + async setupNotifications(machineId: string, remainingTime: number) { + if (!this.isMachineWatched(machineId)) { let endNotifID = await NotificationsManager.scheduleNotification( i18n.t('proxiwashScreen.notifications.machineFinishedTitle'), - i18n.t('proxiwashScreen.notifications.machineFinishedBody', {number: number}), + i18n.t('proxiwashScreen.notifications.machineFinishedBody', {number: machineId}), new Date().getTime() + remainingTime * (60 * 1000) // Convert back to milliseconds ); let reminderNotifID = undefined; @@ -127,41 +136,41 @@ export default class ProxiwashScreen extends React.Component { if (remainingTime > reminderNotifTime && reminderNotifTime > 0) { reminderNotifID = await NotificationsManager.scheduleNotification( i18n.t('proxiwashScreen.notifications.machineRunningTitle', {time: reminderNotifTime}), - i18n.t('proxiwashScreen.notifications.machineRunningBody', {number: number}), + i18n.t('proxiwashScreen.notifications.machineRunningBody', {number: machineId}), new Date().getTime() + (remainingTime - reminderNotifTime) * (60 * 1000) // Convert back to milliseconds ); } let data = this.state.machinesWatched; - data.push({machineNumber: number, endNotifID: endNotifID, reminderNotifID: reminderNotifID}); + data.push({machineNumber: machineId, endNotifID: endNotifID, reminderNotifID: reminderNotifID}); this.setState({machinesWatched: data}); AsyncStorage.setItem(WATCHED_MACHINES_PREFKEY, JSON.stringify(data)); } else - this.disableNotification(number); + this.disableNotification(machineId); } - disableNotification(number) { - let data = this.state.machinesWatched; + disableNotification(machineId: string) { + let data: Object = this.state.machinesWatched; if (data.length > 0) { let elem = this.state.machinesWatched.find(function (elem) { - return elem.machineNumber === number + return elem.machineNumber === machineId }); let arrayIndex = data.indexOf(elem); - NotificationsManager.cancelScheduledNoification(data[arrayIndex].endNotifID); + NotificationsManager.cancelScheduledNotification(data[arrayIndex].endNotifID); if (data[arrayIndex].reminderNotifID !== undefined) - NotificationsManager.cancelScheduledNoification(data[arrayIndex].reminderNotifID); + NotificationsManager.cancelScheduledNotification(data[arrayIndex].reminderNotifID); data.splice(arrayIndex, 1); this.setState({machinesWatched: data}); AsyncStorage.setItem(WATCHED_MACHINES_PREFKEY, JSON.stringify(data)); } } - isMachineWatched(number) { + isMachineWatched(number: string) { return this.state.machinesWatched.find(function (elem) { return elem.machineNumber === number }) !== undefined; } - renderItem(item, section, data) { + renderItem(item: Object, section: Object, data: Object) { return ( { state = { nightMode: ThemeManager.getInstance().getNightMode(), proxiwashNotifPickerSelected: "5" @@ -38,21 +49,21 @@ export default class SettingsScreen extends React.Component { } - onProxiwashNotidPickerValueChange(value) { + onProxiwashNotifPickerValueChange(value: string) { AsyncStorage.setItem(proxiwashNotifKey, value); this.setState({ proxiwashNotifPickerSelected: value }); } - getproxiwashNotifPicker() { + getProxiwashNotifPicker() { return ( this.onProxiwashNotidPickerValueChange(value)} + onValueChange={(value) => this.onProxiwashNotifPickerValueChange(value)} > @@ -67,7 +78,7 @@ export default class SettingsScreen extends React.Component { } toggleNightMode() { - ThemeManager.getInstance().setNightmode(!this.state.nightMode); + ThemeManager.getInstance().setNightMode(!this.state.nightMode); this.setState({nightMode: !this.state.nightMode}); // Alert.alert(i18n.t('settingsScreen.nightMode'), i18n.t('settingsScreen.restart')); this.resetStack(); @@ -83,7 +94,7 @@ export default class SettingsScreen extends React.Component { this.props.navigation.navigate('Settings'); } - getToggleItem(onPressCallback, icon, text, subtitle) { + getToggleItem(onPressCallback: Function, icon: string, text: string, subtitle: string) { return ( Proxiwash - {this.getGeneralItem(this.getproxiwashNotifPicker(), 'washing-machine', i18n.t('settingsScreen.proxiwashNotifReminder'), i18n.t('settingsScreen.proxiwashNotifReminderSub'))} + {SettingsScreen.getGeneralItem(this.getProxiwashNotifPicker(), 'washing-machine', i18n.t('settingsScreen.proxiwashNotifReminder'), i18n.t('settingsScreen.proxiwashNotifReminderSub'))} diff --git a/utils/LocaleManager.js b/utils/LocaleManager.js index 36eb711..b0bf120 100644 --- a/utils/LocaleManager.js +++ b/utils/LocaleManager.js @@ -1,21 +1,20 @@ +// @flow + import i18n from 'i18n-js'; import * as Localization from 'expo-localization'; import en from '../translations/en'; import fr from '../translations/fr'; +/** + * Static class used to manage locales + */ export default class LocaleManager { - static instance = null; - - static getInstance() { - if (LocaleManager.instance == null) { - LocaleManager.instance = new LocaleManager(); - } - return this.instance; - } - - initTranslations() { + /** + * Initialize translations using language files + */ + static initTranslations() { i18n.fallbacks = true; i18n.translations = {fr, en}; i18n.locale = Localization.locale; diff --git a/utils/NotificationsManager.js b/utils/NotificationsManager.js index 42d706c..608343e 100644 --- a/utils/NotificationsManager.js +++ b/utils/NotificationsManager.js @@ -1,8 +1,18 @@ +// @flow + import * as Permissions from 'expo-permissions'; import { Notifications } from 'expo'; +/** + * Static class used to manage notifications sent to the user + */ export default class NotificationsManager { + /** + * Async function asking permission to send notifications to the user + * + * @returns {Promise} + */ static async askPermissions() { const {status: existingStatus} = await Permissions.getAsync(Permissions.NOTIFICATIONS); let finalStatus = existingStatus; @@ -13,7 +23,14 @@ export default class NotificationsManager { return finalStatus === 'granted'; } - static async sendNotificationImmediately (title, body) { + /** + * Async function sending a notification without delay to the user + * + * @param title {String} Notification title + * @param body {String} Notification body text + * @returns {Promise} Notification Id + */ + static async sendNotificationImmediately (title: string, body: string) { await NotificationsManager.askPermissions(); return await Notifications.presentLocalNotificationAsync({ title: title, @@ -21,7 +38,15 @@ export default class NotificationsManager { }); }; - static async scheduleNotification(title, body, time) { + /** + * Async function sending notification at the specified time + * + * @param title Notification title + * @param body Notification body text + * @param time Time at which we should send the notification + * @returns {Promise} Notification Id + */ + static async scheduleNotification(title: string, body: string, time: number): Promise { await NotificationsManager.askPermissions(); return Notifications.scheduleLocalNotificationAsync( { @@ -34,7 +59,12 @@ export default class NotificationsManager { ); }; - static async cancelScheduledNoification(notifID) { - await Notifications.cancelScheduledNotificationAsync(notifID); + /** + * Async function used to cancel the notification of a specific ID + * @param notificationID {Number} The notification ID + * @returns {Promise} + */ + static async cancelScheduledNotification(notificationID: number) { + await Notifications.cancelScheduledNotificationAsync(notificationID); } } diff --git a/utils/ThemeManager.js b/utils/ThemeManager.js index 1831643..2ef4289 100644 --- a/utils/ThemeManager.js +++ b/utils/ThemeManager.js @@ -1,3 +1,5 @@ +// @flow + import {AsyncStorage} from 'react-native' import platform from '../native-base-theme/variables/platform'; import platformDark from '../native-base-theme/variables/platformDark'; @@ -5,53 +7,85 @@ import getTheme from '../native-base-theme/components'; const nightModeKey = 'nightMode'; +/** + * Singleton class used to manage themes + */ export default class ThemeManager { - static instance = null; + static instance: ThemeManager | null = null; + nightMode: boolean; + updateThemeCallback: Function; constructor() { this.nightMode = false; - this.updateThemeCallback = undefined; + this.updateThemeCallback = null; } - static getInstance() { - if (ThemeManager.instance == null) { - ThemeManager.instance = new ThemeManager(); - } - return this.instance; + /** + * Get this class instance or create one if none is found + * @returns {ThemeManager} + */ + static getInstance(): ThemeManager { + return ThemeManager.instance === null ? + ThemeManager.instance = new ThemeManager() : + ThemeManager.instance; } - setUpdateThemeCallback(callback) { + /** + * Set 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 + */ + setUpdateThemeCallback(callback: ?Function) { this.updateThemeCallback = callback; } - async getDataFromPreferences() { - let result = await AsyncStorage.getItem(nightModeKey); + /** + * Read async storage to get preferences + * @returns {Promise} + */ + async getDataFromPreferences(): Promise { + let result: string = await AsyncStorage.getItem(nightModeKey); if (result === '1') this.nightMode = true; - console.log('nightmode: ' + this.nightMode); + // console.log('nightmode: ' + this.nightMode); } - setNightmode(isNightMode) { + /** + * Set night mode and save it to preferences + * + * @param isNightMode Whether to enable night mode + */ + setNightMode(isNightMode: boolean) { this.nightMode = isNightMode; AsyncStorage.setItem(nightModeKey, isNightMode ? '1' : '0'); - if (this.updateThemeCallback !== undefined) + if (this.updateThemeCallback !== null) this.updateThemeCallback(); } - getNightMode() { + /** + * @returns {boolean} Night mode state + */ + getNightMode(): boolean { return this.nightMode; } - getCurrentTheme() { + /** + * Get the current theme based on night mode + * @returns {Object} + */ + getCurrentTheme(): Object { if (this.nightMode) return getTheme(platformDark); else return getTheme(platform); } - getCurrentThemeVariables() { + /** + * Get the variables contained in the current theme + * @returns {Object} + */ + getCurrentThemeVariables(): Object { return this.getCurrentTheme().variables; }