Started writing documentation and ported app to use Flow

This commit is contained in:
keplyx 2019-06-29 13:37:21 +02:00
parent b16aab8adc
commit 0385bbb882
20 changed files with 351 additions and 243 deletions

11
.flowconfig Normal file
View file

@ -0,0 +1,11 @@
[ignore]
[include]
[libs]
[lints]
[options]
[strict]

41
App.js
View file

@ -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<Props, State> {
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 <View/>;

View file

@ -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<Props> {
static defaultProps = {
backButton: false,
rightMenu: <Right/>,
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 =
<Touchable
style={{padding: 6}}
@ -30,11 +46,6 @@ export default class CustomHeader extends React.Component {
type={'MaterialCommunityIcons'}/>
</Touchable>;
if (this.props.rightMenu)
rightMenu = this.props.rightMenu;
else
rightMenu = <Right/>;
return (
<Header style={styles.header}>
<Left>
@ -43,7 +54,7 @@ export default class CustomHeader extends React.Component {
<Body>
<Title>{this.props.title}</Title>
</Body>
{rightMenu}
{this.props.rightMenu}
</Header>);
}
};

View file

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

View file

@ -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<Props, State> {
dataSet: Array<Object>;
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 {
<FlatList
data={this.dataSet}
extraData={this.state}
keyExtractor={(item, index) => item.route}
keyExtractor={(item) => item.route}
renderItem={({item}) =>
<ListItem
button

View file

@ -1,16 +0,0 @@
import React from 'react';
import {Ionicons} from '@expo/vector-icons/build/Icons';
export default class TabBarIcon extends React.Component {
render() {
return (
<Ionicons
name={this.props.name}
size={26}
style={{marginBottom: -3}}
color={this.props.focused ? Colors.tabIconSelected : Colors.tabIconDefault}
/>
);
}
}

View file

@ -1,3 +1,5 @@
// @flow
import {createAppContainer, createStackNavigator} from 'react-navigation';
import MainDrawerNavigator from './MainDrawerNavigator';

View file

@ -1,4 +1,6 @@
import React from 'react';
// @flow
import * as React from 'react';
import {createDrawerNavigator} from 'react-navigation';
import HomeScreen from '../screens/HomeScreen';

View file

@ -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}) => (
<TabBarIcon
focused={focused}
name={
Platform.OS === 'ios'
? 'ios-home'
: 'md-home'
}
/>
),
};
const ProfileStack = createStackNavigator({
Profile: PlanningScreen,
});
ProfileStack.navigationOptions = {
tabBarLabel: 'Profile',
tabBarIcon: ({focused}) => (
<TabBarIcon
focused={focused}
name={
Platform.OS === 'ios'
? 'ios-people'
: 'md-people'
}
/>
),
};
export default createMaterialBottomTabNavigator(
{
Home: HomeStack,
Profile: ProfileStack
}, {
initialRouteName: 'Home',
shifting: true,
activeColor: Colors.tabIconSelected,
inactiveColor: Colors.tabIconDefault,
barStyle: {backgroundColor: Colors.mainColor},
}
);

View file

@ -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<Props> {
render() {
const nav = this.props.navigation;
@ -26,7 +31,7 @@ export default class AboutDependenciesScreen extends React.Component {
<Content>
<FlatList
data={data}
keyExtractor={(item, index) => item.name}
keyExtractor={(item) => item.name}
style={{minHeight: 300, width: '100%'}}
renderItem={({item}) =>
<ListItem>

View file

@ -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<Props> {
appData = [
appData: Array<Object> = [
{
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<Object> = [
{
onPressCallback: () => Alert.alert('Coucou', 'Whaou'),
icon: 'account-circle',
@ -86,7 +93,7 @@ export default class AboutScreen extends React.Component {
},
];
technoData = [
technoData: Array<Object> = [
{
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 (
<CardItem button
onPress={onPressCallback}>
@ -178,12 +185,3 @@ export default class AboutScreen extends React.Component {
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});

View file

@ -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<Props> {
render() {
const nav = this.props.navigation;
return (
@ -19,7 +23,7 @@ export default class HomeScreen extends React.Component {
name={'bell-ring'}
type={'MaterialCommunityIcons'}
/>
<Text>Notif</Text>
<Text>Instant Notification</Text>
</Button>
</Content>
</Container>

View file

@ -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<Props> {
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',
},
});

View file

@ -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<Object>,
currentSortMode: string,
isSortReversed: boolean,
sortPriceIcon: React.Node,
sortNameIcon: React.Node,
};
setMenuRef = ref => {
export default class ProximoMainScreen extends React.Component<Props, State> {
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 =
<Icon
active

View file

@ -1,6 +1,8 @@
import React from 'react';
// @flow
import * as React from 'react';
import {RefreshControl, SectionList} from 'react-native';
import {Container, Text, Content, ListItem, Left, Right, Body, Badge, Icon, Toast, H2} from 'native-base';
import {Container, Text, ListItem, Left, Right, Body, Badge, Toast, H2} from 'native-base';
import CustomHeader from "../../components/CustomHeader";
import i18n from "i18n-js";
import CustomMaterialIcon from "../../components/CustomMaterialIcon";
@ -15,18 +17,25 @@ const typesIcons = {
Default: "information-outline",
};
export default class ProximoMainScreen extends React.Component {
type Props = {
navigation: Object
}
constructor(props) {
super(props);
this.state = {
refreshing: false,
firstLoading: true,
data: {},
};
}
type State = {
refreshing: boolean,
firstLoading: boolean,
data: Object,
};
static generateDataset(types, data) {
export default class ProximoMainScreen extends React.Component<Props, State> {
state = {
refreshing: false,
firstLoading: true,
data: {},
};
static generateDataset(types: Array<string>, data: Object) {
let finalData = [];
for (let i = 0; i < types.length; i++) {
finalData.push({

View file

@ -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<Object>
};
constructor(props) {
export default class ProxiwashScreen extends React.Component<Props, State> {
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 (
<ListItem
thumbnail

View file

@ -1,4 +1,6 @@
import React from 'react';
// @flow
import * as React from 'react';
import {
Container,
Content,
@ -22,7 +24,16 @@ import {AsyncStorage} from 'react-native'
const proxiwashNotifKey = "proxiwashNotifKey";
export default class SettingsScreen extends React.Component {
type Props = {
navigation: Object,
};
type State = {
nightMode: boolean,
proxiwashNotifPickerSelected: string,
};
export default class SettingsScreen extends React.Component<Props, State> {
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 (
<Picker
note
mode="dropdown"
style={{width: 120}}
selectedValue={this.state.proxiwashNotifPickerSelected}
onValueChange={(value) => this.onProxiwashNotidPickerValueChange(value)}
onValueChange={(value) => this.onProxiwashNotifPickerValueChange(value)}
>
<Picker.Item label={i18n.t('settingsScreen.proxiwashNotifReminderPicker.never')} value="never"/>
<Picker.Item label={i18n.t('settingsScreen.proxiwashNotifReminderPicker.1')} value="1"/>
@ -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 (
<ListItem
button
@ -109,7 +120,7 @@ export default class SettingsScreen extends React.Component {
);
}
getGeneralItem(control, icon, text, subtitle) {
static getGeneralItem(control: React.Node, icon: string, text: string, subtitle: string) {
return (
<ListItem
thumbnail
@ -152,7 +163,7 @@ export default class SettingsScreen extends React.Component {
<Text>Proxiwash</Text>
</CardItem>
<List>
{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'))}
</List>
</Card>
</Content>

View file

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

View file

@ -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<import("react").ReactText>} 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<import("react").ReactText>} Notification Id
*/
static async scheduleNotification(title: string, body: string, time: number): Promise<void> {
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);
}
}

View file

@ -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<void>}
*/
async getDataFromPreferences(): Promise<void> {
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;
}