Use common class to handle section list display
This commit is contained in:
parent
3ec7614061
commit
861b655fe5
9 changed files with 339 additions and 414 deletions
2
App.js
2
App.js
|
@ -31,7 +31,7 @@ export default class App extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads data before components are mounted, like fonts and themes
|
* Loads FetchedData before components are mounted, like fonts and themes
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
async componentWillMount() {
|
async componentWillMount() {
|
||||||
|
|
122
components/FetchedDataSectionList.js
Normal file
122
components/FetchedDataSectionList.js
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import WebDataManager from "../utils/WebDataManager";
|
||||||
|
import {Container, Content, H2} from "native-base";
|
||||||
|
import CustomHeader from "./CustomHeader";
|
||||||
|
import {SectionList, RefreshControl, View} from "react-native";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
navigation: Object,
|
||||||
|
}
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
refreshing: boolean,
|
||||||
|
firstLoading: boolean,
|
||||||
|
fetchedData: Object,
|
||||||
|
machinesWatched : Array<Object>
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class FetchedDataSectionList extends React.Component<Props, State> {
|
||||||
|
|
||||||
|
webDataManager : WebDataManager;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.webDataManager = new WebDataManager(this.getFetchUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
refreshing: false,
|
||||||
|
firstLoading: true,
|
||||||
|
fetchedData: {},
|
||||||
|
machinesWatched : [],
|
||||||
|
};
|
||||||
|
|
||||||
|
getFetchUrl() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
getHeaderTranslation() {
|
||||||
|
return "Header";
|
||||||
|
}
|
||||||
|
|
||||||
|
getUpdateToastTranslations () {
|
||||||
|
return ["whoa", "nah"];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the FetchedData on first screen load
|
||||||
|
*/
|
||||||
|
componentDidMount() {
|
||||||
|
this._onRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onRefresh = () => {
|
||||||
|
this.setState({refreshing: true});
|
||||||
|
this.webDataManager.readData().then((fetchedData) => {
|
||||||
|
this.setState({
|
||||||
|
fetchedData: fetchedData,
|
||||||
|
refreshing: false,
|
||||||
|
firstLoading: false
|
||||||
|
});
|
||||||
|
this.webDataManager.showUpdateToast(this.getUpdateToastTranslations()[0], this.getUpdateToastTranslations()[1]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
getRenderItem(item: Object, section : Object, data : Object) {
|
||||||
|
return <View />;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRenderSectionHeader(title: String) {
|
||||||
|
return <View />;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the dataset to be used in the list from the data fetched
|
||||||
|
* @param fetchedData {Object}
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
createDataset(fetchedData : Object) : Array<Object> {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What item field should be used as a key in the list
|
||||||
|
* @param item {Object}
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
getKeyExtractor(item : Object) {
|
||||||
|
return item.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const nav = this.props.navigation;
|
||||||
|
const dataset = this.createDataset(this.state.fetchedData);
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<CustomHeader navigation={nav} title={this.getHeaderTranslation()}/>
|
||||||
|
<Content padder>
|
||||||
|
<SectionList
|
||||||
|
sections={dataset}
|
||||||
|
keyExtractor={(item) => this.getKeyExtractor(item)}
|
||||||
|
refreshControl={
|
||||||
|
<RefreshControl
|
||||||
|
refreshing={this.state.refreshing}
|
||||||
|
onRefresh={this._onRefresh}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
renderSectionHeader={({section: {title}}) =>
|
||||||
|
this.getRenderSectionHeader(title)
|
||||||
|
}
|
||||||
|
renderItem={({item, section}) =>
|
||||||
|
this.getRenderItem(item, section, dataset)
|
||||||
|
}
|
||||||
|
style={{minHeight: 300, width: '100%'}}
|
||||||
|
/>
|
||||||
|
</Content>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,44 +1,15 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {Image, View, Linking, RefreshControl, FlatList} from 'react-native';
|
import {Image, Linking} from 'react-native';
|
||||||
import {Container, Content, Text, Button, Card, CardItem, Left, Body, Thumbnail, H2, Toast} from 'native-base';
|
import {Text, Button, Card, CardItem, Left, Body, Thumbnail} from 'native-base';
|
||||||
import CustomHeader from '../components/CustomHeader';
|
|
||||||
import i18n from "i18n-js";
|
import i18n from "i18n-js";
|
||||||
import CustomMaterialIcon from '../components/CustomMaterialIcon';
|
import CustomMaterialIcon from '../components/CustomMaterialIcon';
|
||||||
|
import FetchedDataSectionList from "../components/FetchedDataSectionList";
|
||||||
|
|
||||||
const ICON_AMICALE = require('../assets/amicale.png');
|
const ICON_AMICALE = require('../assets/amicale.png');
|
||||||
|
const NAME_AMICALE = 'Amicale INSA Toulouse';
|
||||||
type Props = {
|
const FB_URL = "https://graph.facebook.com/v3.3/amicale.deseleves/posts?fields=message%2Cfull_picture%2Ccreated_time%2Cpermalink_url&&date_format=U&access_token=EAAGliUs4Ei8BAGwHmg7SNnosoEDMuDhP3i5lYOGrIGzZBNeMeGzGhpUigJt167cKXEIM0GiurSgaC0PS4Xg2GBzOVNiZCfr8u48VVB15a9YbOsuhjBqhHAMb2sz6ibwOuDhHSvwRZCUpBZCjmAW12e7RjWJp0jvyNoYYvIQbfaLWi3Nk2mBc";
|
||||||
navigation: Object,
|
|
||||||
}
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
refreshing: boolean,
|
|
||||||
firstLoading: boolean,
|
|
||||||
data: Object,
|
|
||||||
};
|
|
||||||
|
|
||||||
const FB_URL = "https://graph.facebook.com/v3.3/amicale.deseleves/posts?fields=message%2Cfull_picture%2Ccreated_time%2Cpermalink_url&access_token=EAAGliUs4Ei8BAGwHmg7SNnosoEDMuDhP3i5lYOGrIGzZBNeMeGzGhpUigJt167cKXEIM0GiurSgaC0PS4Xg2GBzOVNiZCfr8u48VVB15a9YbOsuhjBqhHAMb2sz6ibwOuDhHSvwRZCUpBZCjmAW12e7RjWJp0jvyNoYYvIQbfaLWi3Nk2mBc";
|
|
||||||
|
|
||||||
let test_data = [
|
|
||||||
{
|
|
||||||
title: "News de l'Amicale",
|
|
||||||
date: "June 15, 2019",
|
|
||||||
thumbnail: require("../assets/amicale.png"),
|
|
||||||
image: require("../assets/drawer-cover.png"),
|
|
||||||
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus congue sapien leo, ac dignissim odio dignissim sit amet. Quisque tempor, turpis sed scelerisque faucibus, dolor tortor porta sapien, eget tincidunt ante elit et ex. Sed sagittis dui non nisl aliquet viverra. Integer quis convallis enim, sit amet auctor ante. Praesent quis lacinia magna. Sed augue lacus, congue eu turpis vel, consectetur pellentesque nulla. Maecenas blandit diam odio, et finibus urna egestas non. Quisque congue finibus efficitur. Sed pretium mauris nec neque mattis, eu condimentum velit ultrices. Fusce eleifend porttitor nunc non suscipit. Aenean porttitor feugiat ipsum sit amet interdum. Maecenas tempor felis non tempus vehicula. Suspendisse sit amet eros neque. ",
|
|
||||||
link: "https://en.wikipedia.org/wiki/Main_Page"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Lancement de la super appli de la mort avec un titre super long",
|
|
||||||
date: "June 14, 2019",
|
|
||||||
thumbnail: require("../assets/amicale.png"),
|
|
||||||
image: require("../assets/image-missing.png"),
|
|
||||||
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus congue sapien leo, eget tincidunt ante elit et ex. Sed sagittis dui non nisl aliquet viverra. Integer quis convallis enim, sit amet auctor ante. Praesent quis lacinia magna. Sed augue lacus, congue eu turpis vel, consectetur pellentesque nulla. Maecenas blandit diam odio, et finibus urna egestas non. Quisque congue finibus efficitur. Sed pretium mauris nec neque mattis, eu condimentum velit ultrices. Fusce eleifend porttitor nunc non suscipit.",
|
|
||||||
link: "https://en.wikipedia.org/wiki/Central_Link"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens a link in the device's browser
|
* Opens a link in the device's browser
|
||||||
|
@ -51,68 +22,56 @@ function openWebLink(link) {
|
||||||
/**
|
/**
|
||||||
* Class defining the app's home screen
|
* Class defining the app's home screen
|
||||||
*/
|
*/
|
||||||
export default class HomeScreen extends React.Component<Props, State> {
|
export default class HomeScreen extends FetchedDataSectionList {
|
||||||
|
|
||||||
state = {
|
getHeaderTranslation() {
|
||||||
refreshing: false,
|
return i18n.t("screens.home");
|
||||||
firstLoading: true,
|
|
||||||
data: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
async readData() {
|
|
||||||
try {
|
|
||||||
let response = await fetch(FB_URL);
|
|
||||||
let responseJson = await response.json();
|
|
||||||
this.setState({
|
|
||||||
data: responseJson
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.log('Could not read data from server');
|
|
||||||
console.log(error);
|
|
||||||
this.setState({
|
|
||||||
data: {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isDataObjectValid() {
|
getUpdateToastTranslations () {
|
||||||
return Object.keys(this.state.data).length > 0;
|
return [i18n.t("homeScreen.listUpdated"),i18n.t("homeScreen.listUpdateFail")];
|
||||||
}
|
}
|
||||||
|
|
||||||
_onRefresh = () => {
|
getKeyExtractor(item : Object) {
|
||||||
this.setState({refreshing: true});
|
return item.id;
|
||||||
this.readData().then(() => {
|
|
||||||
this.setState({
|
|
||||||
refreshing: false,
|
|
||||||
firstLoading: false
|
|
||||||
});
|
|
||||||
if (this.isDataObjectValid()) {
|
|
||||||
Toast.show({
|
|
||||||
text: i18n.t('proxiwashScreen.listUpdated'),
|
|
||||||
buttonText: 'OK',
|
|
||||||
type: "success",
|
|
||||||
duration: 2000
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Toast.show({
|
|
||||||
text: i18n.t('proxiwashScreen.listUpdateFail'),
|
|
||||||
buttonText: 'OK',
|
|
||||||
type: "danger",
|
|
||||||
duration: 4000
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
getRenderItem(item: Object) {
|
createDataset(fetchedData : Object) {
|
||||||
|
let data = [];
|
||||||
|
if (fetchedData.data !== undefined)
|
||||||
|
data = fetchedData.data;
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: '',
|
||||||
|
data: data,
|
||||||
|
extraData: super.state
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
getFetchUrl() {
|
||||||
|
return FB_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a dateString using Unix Timestamp to a formatted date
|
||||||
|
* @param dateString {string} The Unix Timestamp representation of a date
|
||||||
|
* @return {string} The formatted output date
|
||||||
|
*/
|
||||||
|
static getFormattedDate(dateString: string) {
|
||||||
|
let date = new Date(Number.parseInt(dateString) * 1000);
|
||||||
|
return date.toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
|
getRenderItem(item: Object, section : Object, data : Object) {
|
||||||
return (
|
return (
|
||||||
<Card style={{flex: 0}}>
|
<Card style={{flex: 0}}>
|
||||||
<CardItem>
|
<CardItem>
|
||||||
<Left>
|
<Left>
|
||||||
<Thumbnail source={ICON_AMICALE}/>
|
<Thumbnail source={ICON_AMICALE}/>
|
||||||
<Body>
|
<Body>
|
||||||
<Text>Amicale</Text>
|
<Text>{NAME_AMICALE}</Text>
|
||||||
<Text note>{item.created_time}</Text>
|
<Text note>{HomeScreen.getFormattedDate(item.created_time)}</Text>
|
||||||
</Body>
|
</Body>
|
||||||
</Left>
|
</Left>
|
||||||
</CardItem>
|
</CardItem>
|
||||||
|
@ -139,38 +98,4 @@ export default class HomeScreen extends React.Component<Props, State> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Refresh the data on first screen load
|
|
||||||
*/
|
|
||||||
componentDidMount() {
|
|
||||||
this._onRefresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const nav = this.props.navigation;
|
|
||||||
let displayData = this.state.data.data;
|
|
||||||
return (
|
|
||||||
<Container>
|
|
||||||
<CustomHeader navigation={nav} title={i18n.t('screens.home')}/>
|
|
||||||
<Content padder>
|
|
||||||
<FlatList
|
|
||||||
data={displayData}
|
|
||||||
extraData={this.state}
|
|
||||||
keyExtractor={(item) => item.id}
|
|
||||||
refreshControl={
|
|
||||||
<RefreshControl
|
|
||||||
refreshing={this.state.refreshing}
|
|
||||||
onRefresh={this._onRefresh}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
renderItem={({item}) =>
|
|
||||||
this.getRenderItem(item)
|
|
||||||
}
|
|
||||||
style={{minHeight: 300, width: '100%'}}
|
|
||||||
/>
|
|
||||||
</Content>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {RefreshControl, SectionList} from 'react-native';
|
import {Badge, Body, H2, Left, ListItem, Right, Text} 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 i18n from "i18n-js";
|
||||||
import CustomMaterialIcon from "../../components/CustomMaterialIcon";
|
import CustomMaterialIcon from "../../components/CustomMaterialIcon";
|
||||||
|
import FetchedDataSectionList from "../../components/FetchedDataSectionList";
|
||||||
|
|
||||||
const DATA_URL = "https://etud.insa-toulouse.fr/~vergnet/appli-amicale/dataProximo.json";
|
const DATA_URL = "https://etud.insa-toulouse.fr/~vergnet/appli-amicale/dataProximo.json";
|
||||||
|
|
||||||
|
@ -17,145 +16,73 @@ const typesIcons = {
|
||||||
Default: "information-outline",
|
Default: "information-outline",
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
|
||||||
navigation: Object
|
|
||||||
}
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
refreshing: boolean,
|
|
||||||
firstLoading: boolean,
|
|
||||||
data: Object,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class defining the main proximo screen. This screen shows the different categories of articles
|
* Class defining the main proximo screen. This screen shows the different categories of articles
|
||||||
* offered by proximo.
|
* offered by proximo.
|
||||||
*/
|
*/
|
||||||
export default class ProximoMainScreen extends React.Component<Props, State> {
|
export default class ProximoMainScreen extends FetchedDataSectionList {
|
||||||
|
|
||||||
state = {
|
getFetchUrl() {
|
||||||
refreshing: false,
|
return DATA_URL;
|
||||||
firstLoading: true,
|
}
|
||||||
data: {},
|
|
||||||
};
|
getHeaderTranslation() {
|
||||||
|
return i18n.t("screens.proximo");
|
||||||
|
}
|
||||||
|
|
||||||
|
getUpdateToastTranslations() {
|
||||||
|
return [i18n.t("proximoScreen.listUpdated"), i18n.t("proximoScreen.listUpdateFail")];
|
||||||
|
}
|
||||||
|
|
||||||
|
getKeyExtractor(item: Object) {
|
||||||
|
return item.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
createDataset(fetchedData: Object) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: i18n.t('proximoScreen.listTitle'),
|
||||||
|
data: ProximoMainScreen.generateData(fetchedData),
|
||||||
|
extraData: super.state,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate the dataset using types and data.
|
* Generate the data using types and FetchedData.
|
||||||
* This will group items under the same type.
|
* This will group items under the same type.
|
||||||
*
|
*
|
||||||
* @param types An array containing the types available (categories)
|
* @param fetchedData The array of articles represented by objects
|
||||||
* @param data The array of articles represented by objects
|
|
||||||
* @returns {Array} The formatted dataset
|
* @returns {Array} The formatted dataset
|
||||||
*/
|
*/
|
||||||
static generateDataset(types: Array<string>, data: Array<Object>) {
|
static generateData(fetchedData: Object) {
|
||||||
let finalData = [];
|
let finalData = [];
|
||||||
|
if (fetchedData.types !== undefined && fetchedData.articles !== undefined) {
|
||||||
|
let types = fetchedData.types;
|
||||||
|
let articles = fetchedData.articles;
|
||||||
for (let i = 0; i < types.length; i++) {
|
for (let i = 0; i < types.length; i++) {
|
||||||
finalData.push({
|
finalData.push({
|
||||||
type: types[i],
|
type: types[i],
|
||||||
data: []
|
data: []
|
||||||
});
|
});
|
||||||
for (let k = 0; k < data.length; k++) {
|
for (let k = 0; k < articles.length; k++) {
|
||||||
if (data[k]['type'].includes(types[i])) {
|
if (articles[k]['type'].includes(types[i])) {
|
||||||
finalData[i].data.push(data[k]);
|
finalData[i].data.push(articles[k]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return finalData;
|
return finalData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
getRenderItem(item: Object, section : Object, data : Object) {
|
||||||
* Async function reading data from the proximo website and setting the state to rerender the list
|
|
||||||
*
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async readData() {
|
|
||||||
try {
|
|
||||||
let response = await fetch(DATA_URL);
|
|
||||||
let responseJson = await response.json();
|
|
||||||
|
|
||||||
if (responseJson['articles'].length !== 0 && responseJson['types'].length !== 0) {
|
|
||||||
let data = ProximoMainScreen.generateDataset(responseJson['types'], responseJson['articles']);
|
|
||||||
this.setState({
|
|
||||||
data: data
|
|
||||||
});
|
|
||||||
} else
|
|
||||||
this.setState({data: undefined});
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refresh the list on first screen load
|
|
||||||
*/
|
|
||||||
componentDidMount() {
|
|
||||||
this._onRefresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display a loading indicator and fetch data from the internet
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_onRefresh = () => {
|
|
||||||
this.setState({refreshing: true});
|
|
||||||
this.readData().then(() => {
|
|
||||||
this.setState({
|
|
||||||
refreshing: false,
|
|
||||||
firstLoading: false
|
|
||||||
});
|
|
||||||
Toast.show({
|
|
||||||
text: i18n.t('proximoScreen.listUpdated'),
|
|
||||||
buttonText: 'OK',
|
|
||||||
type: "success",
|
|
||||||
duration: 2000
|
|
||||||
})
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the proximo categories list.
|
|
||||||
* If we are loading for the first time, change the data for the SectionList to display a loading message.
|
|
||||||
*
|
|
||||||
* @returns {react.Node}
|
|
||||||
*/
|
|
||||||
render() {
|
|
||||||
const nav = this.props.navigation;
|
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
title: i18n.t('proximoScreen.listTitle'),
|
|
||||||
data: this.state.data,
|
|
||||||
extraData: this.state,
|
|
||||||
}
|
|
||||||
];
|
|
||||||
const loadingData = [
|
|
||||||
{
|
|
||||||
title: i18n.t('proximoScreen.loading'),
|
|
||||||
data: []
|
|
||||||
}
|
|
||||||
];
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
|
||||||
<CustomHeader navigation={nav} title={'Proximo'}/>
|
|
||||||
<SectionList
|
|
||||||
sections={this.state.firstLoading ? loadingData : data}
|
|
||||||
keyExtractor={(item, index) => item.type}
|
|
||||||
refreshControl={
|
|
||||||
<RefreshControl
|
|
||||||
refreshing={this.state.refreshing}
|
|
||||||
onRefresh={this._onRefresh}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
style={{minHeight: 300, width: '100%'}}
|
|
||||||
renderSectionHeader={({section: {title}}) => (
|
|
||||||
<H2 style={{textAlign: 'center', paddingVertical: 10}}>{title}</H2>
|
|
||||||
)}
|
|
||||||
renderItem={({item}) =>
|
|
||||||
<ListItem
|
<ListItem
|
||||||
button
|
button
|
||||||
thumbnail
|
thumbnail
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
nav.navigate('ProximoListScreen', item);
|
this.props.navigation.navigate('ProximoListScreen', item);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Left>
|
<Left>
|
||||||
|
@ -175,10 +102,12 @@ export default class ProximoMainScreen extends React.Component<Props, State> {
|
||||||
<Right>
|
<Right>
|
||||||
<CustomMaterialIcon icon="chevron-right"/>
|
<CustomMaterialIcon icon="chevron-right"/>
|
||||||
</Right>
|
</Right>
|
||||||
</ListItem>}
|
</ListItem>
|
||||||
/>
|
|
||||||
</Container>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRenderSectionHeader(title: String) {
|
||||||
|
return <H2 style={{textAlign: 'center', paddingVertical: 10}}>{title}</H2>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {SectionList, RefreshControl, View} from 'react-native';
|
import {AsyncStorage, View} from 'react-native';
|
||||||
import {Body, Container, Icon, Left, ListItem, Right, Text, Toast, H2, Button} from 'native-base';
|
import {Body, Button, H2, Icon, Left, ListItem, Right, Text} from 'native-base';
|
||||||
import CustomHeader from "../components/CustomHeader";
|
|
||||||
import ThemeManager from '../utils/ThemeManager';
|
import ThemeManager from '../utils/ThemeManager';
|
||||||
import NotificationsManager from '../utils/NotificationsManager';
|
|
||||||
import i18n from "i18n-js";
|
import i18n from "i18n-js";
|
||||||
import {AsyncStorage} from 'react-native'
|
|
||||||
import CustomMaterialIcon from "../components/CustomMaterialIcon";
|
import CustomMaterialIcon from "../components/CustomMaterialIcon";
|
||||||
|
import FetchedDataSectionList from "../components/FetchedDataSectionList";
|
||||||
|
import NotificationsManager from "../utils/NotificationsManager";
|
||||||
|
|
||||||
const DATA_URL = "https://etud.insa-toulouse.fr/~vergnet/appli-amicale/dataProxiwash.json";
|
const DATA_URL = "https://etud.insa-toulouse.fr/~vergnet/appli-amicale/dataProxiwash.json";
|
||||||
const WATCHED_MACHINES_PREFKEY = "proxiwash.watchedMachines";
|
const WATCHED_MACHINES_PREFKEY = "proxiwash.watchedMachines";
|
||||||
|
@ -27,37 +26,25 @@ let stateStrings = {};
|
||||||
|
|
||||||
let stateColors = {};
|
let stateColors = {};
|
||||||
|
|
||||||
type Props = {
|
|
||||||
navigation: Object,
|
|
||||||
};
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
refreshing: boolean,
|
|
||||||
firstLoading: boolean,
|
|
||||||
data: Object,
|
|
||||||
machinesWatched: Array<Object>
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class defining the app's proxiwash screen. This screen shows information about washing machines and
|
* Class defining the app's proxiwash screen. This screen shows information about washing machines and
|
||||||
* dryers, taken from a scrapper reading proxiwash website
|
* dryers, taken from a scrapper reading proxiwash website
|
||||||
*/
|
*/
|
||||||
export default class ProxiwashScreen extends React.Component<Props, State> {
|
export default class ProxiwashScreen extends FetchedDataSectionList {
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
refreshing: false,
|
refreshing: false,
|
||||||
firstLoading: true,
|
firstLoading: true,
|
||||||
data: {},
|
fetchedData: {},
|
||||||
machinesWatched: [],
|
machinesWatched : [],
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates machine state parameters using current theme and translations
|
* Creates machine state parameters using current theme and translations
|
||||||
*
|
|
||||||
* @param props
|
|
||||||
*/
|
*/
|
||||||
constructor(props: Props) {
|
constructor() {
|
||||||
super(props);
|
super();
|
||||||
let colors = ThemeManager.getInstance().getCurrentThemeVariables();
|
let colors = ThemeManager.getInstance().getCurrentThemeVariables();
|
||||||
stateColors[MACHINE_STATES.TERMINE] = colors.proxiwashFinishedColor;
|
stateColors[MACHINE_STATES.TERMINE] = colors.proxiwashFinishedColor;
|
||||||
stateColors[MACHINE_STATES.DISPONIBLE] = colors.proxiwashReadyColor;
|
stateColors[MACHINE_STATES.DISPONIBLE] = colors.proxiwashReadyColor;
|
||||||
|
@ -72,34 +59,20 @@ export default class ProxiwashScreen extends React.Component<Props, State> {
|
||||||
stateStrings[MACHINE_STATES.ERREUR] = i18n.t('proxiwashScreen.states.error');
|
stateStrings[MACHINE_STATES.ERREUR] = i18n.t('proxiwashScreen.states.error');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
getFetchUrl() {
|
||||||
* Check if the data object contains valid entries
|
return DATA_URL;
|
||||||
*
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isDataObjectValid() {
|
|
||||||
return Object.keys(this.state.data).length > 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
getHeaderTranslation() {
|
||||||
* Read the data from the proxiwash scrapper and set it to current state to reload the screen
|
return i18n.t("screens.proxiwash");
|
||||||
*
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async readData() {
|
|
||||||
try {
|
|
||||||
let response = await fetch(DATA_URL);
|
|
||||||
let responseJson = await response.json();
|
|
||||||
this.setState({
|
|
||||||
data: responseJson
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.log('Could not read data from server');
|
|
||||||
console.log(error);
|
|
||||||
this.setState({
|
|
||||||
data: {}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUpdateToastTranslations() {
|
||||||
|
return [i18n.t("proxiwashScreen.listUpdated"), i18n.t("proxiwashScreen.listUpdateFail")];
|
||||||
|
}
|
||||||
|
|
||||||
|
getKeyExtractor(item: Object) {
|
||||||
|
return item.number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -116,43 +89,6 @@ export default class ProxiwashScreen extends React.Component<Props, State> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Refresh the data on first screen load
|
|
||||||
*/
|
|
||||||
componentDidMount() {
|
|
||||||
this._onRefresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the refresh indicator and wait for data to be fetched from the scrapper
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_onRefresh = () => {
|
|
||||||
this.setState({refreshing: true});
|
|
||||||
this.readData().then(() => {
|
|
||||||
this.setState({
|
|
||||||
refreshing: false,
|
|
||||||
firstLoading: false
|
|
||||||
});
|
|
||||||
if (this.isDataObjectValid()) {
|
|
||||||
Toast.show({
|
|
||||||
text: i18n.t('proxiwashScreen.listUpdated'),
|
|
||||||
buttonText: 'OK',
|
|
||||||
type: "success",
|
|
||||||
duration: 2000
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Toast.show({
|
|
||||||
text: i18n.t('proxiwashScreen.listUpdateFail'),
|
|
||||||
buttonText: 'OK',
|
|
||||||
type: "danger",
|
|
||||||
duration: 4000
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the time remaining based on start/end time and done percent
|
* Get the time remaining based on start/end time and done percent
|
||||||
*
|
*
|
||||||
|
@ -247,15 +183,30 @@ export default class ProxiwashScreen extends React.Component<Props, State> {
|
||||||
}) !== undefined;
|
}) !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createDataset(fetchedData: Object) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: i18n.t('proxiwashScreen.dryers'),
|
||||||
|
data: fetchedData.dryers === undefined ? [] : fetchedData.dryers,
|
||||||
|
extraData: super.state
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18n.t('proxiwashScreen.washers'),
|
||||||
|
data: fetchedData.washers === undefined ? [] : fetchedData.washers,
|
||||||
|
extraData: super.state
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get list item to be rendered
|
* Get list item to be rendered
|
||||||
*
|
*
|
||||||
* @param item The object containing the item's data
|
* @param item The object containing the item's FetchedData
|
||||||
* @param section The object describing the current SectionList section
|
* @param section The object describing the current SectionList section
|
||||||
* @param data The full data used by the SectionList
|
* @param data The full FetchedData used by the SectionList
|
||||||
* @returns {React.Node}
|
* @returns {React.Node}
|
||||||
*/
|
*/
|
||||||
renderItem(item: Object, section: Object, data: Object) {
|
getRenderItem(item: Object, section: Object, data: Object) {
|
||||||
return (
|
return (
|
||||||
<ListItem
|
<ListItem
|
||||||
thumbnail
|
thumbnail
|
||||||
|
@ -293,11 +244,13 @@ export default class ProxiwashScreen extends React.Component<Props, State> {
|
||||||
{backgroundColor: '#ba7c1f'} : {}}
|
{backgroundColor: '#ba7c1f'} : {}}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
this.setupNotifications(item.number, ProxiwashScreen.getRemainingTime(item.startTime, item.endTime, item.donePercent))
|
this.setupNotifications(item.number, ProxiwashScreen.getRemainingTime(item.startTime, item.endTime, item.donePercent))
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<Text>
|
<Text>
|
||||||
{ProxiwashScreen.getRemainingTime(item.startTime, item.endTime, item.donePercent) + ' ' + i18n.t('proxiwashScreen.min')}
|
{ProxiwashScreen.getRemainingTime(item.startTime, item.endTime, item.donePercent) + ' ' + i18n.t('proxiwashScreen.min')}
|
||||||
</Text>
|
</Text>
|
||||||
<Icon name={this.isMachineWatched(item.number) ? 'bell-ring' : 'bell'}
|
<Icon
|
||||||
|
name={this.isMachineWatched(item.number) ? 'bell-ring' : 'bell'}
|
||||||
type={'MaterialCommunityIcons'}
|
type={'MaterialCommunityIcons'}
|
||||||
style={{fontSize: 30, width: 30}}
|
style={{fontSize: 30, width: 30}}
|
||||||
/>
|
/>
|
||||||
|
@ -311,63 +264,7 @@ export default class ProxiwashScreen extends React.Component<Props, State> {
|
||||||
</ListItem>);
|
</ListItem>);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
getRenderSectionHeader(title: String) {
|
||||||
* Renders the machines list.
|
return <H2 style={{textAlign: 'center', paddingVertical: 10}}>{title}</H2>;
|
||||||
* If we are loading for the first time, change the data for the SectionList to display a loading message.
|
|
||||||
*
|
|
||||||
* @returns {react.Node}
|
|
||||||
*/
|
|
||||||
render() {
|
|
||||||
const nav = this.props.navigation;
|
|
||||||
let data = [];
|
|
||||||
if (!this.isDataObjectValid()) {
|
|
||||||
data = [
|
|
||||||
{
|
|
||||||
title: i18n.t('proxiwashScreen.error'),
|
|
||||||
data: []
|
|
||||||
}
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
data = [
|
|
||||||
{
|
|
||||||
title: i18n.t('proxiwashScreen.dryers'),
|
|
||||||
data: this.state.data.dryers === undefined ? [] : this.state.data.dryers,
|
|
||||||
extraData: this.state
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: i18n.t('proxiwashScreen.washers'),
|
|
||||||
data: this.state.data.washers === undefined ? [] : this.state.data.washers,
|
|
||||||
extraData: this.state
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadingData = [
|
|
||||||
{
|
|
||||||
title: i18n.t('proxiwashScreen.loading'),
|
|
||||||
data: []
|
|
||||||
}
|
|
||||||
];
|
|
||||||
return (
|
|
||||||
<Container>
|
|
||||||
<CustomHeader navigation={nav} title={'Proxiwash'}/>
|
|
||||||
<SectionList
|
|
||||||
sections={this.state.firstLoading ? loadingData : data}
|
|
||||||
keyExtractor={(item) => item.number}
|
|
||||||
refreshControl={
|
|
||||||
<RefreshControl
|
|
||||||
refreshing={this.state.refreshing}
|
|
||||||
onRefresh={this._onRefresh}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
renderSectionHeader={({section: {title}}) => (
|
|
||||||
<H2 style={{textAlign: 'center', paddingVertical: 10}}>{title}</H2>
|
|
||||||
)}
|
|
||||||
renderItem={({item, section}) =>
|
|
||||||
this.renderItem(item, section, data)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ export default class SettingsScreen extends React.Component<Props, State> {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets data from preferences before rendering components
|
* Gets FetchedData from preferences before rendering components
|
||||||
*
|
*
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
"screens": {
|
"screens": {
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"planning": "Planning",
|
"planning": "Planning",
|
||||||
|
"proxiwash": "Proxiwash",
|
||||||
|
"proximo": "Proximo",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"about": "About"
|
"about": "About"
|
||||||
},
|
},
|
||||||
|
@ -22,6 +24,10 @@
|
||||||
"30": "30 min"
|
"30": "30 min"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"homeScreen": {
|
||||||
|
"listUpdated": "List updated!",
|
||||||
|
"listUpdateFail": "Error while updating list"
|
||||||
|
},
|
||||||
"aboutScreen": {
|
"aboutScreen": {
|
||||||
"appstore": "See on the Appstore",
|
"appstore": "See on the Appstore",
|
||||||
"playstore": "See on the Playstore",
|
"playstore": "See on the Playstore",
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
"screens": {
|
"screens": {
|
||||||
"home": "Accueil",
|
"home": "Accueil",
|
||||||
"planning": "Planning",
|
"planning": "Planning",
|
||||||
|
"proxiwash": "Proxiwash",
|
||||||
|
"proximo": "Proximo",
|
||||||
"settings": "Paramètres",
|
"settings": "Paramètres",
|
||||||
"about": "À Propos"
|
"about": "À Propos"
|
||||||
},
|
},
|
||||||
|
@ -22,6 +24,10 @@
|
||||||
"30": "30 min"
|
"30": "30 min"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"homeScreen": {
|
||||||
|
"listUpdated": "List mise à jour!",
|
||||||
|
"listUpdateFail": "Erreur lors de la mise à jour de la liste"
|
||||||
|
},
|
||||||
"aboutScreen": {
|
"aboutScreen": {
|
||||||
"appstore": "Voir sur l'Appstore",
|
"appstore": "Voir sur l'Appstore",
|
||||||
"playstore": "Voir sur le Playstore",
|
"playstore": "Voir sur le Playstore",
|
||||||
|
|
40
utils/WebDataManager.js
Normal file
40
utils/WebDataManager.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import {Toast} from "native-base";
|
||||||
|
|
||||||
|
export default class WebDataManager {
|
||||||
|
|
||||||
|
FETCH_URL : string;
|
||||||
|
lastDataFetched : Object = {};
|
||||||
|
|
||||||
|
|
||||||
|
constructor(url) {
|
||||||
|
this.FETCH_URL = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
async readData() {
|
||||||
|
let fetchedData : Object = {};
|
||||||
|
try {
|
||||||
|
let response = await fetch(this.FETCH_URL);
|
||||||
|
fetchedData = await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Could not read FetchedData from server');
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
this.lastDataFetched = fetchedData;
|
||||||
|
return fetchedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
isDataObjectValid() {
|
||||||
|
return Object.keys(this.lastDataFetched).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
showUpdateToast(successString, errorString) {
|
||||||
|
let isSuccess = this.isDataObjectValid();
|
||||||
|
Toast.show({
|
||||||
|
text: isSuccess ? successString : errorString,
|
||||||
|
buttonText: 'OK',
|
||||||
|
type: isSuccess ? "success" : "danger",
|
||||||
|
duration: 2000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue