forked from vergnet/application-amicale
Started writing documentation and ported app to use Flow
This commit is contained in:
parent
b16aab8adc
commit
0385bbb882
20 changed files with 351 additions and 243 deletions
11
.flowconfig
Normal file
11
.flowconfig
Normal file
|
@ -0,0 +1,11 @@
|
|||
[ignore]
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
|
||||
[lints]
|
||||
|
||||
[options]
|
||||
|
||||
[strict]
|
41
App.js
41
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<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/>;
|
||||
|
|
|
@ -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>);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import {createAppContainer, createStackNavigator} from 'react-navigation';
|
||||
|
||||
import MainDrawerNavigator from './MainDrawerNavigator';
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import React from 'react';
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {createDrawerNavigator} from 'react-navigation';
|
||||
|
||||
import HomeScreen from '../screens/HomeScreen';
|
||||
|
|
|
@ -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},
|
||||
}
|
||||
);
|
|
@ -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>
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue