improved assets, added more details in app.json

This commit is contained in:
keplyx 2019-08-06 13:28:11 +02:00
parent b7ef071565
commit 405f1769d3
19 changed files with 243 additions and 93 deletions

24
App.js
View file

@ -1,9 +1,8 @@
// @flow // @flow
import React from 'react'; import * as React from 'react';
import {Root, StyleProvider, Text} from 'native-base'; import {Root, StyleProvider, Text} from 'native-base';
import {Ionicons} from '@expo/vector-icons'; import {Image, StyleSheet, View} from 'react-native'
import {StyleSheet, View, Image} from 'react-native'
import AppNavigator from './navigation/AppNavigator'; import AppNavigator from './navigation/AppNavigator';
import ThemeManager from './utils/ThemeManager'; import ThemeManager from './utils/ThemeManager';
import LocaleManager from './utils/LocaleManager'; import LocaleManager from './utils/LocaleManager';
@ -43,6 +42,7 @@ const styles = StyleSheet.create({
}, },
}); });
// Content to be used int the intro slides
const slides = [ const slides = [
{ {
key: '1', key: '1',
@ -61,7 +61,7 @@ const slides = [
{ {
key: '3', key: '3',
title: 'Le proximo', title: 'Le proximo',
text: 'Regardez le stock de la supérette de l\'INSA depuis n\'importe où' , text: 'Regardez le stock de la supérette de l\'INSA depuis n\'importe où',
icon: 'shopping', icon: 'shopping',
colors: ['#f9a967', '#da5204'], colors: ['#f9a967', '#da5204'],
}, },
@ -101,12 +101,14 @@ export default class App extends React.Component<Props, State> {
* @returns {Promise} * @returns {Promise}
*/ */
async componentWillMount() { async componentWillMount() {
// Wait for custom fonts to be loaded before showing the app
await Font.loadAsync({ await Font.loadAsync({
'Roboto': require('native-base/Fonts/Roboto.ttf'), 'Roboto': require('native-base/Fonts/Roboto.ttf'),
'Roboto_medium': require('native-base/Fonts/Roboto_medium.ttf'), 'Roboto_medium': require('native-base/Fonts/Roboto_medium.ttf'),
}); });
await AsyncStorageManager.getInstance().loadPreferences(); await AsyncStorageManager.getInstance().loadPreferences();
ThemeManager.getInstance().setUpdateThemeCallback(() => this.updateTheme()); ThemeManager.getInstance().setUpdateThemeCallback(() => this.updateTheme());
// Only show intro if this is the first time starting the app
this.setState({ this.setState({
isLoading: false, isLoading: false,
currentTheme: ThemeManager.getCurrentTheme(), currentTheme: ThemeManager.getCurrentTheme(),
@ -115,17 +117,20 @@ export default class App extends React.Component<Props, State> {
} }
/** /**
* Updates the theme and clears the cache to force reloading the app colors * Updates the theme and clears the cache to force reloading the app colors. Need to edit shoutem theme for ti to work
*/ */
updateTheme() { updateTheme() {
// console.log('update theme called');
this.setState({ this.setState({
currentTheme: ThemeManager.getCurrentTheme() currentTheme: ThemeManager.getCurrentTheme()
}); });
clearThemeCache(); clearThemeCache();
} }
/**
* Render item to be used for the intro slides
* @param item
* @param dimensions
*/
getIntroRenderItem(item: Object, dimensions: Object) { getIntroRenderItem(item: Object, dimensions: Object) {
return ( return (
<LinearGradient <LinearGradient
@ -148,6 +153,9 @@ export default class App extends React.Component<Props, State> {
); );
} }
/**
* Callback when user ends the intro. Save in preferences to avaoid showing back the slides
*/
onIntroDone() { onIntroDone() {
this.setState({showIntro: false}); this.setState({showIntro: false});
AsyncStorageManager.getInstance().savePref(AsyncStorageManager.getInstance().preferences.showIntro.key, '0'); AsyncStorageManager.getInstance().savePref(AsyncStorageManager.getInstance().preferences.showIntro.key, '0');
@ -155,8 +163,6 @@ export default class App extends React.Component<Props, State> {
/** /**
* Renders the app based on loading state * Renders the app based on loading state
*
* @returns {*}
*/ */
render() { render() {
if (this.state.isLoading) { if (this.state.isLoading) {

View file

@ -1,6 +1,7 @@
{ {
"expo": { "expo": {
"name": "Amicale INSAT", "name": "Amicale INSAT",
"description": "Application mobile compatible Android et iOS pour l'Amicale INSA Toulouse. Grâce à cette application, vous avez facilement accès aux news du campus, aux emplois du temps, à l'état de la laverie, et bien d'autres services ! Ceci est une version Beta, Toutes les fonctionnalités ne sont pas encore implémentées, et il est possible de rencontrer quelques bugs.",
"slug": "application-amicale", "slug": "application-amicale",
"privacy": "public", "privacy": "public",
"sdkVersion": "33.0.0", "sdkVersion": "33.0.0",
@ -9,29 +10,42 @@
"android", "android",
"web" "web"
], ],
"version": "0.0.4", "version": "0.0.5",
"orientation": "portrait", "orientation": "portrait",
"icon": "./assets/icon.png",
"primaryColor": "#e42612", "primaryColor": "#e42612",
"icon": "./assets/icon.png",
"splash": { "splash": {
"image": "./assets/splash.png", "backgroundColor": "#fff",
"resizeMode": "cover", "resizeMode": "contain",
"backgroundColor": "#fff" "image": "./assets/splash.png"
},
"notification": {
"icon": "./assets/amicale-notification.png",
"color": "#e42612",
"androidMode": "default"
}, },
"updates": { "updates": {
"fallbackToCacheTimeout": 0 "enabled": false
}, },
"assetBundlePatterns": [ "assetBundlePatterns": [
"**/*" "**/*"
], ],
"ios": { "ios": {
"supportsTablet": true, "bundleIdentifier": "amicale.insat.application",
"icon": "./assets/ios.icon.png", "buildNumber": "0.0.5",
"bundleIdentifier": "com.test.applicationamicale" "icon": "./assets/ios.icon.png"
}, },
"android": { "android": {
"package": "amicale.insat.application",
"versionCode": 1,
"icon": "./assets/android.icon.png", "icon": "./assets/android.icon.png",
"package": "com.test.applicationamicale" "adaptiveIcon": {
"foregroundImage": "./assets/android.adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"permissions": [
"VIBRATE"
]
} }
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 110 KiB

View file

@ -1,8 +1,8 @@
// @flow // @flow
import * as React from "react"; import * as React from "react";
import {Body, Header, Icon, Left, Right, Title} from "native-base"; import {Body, Header, Left, Right, Title} from "native-base";
import {StyleSheet, Platform} from "react-native"; import {Platform, StyleSheet} from "react-native";
import {getStatusBarHeight} from "react-native-status-bar-height"; import {getStatusBarHeight} from "react-native-status-bar-height";
import Touchable from 'react-native-platform-touchable'; import Touchable from 'react-native-platform-touchable';
import ThemeManager from "../utils/ThemeManager"; import ThemeManager from "../utils/ThemeManager";
@ -34,6 +34,7 @@ export default class CustomHeader extends React.Component<Props> {
render() { render() {
let button; let button;
// Does the app have a back button or a burger menu ?
if (this.props.backButton) if (this.props.backButton)
button = button =
<Touchable <Touchable

View file

@ -9,7 +9,7 @@ type Props = {
icon: string, icon: string,
color: ?string, color: ?string,
fontSize: number, fontSize: number,
width: number|string, width: number | string,
} }
/** /**

View file

@ -20,16 +20,20 @@ type State = {
machinesWatched: Array<Object>, machinesWatched: Array<Object>,
}; };
/**
* Class used to create a basic list view using online json data.
* Used by inheriting from it and redefining getters.
*/
export default class FetchedDataSectionList extends React.Component<Props, State> { export default class FetchedDataSectionList extends React.Component<Props, State> {
webDataManager: WebDataManager; webDataManager: WebDataManager;
willFocusSubscription : function; willFocusSubscription: function;
willBlurSubscription : function; willBlurSubscription: function;
refreshInterval: IntervalID; refreshInterval: IntervalID;
refreshTime: number; refreshTime: number;
constructor(fetchUrl: string, refreshTime : number) { constructor(fetchUrl: string, refreshTime: number) {
super(); super();
this.webDataManager = new WebDataManager(fetchUrl); this.webDataManager = new WebDataManager(fetchUrl);
this.refreshTime = refreshTime; this.refreshTime = refreshTime;
@ -42,16 +46,25 @@ export default class FetchedDataSectionList extends React.Component<Props, State
machinesWatched: [], machinesWatched: [],
}; };
getHeaderTranslation() { /**
* Get the translation for the header in the current language
* @return {string}
*/
getHeaderTranslation() : string {
return "Header"; return "Header";
} }
getUpdateToastTranslations() { /**
* Get the translation for the toasts in the current language
* @return {string}
*/
getUpdateToastTranslations(): Array<string> {
return ["whoa", "nah"]; return ["whoa", "nah"];
} }
/** /**
* Register react navigation events on first screen load * Register react navigation events on first screen load.
* Allows to detect when the screen is focused
*/ */
componentDidMount() { componentDidMount() {
this.willFocusSubscription = this.props.navigation.addListener( this.willFocusSubscription = this.props.navigation.addListener(
@ -68,28 +81,37 @@ export default class FetchedDataSectionList extends React.Component<Props, State
); );
} }
/**
* Refresh data when focusing the screen and setup a refresh interval if asked to
*/
onScreenFocus() { onScreenFocus() {
this._onRefresh(); this._onRefresh();
if (this.refreshTime > 0) if (this.refreshTime > 0)
this.refreshInterval = setInterval(() => this._onRefresh(), this.refreshTime) this.refreshInterval = setInterval(() => this._onRefresh(), this.refreshTime)
} }
/**
* Remove any interval on un-focus
*/
onScreenBlur() { onScreenBlur() {
clearInterval(this.refreshInterval); clearInterval(this.refreshInterval);
} }
/**
* Unregister from event when un-mounting components
*/
componentWillUnmount() { componentWillUnmount() {
if (this.willBlurSubscription !== undefined) if (this.willBlurSubscription !== undefined)
this.willBlurSubscription.remove(); this.willBlurSubscription.remove();
if (this.willFocusSubscription !== undefined) if (this.willFocusSubscription !== undefined)
this.willFocusSubscription.remove(); this.willFocusSubscription.remove();
} }
/**
* Refresh data and show a toast if any error occurred
* @private
*/
_onRefresh = () => { _onRefresh = () => {
console.log('refresh');
this.setState({refreshing: true}); this.setState({refreshing: true});
this.webDataManager.readData().then((fetchedData) => { this.webDataManager.readData().then((fetchedData) => {
this.setState({ this.setState({
@ -101,14 +123,38 @@ export default class FetchedDataSectionList extends React.Component<Props, State
}); });
}; };
/**
* Get the render item to be used for display in the list.
* Must be overridden by inheriting class.
*
* @param item
* @param section
* @param data
* @return {*}
*/
getRenderItem(item: Object, section: Object, data: Object) { getRenderItem(item: Object, section: Object, data: Object) {
return <View/>; return <View/>;
} }
/**
* Get the render item to be used for the section title in the list.
* Must be overridden by inheriting class.
*
* @param title
* @return {*}
*/
getRenderSectionHeader(title: String) { getRenderSectionHeader(title: String) {
return <View/>; return <View/>;
} }
/**
* Get the render item to be used when the list is empty.
* No need to be overridden, has good defaults.
*
* @param text
* @param icon
* @return {*}
*/
getEmptyRenderItem(text: string, icon: string) { getEmptyRenderItem(text: string, icon: string) {
return ( return (
<View> <View>
@ -138,7 +184,9 @@ export default class FetchedDataSectionList extends React.Component<Props, State
} }
/** /**
* Create the dataset to be used in the list from the data fetched * Create the dataset to be used in the list from the data fetched.
* Must be overridden.
*
* @param fetchedData {Object} * @param fetchedData {Object}
* @return {Array} * @return {Array}
*/ */
@ -146,6 +194,12 @@ export default class FetchedDataSectionList extends React.Component<Props, State
return []; return [];
} }
/**
* Create the dataset when no fetched data is available.
* No need to be overridden, has good defaults.
*
* @return
*/
createEmptyDataset() { createEmptyDataset() {
return [ return [
{ {
@ -165,10 +219,23 @@ export default class FetchedDataSectionList extends React.Component<Props, State
]; ];
} }
/**
* Should the app use a tab layout instead of a section list ?
* If yes, each section will be rendered in a new tab.
* Can be overridden.
*
* @return {boolean}
*/
hasTabs() { hasTabs() {
return false; return false;
} }
/**
* Get the section list render using the generated dataset
*
* @param dataset
* @return
*/
getSectionList(dataset: Array<Object>) { getSectionList(dataset: Array<Object>) {
let isEmpty = dataset[0].data.length === 0; let isEmpty = dataset[0].data.length === 0;
if (isEmpty) if (isEmpty)
@ -201,6 +268,12 @@ export default class FetchedDataSectionList extends React.Component<Props, State
); );
} }
/**
* Generate the tabs containing the lists
*
* @param dataset
* @return
*/
getTabbedView(dataset: Array<Object>) { getTabbedView(dataset: Array<Object>) {
let tabbedView = []; let tabbedView = [];
for (let i = 0; i < dataset.length; i++) { for (let i = 0; i < dataset.length; i++) {
@ -214,7 +287,7 @@ export default class FetchedDataSectionList extends React.Component<Props, State
<Text>{dataset[i].title}</Text> <Text>{dataset[i].title}</Text>
</TabHeading>} </TabHeading>}
key={dataset[i].title} key={dataset[i].title}
style={{backgroundColor: ThemeManager.getCurrentThemeVariables().containerBgColor}}> style={{backgroundColor: ThemeManager.getCurrentThemeVariables().containerBgColor}}>
{this.getSectionList( {this.getSectionList(
[ [
{ {

View file

@ -1,8 +1,8 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {Platform, Dimensions, StyleSheet, Image, FlatList, Linking} from 'react-native'; import {Dimensions, FlatList, Image, Linking, Platform, StyleSheet} from 'react-native';
import {Badge, Text, Container, Content, Left, ListItem, Right} from "native-base"; import {Badge, Container, Content, Left, ListItem, Right, Text} from "native-base";
import i18n from "i18n-js"; import i18n from "i18n-js";
import CustomMaterialIcon from '../components/CustomMaterialIcon'; import CustomMaterialIcon from '../components/CustomMaterialIcon';
@ -40,72 +40,56 @@ export default class SideBar extends React.Component<Props, State> {
*/ */
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
// Dataset used to render the drawer
// If the link field is defined, clicking on the item will open the link
this.dataSet = [ this.dataSet = [
{ {
name: i18n.t('screens.home'), name: i18n.t('screens.home'),
route: "Home", route: "Home",
icon: "home", icon: "home",
bg: "#C5F442"
// types: "11" // Shows the badge
}, },
{ {
name: i18n.t('screens.planning'), name: i18n.t('screens.planning'),
route: "Planning", route: "Planning",
icon: "calendar-range", icon: "calendar-range",
bg: "#477EEA",
// types: "11"
}, },
{ {
name: "Proxiwash", name: "Proxiwash",
route: "Proxiwash", route: "Proxiwash",
icon: "washing-machine", icon: "washing-machine",
bg: "#477EEA",
// types: "11"
}, },
{ {
name: "Proximo", name: "Proximo",
route: "Proximo", route: "Proximo",
icon: "shopping", icon: "shopping",
bg: "#477EEA",
// types: "11"
}, },
{ {
name: "Amicale", name: "Amicale",
route: "amicale", route: "amicale",
icon: "web", icon: "web",
bg: "#477EEA",
link: Amicale_LINK link: Amicale_LINK
// types: "11"
}, },
{ {
name: i18n.t('screens.timetable'), name: i18n.t('screens.timetable'),
route: "timetable", route: "timetable",
icon: "timetable", icon: "timetable",
bg: "#477EEA",
link: TIMETABLE_LINK link: TIMETABLE_LINK
// types: "11"
}, },
{ {
name: "Wiketud", name: "Wiketud",
route: "wiketud", route: "wiketud",
icon: "wikipedia", icon: "wikipedia",
bg: "#477EEA",
link: WIKETUD_LINK link: WIKETUD_LINK
// types: "11"
}, },
{ {
name: i18n.t('screens.settings'), name: i18n.t('screens.settings'),
route: "Settings", route: "Settings",
icon: "settings", icon: "settings",
bg: "#477EEA",
// types: "11"
}, },
{ {
name: i18n.t('screens.about'), name: i18n.t('screens.about'),
route: "About", route: "About",
icon: "information", icon: "information",
bg: "#477EEA",
// types: "11"
}, },
]; ];
} }

View file

@ -1,7 +1,7 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {Container, Text, Content, ListItem, Body} from 'native-base'; import {Body, Container, Content, ListItem, Text} from 'native-base';
import CustomHeader from "../../components/CustomHeader"; import CustomHeader from "../../components/CustomHeader";
import {FlatList} from "react-native"; import {FlatList} from "react-native";
import i18n from "i18n-js"; import i18n from "i18n-js";
@ -30,7 +30,7 @@ export default class AboutDependenciesScreen extends React.Component<Props> {
const data = generateListFromObject(nav.getParam('data', {})); const data = generateListFromObject(nav.getParam('data', {}));
return ( return (
<Container> <Container>
<CustomHeader backButton={true} navigation={nav} title={i18n.t('aboutScreen.libs')} /> <CustomHeader backButton={true} navigation={nav} title={i18n.t('aboutScreen.libs')}/>
<Content> <Content>
<FlatList <FlatList
data={data} data={data}

View file

@ -1,8 +1,8 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {Platform, StyleSheet, Linking, Alert, FlatList} from 'react-native'; import {Alert, FlatList, Linking, Platform} from 'react-native';
import {Container, Content, Text, Card, CardItem, Body, Left, Right, Thumbnail, H1} from 'native-base'; import {Body, Card, CardItem, Container, Content, H1, Left, Right, Text, Thumbnail} from 'native-base';
import CustomHeader from "../../components/CustomHeader"; import CustomHeader from "../../components/CustomHeader";
import i18n from "i18n-js"; import i18n from "i18n-js";
import appJson from '../../app'; import appJson from '../../app';

View file

@ -65,7 +65,7 @@ export default class PlanningScreen extends React.Component<Props> {
</Text> </Text>
{Platform.OS === 'android' ? {Platform.OS === 'android' ?
<Button block style={{marginTop: 20, marginRight: 10, marginLeft: 10}} <Button block style={{marginTop: 20, marginRight: 10, marginLeft: 10}}
onPress={() => openWebLink('https://expo.io/@amicaleinsat/application-amicale')}> onPress={() => openWebLink('https://expo.io/@amicaleinsat/application-amicale')}>
<Text>Try the beta</Text> <Text>Try the beta</Text>
</Button> </Button>
: <View/>} : <View/>}

View file

@ -1,7 +1,7 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {Container, Text, Content, ListItem, Left, Thumbnail, Right, Body} from 'native-base'; import {Body, Container, Content, Left, ListItem, Right, Text, Thumbnail} from 'native-base';
import CustomHeader from "../../components/CustomHeader"; import CustomHeader from "../../components/CustomHeader";
import {FlatList, Platform} from "react-native"; import {FlatList, Platform} from "react-native";
import Touchable from 'react-native-platform-touchable'; import Touchable from 'react-native-platform-touchable';
@ -132,8 +132,14 @@ export default class ProximoListScreen extends React.Component<Props, State> {
this.setSortMode(this.state.currentSortMode, this.state.isSortReversed); this.setSortMode(this.state.currentSortMode, this.state.isSortReversed);
} }
/**
* get color depending on quantity available
*
* @param availableStock
* @return
*/
getStockColor(availableStock: number) { getStockColor(availableStock: number) {
let color : string; let color: string;
if (availableStock > 3) if (availableStock > 3)
color = ThemeManager.getCurrentThemeVariables().brandSuccess; color = ThemeManager.getCurrentThemeVariables().brandSuccess;
else if (availableStock > 0) else if (availableStock > 0)
@ -234,7 +240,8 @@ export default class ProximoListScreen extends React.Component<Props, State> {
</Text> </Text>
<Text note style={{ <Text note style={{
marginLeft: 20, marginLeft: 20,
color: this.getStockColor(parseInt(item.quantity))}}> color: this.getStockColor(parseInt(item.quantity))
}}>
{item.quantity + ' ' + i18n.t('proximoScreen.inStock')} {item.quantity + ' ' + i18n.t('proximoScreen.inStock')}
</Text> </Text>
</Body> </Body>

View file

@ -78,7 +78,7 @@ export default class ProximoMainScreen extends FetchedDataSectionList {
return finalData; return finalData;
} }
getRenderItem(item: Object, section : Object, data : Object) { getRenderItem(item: Object, section: Object, data: Object) {
if (item.data.length > 0) { if (item.data.length > 0) {
return ( return (
<ListItem <ListItem

View file

@ -1,7 +1,7 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {Alert, View, Platform} from 'react-native'; import {Alert, Platform, View} from 'react-native';
import {Body, Card, CardItem, Left, Right, Text} from 'native-base'; import {Body, Card, CardItem, Left, Right, Text} from 'native-base';
import ThemeManager from '../utils/ThemeManager'; import ThemeManager from '../utils/ThemeManager';
import i18n from "i18n-js"; import i18n from "i18n-js";
@ -36,7 +36,7 @@ let stateColors = {};
*/ */
export default class ProxiwashScreen extends FetchedDataSectionList { export default class ProxiwashScreen extends FetchedDataSectionList {
refreshInterval : IntervalID; refreshInterval: IntervalID;
/** /**
* Creates machine state parameters using current theme and translations * Creates machine state parameters using current theme and translations
@ -77,6 +77,9 @@ export default class ProxiwashScreen extends FetchedDataSectionList {
}; };
} }
/**
* Setup notification channel for android and add listeners to detect notifications fired
*/
componentDidMount() { componentDidMount() {
super.componentDidMount(); super.componentDidMount();
if (Platform.OS === 'android') { if (Platform.OS === 'android') {
@ -104,10 +107,6 @@ export default class ProxiwashScreen extends FetchedDataSectionList {
return [i18n.t("proxiwashScreen.listUpdated"), i18n.t("proxiwashScreen.listUpdateFail")]; return [i18n.t("proxiwashScreen.listUpdated"), i18n.t("proxiwashScreen.listUpdateFail")];
} }
getKeyExtractor(item: Object) {
return item !== undefined ? item.number : undefined;
}
getDryersKeyExtractor(item: Object) { getDryersKeyExtractor(item: Object) {
return item !== undefined ? "dryer" + item.number : undefined; return item !== undefined ? "dryer" + item.number : undefined;
} }
@ -202,13 +201,26 @@ export default class ProxiwashScreen extends FetchedDataSectionList {
} }
} }
getMachineIndexInWatchList(machineId: string) { /**
* Get the index of the given machine ID in the watchlist array
*
* @param machineId
* @return
*/
getMachineIndexInWatchList(machineId: string): number {
let elem = this.state.machinesWatched.find(function (elem) { let elem = this.state.machinesWatched.find(function (elem) {
return elem.machineNumber === machineId return elem.machineNumber === machineId
}); });
return this.state.machinesWatched.indexOf(elem); return this.state.machinesWatched.indexOf(elem);
} }
/**
* Add the given notifications associated to a machine ID to the watchlist, and save the array to the preferences
*
* @param machineId
* @param endNotificationID
* @param reminderNotificationID
*/
saveNotificationToPrefs(machineId: string, endNotificationID: string, reminderNotificationID: string | null) { saveNotificationToPrefs(machineId: string, endNotificationID: string, reminderNotificationID: string | null) {
let data = this.state.machinesWatched; let data = this.state.machinesWatched;
data.push({ data.push({
@ -219,13 +231,23 @@ export default class ProxiwashScreen extends FetchedDataSectionList {
this.updateNotificationPrefs(data); this.updateNotificationPrefs(data);
} }
/**
* remove the given index from the watchlist array and save it to preferences
*
* @param index
*/
removeNotificationFromPrefs(index: number) { removeNotificationFromPrefs(index: number) {
let data = this.state.machinesWatched; let data = this.state.machinesWatched;
data.splice(index, 1); data.splice(index, 1);
this.updateNotificationPrefs(data); this.updateNotificationPrefs(data);
} }
updateNotificationPrefs(data: Object) { /**
* Set the given data as the watchlist and save it to preferences
*
* @param data
*/
updateNotificationPrefs(data: Array<Object>) {
this.setState({machinesWatched: data}); this.setState({machinesWatched: data});
let prefKey = AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.key; let prefKey = AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.key;
AsyncStorageManager.getInstance().savePref(prefKey, JSON.stringify(data)); AsyncStorageManager.getInstance().savePref(prefKey, JSON.stringify(data));
@ -266,6 +288,13 @@ export default class ProxiwashScreen extends FetchedDataSectionList {
return true; return true;
} }
/**
* Show an alert fo a machine, allowing to enable/disable notifications if running
*
* @param title
* @param item
* @param remainingTime
*/
showAlert(title: string, item: Object, remainingTime: number) { showAlert(title: string, item: Object, remainingTime: number) {
let buttons = [{text: i18n.t("proxiwashScreen.modal.ok")}]; let buttons = [{text: i18n.t("proxiwashScreen.modal.ok")}];
let message = modalStateStrings[MACHINE_STATES[item.state]]; let message = modalStateStrings[MACHINE_STATES[item.state]];

View file

@ -2,18 +2,18 @@
import * as React from 'react'; import * as React from 'react';
import { import {
Body,
Card,
CardItem,
CheckBox,
Container, Container,
Content, Content,
Left, Left,
List,
ListItem, ListItem,
Picker,
Right, Right,
Text, Text,
List,
CheckBox,
Body,
CardItem,
Card,
Picker,
} from "native-base"; } from "native-base";
import CustomHeader from "../components/CustomHeader"; import CustomHeader from "../components/CustomHeader";
import ThemeManager from '../utils/ThemeManager'; import ThemeManager from '../utils/ThemeManager';

View file

@ -3,7 +3,9 @@
import {AsyncStorage} from "react-native"; import {AsyncStorage} from "react-native";
/** /**
* Static class used to manage preferences * Static class used to manage preferences.
* Preferences are fetched at the start of the app and saved in an instance object.
* This allows for a synchronous access to saved data.
*/ */
export default class AsyncStorageManager { export default class AsyncStorageManager {
@ -20,30 +22,36 @@ export default class AsyncStorageManager {
AsyncStorageManager.instance; AsyncStorageManager.instance;
} }
// Object storing preferences keys, default and current values for use in the app
preferences = { preferences = {
showIntro: { showIntro: {
key: 'showIntro', key: 'showIntro',
default: '1', default: '1',
current : '', current: '',
}, },
proxiwashNotifications: { proxiwashNotifications: {
key: 'proxiwashNotifications', key: 'proxiwashNotifications',
default: '5', default: '5',
current : '', current: '',
}, },
proxiwashWatchedMachines : { proxiwashWatchedMachines: {
key: 'proxiwashWatchedMachines', key: 'proxiwashWatchedMachines',
default: '[]', default: '[]',
current : '', current: '',
}, },
nightMode: { nightMode: {
key: 'nightMode', key: 'nightMode',
default : '0', default: '0',
current : '', current: '',
} }
}; };
/**
* Set preferences object current values from AsyncStorage.
* This function should be called at the app's start.
*
* @return {Promise<void>}
*/
async loadPreferences() { async loadPreferences() {
let prefKeys = []; let prefKeys = [];
// Get all available keys // Get all available keys
@ -51,18 +59,25 @@ export default class AsyncStorageManager {
prefKeys.push(value.key); prefKeys.push(value.key);
} }
// Get corresponding values // Get corresponding values
let resultArray : Array<Array<string>> = await AsyncStorage.multiGet(prefKeys); let resultArray: Array<Array<string>> = await AsyncStorage.multiGet(prefKeys);
// Save those values for later use // Save those values for later use
for (let i = 0; i < resultArray.length; i++) { for (let i = 0; i < resultArray.length; i++) {
let key : string = resultArray[i][0]; let key: string = resultArray[i][0];
let val : string | null = resultArray[i][1]; let val: string | null = resultArray[i][1];
if (val === null) if (val === null)
val = this.preferences[key].default; val = this.preferences[key].default;
this.preferences[key].current = val; this.preferences[key].current = val;
} }
} }
savePref(key : string, val : string) { /**
* Save the value associated to the given key to preferences.
* This updates the preferences object and saves it to AsynStorage.
*
* @param key
* @param val
*/
savePref(key: string, val: string) {
this.preferences[key].current = val; this.preferences[key].current = val;
AsyncStorage.setItem(key, val); AsyncStorage.setItem(key, val);
} }

View file

@ -4,6 +4,7 @@ import platform from '../native-base-theme/variables/platform';
import platformDark from '../native-base-theme/variables/platformDark'; import platformDark from '../native-base-theme/variables/platformDark';
import getTheme from '../native-base-theme/components'; import getTheme from '../native-base-theme/components';
import AsyncStorageManager from "./AsyncStorageManager"; import AsyncStorageManager from "./AsyncStorageManager";
/** /**
* Singleton class used to manage themes * Singleton class used to manage themes
*/ */

View file

@ -1,17 +1,26 @@
import {Toast} from "native-base"; import {Toast} from "native-base";
/**
* Class used to get json data from the web
*/
export default class WebDataManager { export default class WebDataManager {
FETCH_URL : string; FETCH_URL: string;
lastDataFetched : Object = {}; lastDataFetched: Object = {};
constructor(url) { constructor(url) {
this.FETCH_URL = url; this.FETCH_URL = url;
} }
/**
* Read data from FETCH_URL and return it.
* If no data was found, returns an empty object
*
* @return {Promise<Object>}
*/
async readData() { async readData() {
let fetchedData : Object = {}; let fetchedData: Object = {};
try { try {
let response = await fetch(this.FETCH_URL); let response = await fetch(this.FETCH_URL);
fetchedData = await response.json(); fetchedData = await response.json();
@ -23,10 +32,21 @@ export default class WebDataManager {
return fetchedData; return fetchedData;
} }
isDataObjectValid() { /**
* Detects if the fetched data is not an empty object
*
* @return
*/
isDataObjectValid(): boolean {
return Object.keys(this.lastDataFetched).length > 0; return Object.keys(this.lastDataFetched).length > 0;
} }
/**
* Show a toast message depending on the validity of the fetched data
*
* @param successString
* @param errorString
*/
showUpdateToast(successString, errorString) { showUpdateToast(successString, errorString) {
let isSuccess = this.isDataObjectValid(); let isSuccess = this.isDataObjectValid();
if (!isSuccess) { if (!isSuccess) {