forked from vergnet/application-amicale
Reworked section list to use react native design
This commit is contained in:
parent
f5702297f5
commit
b562357a95
10 changed files with 484 additions and 604 deletions
2
App.js
2
App.js
|
@ -14,6 +14,7 @@ import ThemeManager from './utils/ThemeManager';
|
|||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
import DrawerNavigator from './navigation/DrawerNavigator';
|
||||
import { enableScreens } from 'react-native-screens';
|
||||
|
||||
type Props = {};
|
||||
|
||||
|
@ -38,6 +39,7 @@ export default class App extends React.Component<Props, State> {
|
|||
constructor(props: Object) {
|
||||
super(props);
|
||||
LocaleManager.initTranslations();
|
||||
enableScreens();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,7 +8,6 @@ import Touchable from 'react-native-platform-touchable';
|
|||
import ThemeManager from "../utils/ThemeManager";
|
||||
import CustomMaterialIcon from "./CustomMaterialIcon";
|
||||
import i18n from "i18n-js";
|
||||
import {NavigationActions} from 'react-navigation';
|
||||
|
||||
type Props = {
|
||||
hasBackButton: boolean,
|
||||
|
@ -105,8 +104,7 @@ export default class CustomHeader extends React.Component<Props> {
|
|||
|
||||
|
||||
onPressBack() {
|
||||
const backAction = NavigationActions.back();
|
||||
this.props.navigation.dispatch(backAction);
|
||||
this.props.navigation.goBack();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -1,399 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import WebDataManager from "../utils/WebDataManager";
|
||||
import {H3, Spinner, Tab, TabHeading, Tabs, Text} from "native-base";
|
||||
import {RefreshControl, SectionList, View} from "react-native";
|
||||
import CustomMaterialIcon from "./CustomMaterialIcon";
|
||||
import i18n from 'i18n-js';
|
||||
import ThemeManager from "../utils/ThemeManager";
|
||||
import BaseContainer from "./BaseContainer";
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
}
|
||||
|
||||
type State = {
|
||||
refreshing: boolean,
|
||||
firstLoading: boolean,
|
||||
fetchedData: Object,
|
||||
machinesWatched: Array<string>,
|
||||
};
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
webDataManager: WebDataManager;
|
||||
|
||||
willFocusSubscription: function;
|
||||
willBlurSubscription: function;
|
||||
refreshInterval: IntervalID;
|
||||
refreshTime: number;
|
||||
lastRefresh: Date;
|
||||
|
||||
minTimeBetweenRefresh = 60;
|
||||
state = {
|
||||
refreshing: false,
|
||||
firstLoading: true,
|
||||
fetchedData: {},
|
||||
machinesWatched: [],
|
||||
};
|
||||
|
||||
onRefresh: Function;
|
||||
onFetchSuccess: Function;
|
||||
onFetchError: Function;
|
||||
renderSectionHeaderEmpty: Function;
|
||||
renderSectionHeaderNotEmpty: Function;
|
||||
renderItemEmpty: Function;
|
||||
renderItemNotEmpty: Function;
|
||||
|
||||
constructor(fetchUrl: string, refreshTime: number) {
|
||||
super();
|
||||
this.webDataManager = new WebDataManager(fetchUrl);
|
||||
this.refreshTime = refreshTime;
|
||||
// creating references to functions used in render()
|
||||
this.onRefresh = this.onRefresh.bind(this);
|
||||
this.onFetchSuccess = this.onFetchSuccess.bind(this);
|
||||
this.onFetchError = this.onFetchError.bind(this);
|
||||
this.renderSectionHeaderEmpty = this.renderSectionHeader.bind(this, true);
|
||||
this.renderSectionHeaderNotEmpty = this.renderSectionHeader.bind(this, false);
|
||||
this.renderItemEmpty = this.renderItem.bind(this, true);
|
||||
this.renderItemNotEmpty = this.renderItem.bind(this, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the translation for the header in the current language
|
||||
* @return {string}
|
||||
*/
|
||||
getHeaderTranslation(): string {
|
||||
return "Header";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the translation for the toasts in the current language
|
||||
* @return {string}
|
||||
*/
|
||||
getUpdateToastTranslations(): Array<string> {
|
||||
return ["whoa", "nah"];
|
||||
}
|
||||
|
||||
setMinTimeRefresh(value: number) {
|
||||
this.minTimeBetweenRefresh = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register react navigation events on first screen load.
|
||||
* Allows to detect when the screen is focused
|
||||
*/
|
||||
componentDidMount() {
|
||||
this.willFocusSubscription = this.props.navigation.addListener(
|
||||
'willFocus', this.onScreenFocus.bind(this));
|
||||
this.willBlurSubscription = this.props.navigation.addListener(
|
||||
'willBlur', this.onScreenBlur.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh data when focusing the screen and setup a refresh interval if asked to
|
||||
*/
|
||||
onScreenFocus() {
|
||||
this.onRefresh();
|
||||
if (this.refreshTime > 0)
|
||||
this.refreshInterval = setInterval(this.onRefresh.bind(this), this.refreshTime)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any interval on un-focus
|
||||
*/
|
||||
onScreenBlur() {
|
||||
clearInterval(this.refreshInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister from event when un-mounting components
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
if (this.willBlurSubscription !== undefined)
|
||||
this.willBlurSubscription.remove();
|
||||
if (this.willFocusSubscription !== undefined)
|
||||
this.willFocusSubscription.remove();
|
||||
}
|
||||
|
||||
onFetchSuccess(fetchedData: Object) {
|
||||
this.setState({
|
||||
fetchedData: fetchedData,
|
||||
refreshing: false,
|
||||
firstLoading: false
|
||||
});
|
||||
this.lastRefresh = new Date();
|
||||
}
|
||||
|
||||
onFetchError() {
|
||||
this.setState({
|
||||
fetchedData: {},
|
||||
refreshing: false,
|
||||
firstLoading: false
|
||||
});
|
||||
this.webDataManager.showUpdateToast(this.getUpdateToastTranslations()[0], this.getUpdateToastTranslations()[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh data and show a toast if any error occurred
|
||||
* @private
|
||||
*/
|
||||
onRefresh() {
|
||||
let canRefresh;
|
||||
if (this.lastRefresh !== undefined)
|
||||
canRefresh = (new Date().getTime() - this.lastRefresh.getTime()) / 1000 > this.minTimeBetweenRefresh;
|
||||
else
|
||||
canRefresh = true;
|
||||
|
||||
if (canRefresh) {
|
||||
this.setState({refreshing: true});
|
||||
this.webDataManager.readData()
|
||||
.then(this.onFetchSuccess)
|
||||
.catch(this.onFetchError);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the render item to be used for display in the list.
|
||||
* Must be overridden by inheriting class.
|
||||
*
|
||||
* @param item
|
||||
* @param section
|
||||
* @return {*}
|
||||
*/
|
||||
getRenderItem(item: Object, section: Object) {
|
||||
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) {
|
||||
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 isSpinner
|
||||
* @param icon
|
||||
* @return {*}
|
||||
*/
|
||||
getEmptyRenderItem(text: string, isSpinner: boolean, icon: string) {
|
||||
return (
|
||||
<View>
|
||||
<View style={{
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
height: 100,
|
||||
marginBottom: 20
|
||||
}}>
|
||||
{isSpinner ?
|
||||
<Spinner/>
|
||||
:
|
||||
<CustomMaterialIcon
|
||||
icon={icon}
|
||||
fontSize={100}
|
||||
width={100}
|
||||
color={ThemeManager.getCurrentThemeVariables().fetchedDataSectionListErrorText}/>}
|
||||
</View>
|
||||
|
||||
<H3 style={{
|
||||
textAlign: 'center',
|
||||
marginRight: 20,
|
||||
marginLeft: 20,
|
||||
color: ThemeManager.getCurrentThemeVariables().fetchedDataSectionListErrorText
|
||||
}}>
|
||||
{text}
|
||||
</H3>
|
||||
</View>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the dataset to be used in the list from the data fetched.
|
||||
* Must be overridden.
|
||||
*
|
||||
* @param fetchedData {Object}
|
||||
* @return {Array}
|
||||
*/
|
||||
createDataset(fetchedData: Object): Array<Object> {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
datasetKeyExtractor(item: Object) {
|
||||
return item.text
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the dataset when no fetched data is available.
|
||||
* No need to be overridden, has good defaults.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
createEmptyDataset() {
|
||||
return [
|
||||
{
|
||||
title: '',
|
||||
data: [
|
||||
{
|
||||
text: this.state.refreshing ?
|
||||
i18n.t('general.loading') :
|
||||
i18n.t('general.networkError'),
|
||||
isSpinner: this.state.refreshing,
|
||||
icon: this.state.refreshing ?
|
||||
'refresh' :
|
||||
'access-point-network-off'
|
||||
}
|
||||
],
|
||||
keyExtractor: this.datasetKeyExtractor,
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
return false;
|
||||
}
|
||||
|
||||
hasBackButton() {
|
||||
return false;
|
||||
}
|
||||
|
||||
getRightButton() {
|
||||
return <View/>
|
||||
}
|
||||
|
||||
hasStickyHeader() {
|
||||
return false;
|
||||
}
|
||||
|
||||
hasSideMenu() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
renderSectionHeader(isEmpty: boolean, {section: {title}} : Object) {
|
||||
return isEmpty ?
|
||||
<View/> :
|
||||
this.getRenderSectionHeader(title)
|
||||
}
|
||||
|
||||
renderItem(isEmpty: boolean, {item, section}: Object) {
|
||||
return isEmpty ?
|
||||
this.getEmptyRenderItem(item.text, item.isSpinner, item.icon) :
|
||||
this.getRenderItem(item, section)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the section list render using the generated dataset
|
||||
*
|
||||
* @param dataset
|
||||
* @return
|
||||
*/
|
||||
getSectionList(dataset: Array<Object>) {
|
||||
let isEmpty = dataset[0].data.length === 0;
|
||||
if (isEmpty)
|
||||
dataset = this.createEmptyDataset();
|
||||
return (
|
||||
<SectionList
|
||||
sections={dataset}
|
||||
stickySectionHeadersEnabled={this.hasStickyHeader()}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={this.state.refreshing}
|
||||
onRefresh={this.onRefresh}
|
||||
/>
|
||||
}
|
||||
renderSectionHeader={isEmpty ? this.renderSectionHeaderEmpty : this.renderSectionHeaderNotEmpty}
|
||||
renderItem={isEmpty ? this.renderItemEmpty : this.renderItemNotEmpty}
|
||||
style={{minHeight: 300, width: '100%'}}
|
||||
contentContainerStyle={
|
||||
isEmpty ?
|
||||
{flexGrow: 1, justifyContent: 'center', alignItems: 'center'} : {}
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the tabs containing the lists
|
||||
*
|
||||
* @param dataset
|
||||
* @return
|
||||
*/
|
||||
getTabbedView(dataset: Array<Object>) {
|
||||
let tabbedView = [];
|
||||
for (let i = 0; i < dataset.length; i++) {
|
||||
tabbedView.push(
|
||||
<Tab heading={
|
||||
<TabHeading>
|
||||
<CustomMaterialIcon
|
||||
icon={dataset[i].icon}
|
||||
color={ThemeManager.getCurrentThemeVariables().tabIconColor}
|
||||
fontSize={20}
|
||||
/>
|
||||
<Text>{dataset[i].title}</Text>
|
||||
</TabHeading>}
|
||||
key={dataset[i].title}
|
||||
style={{backgroundColor: ThemeManager.getCurrentThemeVariables().containerBgColor}}>
|
||||
{this.getSectionList(
|
||||
[
|
||||
{
|
||||
title: dataset[i].title,
|
||||
data: dataset[i].data,
|
||||
extraData: dataset[i].extraData,
|
||||
keyExtractor: dataset[i].keyExtractor
|
||||
}
|
||||
]
|
||||
)}
|
||||
</Tab>);
|
||||
}
|
||||
return tabbedView;
|
||||
}
|
||||
|
||||
render() {
|
||||
// console.log("rendering FetchedDataSectionList");
|
||||
const dataset = this.createDataset(this.state.fetchedData);
|
||||
return (
|
||||
<BaseContainer
|
||||
navigation={this.props.navigation}
|
||||
headerTitle={this.getHeaderTranslation()}
|
||||
headerRightButton={this.getRightButton()}
|
||||
hasTabs={this.hasTabs()}
|
||||
hasBackButton={this.hasBackButton()}
|
||||
hasSideMenu={this.hasSideMenu()}
|
||||
>
|
||||
{this.hasTabs() ?
|
||||
<Tabs
|
||||
tabContainerStyle={{
|
||||
elevation: 0, // Fix for android shadow
|
||||
}}
|
||||
>
|
||||
{this.getTabbedView(dataset)}
|
||||
</Tabs>
|
||||
:
|
||||
this.getSectionList(dataset)
|
||||
}
|
||||
</BaseContainer>
|
||||
);
|
||||
}
|
||||
}
|
221
components/WebSectionList.js
Normal file
221
components/WebSectionList.js
Normal file
|
@ -0,0 +1,221 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {H3, Spinner, View} from "native-base";
|
||||
import ThemeManager from '../utils/ThemeManager';
|
||||
import WebDataManager from "../utils/WebDataManager";
|
||||
import CustomMaterialIcon from "./CustomMaterialIcon";
|
||||
import i18n from "i18n-js";
|
||||
import {RefreshControl, SectionList} from "react-native";
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
fetchUrl: string,
|
||||
refreshTime: number,
|
||||
renderItem: React.Node,
|
||||
renderSectionHeader: React.Node,
|
||||
stickyHeader: boolean,
|
||||
createDataset: Function,
|
||||
updateErrorText: string,
|
||||
}
|
||||
|
||||
type State = {
|
||||
refreshing: boolean,
|
||||
firstLoading: boolean,
|
||||
fetchedData: Object,
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom component defining a material icon using native base
|
||||
*
|
||||
* @prop active {boolean} Whether to set the icon color to active
|
||||
* @prop icon {string} The icon string to use from MaterialCommunityIcons
|
||||
* @prop color {string} The icon color. Use default theme color if unspecified
|
||||
* @prop fontSize {number} The icon size. Use 26 if unspecified
|
||||
* @prop width {number} The icon width. Use 30 if unspecified
|
||||
*/
|
||||
export default class WebSectionList extends React.Component<Props, State> {
|
||||
|
||||
static defaultProps = {
|
||||
renderSectionHeader: undefined,
|
||||
stickyHeader: false,
|
||||
};
|
||||
|
||||
webDataManager: WebDataManager;
|
||||
|
||||
refreshInterval: IntervalID;
|
||||
lastRefresh: Date;
|
||||
|
||||
state = {
|
||||
refreshing: false,
|
||||
firstLoading: true,
|
||||
fetchedData: {},
|
||||
};
|
||||
|
||||
onRefresh: Function;
|
||||
onFetchSuccess: Function;
|
||||
onFetchError: Function;
|
||||
getEmptyRenderItem: Function;
|
||||
getEmptySectionHeader: Function;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
// creating references to functions used in render()
|
||||
this.onRefresh = this.onRefresh.bind(this);
|
||||
this.onFetchSuccess = this.onFetchSuccess.bind(this);
|
||||
this.onFetchError = this.onFetchError.bind(this);
|
||||
this.getEmptyRenderItem = this.getEmptyRenderItem.bind(this);
|
||||
this.getEmptySectionHeader = this.getEmptySectionHeader.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register react navigation events on first screen load.
|
||||
* Allows to detect when the screen is focused
|
||||
*/
|
||||
componentDidMount() {
|
||||
this.webDataManager = new WebDataManager(this.props.fetchUrl);
|
||||
const onScreenFocus = this.onScreenFocus.bind(this);
|
||||
const onScreenBlur = this.onScreenBlur.bind(this);
|
||||
this.props.navigation.addListener('focus', onScreenFocus);
|
||||
this.props.navigation.addListener('blur', onScreenBlur);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh data when focusing the screen and setup a refresh interval if asked to
|
||||
*/
|
||||
onScreenFocus() {
|
||||
this.onRefresh();
|
||||
if (this.props.refreshTime > 0)
|
||||
this.refreshInterval = setInterval(this.onRefresh, this.props.refreshTime)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any interval on un-focus
|
||||
*/
|
||||
onScreenBlur() {
|
||||
clearInterval(this.refreshInterval);
|
||||
}
|
||||
|
||||
|
||||
onFetchSuccess(fetchedData: Object) {
|
||||
this.setState({
|
||||
fetchedData: fetchedData,
|
||||
refreshing: false,
|
||||
firstLoading: false
|
||||
});
|
||||
this.lastRefresh = new Date();
|
||||
}
|
||||
|
||||
onFetchError() {
|
||||
this.setState({
|
||||
fetchedData: {},
|
||||
refreshing: false,
|
||||
firstLoading: false
|
||||
});
|
||||
this.webDataManager.showUpdateToast(this.props.updateErrorText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh data and show a toast if any error occurred
|
||||
* @private
|
||||
*/
|
||||
onRefresh() {
|
||||
let canRefresh;
|
||||
if (this.lastRefresh !== undefined)
|
||||
canRefresh = (new Date().getTime() - this.lastRefresh.getTime()) > this.props.refreshTime;
|
||||
else
|
||||
canRefresh = true;
|
||||
if (canRefresh) {
|
||||
this.setState({refreshing: true});
|
||||
this.webDataManager.readData()
|
||||
.then(this.onFetchSuccess)
|
||||
.catch(this.onFetchError);
|
||||
}
|
||||
}
|
||||
|
||||
getEmptySectionHeader({section}: Object) {
|
||||
return <View/>;
|
||||
}
|
||||
|
||||
getEmptyRenderItem({item}: Object) {
|
||||
return (
|
||||
<View>
|
||||
<View style={{
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
height: 100,
|
||||
marginBottom: 20
|
||||
}}>
|
||||
{this.state.refreshing ?
|
||||
<Spinner/>
|
||||
:
|
||||
<CustomMaterialIcon
|
||||
icon={item.icon}
|
||||
fontSize={100}
|
||||
width={100}
|
||||
color={ThemeManager.getCurrentThemeVariables().fetchedDataSectionListErrorText}/>}
|
||||
</View>
|
||||
|
||||
<H3 style={{
|
||||
textAlign: 'center',
|
||||
marginRight: 20,
|
||||
marginLeft: 20,
|
||||
color: ThemeManager.getCurrentThemeVariables().fetchedDataSectionListErrorText
|
||||
}}>
|
||||
{item.text}
|
||||
</H3>
|
||||
</View>);
|
||||
}
|
||||
|
||||
createEmptyDataset() {
|
||||
return [
|
||||
{
|
||||
title: '',
|
||||
data: [
|
||||
{
|
||||
text: this.state.refreshing ?
|
||||
i18n.t('general.loading') :
|
||||
i18n.t('general.networkError'),
|
||||
isSpinner: this.state.refreshing,
|
||||
icon: this.state.refreshing ?
|
||||
'refresh' :
|
||||
'access-point-network-off'
|
||||
}
|
||||
],
|
||||
keyExtractor: this.datasetKeyExtractor,
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
datasetKeyExtractor(item: Object) {
|
||||
return item.text
|
||||
}
|
||||
|
||||
render() {
|
||||
let dataset = this.props.createDataset(this.state.fetchedData);
|
||||
const isEmpty = dataset[0].data.length === 0;
|
||||
const shouldRenderHeader = isEmpty || (this.props.renderSectionHeader !== undefined);
|
||||
if (isEmpty)
|
||||
dataset = this.createEmptyDataset();
|
||||
return (
|
||||
<SectionList
|
||||
sections={dataset}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={this.state.refreshing}
|
||||
onRefresh={this.onRefresh}
|
||||
/>
|
||||
}
|
||||
renderSectionHeader={shouldRenderHeader ? this.getEmptySectionHeader : this.props.renderSectionHeader}
|
||||
renderItem={isEmpty ? this.getEmptyRenderItem : this.props.renderItem}
|
||||
style={{minHeight: 300, width: '100%'}}
|
||||
stickySectionHeadersEnabled={this.props.stickyHeader}
|
||||
contentContainerStyle={
|
||||
isEmpty ?
|
||||
{flexGrow: 1, justifyContent: 'center', alignItems: 'center'} : {}
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,14 +1,16 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Image, Linking, TouchableOpacity, View} from 'react-native';
|
||||
import {Body, Button, Card, CardItem, H1, Left, Text, Thumbnail} from 'native-base';
|
||||
import {Image, TouchableOpacity, View} from 'react-native';
|
||||
import {Body, Button, Card, CardItem, Left, Text, Thumbnail} from 'native-base';
|
||||
import i18n from "i18n-js";
|
||||
import CustomMaterialIcon from '../components/CustomMaterialIcon';
|
||||
import FetchedDataSectionList from "../components/FetchedDataSectionList";
|
||||
import Autolink from 'react-native-autolink';
|
||||
import ThemeManager from "../utils/ThemeManager";
|
||||
import DashboardItem from "../components/DashboardItem";
|
||||
import * as WebBrowser from 'expo-web-browser';
|
||||
import BaseContainer from "../components/BaseContainer";
|
||||
import WebSectionList from "../components/WebSectionList";
|
||||
// import DATA from "../dashboard_data.json";
|
||||
|
||||
|
||||
|
@ -25,46 +27,30 @@ const REFRESH_TIME = 1000 * 20; // Refresh every 20 seconds
|
|||
|
||||
const CARD_BORDER_RADIUS = 10;
|
||||
|
||||
/**
|
||||
* Opens a link in the device's browser
|
||||
* @param link The link to open
|
||||
*/
|
||||
function openWebLink(link) {
|
||||
Linking.openURL(link).catch((err) => console.error('Error opening link', err));
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
}
|
||||
|
||||
/**
|
||||
* Class defining the app's home screen
|
||||
*/
|
||||
export default class HomeScreen extends FetchedDataSectionList {
|
||||
export default class HomeScreen extends React.Component<Props> {
|
||||
|
||||
onProxiwashClick: Function;
|
||||
onTutorInsaClick: Function;
|
||||
onMenuClick: Function;
|
||||
onProximoClick: Function;
|
||||
getRenderItem: Function;
|
||||
createDataset: Function;
|
||||
|
||||
constructor() {
|
||||
super(DATA_URL, REFRESH_TIME);
|
||||
super();
|
||||
this.onProxiwashClick = this.onProxiwashClick.bind(this);
|
||||
this.onTutorInsaClick = this.onTutorInsaClick.bind(this);
|
||||
this.onMenuClick = this.onMenuClick.bind(this);
|
||||
this.onProximoClick = this.onProximoClick.bind(this);
|
||||
}
|
||||
|
||||
onProxiwashClick() {
|
||||
this.props.navigation.navigate('Proxiwash');
|
||||
}
|
||||
|
||||
onTutorInsaClick() {
|
||||
this.props.navigation.navigate('TutorInsaScreen');
|
||||
}
|
||||
|
||||
onProximoClick() {
|
||||
this.props.navigation.navigate('Proximo');
|
||||
}
|
||||
|
||||
onMenuClick() {
|
||||
this.props.navigation.navigate('SelfMenuScreen');
|
||||
this.getRenderItem = this.getRenderItem.bind(this);
|
||||
this.createDataset = this.createDataset.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -77,12 +63,20 @@ export default class HomeScreen extends FetchedDataSectionList {
|
|||
return date.toLocaleString();
|
||||
}
|
||||
|
||||
getHeaderTranslation() {
|
||||
return i18n.t("screens.home");
|
||||
onProxiwashClick() {
|
||||
this.props.navigation.navigate('Proxiwash');
|
||||
}
|
||||
|
||||
getUpdateToastTranslations() {
|
||||
return [i18n.t("homeScreen.listUpdated"), i18n.t("homeScreen.listUpdateFail")];
|
||||
onTutorInsaClick() {
|
||||
WebBrowser.openBrowserAsync("https://www.etud.insa-toulouse.fr/~tutorinsa/");
|
||||
}
|
||||
|
||||
onProximoClick() {
|
||||
this.props.navigation.navigate('Proximo');
|
||||
}
|
||||
|
||||
onMenuClick() {
|
||||
this.props.navigation.navigate('SelfMenuScreen');
|
||||
}
|
||||
|
||||
getKeyExtractor(item: Object) {
|
||||
|
@ -154,25 +148,6 @@ export default class HomeScreen extends FetchedDataSectionList {
|
|||
return dataset
|
||||
}
|
||||
|
||||
getRenderSectionHeader(title: string) {
|
||||
if (title === '') {
|
||||
return <View/>;
|
||||
} else {
|
||||
return (
|
||||
<View style={{
|
||||
backgroundColor: ThemeManager.getCurrentThemeVariables().containerBgColor
|
||||
}}>
|
||||
<H1 style={{
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
marginTop: 10,
|
||||
marginBottom: 10
|
||||
}}>{title}</H1>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getDashboardItem(item: Object) {
|
||||
let content = item['content'];
|
||||
if (item['id'] === 'event')
|
||||
|
@ -318,7 +293,7 @@ export default class HomeScreen extends FetchedDataSectionList {
|
|||
if (isAvailable)
|
||||
this.props.navigation.navigate('PlanningDisplayScreen', {data: displayEvent});
|
||||
else
|
||||
this.props.navigation.navigate('PlanningScreen');
|
||||
this.props.navigation.navigate('Planning');
|
||||
};
|
||||
|
||||
|
||||
|
@ -345,13 +320,13 @@ export default class HomeScreen extends FetchedDataSectionList {
|
|||
subtitle = i18n.t('homeScreen.dashboard.todayEventsSubtitleNA');
|
||||
|
||||
let displayEvent = this.getDisplayEvent(futureEvents);
|
||||
|
||||
const clickAction = this.clickAction.bind(this, isAvailable, displayEvent);
|
||||
return (
|
||||
<DashboardItem
|
||||
subtitle={subtitle}
|
||||
color={color}
|
||||
icon={icon}
|
||||
clickAction={this.clickAction.bind(this, isAvailable, displayEvent)}
|
||||
clickAction={clickAction}
|
||||
title={title}
|
||||
isAvailable={isAvailable}
|
||||
displayEvent={displayEvent}
|
||||
|
@ -523,64 +498,90 @@ export default class HomeScreen extends FetchedDataSectionList {
|
|||
);
|
||||
}
|
||||
|
||||
openLink(link: string) {
|
||||
WebBrowser.openBrowserAsync(link);
|
||||
}
|
||||
|
||||
getRenderItem(item: Object, section: Object) {
|
||||
getFeedItem(item: Object) {
|
||||
const onImagePress = this.openLink.bind(this, item.full_picture);
|
||||
const onOutLinkPress = this.openLink.bind(this, item.permalink_url);
|
||||
return (
|
||||
section['id'] === SECTIONS_ID[0] ? this.getDashboardItem(item) :
|
||||
<Card style={{
|
||||
flex: 0,
|
||||
marginLeft: 10,
|
||||
marginRight: 10,
|
||||
borderRadius: CARD_BORDER_RADIUS,
|
||||
<Card style={{
|
||||
flex: 0,
|
||||
marginLeft: 10,
|
||||
marginRight: 10,
|
||||
borderRadius: CARD_BORDER_RADIUS,
|
||||
}}>
|
||||
<CardItem style={{
|
||||
backgroundColor: 'transparent'
|
||||
}}>
|
||||
<CardItem style={{
|
||||
backgroundColor: 'transparent'
|
||||
}}>
|
||||
<Left>
|
||||
<Thumbnail source={ICON_AMICALE} square/>
|
||||
<Body>
|
||||
<Text>{NAME_AMICALE}</Text>
|
||||
<Text note>{HomeScreen.getFormattedDate(item.created_time)}</Text>
|
||||
</Body>
|
||||
</Left>
|
||||
</CardItem>
|
||||
<CardItem style={{
|
||||
backgroundColor: 'transparent'
|
||||
}}>
|
||||
<Left>
|
||||
<Thumbnail source={ICON_AMICALE} square/>
|
||||
<Body>
|
||||
{item.full_picture !== '' && item.full_picture !== undefined ?
|
||||
<TouchableOpacity onPress={openWebLink.bind(null, item.full_picture)}
|
||||
style={{width: '100%', height: 250, marginBottom: 5}}>
|
||||
<Image source={{uri: item.full_picture}}
|
||||
style={{flex: 1, resizeMode: "contain"}}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
: <View/>}
|
||||
{item.message !== undefined ?
|
||||
<Autolink
|
||||
text={item.message}
|
||||
hashtag="facebook"
|
||||
style={{color: ThemeManager.getCurrentThemeVariables().textColor}}
|
||||
/> : <View/>
|
||||
}
|
||||
<Text>{NAME_AMICALE}</Text>
|
||||
<Text note>{HomeScreen.getFormattedDate(item.created_time)}</Text>
|
||||
</Body>
|
||||
</CardItem>
|
||||
<CardItem style={{
|
||||
backgroundColor: 'transparent'
|
||||
}}>
|
||||
<Left>
|
||||
<Button transparent
|
||||
onPress={openWebLink.bind(null, item.permalink_url)}>
|
||||
<CustomMaterialIcon
|
||||
icon="facebook"
|
||||
color="#57aeff"
|
||||
width={20}/>
|
||||
<Text>En savoir plus</Text>
|
||||
</Button>
|
||||
</Left>
|
||||
</CardItem>
|
||||
</Card>
|
||||
</Left>
|
||||
</CardItem>
|
||||
<CardItem style={{
|
||||
backgroundColor: 'transparent'
|
||||
}}>
|
||||
<Body>
|
||||
{item.full_picture !== '' && item.full_picture !== undefined ?
|
||||
<TouchableOpacity onPress={onImagePress}
|
||||
style={{width: '100%', height: 250, marginBottom: 5}}>
|
||||
<Image source={{uri: item.full_picture}}
|
||||
style={{flex: 1, resizeMode: "contain"}}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
: <View/>}
|
||||
{item.message !== undefined ?
|
||||
<Autolink
|
||||
text={item.message}
|
||||
hashtag="facebook"
|
||||
style={{color: ThemeManager.getCurrentThemeVariables().textColor}}
|
||||
/> : <View/>
|
||||
}
|
||||
</Body>
|
||||
</CardItem>
|
||||
<CardItem style={{
|
||||
backgroundColor: 'transparent'
|
||||
}}>
|
||||
<Left>
|
||||
<Button transparent
|
||||
onPress={onOutLinkPress}>
|
||||
<CustomMaterialIcon
|
||||
icon="facebook"
|
||||
color="#57aeff"
|
||||
width={20}/>
|
||||
<Text>En savoir plus</Text>
|
||||
</Button>
|
||||
</Left>
|
||||
</CardItem>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
getRenderItem({item, section}: Object) {
|
||||
return (section['id'] === SECTIONS_ID[0] ?
|
||||
this.getDashboardItem(item) : this.getFeedItem(item));
|
||||
}
|
||||
|
||||
render() {
|
||||
const nav = this.props.navigation;
|
||||
return (
|
||||
<BaseContainer
|
||||
navigation={nav}
|
||||
headerTitle={i18n.t('screens.home')}>
|
||||
<WebSectionList
|
||||
createDataset={this.createDataset}
|
||||
navigation={nav}
|
||||
refreshTime={REFRESH_TIME}
|
||||
fetchUrl={DATA_URL}
|
||||
renderItem={this.getRenderItem}
|
||||
updateErrorText={i18n.t("homeScreen.listUpdateFail")}/>
|
||||
</BaseContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,8 @@ function sortNameReverse(a, b) {
|
|||
}
|
||||
|
||||
type Props = {
|
||||
navigation: Object
|
||||
navigation: Object,
|
||||
route: Object,
|
||||
}
|
||||
|
||||
type State = {
|
||||
|
@ -60,16 +61,8 @@ export default class ProximoListScreen extends React.Component<Props, State> {
|
|||
|
||||
modalRef: { current: null | Modalize };
|
||||
originalData: Array<Object>;
|
||||
navData = this.props.navigation.getParam('data', []);
|
||||
shouldFocusSearchBar = this.props.navigation.getParam('shouldFocusSearchBar', false);
|
||||
state = {
|
||||
currentlyDisplayedData: this.navData['data'].sort(sortPrice),
|
||||
currentSortMode: sortMode.price,
|
||||
isSortReversed: false,
|
||||
sortPriceIcon: '',
|
||||
sortNameIcon: '',
|
||||
modalCurrentDisplayItem: {},
|
||||
};
|
||||
shouldFocusSearchBar: boolean;
|
||||
|
||||
sortMenuRef: Menu;
|
||||
|
||||
onMenuRef: Function;
|
||||
|
@ -82,7 +75,16 @@ export default class ProximoListScreen extends React.Component<Props, State> {
|
|||
constructor(props: any) {
|
||||
super(props);
|
||||
this.modalRef = React.createRef();
|
||||
this.originalData = this.navData['data'];
|
||||
this.originalData = this.props.route.params['data']['data'];
|
||||
this.shouldFocusSearchBar = this.props.route.params['shouldFocusSearchBar'];
|
||||
this.state = {
|
||||
currentlyDisplayedData: this.originalData,
|
||||
currentSortMode: sortMode.price,
|
||||
isSortReversed: false,
|
||||
sortPriceIcon: '',
|
||||
sortNameIcon: '',
|
||||
modalCurrentDisplayItem: {},
|
||||
};
|
||||
|
||||
this.onMenuRef = this.onMenuRef.bind(this);
|
||||
this.onSearchStringChange = this.onSearchStringChange.bind(this);
|
||||
|
@ -365,7 +367,6 @@ export default class ProximoListScreen extends React.Component<Props, State> {
|
|||
shouldFocusSearchBar={this.shouldFocusSearchBar}
|
||||
rightButton={this.getSortMenu()}
|
||||
/>
|
||||
|
||||
<FlatList
|
||||
data={this.state.currentlyDisplayedData}
|
||||
extraData={this.state.currentlyDisplayedData}
|
||||
|
|
|
@ -5,26 +5,40 @@ import {Platform, View} from 'react-native'
|
|||
import {Body, Left, ListItem, Right, Text} from 'native-base';
|
||||
import i18n from "i18n-js";
|
||||
import CustomMaterialIcon from "../../components/CustomMaterialIcon";
|
||||
import FetchedDataSectionList from "../../components/FetchedDataSectionList";
|
||||
import ThemeManager from "../../utils/ThemeManager";
|
||||
import Touchable from "react-native-platform-touchable";
|
||||
import BaseContainer from "../../components/BaseContainer";
|
||||
import WebSectionList from "../../components/WebSectionList";
|
||||
|
||||
const DATA_URL = "https://etud.insa-toulouse.fr/~proximo/data/stock-v2.json";
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
}
|
||||
|
||||
type State = {
|
||||
fetchedData: Object,
|
||||
}
|
||||
|
||||
/**
|
||||
* Class defining the main proximo screen. This screen shows the different categories of articles
|
||||
* offered by proximo.
|
||||
*/
|
||||
export default class ProximoMainScreen extends FetchedDataSectionList {
|
||||
export default class ProximoMainScreen extends React.Component<Props, State> {
|
||||
|
||||
articles: Object;
|
||||
|
||||
onPressSearchBtn: Function;
|
||||
onPressAboutBtn: Function;
|
||||
getRenderItem: Function;
|
||||
createDataset: Function;
|
||||
|
||||
constructor() {
|
||||
super(DATA_URL, 0);
|
||||
super();
|
||||
this.onPressSearchBtn = this.onPressSearchBtn.bind(this);
|
||||
this.onPressAboutBtn = this.onPressAboutBtn.bind(this);
|
||||
this.getRenderItem = this.getRenderItem.bind(this);
|
||||
this.createDataset = this.createDataset.bind(this);
|
||||
}
|
||||
|
||||
static sortFinalData(a: Object, b: Object) {
|
||||
|
@ -45,14 +59,6 @@ export default class ProximoMainScreen extends FetchedDataSectionList {
|
|||
return 0;
|
||||
}
|
||||
|
||||
getHeaderTranslation() {
|
||||
return i18n.t("screens.proximo");
|
||||
}
|
||||
|
||||
getUpdateToastTranslations() {
|
||||
return [i18n.t("proximoScreen.listUpdated"), i18n.t("proximoScreen.listUpdateFail")];
|
||||
}
|
||||
|
||||
getKeyExtractor(item: Object) {
|
||||
return item !== undefined ? item.type['id'] : undefined;
|
||||
}
|
||||
|
@ -62,7 +68,7 @@ export default class ProximoMainScreen extends FetchedDataSectionList {
|
|||
{
|
||||
title: '',
|
||||
data: this.generateData(fetchedData),
|
||||
extraData: super.state,
|
||||
extraData: this.state,
|
||||
keyExtractor: this.getKeyExtractor
|
||||
}
|
||||
];
|
||||
|
@ -77,21 +83,22 @@ export default class ProximoMainScreen extends FetchedDataSectionList {
|
|||
*/
|
||||
generateData(fetchedData: Object) {
|
||||
let finalData = [];
|
||||
this.articles = undefined;
|
||||
if (fetchedData.types !== undefined && fetchedData.articles !== undefined) {
|
||||
let types = fetchedData.types;
|
||||
let articles = fetchedData.articles;
|
||||
this.articles = fetchedData.articles;
|
||||
finalData.push({
|
||||
type: {
|
||||
id: -1,
|
||||
name: i18n.t('proximoScreen.all'),
|
||||
icon: 'star'
|
||||
},
|
||||
data: this.getAvailableArticles(articles, undefined)
|
||||
data: this.getAvailableArticles(this.articles, undefined)
|
||||
});
|
||||
for (let i = 0; i < types.length; i++) {
|
||||
finalData.push({
|
||||
type: types[i],
|
||||
data: this.getAvailableArticles(articles, types[i])
|
||||
data: this.getAvailableArticles(this.articles, types[i])
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -128,8 +135,8 @@ export default class ProximoMainScreen extends FetchedDataSectionList {
|
|||
name: i18n.t('proximoScreen.all'),
|
||||
icon: 'star'
|
||||
},
|
||||
data: this.state.fetchedData.articles !== undefined ?
|
||||
this.getAvailableArticles(this.state.fetchedData.articles, undefined) : []
|
||||
data: this.articles !== undefined ?
|
||||
this.getAvailableArticles(this.articles, undefined) : []
|
||||
},
|
||||
};
|
||||
this.props.navigation.navigate('ProximoListScreen', searchScreenData);
|
||||
|
@ -163,7 +170,7 @@ export default class ProximoMainScreen extends FetchedDataSectionList {
|
|||
);
|
||||
}
|
||||
|
||||
getRenderItem(item: Object, section: Object) {
|
||||
getRenderItem({item}: Object) {
|
||||
let dataToSend = {
|
||||
shouldFocusSearchBar: false,
|
||||
data: item,
|
||||
|
@ -196,10 +203,26 @@ export default class ProximoMainScreen extends FetchedDataSectionList {
|
|||
</Right>
|
||||
</ListItem>
|
||||
);
|
||||
} else {
|
||||
} else
|
||||
return <View/>;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const nav = this.props.navigation;
|
||||
return (
|
||||
<BaseContainer
|
||||
navigation={nav}
|
||||
headerTitle={i18n.t('screens.proximo')}
|
||||
headerRightButton={this.getRightButton()}>
|
||||
<WebSectionList
|
||||
createDataset={this.createDataset}
|
||||
navigation={nav}
|
||||
refreshTime={0}
|
||||
fetchUrl={DATA_URL}
|
||||
renderItem={this.getRenderItem}
|
||||
updateErrorText={i18n.t("homeScreen.listUpdateFail")}/>
|
||||
</BaseContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,12 +6,13 @@ import {Body, Card, CardItem, Left, Right, Text} from 'native-base';
|
|||
import ThemeManager from '../../utils/ThemeManager';
|
||||
import i18n from "i18n-js";
|
||||
import CustomMaterialIcon from "../../components/CustomMaterialIcon";
|
||||
import FetchedDataSectionList from "../../components/FetchedDataSectionList";
|
||||
import WebSectionList from "../../components/WebSectionList";
|
||||
import NotificationsManager from "../../utils/NotificationsManager";
|
||||
import PlatformTouchable from "react-native-platform-touchable";
|
||||
import Touchable from "react-native-platform-touchable";
|
||||
import AsyncStorageManager from "../../utils/AsyncStorageManager";
|
||||
import * as Expo from "expo";
|
||||
import BaseContainer from "../../components/BaseContainer";
|
||||
|
||||
const DATA_URL = "https://etud.insa-toulouse.fr/~amicale_app/washinsa/washinsa.json";
|
||||
|
||||
|
@ -30,19 +31,41 @@ let stateColors = {};
|
|||
|
||||
const REFRESH_TIME = 1000 * 10; // Refresh every 10 seconds
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
}
|
||||
|
||||
type State = {
|
||||
refreshing: boolean,
|
||||
firstLoading: boolean,
|
||||
fetchedData: Object,
|
||||
machinesWatched: Array<string>,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Class defining the app's proxiwash screen. This screen shows information about washing machines and
|
||||
* dryers, taken from a scrapper reading proxiwash website
|
||||
*/
|
||||
export default class ProxiwashScreen extends FetchedDataSectionList {
|
||||
export default class ProxiwashScreen extends React.Component<Props, State> {
|
||||
|
||||
onAboutPress: Function;
|
||||
getRenderItem: Function;
|
||||
createDataset: Function;
|
||||
|
||||
state = {
|
||||
refreshing: false,
|
||||
firstLoading: true,
|
||||
fetchedData: {},
|
||||
// machinesWatched: JSON.parse(dataString),
|
||||
machinesWatched: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates machine state parameters using current theme and translations
|
||||
*/
|
||||
constructor() {
|
||||
super(DATA_URL, REFRESH_TIME);
|
||||
super();
|
||||
let colors = ThemeManager.getCurrentThemeVariables();
|
||||
stateColors[MACHINE_STATES.TERMINE] = colors.proxiwashFinishedColor;
|
||||
stateColors[MACHINE_STATES.DISPONIBLE] = colors.proxiwashReadyColor;
|
||||
|
@ -69,23 +92,16 @@ export default class ProxiwashScreen extends FetchedDataSectionList {
|
|||
stateIcons[MACHINE_STATES.ERREUR] = 'alert';
|
||||
|
||||
// let dataString = AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.current;
|
||||
this.state = {
|
||||
refreshing: false,
|
||||
firstLoading: true,
|
||||
fetchedData: {},
|
||||
// machinesWatched: JSON.parse(dataString),
|
||||
machinesWatched: [],
|
||||
};
|
||||
this.setMinTimeRefresh(30);
|
||||
|
||||
// this.setMinTimeRefresh(30);
|
||||
this.onAboutPress = this.onAboutPress.bind(this);
|
||||
this.getRenderItem = this.getRenderItem.bind(this);
|
||||
this.createDataset = this.createDataset.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup notification channel for android and add listeners to detect notifications fired
|
||||
*/
|
||||
componentDidMount() {
|
||||
super.componentDidMount();
|
||||
if (AsyncStorageManager.getInstance().preferences.expoToken.current !== '') {
|
||||
// Get latest watchlist from server
|
||||
NotificationsManager.getMachineNotificationWatchlist((fetchedList) => {
|
||||
|
@ -107,14 +123,6 @@ export default class ProxiwashScreen extends FetchedDataSectionList {
|
|||
}
|
||||
}
|
||||
|
||||
getHeaderTranslation() {
|
||||
return i18n.t("screens.proxiwash");
|
||||
}
|
||||
|
||||
getUpdateToastTranslations() {
|
||||
return [i18n.t("proxiwashScreen.listUpdated"), i18n.t("proxiwashScreen.listUpdateFail")];
|
||||
}
|
||||
|
||||
getDryersKeyExtractor(item: Object) {
|
||||
return item !== undefined ? "dryer" + item.number : undefined;
|
||||
}
|
||||
|
@ -212,28 +220,23 @@ export default class ProxiwashScreen extends FetchedDataSectionList {
|
|||
|
||||
createDataset(fetchedData: Object) {
|
||||
return [
|
||||
{
|
||||
title: i18n.t('proxiwashScreen.washers'),
|
||||
icon: 'washing-machine',
|
||||
data: fetchedData.washers === undefined ? [] : fetchedData.washers,
|
||||
extraData: super.state,
|
||||
keyExtractor: this.getWashersKeyExtractor
|
||||
},
|
||||
{
|
||||
title: i18n.t('proxiwashScreen.dryers'),
|
||||
icon: 'tumble-dryer',
|
||||
data: fetchedData.dryers === undefined ? [] : fetchedData.dryers,
|
||||
extraData: super.state,
|
||||
extraData: this.state,
|
||||
keyExtractor: this.getDryersKeyExtractor
|
||||
},
|
||||
|
||||
{
|
||||
title: i18n.t('proxiwashScreen.washers'),
|
||||
icon: 'washing-machine',
|
||||
data: fetchedData.washers === undefined ? [] : fetchedData.washers,
|
||||
extraData: this.state,
|
||||
keyExtractor: this.getWashersKeyExtractor
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
hasTabs(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an alert fo a machine, allowing to enable/disable notifications if running
|
||||
*
|
||||
|
@ -292,6 +295,24 @@ export default class ProxiwashScreen extends FetchedDataSectionList {
|
|||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const nav = this.props.navigation;
|
||||
return (
|
||||
<BaseContainer
|
||||
navigation={nav}
|
||||
headerTitle={i18n.t('screens.proxiwash')}
|
||||
headerRightButton={this.getRightButton()}>
|
||||
<WebSectionList
|
||||
createDataset={this.createDataset}
|
||||
navigation={nav}
|
||||
refreshTime={REFRESH_TIME}
|
||||
fetchUrl={DATA_URL}
|
||||
renderItem={this.getRenderItem}
|
||||
updateErrorText={i18n.t("proxiwashScreen.listUpdateFail")}/>
|
||||
</BaseContainer>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list item to be rendered
|
||||
*
|
||||
|
@ -299,7 +320,7 @@ export default class ProxiwashScreen extends FetchedDataSectionList {
|
|||
* @param section The object describing the current SectionList section
|
||||
* @returns {React.Node}
|
||||
*/
|
||||
getRenderItem(item: Object, section: Object) {
|
||||
getRenderItem({item, section} : Object) {
|
||||
let isMachineRunning = MACHINE_STATES[item.state] === MACHINE_STATES["EN COURS"];
|
||||
let machineName = (section.title === i18n.t('proxiwashScreen.dryers') ? i18n.t('proxiwashScreen.dryer') : i18n.t('proxiwashScreen.washer')) + ' n°' + item.number;
|
||||
let isDryer = section.title === i18n.t('proxiwashScreen.dryers');
|
||||
|
|
|
@ -5,22 +5,31 @@ import {View} from 'react-native';
|
|||
import {Card, CardItem, H2, H3, Text} from 'native-base';
|
||||
import ThemeManager from "../utils/ThemeManager";
|
||||
import i18n from "i18n-js";
|
||||
import FetchedDataSectionList from "../components/FetchedDataSectionList";
|
||||
import BaseContainer from "../components/BaseContainer";
|
||||
import WebSectionList from "../components/WebSectionList";
|
||||
|
||||
const DATA_URL = "https://etud.insa-toulouse.fr/~amicale_app/menu/menu_data.json";
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
}
|
||||
|
||||
/**
|
||||
* Class defining the app's menu screen.
|
||||
* This screen fetches data from etud to render the RU menu
|
||||
*/
|
||||
export default class SelfMenuScreen extends FetchedDataSectionList {
|
||||
export default class SelfMenuScreen extends React.Component<Props> {
|
||||
|
||||
// Hard code strings as toLocaleDateString does not work on current android JS engine
|
||||
daysOfWeek = [];
|
||||
monthsOfYear = [];
|
||||
|
||||
getRenderItem: Function;
|
||||
getRenderSectionHeader: Function;
|
||||
createDataset: Function;
|
||||
|
||||
constructor() {
|
||||
super(DATA_URL, 0);
|
||||
super();
|
||||
this.daysOfWeek.push(i18n.t("date.daysOfWeek.monday"));
|
||||
this.daysOfWeek.push(i18n.t("date.daysOfWeek.tuesday"));
|
||||
this.daysOfWeek.push(i18n.t("date.daysOfWeek.wednesday"));
|
||||
|
@ -41,32 +50,16 @@ export default class SelfMenuScreen extends FetchedDataSectionList {
|
|||
this.monthsOfYear.push(i18n.t("date.monthsOfYear.october"));
|
||||
this.monthsOfYear.push(i18n.t("date.monthsOfYear.november"));
|
||||
this.monthsOfYear.push(i18n.t("date.monthsOfYear.december"));
|
||||
}
|
||||
|
||||
getHeaderTranslation() {
|
||||
return i18n.t("screens.menuSelf");
|
||||
}
|
||||
|
||||
getUpdateToastTranslations() {
|
||||
return [i18n.t("homeScreen.listUpdated"), i18n.t("homeScreen.listUpdateFail")];
|
||||
this.getRenderItem = this.getRenderItem.bind(this);
|
||||
this.getRenderSectionHeader = this.getRenderSectionHeader.bind(this);
|
||||
this.createDataset = this.createDataset.bind(this);
|
||||
}
|
||||
|
||||
getKeyExtractor(item: Object) {
|
||||
return item !== undefined ? item['name'] : undefined;
|
||||
}
|
||||
|
||||
hasBackButton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
hasStickyHeader(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
hasSideMenu(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
createDataset(fetchedData: Object) {
|
||||
let result = [];
|
||||
// Prevent crash by giving a default value when fetchedData is empty (not yet available)
|
||||
|
@ -101,7 +94,8 @@ export default class SelfMenuScreen extends FetchedDataSectionList {
|
|||
return this.daysOfWeek[date.getDay() - 1] + " " + date.getDate() + " " + this.monthsOfYear[date.getMonth()] + " " + date.getFullYear();
|
||||
}
|
||||
|
||||
getRenderSectionHeader(title: string) {
|
||||
getRenderSectionHeader({section}: Object) {
|
||||
let title = "";
|
||||
return (
|
||||
<Card style={{
|
||||
marginLeft: 10,
|
||||
|
@ -114,12 +108,12 @@ export default class SelfMenuScreen extends FetchedDataSectionList {
|
|||
textAlign: 'center',
|
||||
marginTop: 10,
|
||||
marginBottom: 10
|
||||
}}>{title}</H2>
|
||||
}}>{section.title}</H2>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
getRenderItem(item: Object, section: Object) {
|
||||
getRenderItem({item}: Object) {
|
||||
return (
|
||||
<Card style={{
|
||||
flex: 0,
|
||||
|
@ -167,5 +161,24 @@ export default class SelfMenuScreen extends FetchedDataSectionList {
|
|||
return name.charAt(0) + name.substr(1).toLowerCase();
|
||||
}
|
||||
|
||||
render() {
|
||||
const nav = this.props.navigation;
|
||||
return (
|
||||
<BaseContainer
|
||||
navigation={nav}
|
||||
headerTitle={i18n.t('screens.menuSelf')}
|
||||
hasBackButton={true}>
|
||||
<WebSectionList
|
||||
createDataset={this.createDataset}
|
||||
navigation={nav}
|
||||
refreshTime={0}
|
||||
fetchUrl={DATA_URL}
|
||||
renderItem={this.getRenderItem}
|
||||
renderSectionHeader={this.getRenderSectionHeader}
|
||||
updateErrorText={i18n.t("homeScreen.listUpdateFail")}
|
||||
stickyHeader={true}/>
|
||||
</BaseContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,14 +45,13 @@ export default class WebDataManager {
|
|||
/**
|
||||
* Show a toast message depending on the validity of the fetched data
|
||||
*
|
||||
* @param successString
|
||||
* @param errorString
|
||||
*/
|
||||
showUpdateToast(successString, errorString) {
|
||||
showUpdateToast(errorString) {
|
||||
let isSuccess = this.isDataObjectValid();
|
||||
if (!isSuccess) {
|
||||
Toast.show({
|
||||
text: isSuccess ? successString : errorString,
|
||||
text: errorString,
|
||||
buttonText: 'OK',
|
||||
type: isSuccess ? "success" : "danger",
|
||||
duration: 2000
|
||||
|
|
Loading…
Reference in a new issue