forked from vergnet/application-amicale
Improve Proximo components to match linter
This commit is contained in:
parent
ab86c1c85c
commit
547af66977
5 changed files with 949 additions and 803 deletions
|
@ -2,48 +2,48 @@
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {Avatar, List, Text, withTheme} from 'react-native-paper';
|
import {Avatar, List, Text, withTheme} from 'react-native-paper';
|
||||||
import i18n from "i18n-js";
|
import i18n from 'i18n-js';
|
||||||
|
import type {ProximoArticleType} from '../../../screens/Services/Proximo/ProximoMainScreen';
|
||||||
|
|
||||||
type Props = {
|
type PropsType = {
|
||||||
onPress: Function,
|
onPress: () => void,
|
||||||
color: string,
|
color: string,
|
||||||
item: Object,
|
item: ProximoArticleType,
|
||||||
height: number,
|
height: number,
|
||||||
}
|
};
|
||||||
|
|
||||||
class ProximoListItem extends React.Component<Props> {
|
class ProximoListItem extends React.Component<PropsType> {
|
||||||
|
shouldComponentUpdate(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
colors: Object;
|
render(): React.Node {
|
||||||
|
const {props} = this;
|
||||||
constructor(props) {
|
return (
|
||||||
super(props);
|
<List.Item
|
||||||
this.colors = props.theme.colors;
|
title={props.item.name}
|
||||||
}
|
description={`${props.item.quantity} ${i18n.t(
|
||||||
|
'screens.proximo.inStock',
|
||||||
shouldComponentUpdate() {
|
)}`}
|
||||||
return false;
|
descriptionStyle={{color: props.color}}
|
||||||
}
|
onPress={props.onPress}
|
||||||
|
left={(): React.Node => (
|
||||||
render() {
|
<Avatar.Image
|
||||||
return (
|
style={{backgroundColor: 'transparent'}}
|
||||||
<List.Item
|
size={64}
|
||||||
title={this.props.item.name}
|
source={{uri: props.item.image}}
|
||||||
description={this.props.item.quantity + ' ' + i18n.t('screens.proximo.inStock')}
|
/>
|
||||||
descriptionStyle={{color: this.props.color}}
|
)}
|
||||||
onPress={this.props.onPress}
|
right={(): React.Node => (
|
||||||
left={() => <Avatar.Image style={{backgroundColor: 'transparent'}} size={64}
|
<Text style={{fontWeight: 'bold'}}>{props.item.price}€</Text>
|
||||||
source={{uri: this.props.item.image}}/>}
|
)}
|
||||||
right={() =>
|
style={{
|
||||||
<Text style={{fontWeight: "bold"}}>
|
height: props.height,
|
||||||
{this.props.item.price}€
|
justifyContent: 'center',
|
||||||
</Text>}
|
}}
|
||||||
style={{
|
/>
|
||||||
height: this.props.height,
|
);
|
||||||
justifyContent: 'center',
|
}
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withTheme(ProximoListItem);
|
export default withTheme(ProximoListItem);
|
||||||
|
|
|
@ -1,44 +1,55 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {ERROR_TYPE, readData} from "../../utils/WebData";
|
import i18n from 'i18n-js';
|
||||||
import i18n from "i18n-js";
|
|
||||||
import {Snackbar} from 'react-native-paper';
|
import {Snackbar} from 'react-native-paper';
|
||||||
import {RefreshControl, View} from "react-native";
|
import {RefreshControl, View} from 'react-native';
|
||||||
import ErrorView from "./ErrorView";
|
|
||||||
import BasicLoadingScreen from "./BasicLoadingScreen";
|
|
||||||
import {withCollapsible} from "../../utils/withCollapsible";
|
|
||||||
import * as Animatable from 'react-native-animatable';
|
import * as Animatable from 'react-native-animatable';
|
||||||
import CustomTabBar from "../Tabbar/CustomTabBar";
|
import {Collapsible} from 'react-navigation-collapsible';
|
||||||
import {Collapsible} from "react-navigation-collapsible";
|
import {StackNavigationProp} from '@react-navigation/stack';
|
||||||
import {StackNavigationProp} from "@react-navigation/stack";
|
import ErrorView from './ErrorView';
|
||||||
import CollapsibleSectionList from "../Collapsible/CollapsibleSectionList";
|
import BasicLoadingScreen from './BasicLoadingScreen';
|
||||||
|
import {withCollapsible} from '../../utils/withCollapsible';
|
||||||
|
import CustomTabBar from '../Tabbar/CustomTabBar';
|
||||||
|
import {ERROR_TYPE, readData} from '../../utils/WebData';
|
||||||
|
import CollapsibleSectionList from '../Collapsible/CollapsibleSectionList';
|
||||||
|
import type {ApiGenericDataType} from '../../utils/WebData';
|
||||||
|
|
||||||
type Props = {
|
export type SectionListDataType<T> = Array<{
|
||||||
navigation: StackNavigationProp,
|
title: string,
|
||||||
fetchUrl: string,
|
data: Array<T>,
|
||||||
autoRefreshTime: number,
|
keyExtractor?: (T) => string,
|
||||||
refreshOnFocus: boolean,
|
}>;
|
||||||
renderItem: (data: { [key: string]: any }) => React.Node,
|
|
||||||
createDataset: (data: { [key: string]: any } | null, isLoading?: boolean) => Array<Object>,
|
|
||||||
onScroll: (event: SyntheticEvent<EventTarget>) => void,
|
|
||||||
collapsibleStack: Collapsible,
|
|
||||||
|
|
||||||
showError: boolean,
|
type PropsType<T> = {
|
||||||
itemHeight?: number,
|
navigation: StackNavigationProp,
|
||||||
updateData?: number,
|
fetchUrl: string,
|
||||||
renderListHeaderComponent?: (data: { [key: string]: any } | null) => React.Node,
|
autoRefreshTime: number,
|
||||||
renderSectionHeader?: (data: { section: { [key: string]: any } }, isLoading?: boolean) => React.Node,
|
refreshOnFocus: boolean,
|
||||||
stickyHeader?: boolean,
|
renderItem: (data: {item: T}) => React.Node,
|
||||||
}
|
createDataset: (
|
||||||
|
data: ApiGenericDataType | null,
|
||||||
|
isLoading?: boolean,
|
||||||
|
) => SectionListDataType<T>,
|
||||||
|
onScroll: (event: SyntheticEvent<EventTarget>) => void,
|
||||||
|
collapsibleStack: Collapsible,
|
||||||
|
|
||||||
type State = {
|
showError?: boolean,
|
||||||
refreshing: boolean,
|
itemHeight?: number | null,
|
||||||
firstLoading: boolean,
|
updateData?: number,
|
||||||
fetchedData: { [key: string]: any } | null,
|
renderListHeaderComponent?: (data: ApiGenericDataType | null) => React.Node,
|
||||||
snackbarVisible: boolean
|
renderSectionHeader?: (
|
||||||
|
data: {section: {title: string}},
|
||||||
|
isLoading?: boolean,
|
||||||
|
) => React.Node,
|
||||||
|
stickyHeader?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type StateType = {
|
||||||
|
refreshing: boolean,
|
||||||
|
fetchedData: ApiGenericDataType | null,
|
||||||
|
snackbarVisible: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
const MIN_REFRESH_TIME = 5 * 1000;
|
const MIN_REFRESH_TIME = 5 * 1000;
|
||||||
|
|
||||||
|
@ -48,211 +59,216 @@ const MIN_REFRESH_TIME = 5 * 1000;
|
||||||
* This is a pure component, meaning it will only update if a shallow comparison of state and props is different.
|
* This is a pure component, meaning it will only update if a shallow comparison of state and props is different.
|
||||||
* To force the component to update, change the value of updateData.
|
* To force the component to update, change the value of updateData.
|
||||||
*/
|
*/
|
||||||
class WebSectionList extends React.PureComponent<Props, State> {
|
class WebSectionList<T> extends React.PureComponent<PropsType<T>, StateType> {
|
||||||
|
static defaultProps = {
|
||||||
|
showError: true,
|
||||||
|
itemHeight: null,
|
||||||
|
updateData: 0,
|
||||||
|
renderListHeaderComponent: (): React.Node => null,
|
||||||
|
renderSectionHeader: (): React.Node => null,
|
||||||
|
stickyHeader: false,
|
||||||
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
refreshInterval: IntervalID;
|
||||||
stickyHeader: false,
|
|
||||||
updateData: 0,
|
lastRefresh: Date | null;
|
||||||
showError: true,
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.state = {
|
||||||
|
refreshing: false,
|
||||||
|
fetchedData: null,
|
||||||
|
snackbarVisible: false,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
refreshInterval: IntervalID;
|
/**
|
||||||
lastRefresh: Date | null;
|
* Registers react navigation events on first screen load.
|
||||||
|
* Allows to detect when the screen is focused
|
||||||
|
*/
|
||||||
|
componentDidMount() {
|
||||||
|
const {navigation} = this.props;
|
||||||
|
navigation.addListener('focus', this.onScreenFocus);
|
||||||
|
navigation.addListener('blur', this.onScreenBlur);
|
||||||
|
this.lastRefresh = null;
|
||||||
|
this.onRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
state = {
|
/**
|
||||||
refreshing: false,
|
* Refreshes data when focusing the screen and setup a refresh interval if asked to
|
||||||
firstLoading: true,
|
*/
|
||||||
fetchedData: null,
|
onScreenFocus = () => {
|
||||||
snackbarVisible: false
|
const {props} = this;
|
||||||
|
if (props.refreshOnFocus && this.lastRefresh) this.onRefresh();
|
||||||
|
if (props.autoRefreshTime > 0)
|
||||||
|
this.refreshInterval = setInterval(this.onRefresh, props.autoRefreshTime);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes any interval on un-focus
|
||||||
|
*/
|
||||||
|
onScreenBlur = () => {
|
||||||
|
clearInterval(this.refreshInterval);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback used when fetch is successful.
|
||||||
|
* It will update the displayed data and stop the refresh animation
|
||||||
|
*
|
||||||
|
* @param fetchedData The newly fetched data
|
||||||
|
*/
|
||||||
|
onFetchSuccess = (fetchedData: ApiGenericDataType) => {
|
||||||
|
this.setState({
|
||||||
|
fetchedData,
|
||||||
|
refreshing: false,
|
||||||
|
});
|
||||||
|
this.lastRefresh = new Date();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback used when fetch encountered an error.
|
||||||
|
* It will reset the displayed data and show an error.
|
||||||
|
*/
|
||||||
|
onFetchError = () => {
|
||||||
|
this.setState({
|
||||||
|
fetchedData: null,
|
||||||
|
refreshing: false,
|
||||||
|
});
|
||||||
|
this.showSnackBar();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes data and shows an animations while doing it
|
||||||
|
*/
|
||||||
|
onRefresh = () => {
|
||||||
|
const {fetchUrl} = this.props;
|
||||||
|
let canRefresh;
|
||||||
|
if (this.lastRefresh != null) {
|
||||||
|
const last = this.lastRefresh;
|
||||||
|
canRefresh = new Date().getTime() - last.getTime() > MIN_REFRESH_TIME;
|
||||||
|
} else canRefresh = true;
|
||||||
|
if (canRefresh) {
|
||||||
|
this.setState({refreshing: true});
|
||||||
|
readData(fetchUrl).then(this.onFetchSuccess).catch(this.onFetchError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the error popup
|
||||||
|
*/
|
||||||
|
showSnackBar = () => {
|
||||||
|
this.setState({snackbarVisible: true});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the error popup
|
||||||
|
*/
|
||||||
|
hideSnackBar = () => {
|
||||||
|
this.setState({snackbarVisible: false});
|
||||||
|
};
|
||||||
|
|
||||||
|
getItemLayout = (
|
||||||
|
data: T,
|
||||||
|
index: number,
|
||||||
|
): {length: number, offset: number, index: number} | null => {
|
||||||
|
const {itemHeight} = this.props;
|
||||||
|
if (itemHeight == null) return null;
|
||||||
|
return {
|
||||||
|
length: itemHeight,
|
||||||
|
offset: itemHeight * index,
|
||||||
|
index,
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
getRenderSectionHeader = (data: {section: {title: string}}): React.Node => {
|
||||||
* Registers react navigation events on first screen load.
|
const {renderSectionHeader} = this.props;
|
||||||
* Allows to detect when the screen is focused
|
const {refreshing} = this.state;
|
||||||
*/
|
if (renderSectionHeader != null) {
|
||||||
componentDidMount() {
|
return (
|
||||||
this.props.navigation.addListener('focus', this.onScreenFocus);
|
<Animatable.View animation="fadeInUp" duration={500} useNativeDriver>
|
||||||
this.props.navigation.addListener('blur', this.onScreenBlur);
|
{renderSectionHeader(data, refreshing)}
|
||||||
this.lastRefresh = null;
|
</Animatable.View>
|
||||||
this.onRefresh();
|
);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
getRenderItem = (data: {item: T}): React.Node => {
|
||||||
* Refreshes data when focusing the screen and setup a refresh interval if asked to
|
const {renderItem} = this.props;
|
||||||
*/
|
return (
|
||||||
onScreenFocus = () => {
|
<Animatable.View animation="fadeInUp" duration={500} useNativeDriver>
|
||||||
if (this.props.refreshOnFocus && this.lastRefresh)
|
{renderItem(data)}
|
||||||
this.onRefresh();
|
</Animatable.View>
|
||||||
if (this.props.autoRefreshTime > 0)
|
);
|
||||||
this.refreshInterval = setInterval(this.onRefresh, this.props.autoRefreshTime)
|
};
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
onScroll = (event: SyntheticEvent<EventTarget>) => {
|
||||||
* Removes any interval on un-focus
|
const {onScroll} = this.props;
|
||||||
*/
|
if (onScroll != null) onScroll(event);
|
||||||
onScreenBlur = () => {
|
};
|
||||||
clearInterval(this.refreshInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
render(): React.Node {
|
||||||
|
const {props, state} = this;
|
||||||
|
let dataset = [];
|
||||||
|
if (
|
||||||
|
state.fetchedData != null ||
|
||||||
|
(state.fetchedData == null && !props.showError)
|
||||||
|
)
|
||||||
|
dataset = props.createDataset(state.fetchedData, state.refreshing);
|
||||||
|
|
||||||
/**
|
const {containerPaddingTop} = props.collapsibleStack;
|
||||||
* Callback used when fetch is successful.
|
return (
|
||||||
* It will update the displayed data and stop the refresh animation
|
<View>
|
||||||
*
|
<CollapsibleSectionList
|
||||||
* @param fetchedData The newly fetched data
|
sections={dataset}
|
||||||
*/
|
extraData={props.updateData}
|
||||||
onFetchSuccess = (fetchedData: { [key: string]: any }) => {
|
refreshControl={
|
||||||
this.setState({
|
<RefreshControl
|
||||||
fetchedData: fetchedData,
|
progressViewOffset={containerPaddingTop}
|
||||||
refreshing: false,
|
refreshing={state.refreshing}
|
||||||
firstLoading: false
|
onRefresh={this.onRefresh}
|
||||||
});
|
/>
|
||||||
this.lastRefresh = new Date();
|
}
|
||||||
};
|
renderSectionHeader={this.getRenderSectionHeader}
|
||||||
|
renderItem={this.getRenderItem}
|
||||||
/**
|
stickySectionHeadersEnabled={props.stickyHeader}
|
||||||
* Callback used when fetch encountered an error.
|
style={{minHeight: '100%'}}
|
||||||
* It will reset the displayed data and show an error.
|
ListHeaderComponent={
|
||||||
*/
|
props.renderListHeaderComponent != null
|
||||||
onFetchError = () => {
|
? props.renderListHeaderComponent(state.fetchedData)
|
||||||
this.setState({
|
: null
|
||||||
fetchedData: null,
|
}
|
||||||
refreshing: false,
|
ListEmptyComponent={
|
||||||
firstLoading: false
|
state.refreshing ? (
|
||||||
});
|
<BasicLoadingScreen />
|
||||||
this.showSnackBar();
|
) : (
|
||||||
};
|
<ErrorView
|
||||||
|
navigation={props.navigation}
|
||||||
/**
|
errorCode={ERROR_TYPE.CONNECTION_ERROR}
|
||||||
* Refreshes data and shows an animations while doing it
|
onRefresh={this.onRefresh}
|
||||||
*/
|
/>
|
||||||
onRefresh = () => {
|
)
|
||||||
let canRefresh;
|
}
|
||||||
if (this.lastRefresh != null) {
|
getItemLayout={props.itemHeight != null ? this.getItemLayout : null}
|
||||||
const last = this.lastRefresh;
|
onScroll={this.onScroll}
|
||||||
canRefresh = (new Date().getTime() - last.getTime()) > MIN_REFRESH_TIME;
|
hasTab
|
||||||
} else
|
/>
|
||||||
canRefresh = true;
|
<Snackbar
|
||||||
if (canRefresh) {
|
visible={state.snackbarVisible}
|
||||||
this.setState({refreshing: true});
|
onDismiss={this.hideSnackBar}
|
||||||
readData(this.props.fetchUrl)
|
action={{
|
||||||
.then(this.onFetchSuccess)
|
label: 'OK',
|
||||||
.catch(this.onFetchError);
|
onPress: () => {},
|
||||||
}
|
}}
|
||||||
};
|
duration={4000}
|
||||||
|
style={{
|
||||||
/**
|
bottom: CustomTabBar.TAB_BAR_HEIGHT,
|
||||||
* Shows the error popup
|
}}>
|
||||||
*/
|
{i18n.t('general.listUpdateFail')}
|
||||||
showSnackBar = () => this.setState({snackbarVisible: true});
|
</Snackbar>
|
||||||
|
</View>
|
||||||
/**
|
);
|
||||||
* Hides the error popup
|
}
|
||||||
*/
|
|
||||||
hideSnackBar = () => this.setState({snackbarVisible: false});
|
|
||||||
|
|
||||||
itemLayout = (data: { [key: string]: any }, index: number) => {
|
|
||||||
const height = this.props.itemHeight;
|
|
||||||
if (height == null)
|
|
||||||
return undefined;
|
|
||||||
return {
|
|
||||||
length: height,
|
|
||||||
offset: height * index,
|
|
||||||
index
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
renderSectionHeader = (data: { section: { [key: string]: any } }) => {
|
|
||||||
if (this.props.renderSectionHeader != null) {
|
|
||||||
return (
|
|
||||||
<Animatable.View
|
|
||||||
animation={"fadeInUp"}
|
|
||||||
duration={500}
|
|
||||||
useNativeDriver
|
|
||||||
>
|
|
||||||
{this.props.renderSectionHeader(data, this.state.refreshing)}
|
|
||||||
</Animatable.View>
|
|
||||||
);
|
|
||||||
} else
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderItem = (data: {
|
|
||||||
item: { [key: string]: any },
|
|
||||||
index: number,
|
|
||||||
section: { [key: string]: any },
|
|
||||||
separators: { [key: string]: any },
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<Animatable.View
|
|
||||||
animation={"fadeInUp"}
|
|
||||||
duration={500}
|
|
||||||
useNativeDriver
|
|
||||||
>
|
|
||||||
{this.props.renderItem(data)}
|
|
||||||
</Animatable.View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onScroll = (event: SyntheticEvent<EventTarget>) => {
|
|
||||||
if (this.props.onScroll)
|
|
||||||
this.props.onScroll(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
let dataset = [];
|
|
||||||
if (this.state.fetchedData != null || (this.state.fetchedData == null && !this.props.showError)) {
|
|
||||||
dataset = this.props.createDataset(this.state.fetchedData, this.state.refreshing);
|
|
||||||
}
|
|
||||||
const {containerPaddingTop} = this.props.collapsibleStack;
|
|
||||||
return (
|
|
||||||
<View>
|
|
||||||
<CollapsibleSectionList
|
|
||||||
sections={dataset}
|
|
||||||
extraData={this.props.updateData}
|
|
||||||
refreshControl={
|
|
||||||
<RefreshControl
|
|
||||||
progressViewOffset={containerPaddingTop}
|
|
||||||
refreshing={this.state.refreshing}
|
|
||||||
onRefresh={this.onRefresh}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
renderSectionHeader={this.renderSectionHeader}
|
|
||||||
renderItem={this.renderItem}
|
|
||||||
stickySectionHeadersEnabled={this.props.stickyHeader}
|
|
||||||
style={{minHeight: '100%'}}
|
|
||||||
ListHeaderComponent={this.props.renderListHeaderComponent != null
|
|
||||||
? this.props.renderListHeaderComponent(this.state.fetchedData)
|
|
||||||
: null}
|
|
||||||
ListEmptyComponent={this.state.refreshing
|
|
||||||
? <BasicLoadingScreen/>
|
|
||||||
: <ErrorView
|
|
||||||
{...this.props}
|
|
||||||
errorCode={ERROR_TYPE.CONNECTION_ERROR}
|
|
||||||
onRefresh={this.onRefresh}/>
|
|
||||||
}
|
|
||||||
getItemLayout={this.props.itemHeight != null ? this.itemLayout : undefined}
|
|
||||||
onScroll={this.onScroll}
|
|
||||||
hasTab={true}
|
|
||||||
/>
|
|
||||||
<Snackbar
|
|
||||||
visible={this.state.snackbarVisible}
|
|
||||||
onDismiss={this.hideSnackBar}
|
|
||||||
action={{
|
|
||||||
label: 'OK',
|
|
||||||
onPress: () => {
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
duration={4000}
|
|
||||||
style={{
|
|
||||||
bottom: CustomTabBar.TAB_BAR_HEIGHT
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{i18n.t("general.listUpdateFail")}
|
|
||||||
</Snackbar>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withCollapsible(WebSectionList);
|
export default withCollapsible(WebSectionList);
|
||||||
|
|
|
@ -2,58 +2,74 @@
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {Image, View} from 'react-native';
|
import {Image, View} from 'react-native';
|
||||||
import i18n from "i18n-js";
|
import i18n from 'i18n-js';
|
||||||
import {Card, List, Paragraph, Text} from 'react-native-paper';
|
import {Card, List, Paragraph, Text} from 'react-native-paper';
|
||||||
import CustomTabBar from "../../../components/Tabbar/CustomTabBar";
|
import CustomTabBar from '../../../components/Tabbar/CustomTabBar';
|
||||||
import {StackNavigationProp} from "@react-navigation/stack";
|
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
|
||||||
import CollapsibleScrollView from "../../../components/Collapsible/CollapsibleScrollView";
|
|
||||||
|
|
||||||
type Props = {
|
const LOGO = 'https://etud.insa-toulouse.fr/~amicale_app/images/Proximo.png';
|
||||||
navigation: StackNavigationProp,
|
|
||||||
};
|
|
||||||
|
|
||||||
const LOGO = "https://etud.insa-toulouse.fr/~amicale_app/images/Proximo.png";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class defining the proximo about screen.
|
* Class defining the proximo about screen.
|
||||||
*/
|
*/
|
||||||
export default class ProximoAboutScreen extends React.Component<Props> {
|
// eslint-disable-next-line react/prefer-stateless-function
|
||||||
|
export default class ProximoAboutScreen extends React.Component<null> {
|
||||||
render() {
|
render(): React.Node {
|
||||||
return (
|
return (
|
||||||
<CollapsibleScrollView style={{padding: 5}}>
|
<CollapsibleScrollView style={{padding: 5}}>
|
||||||
<View style={{
|
<View
|
||||||
width: '100%',
|
style={{
|
||||||
height: 100,
|
width: '100%',
|
||||||
marginTop: 20,
|
height: 100,
|
||||||
marginBottom: 20,
|
marginTop: 20,
|
||||||
justifyContent: 'center',
|
marginBottom: 20,
|
||||||
alignItems: 'center'
|
justifyContent: 'center',
|
||||||
}}>
|
alignItems: 'center',
|
||||||
<Image
|
}}>
|
||||||
source={{uri: LOGO}}
|
<Image
|
||||||
style={{height: '100%', width: '100%', resizeMode: "contain"}}/>
|
source={{uri: LOGO}}
|
||||||
</View>
|
style={{height: '100%', width: '100%', resizeMode: 'contain'}}
|
||||||
<Text>{i18n.t('screens.proximo.description')}</Text>
|
/>
|
||||||
<Card style={{margin: 5}}>
|
</View>
|
||||||
<Card.Title
|
<Text>{i18n.t('screens.proximo.description')}</Text>
|
||||||
title={i18n.t('screens.proximo.openingHours')}
|
<Card style={{margin: 5}}>
|
||||||
left={props => <List.Icon {...props} icon={'clock-outline'}/>}
|
<Card.Title
|
||||||
/>
|
title={i18n.t('screens.proximo.openingHours')}
|
||||||
<Card.Content>
|
left={({
|
||||||
<Paragraph>18h30 - 19h30</Paragraph>
|
size,
|
||||||
</Card.Content>
|
color,
|
||||||
</Card>
|
}: {
|
||||||
<Card style={{margin: 5, marginBottom: CustomTabBar.TAB_BAR_HEIGHT + 20}}>
|
size: number,
|
||||||
<Card.Title
|
color: string,
|
||||||
title={i18n.t('screens.proximo.paymentMethods')}
|
}): React.Node => (
|
||||||
left={props => <List.Icon {...props} icon={'cash'}/>}
|
<List.Icon size={size} color={color} icon="clock-outline" />
|
||||||
/>
|
)}
|
||||||
<Card.Content>
|
/>
|
||||||
<Paragraph>{i18n.t('screens.proximo.paymentMethodsDescription')}</Paragraph>
|
<Card.Content>
|
||||||
</Card.Content>
|
<Paragraph>18h30 - 19h30</Paragraph>
|
||||||
</Card>
|
</Card.Content>
|
||||||
</CollapsibleScrollView>
|
</Card>
|
||||||
);
|
<Card
|
||||||
}
|
style={{margin: 5, marginBottom: CustomTabBar.TAB_BAR_HEIGHT + 20}}>
|
||||||
|
<Card.Title
|
||||||
|
title={i18n.t('screens.proximo.paymentMethods')}
|
||||||
|
left={({
|
||||||
|
size,
|
||||||
|
color,
|
||||||
|
}: {
|
||||||
|
size: number,
|
||||||
|
color: string,
|
||||||
|
}): React.Node => (
|
||||||
|
<List.Icon size={size} color={color} icon="cash" />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Card.Content>
|
||||||
|
<Paragraph>
|
||||||
|
{i18n.t('screens.proximo.paymentMethodsDescription')}
|
||||||
|
</Paragraph>
|
||||||
|
</Card.Content>
|
||||||
|
</Card>
|
||||||
|
</CollapsibleScrollView>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,323 +1,381 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {Image, Platform, ScrollView, View} from "react-native";
|
import {Image, Platform, ScrollView, View} from 'react-native';
|
||||||
import i18n from "i18n-js";
|
import i18n from 'i18n-js';
|
||||||
import CustomModal from "../../../components/Overrides/CustomModal";
|
import {
|
||||||
import {RadioButton, Searchbar, Subheading, Text, Title, withTheme} from "react-native-paper";
|
RadioButton,
|
||||||
import {stringMatchQuery} from "../../../utils/Search";
|
Searchbar,
|
||||||
import ProximoListItem from "../../../components/Lists/Proximo/ProximoListItem";
|
Subheading,
|
||||||
import MaterialHeaderButtons, {Item} from "../../../components/Overrides/CustomHeaderButton";
|
Text,
|
||||||
import {StackNavigationProp} from "@react-navigation/stack";
|
Title,
|
||||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
withTheme,
|
||||||
import CollapsibleFlatList from "../../../components/Collapsible/CollapsibleFlatList";
|
} from 'react-native-paper';
|
||||||
|
import {StackNavigationProp} from '@react-navigation/stack';
|
||||||
|
import {Modalize} from 'react-native-modalize';
|
||||||
|
import CustomModal from '../../../components/Overrides/CustomModal';
|
||||||
|
import {stringMatchQuery} from '../../../utils/Search';
|
||||||
|
import ProximoListItem from '../../../components/Lists/Proximo/ProximoListItem';
|
||||||
|
import MaterialHeaderButtons, {
|
||||||
|
Item,
|
||||||
|
} from '../../../components/Overrides/CustomHeaderButton';
|
||||||
|
import type {CustomTheme} from '../../../managers/ThemeManager';
|
||||||
|
import CollapsibleFlatList from '../../../components/Collapsible/CollapsibleFlatList';
|
||||||
|
import type {ProximoArticleType} from './ProximoMainScreen';
|
||||||
|
|
||||||
function sortPrice(a, b) {
|
function sortPrice(a: ProximoArticleType, b: ProximoArticleType): number {
|
||||||
return a.price - b.price;
|
return parseInt(a.price, 10) - parseInt(b.price, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortPriceReverse(a, b) {
|
function sortPriceReverse(
|
||||||
return b.price - a.price;
|
a: ProximoArticleType,
|
||||||
|
b: ProximoArticleType,
|
||||||
|
): number {
|
||||||
|
return parseInt(b.price, 10) - parseInt(a.price, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortName(a, b) {
|
function sortName(a: ProximoArticleType, b: ProximoArticleType): number {
|
||||||
if (a.name.toLowerCase() < b.name.toLowerCase())
|
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1;
|
||||||
return -1;
|
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1;
|
||||||
if (a.name.toLowerCase() > b.name.toLowerCase())
|
return 0;
|
||||||
return 1;
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortNameReverse(a, b) {
|
function sortNameReverse(a: ProximoArticleType, b: ProximoArticleType): number {
|
||||||
if (a.name.toLowerCase() < b.name.toLowerCase())
|
if (a.name.toLowerCase() < b.name.toLowerCase()) return 1;
|
||||||
return 1;
|
if (a.name.toLowerCase() > b.name.toLowerCase()) return -1;
|
||||||
if (a.name.toLowerCase() > b.name.toLowerCase())
|
return 0;
|
||||||
return -1;
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const LIST_ITEM_HEIGHT = 84;
|
const LIST_ITEM_HEIGHT = 84;
|
||||||
|
|
||||||
type Props = {
|
type PropsType = {
|
||||||
navigation: StackNavigationProp,
|
navigation: StackNavigationProp,
|
||||||
route: { params: { data: { data: Object }, shouldFocusSearchBar: boolean } },
|
route: {
|
||||||
theme: CustomTheme,
|
params: {
|
||||||
}
|
data: {data: Array<ProximoArticleType>},
|
||||||
|
shouldFocusSearchBar: boolean,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
theme: CustomTheme,
|
||||||
|
};
|
||||||
|
|
||||||
type State = {
|
type StateType = {
|
||||||
currentSortMode: number,
|
currentSortMode: number,
|
||||||
modalCurrentDisplayItem: React.Node,
|
modalCurrentDisplayItem: React.Node,
|
||||||
currentSearchString: string,
|
currentSearchString: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class defining proximo's article list of a certain category.
|
* Class defining Proximo article list of a certain category.
|
||||||
*/
|
*/
|
||||||
class ProximoListScreen extends React.Component<Props, State> {
|
class ProximoListScreen extends React.Component<PropsType, StateType> {
|
||||||
|
modalRef: Modalize | null;
|
||||||
|
|
||||||
modalRef: Object;
|
listData: Array<ProximoArticleType>;
|
||||||
listData: Array<Object>;
|
|
||||||
shouldFocusSearchBar: boolean;
|
|
||||||
|
|
||||||
constructor(props) {
|
shouldFocusSearchBar: boolean;
|
||||||
super(props);
|
|
||||||
this.listData = this.props.route.params['data']['data'].sort(sortName);
|
constructor(props: PropsType) {
|
||||||
this.shouldFocusSearchBar = this.props.route.params['shouldFocusSearchBar'];
|
super(props);
|
||||||
this.state = {
|
this.listData = props.route.params.data.data.sort(sortName);
|
||||||
currentSearchString: '',
|
this.shouldFocusSearchBar = props.route.params.shouldFocusSearchBar;
|
||||||
currentSortMode: 3,
|
this.state = {
|
||||||
modalCurrentDisplayItem: null,
|
currentSearchString: '',
|
||||||
};
|
currentSortMode: 3,
|
||||||
|
modalCurrentDisplayItem: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the header content
|
||||||
|
*/
|
||||||
|
componentDidMount() {
|
||||||
|
const {navigation} = this.props;
|
||||||
|
navigation.setOptions({
|
||||||
|
headerRight: this.getSortMenuButton,
|
||||||
|
headerTitle: this.getSearchBar,
|
||||||
|
headerBackTitleVisible: false,
|
||||||
|
headerTitleContainerStyle:
|
||||||
|
Platform.OS === 'ios'
|
||||||
|
? {marginHorizontal: 0, width: '70%'}
|
||||||
|
: {marginHorizontal: 0, right: 50, left: 50},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback used when clicking on the sort menu button.
|
||||||
|
* It will open the modal to show a sort selection
|
||||||
|
*/
|
||||||
|
onSortMenuPress = () => {
|
||||||
|
this.setState({
|
||||||
|
modalCurrentDisplayItem: this.getModalSortMenu(),
|
||||||
|
});
|
||||||
|
if (this.modalRef) {
|
||||||
|
this.modalRef.open();
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback used when the search changes
|
||||||
|
*
|
||||||
|
* @param str The new search string
|
||||||
|
*/
|
||||||
|
onSearchStringChange = (str: string) => {
|
||||||
|
this.setState({currentSearchString: str});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the header content
|
* Callback used when clicking an article in the list.
|
||||||
*/
|
* It opens the modal to show detailed information about the article
|
||||||
componentDidMount() {
|
*
|
||||||
this.props.navigation.setOptions({
|
* @param item The article pressed
|
||||||
headerRight: this.getSortMenuButton,
|
*/
|
||||||
headerTitle: this.getSearchBar,
|
onListItemPress(item: ProximoArticleType) {
|
||||||
headerBackTitleVisible: false,
|
this.setState({
|
||||||
headerTitleContainerStyle: Platform.OS === 'ios' ?
|
modalCurrentDisplayItem: this.getModalItemContent(item),
|
||||||
{marginHorizontal: 0, width: '70%'} :
|
});
|
||||||
{marginHorizontal: 0, right: 50, left: 50},
|
if (this.modalRef) {
|
||||||
});
|
this.modalRef.open();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the header search bar
|
* Sets the current sort mode.
|
||||||
*
|
*
|
||||||
* @return {*}
|
* @param mode The number representing the mode
|
||||||
*/
|
*/
|
||||||
getSearchBar = () => {
|
setSortMode(mode: string) {
|
||||||
return (
|
const {currentSortMode} = this.state;
|
||||||
<Searchbar
|
const currentMode = parseInt(mode, 10);
|
||||||
placeholder={i18n.t('screens.proximo.search')}
|
this.setState({
|
||||||
onChangeText={this.onSearchStringChange}
|
currentSortMode: currentMode,
|
||||||
|
});
|
||||||
|
switch (currentMode) {
|
||||||
|
case 1:
|
||||||
|
this.listData.sort(sortPrice);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
this.listData.sort(sortPriceReverse);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
this.listData.sort(sortName);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
this.listData.sort(sortNameReverse);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.listData.sort(sortName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (this.modalRef && currentMode !== currentSortMode) this.modalRef.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a color depending on the quantity available
|
||||||
|
*
|
||||||
|
* @param availableStock The quantity available
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
getStockColor(availableStock: number): string {
|
||||||
|
const {theme} = this.props;
|
||||||
|
let color: string;
|
||||||
|
if (availableStock > 3) color = theme.colors.success;
|
||||||
|
else if (availableStock > 0) color = theme.colors.warning;
|
||||||
|
else color = theme.colors.danger;
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the sort menu header button
|
||||||
|
*
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
getSortMenuButton = (): React.Node => {
|
||||||
|
return (
|
||||||
|
<MaterialHeaderButtons>
|
||||||
|
<Item title="main" iconName="sort" onPress={this.onSortMenuPress} />
|
||||||
|
</MaterialHeaderButtons>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the header search bar
|
||||||
|
*
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
getSearchBar = (): React.Node => {
|
||||||
|
return (
|
||||||
|
<Searchbar
|
||||||
|
placeholder={i18n.t('screens.proximo.search')}
|
||||||
|
onChangeText={this.onSearchStringChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the modal content depending on the given article
|
||||||
|
*
|
||||||
|
* @param item The article to display
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
getModalItemContent(item: ProximoArticleType): React.Node {
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
padding: 20,
|
||||||
|
}}>
|
||||||
|
<Title>{item.name}</Title>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
width: '100%',
|
||||||
|
marginTop: 10,
|
||||||
|
}}>
|
||||||
|
<Subheading
|
||||||
|
style={{
|
||||||
|
color: this.getStockColor(parseInt(item.quantity, 10)),
|
||||||
|
}}>
|
||||||
|
{`${item.quantity} ${i18n.t('screens.proximo.inStock')}`}
|
||||||
|
</Subheading>
|
||||||
|
<Subheading style={{marginLeft: 'auto'}}>{item.price}€</Subheading>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<ScrollView>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: 150,
|
||||||
|
marginTop: 20,
|
||||||
|
marginBottom: 20,
|
||||||
|
}}>
|
||||||
|
<Image
|
||||||
|
style={{flex: 1, resizeMode: 'contain'}}
|
||||||
|
source={{uri: item.image}}
|
||||||
/>
|
/>
|
||||||
);
|
</View>
|
||||||
};
|
<Text>{item.description}</Text>
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the sort menu header button
|
* Gets the modal content to display a sort menu
|
||||||
*
|
*
|
||||||
* @return {*}
|
* @return {*}
|
||||||
*/
|
*/
|
||||||
getSortMenuButton = () => {
|
getModalSortMenu(): React.Node {
|
||||||
return <MaterialHeaderButtons>
|
const {currentSortMode} = this.state;
|
||||||
<Item title="main" iconName="sort" onPress={this.onSortMenuPress}/>
|
return (
|
||||||
</MaterialHeaderButtons>;
|
<View
|
||||||
};
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
padding: 20,
|
||||||
|
}}>
|
||||||
|
<Title style={{marginBottom: 10}}>
|
||||||
|
{i18n.t('screens.proximo.sortOrder')}
|
||||||
|
</Title>
|
||||||
|
<RadioButton.Group
|
||||||
|
onValueChange={(value: string) => {
|
||||||
|
this.setSortMode(value);
|
||||||
|
}}
|
||||||
|
value={currentSortMode}>
|
||||||
|
<RadioButton.Item
|
||||||
|
label={i18n.t('screens.proximo.sortPrice')}
|
||||||
|
value={1}
|
||||||
|
/>
|
||||||
|
<RadioButton.Item
|
||||||
|
label={i18n.t('screens.proximo.sortPriceReverse')}
|
||||||
|
value={2}
|
||||||
|
/>
|
||||||
|
<RadioButton.Item
|
||||||
|
label={i18n.t('screens.proximo.sortName')}
|
||||||
|
value={3}
|
||||||
|
/>
|
||||||
|
<RadioButton.Item
|
||||||
|
label={i18n.t('screens.proximo.sortNameReverse')}
|
||||||
|
value={4}
|
||||||
|
/>
|
||||||
|
</RadioButton.Group>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback used when clicking on the sort menu button.
|
* Gets a render item for the given article
|
||||||
* It will open the modal to show a sort selection
|
*
|
||||||
*/
|
* @param item The article to render
|
||||||
onSortMenuPress = () => {
|
* @return {*}
|
||||||
this.setState({
|
*/
|
||||||
modalCurrentDisplayItem: this.getModalSortMenu()
|
getRenderItem = ({item}: {item: ProximoArticleType}): React.Node => {
|
||||||
});
|
const {currentSearchString} = this.state;
|
||||||
if (this.modalRef) {
|
if (stringMatchQuery(item.name, currentSearchString)) {
|
||||||
this.modalRef.open();
|
const onPress = () => {
|
||||||
}
|
this.onListItemPress(item);
|
||||||
};
|
};
|
||||||
|
const color = this.getStockColor(parseInt(item.quantity, 10));
|
||||||
/**
|
return (
|
||||||
* Sets the current sort mode.
|
<ProximoListItem
|
||||||
*
|
item={item}
|
||||||
* @param mode The number representing the mode
|
onPress={onPress}
|
||||||
*/
|
color={color}
|
||||||
setSortMode(mode: number) {
|
height={LIST_ITEM_HEIGHT}
|
||||||
this.setState({
|
/>
|
||||||
currentSortMode: mode,
|
);
|
||||||
});
|
|
||||||
switch (mode) {
|
|
||||||
case 1:
|
|
||||||
this.listData.sort(sortPrice);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
this.listData.sort(sortPriceReverse);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
this.listData.sort(sortName);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
this.listData.sort(sortNameReverse);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (this.modalRef && mode !== this.state.currentSortMode) {
|
|
||||||
this.modalRef.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a color depending on the quantity available
|
* Extracts a key for the given article
|
||||||
*
|
*
|
||||||
* @param availableStock The quantity available
|
* @param item The article to extract the key from
|
||||||
* @return
|
* @return {string} The extracted key
|
||||||
*/
|
*/
|
||||||
getStockColor(availableStock: number) {
|
keyExtractor = (item: ProximoArticleType): string => item.name + item.code;
|
||||||
let color: string;
|
|
||||||
if (availableStock > 3)
|
|
||||||
color = this.props.theme.colors.success;
|
|
||||||
else if (availableStock > 0)
|
|
||||||
color = this.props.theme.colors.warning;
|
|
||||||
else
|
|
||||||
color = this.props.theme.colors.danger;
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback used when the search changes
|
* Callback used when receiving the modal ref
|
||||||
*
|
*
|
||||||
* @param str The new search string
|
* @param ref
|
||||||
*/
|
*/
|
||||||
onSearchStringChange = (str: string) => {
|
onModalRef = (ref: Modalize) => {
|
||||||
this.setState({currentSearchString: str})
|
this.modalRef = ref;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
itemLayout = (
|
||||||
* Gets the modal content depending on the given article
|
data: ProximoArticleType,
|
||||||
*
|
index: number,
|
||||||
* @param item The article to display
|
): {length: number, offset: number, index: number} => ({
|
||||||
* @return {*}
|
length: LIST_ITEM_HEIGHT,
|
||||||
*/
|
offset: LIST_ITEM_HEIGHT * index,
|
||||||
getModalItemContent(item: Object) {
|
index,
|
||||||
return (
|
});
|
||||||
<View style={{
|
|
||||||
flex: 1,
|
|
||||||
padding: 20
|
|
||||||
}}>
|
|
||||||
<Title>{item.name}</Title>
|
|
||||||
<View style={{
|
|
||||||
flexDirection: 'row',
|
|
||||||
width: '100%',
|
|
||||||
marginTop: 10,
|
|
||||||
}}>
|
|
||||||
<Subheading style={{
|
|
||||||
color: this.getStockColor(parseInt(item.quantity)),
|
|
||||||
}}>
|
|
||||||
{item.quantity + ' ' + i18n.t('screens.proximo.inStock')}
|
|
||||||
</Subheading>
|
|
||||||
<Subheading style={{marginLeft: 'auto'}}>{item.price}€</Subheading>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<ScrollView>
|
render(): React.Node {
|
||||||
<View style={{width: '100%', height: 150, marginTop: 20, marginBottom: 20}}>
|
const {state} = this;
|
||||||
<Image style={{flex: 1, resizeMode: "contain"}}
|
return (
|
||||||
source={{uri: item.image}}/>
|
<View
|
||||||
</View>
|
style={{
|
||||||
<Text>{item.description}</Text>
|
height: '100%',
|
||||||
</ScrollView>
|
}}>
|
||||||
</View>
|
<CustomModal onRef={this.onModalRef}>
|
||||||
);
|
{state.modalCurrentDisplayItem}
|
||||||
}
|
</CustomModal>
|
||||||
|
<CollapsibleFlatList
|
||||||
/**
|
data={this.listData}
|
||||||
* Gets the modal content to display a sort menu
|
extraData={state.currentSearchString + state.currentSortMode}
|
||||||
*
|
keyExtractor={this.keyExtractor}
|
||||||
* @return {*}
|
renderItem={this.getRenderItem}
|
||||||
*/
|
// Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
|
||||||
getModalSortMenu() {
|
removeClippedSubviews
|
||||||
return (
|
getItemLayout={this.itemLayout}
|
||||||
<View style={{
|
initialNumToRender={10}
|
||||||
flex: 1,
|
/>
|
||||||
padding: 20
|
</View>
|
||||||
}}>
|
);
|
||||||
<Title style={{marginBottom: 10}}>{i18n.t('screens.proximo.sortOrder')}</Title>
|
}
|
||||||
<RadioButton.Group
|
|
||||||
onValueChange={value => this.setSortMode(value)}
|
|
||||||
value={this.state.currentSortMode}
|
|
||||||
>
|
|
||||||
<RadioButton.Item label={i18n.t('screens.proximo.sortPrice')} value={1}/>
|
|
||||||
<RadioButton.Item label={i18n.t('screens.proximo.sortPriceReverse')} value={2}/>
|
|
||||||
<RadioButton.Item label={i18n.t('screens.proximo.sortName')} value={3}/>
|
|
||||||
<RadioButton.Item label={i18n.t('screens.proximo.sortNameReverse')} value={4}/>
|
|
||||||
</RadioButton.Group>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback used when clicking an article in the list.
|
|
||||||
* It opens the modal to show detailed information about the article
|
|
||||||
*
|
|
||||||
* @param item The article pressed
|
|
||||||
*/
|
|
||||||
onListItemPress(item: Object) {
|
|
||||||
this.setState({
|
|
||||||
modalCurrentDisplayItem: this.getModalItemContent(item)
|
|
||||||
});
|
|
||||||
if (this.modalRef) {
|
|
||||||
this.modalRef.open();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a render item for the given article
|
|
||||||
*
|
|
||||||
* @param item The article to render
|
|
||||||
* @return {*}
|
|
||||||
*/
|
|
||||||
renderItem = ({item}: Object) => {
|
|
||||||
if (stringMatchQuery(item.name, this.state.currentSearchString)) {
|
|
||||||
const onPress = this.onListItemPress.bind(this, item);
|
|
||||||
const color = this.getStockColor(parseInt(item.quantity));
|
|
||||||
return (
|
|
||||||
<ProximoListItem
|
|
||||||
item={item}
|
|
||||||
onPress={onPress}
|
|
||||||
color={color}
|
|
||||||
height={LIST_ITEM_HEIGHT}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts a key for the given article
|
|
||||||
*
|
|
||||||
* @param item The article to extract the key from
|
|
||||||
* @return {*} The extracted key
|
|
||||||
*/
|
|
||||||
keyExtractor(item: Object) {
|
|
||||||
return item.name + item.code;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback used when receiving the modal ref
|
|
||||||
*
|
|
||||||
* @param ref
|
|
||||||
*/
|
|
||||||
onModalRef = (ref: Object) => {
|
|
||||||
this.modalRef = ref;
|
|
||||||
};
|
|
||||||
|
|
||||||
itemLayout = (data, index) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index});
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<View style={{
|
|
||||||
height: '100%'
|
|
||||||
}}>
|
|
||||||
<CustomModal onRef={this.onModalRef}>
|
|
||||||
{this.state.modalCurrentDisplayItem}
|
|
||||||
</CustomModal>
|
|
||||||
<CollapsibleFlatList
|
|
||||||
data={this.listData}
|
|
||||||
extraData={this.state.currentSearchString + this.state.currentSortMode}
|
|
||||||
keyExtractor={this.keyExtractor}
|
|
||||||
renderItem={this.renderItem}
|
|
||||||
// Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
|
|
||||||
removeClippedSubviews={true}
|
|
||||||
getItemLayout={this.itemLayout}
|
|
||||||
initialNumToRender={10}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withTheme(ProximoListScreen);
|
export default withTheme(ProximoListScreen);
|
||||||
|
|
|
@ -1,233 +1,289 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {View} from 'react-native'
|
import i18n from 'i18n-js';
|
||||||
import i18n from "i18n-js";
|
|
||||||
import WebSectionList from "../../../components/Screens/WebSectionList";
|
|
||||||
import {List, withTheme} from 'react-native-paper';
|
import {List, withTheme} from 'react-native-paper';
|
||||||
import MaterialHeaderButtons, {Item} from "../../../components/Overrides/CustomHeaderButton";
|
import {StackNavigationProp} from '@react-navigation/stack';
|
||||||
import {StackNavigationProp} from "@react-navigation/stack";
|
import WebSectionList from '../../../components/Screens/WebSectionList';
|
||||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
import MaterialHeaderButtons, {
|
||||||
|
Item,
|
||||||
|
} from '../../../components/Overrides/CustomHeaderButton';
|
||||||
|
import type {CustomTheme} from '../../../managers/ThemeManager';
|
||||||
|
import type {SectionListDataType} from '../../../components/Screens/WebSectionList';
|
||||||
|
|
||||||
const DATA_URL = "https://etud.insa-toulouse.fr/~proximo/data/stock-v2.json";
|
const DATA_URL = 'https://etud.insa-toulouse.fr/~proximo/data/stock-v2.json';
|
||||||
const LIST_ITEM_HEIGHT = 84;
|
const LIST_ITEM_HEIGHT = 84;
|
||||||
|
|
||||||
type Props = {
|
export type ProximoCategoryType = {
|
||||||
navigation: StackNavigationProp,
|
name: string,
|
||||||
theme: CustomTheme,
|
icon: string,
|
||||||
}
|
id: string,
|
||||||
|
};
|
||||||
|
|
||||||
type State = {
|
export type ProximoArticleType = {
|
||||||
fetchedData: Object,
|
name: string,
|
||||||
}
|
description: string,
|
||||||
|
quantity: string,
|
||||||
|
price: string,
|
||||||
|
code: string,
|
||||||
|
id: string,
|
||||||
|
type: Array<string>,
|
||||||
|
image: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProximoMainListItemType = {
|
||||||
|
type: ProximoCategoryType,
|
||||||
|
data: Array<ProximoArticleType>,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProximoDataType = {
|
||||||
|
types: Array<ProximoCategoryType>,
|
||||||
|
articles: Array<ProximoArticleType>,
|
||||||
|
};
|
||||||
|
|
||||||
|
type PropsType = {
|
||||||
|
navigation: StackNavigationProp,
|
||||||
|
theme: CustomTheme,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class defining the main proximo screen.
|
* Class defining the main proximo screen.
|
||||||
* This screen shows the different categories of articles offered by proximo.
|
* This screen shows the different categories of articles offered by proximo.
|
||||||
*/
|
*/
|
||||||
class ProximoMainScreen extends React.Component<Props, State> {
|
class ProximoMainScreen extends React.Component<PropsType> {
|
||||||
|
/**
|
||||||
|
* Function used to sort items in the list.
|
||||||
|
* Makes the All category sticks to the top and sorts the others by name ascending
|
||||||
|
*
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
static sortFinalData(
|
||||||
|
a: ProximoMainListItemType,
|
||||||
|
b: ProximoMainListItemType,
|
||||||
|
): number {
|
||||||
|
const str1 = a.type.name.toLowerCase();
|
||||||
|
const str2 = b.type.name.toLowerCase();
|
||||||
|
|
||||||
articles: Object;
|
// Make 'All' category with id -1 stick to the top
|
||||||
|
if (a.type.id === -1) return -1;
|
||||||
|
if (b.type.id === -1) return 1;
|
||||||
|
|
||||||
/**
|
// Sort others by name ascending
|
||||||
* Function used to sort items in the list.
|
if (str1 < str2) return -1;
|
||||||
* Makes the All category stick to the top and sorts the others by name ascending
|
if (str1 > str2) return 1;
|
||||||
*
|
return 0;
|
||||||
* @param a
|
}
|
||||||
* @param b
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
static sortFinalData(a: Object, b: Object) {
|
|
||||||
let str1 = a.type.name.toLowerCase();
|
|
||||||
let str2 = b.type.name.toLowerCase();
|
|
||||||
|
|
||||||
// Make 'All' category with id -1 stick to the top
|
/**
|
||||||
if (a.type.id === -1)
|
* Get an array of available articles (in stock) of the given type
|
||||||
return -1;
|
*
|
||||||
if (b.type.id === -1)
|
* @param articles The list of all articles
|
||||||
return 1;
|
* @param type The type of articles to find (undefined for any type)
|
||||||
|
* @return {Array} The array of available articles
|
||||||
// Sort others by name ascending
|
*/
|
||||||
if (str1 < str2)
|
static getAvailableArticles(
|
||||||
return -1;
|
articles: Array<ProximoArticleType> | null,
|
||||||
if (str1 > str2)
|
type: ?ProximoCategoryType,
|
||||||
return 1;
|
): Array<ProximoArticleType> {
|
||||||
return 0;
|
const availableArticles = [];
|
||||||
|
if (articles != null) {
|
||||||
|
articles.forEach((article: ProximoArticleType) => {
|
||||||
|
if (
|
||||||
|
((type != null && article.type.includes(type.id)) || type == null) &&
|
||||||
|
parseInt(article.quantity, 10) > 0
|
||||||
|
)
|
||||||
|
availableArticles.push(article);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
return availableArticles;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
articles: Array<ProximoArticleType> | null;
|
||||||
* Creates header button
|
|
||||||
*/
|
|
||||||
componentDidMount() {
|
|
||||||
const rightButton = this.getHeaderButtons.bind(this);
|
|
||||||
this.props.navigation.setOptions({
|
|
||||||
headerRight: rightButton,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback used when the search button is pressed.
|
* Creates header button
|
||||||
* This will open a new ProximoListScreen with all items displayed
|
*/
|
||||||
*/
|
componentDidMount() {
|
||||||
onPressSearchBtn = () => {
|
const {navigation} = this.props;
|
||||||
let searchScreenData = {
|
navigation.setOptions({
|
||||||
shouldFocusSearchBar: true,
|
headerRight: (): React.Node => this.getHeaderButtons(),
|
||||||
data: {
|
});
|
||||||
type: {
|
}
|
||||||
id: "0",
|
|
||||||
name: i18n.t('screens.proximo.all'),
|
/**
|
||||||
icon: 'star'
|
* Callback used when the search button is pressed.
|
||||||
},
|
* This will open a new ProximoListScreen with all items displayed
|
||||||
data: this.articles !== undefined ?
|
*/
|
||||||
this.getAvailableArticles(this.articles, undefined) : []
|
onPressSearchBtn = () => {
|
||||||
},
|
const {navigation} = this.props;
|
||||||
};
|
const searchScreenData = {
|
||||||
this.props.navigation.navigate('proximo-list', searchScreenData);
|
shouldFocusSearchBar: true,
|
||||||
|
data: {
|
||||||
|
type: {
|
||||||
|
id: '0',
|
||||||
|
name: i18n.t('screens.proximo.all'),
|
||||||
|
icon: 'star',
|
||||||
|
},
|
||||||
|
data:
|
||||||
|
this.articles != null
|
||||||
|
? ProximoMainScreen.getAvailableArticles(this.articles)
|
||||||
|
: [],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
navigation.navigate('proximo-list', searchScreenData);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback used when the about button is pressed.
|
* Callback used when the about button is pressed.
|
||||||
* This will open the ProximoAboutScreen
|
* This will open the ProximoAboutScreen
|
||||||
*/
|
*/
|
||||||
onPressAboutBtn = () => {
|
onPressAboutBtn = () => {
|
||||||
this.props.navigation.navigate('proximo-about');
|
const {navigation} = this.props;
|
||||||
|
navigation.navigate('proximo-about');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the header buttons
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
getHeaderButtons(): React.Node {
|
||||||
|
return (
|
||||||
|
<MaterialHeaderButtons>
|
||||||
|
<Item
|
||||||
|
title="magnify"
|
||||||
|
iconName="magnify"
|
||||||
|
onPress={this.onPressSearchBtn}
|
||||||
|
/>
|
||||||
|
<Item
|
||||||
|
title="information"
|
||||||
|
iconName="information"
|
||||||
|
onPress={this.onPressAboutBtn}
|
||||||
|
/>
|
||||||
|
</MaterialHeaderButtons>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts a key for the given category
|
||||||
|
*
|
||||||
|
* @param item The category to extract the key from
|
||||||
|
* @return {*} The extracted key
|
||||||
|
*/
|
||||||
|
getKeyExtractor = (item: ProximoMainListItemType): string => item.type.id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the given category render item
|
||||||
|
*
|
||||||
|
* @param item The category to render
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
getRenderItem = ({item}: {item: ProximoMainListItemType}): React.Node => {
|
||||||
|
const {navigation, theme} = this.props;
|
||||||
|
const dataToSend = {
|
||||||
|
shouldFocusSearchBar: false,
|
||||||
|
data: item,
|
||||||
|
};
|
||||||
|
const subtitle = `${item.data.length} ${
|
||||||
|
item.data.length > 1
|
||||||
|
? i18n.t('screens.proximo.articles')
|
||||||
|
: i18n.t('screens.proximo.article')
|
||||||
|
}`;
|
||||||
|
const onPress = () => {
|
||||||
|
navigation.navigate('proximo-list', dataToSend);
|
||||||
|
};
|
||||||
|
if (item.data.length > 0) {
|
||||||
|
return (
|
||||||
|
<List.Item
|
||||||
|
title={item.type.name}
|
||||||
|
description={subtitle}
|
||||||
|
onPress={onPress}
|
||||||
|
left={({size}: {size: number}): React.Node => (
|
||||||
|
<List.Icon
|
||||||
|
size={size}
|
||||||
|
icon={item.type.icon}
|
||||||
|
color={theme.colors.primary}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
right={({size, color}: {size: number, color: string}): React.Node => (
|
||||||
|
<List.Icon size={size} color={color} icon="chevron-right" />
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
height: LIST_ITEM_HEIGHT,
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the header buttons
|
* Creates the dataset to be used in the FlatList
|
||||||
* @return {*}
|
*
|
||||||
*/
|
* @param fetchedData
|
||||||
getHeaderButtons() {
|
* @return {*}
|
||||||
return <MaterialHeaderButtons>
|
* */
|
||||||
<Item title="magnify" iconName="magnify" onPress={this.onPressSearchBtn}/>
|
createDataset = (
|
||||||
<Item title="information" iconName="information" onPress={this.onPressAboutBtn}/>
|
fetchedData: ProximoDataType | null,
|
||||||
</MaterialHeaderButtons>;
|
): SectionListDataType<ProximoMainListItemType> => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: '',
|
||||||
|
data: this.generateData(fetchedData),
|
||||||
|
keyExtractor: this.getKeyExtractor,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the data using types and FetchedData.
|
||||||
|
* This will group items under the same type.
|
||||||
|
*
|
||||||
|
* @param fetchedData The array of articles represented by objects
|
||||||
|
* @returns {Array} The formatted dataset
|
||||||
|
*/
|
||||||
|
generateData(
|
||||||
|
fetchedData: ProximoDataType | null,
|
||||||
|
): Array<ProximoMainListItemType> {
|
||||||
|
const finalData: Array<ProximoMainListItemType> = [];
|
||||||
|
this.articles = null;
|
||||||
|
if (fetchedData != null) {
|
||||||
|
const {types} = fetchedData;
|
||||||
|
this.articles = fetchedData.articles;
|
||||||
|
finalData.push({
|
||||||
|
type: {
|
||||||
|
id: '-1',
|
||||||
|
name: i18n.t('screens.proximo.all'),
|
||||||
|
icon: 'star',
|
||||||
|
},
|
||||||
|
data: ProximoMainScreen.getAvailableArticles(this.articles),
|
||||||
|
});
|
||||||
|
types.forEach((type: ProximoCategoryType) => {
|
||||||
|
finalData.push({
|
||||||
|
type,
|
||||||
|
data: ProximoMainScreen.getAvailableArticles(this.articles, type),
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
finalData.sort(ProximoMainScreen.sortFinalData);
|
||||||
|
return finalData;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
render(): React.Node {
|
||||||
* Extracts a key for the given category
|
const {navigation} = this.props;
|
||||||
*
|
return (
|
||||||
* @param item The category to extract the key from
|
<WebSectionList
|
||||||
* @return {*} The extracted key
|
createDataset={this.createDataset}
|
||||||
*/
|
navigation={navigation}
|
||||||
getKeyExtractor(item: Object) {
|
autoRefreshTime={0}
|
||||||
return item !== undefined ? item.type['id'] : undefined;
|
refreshOnFocus={false}
|
||||||
}
|
fetchUrl={DATA_URL}
|
||||||
|
renderItem={this.getRenderItem}
|
||||||
/**
|
/>
|
||||||
* Creates the dataset to be used in the FlatList
|
);
|
||||||
*
|
}
|
||||||
* @param fetchedData
|
|
||||||
* @return {*}
|
|
||||||
* */
|
|
||||||
createDataset = (fetchedData: Object) => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
title: '',
|
|
||||||
data: this.generateData(fetchedData),
|
|
||||||
keyExtractor: this.getKeyExtractor
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate the data using types and FetchedData.
|
|
||||||
* This will group items under the same type.
|
|
||||||
*
|
|
||||||
* @param fetchedData The array of articles represented by objects
|
|
||||||
* @returns {Array} The formatted dataset
|
|
||||||
*/
|
|
||||||
generateData(fetchedData: Object) {
|
|
||||||
let finalData = [];
|
|
||||||
this.articles = undefined;
|
|
||||||
if (fetchedData.types !== undefined && fetchedData.articles !== undefined) {
|
|
||||||
let types = fetchedData.types;
|
|
||||||
this.articles = fetchedData.articles;
|
|
||||||
finalData.push({
|
|
||||||
type: {
|
|
||||||
id: -1,
|
|
||||||
name: i18n.t('screens.proximo.all'),
|
|
||||||
icon: 'star'
|
|
||||||
},
|
|
||||||
data: this.getAvailableArticles(this.articles, undefined)
|
|
||||||
});
|
|
||||||
for (let i = 0; i < types.length; i++) {
|
|
||||||
finalData.push({
|
|
||||||
type: types[i],
|
|
||||||
data: this.getAvailableArticles(this.articles, types[i])
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finalData.sort(ProximoMainScreen.sortFinalData);
|
|
||||||
return finalData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an array of available articles (in stock) of the given type
|
|
||||||
*
|
|
||||||
* @param articles The list of all articles
|
|
||||||
* @param type The type of articles to find (undefined for any type)
|
|
||||||
* @return {Array} The array of available articles
|
|
||||||
*/
|
|
||||||
getAvailableArticles(articles: Array<Object>, type: ?Object) {
|
|
||||||
let availableArticles = [];
|
|
||||||
for (let k = 0; k < articles.length; k++) {
|
|
||||||
if ((type !== undefined && type !== null && articles[k]['type'].includes(type['id'])
|
|
||||||
|| type === undefined)
|
|
||||||
&& parseInt(articles[k]['quantity']) > 0) {
|
|
||||||
availableArticles.push(articles[k]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return availableArticles;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the given category render item
|
|
||||||
*
|
|
||||||
* @param item The category to render
|
|
||||||
* @return {*}
|
|
||||||
*/
|
|
||||||
getRenderItem = ({item}: Object) => {
|
|
||||||
let dataToSend = {
|
|
||||||
shouldFocusSearchBar: false,
|
|
||||||
data: item,
|
|
||||||
};
|
|
||||||
const subtitle = item.data.length + " " + (item.data.length > 1 ? i18n.t('screens.proximo.articles') : i18n.t('screens.proximo.article'));
|
|
||||||
const onPress = this.props.navigation.navigate.bind(this, 'proximo-list', dataToSend);
|
|
||||||
if (item.data.length > 0) {
|
|
||||||
return (
|
|
||||||
<List.Item
|
|
||||||
title={item.type.name}
|
|
||||||
description={subtitle}
|
|
||||||
onPress={onPress}
|
|
||||||
left={props => <List.Icon
|
|
||||||
{...props}
|
|
||||||
icon={item.type.icon}
|
|
||||||
color={this.props.theme.colors.primary}/>}
|
|
||||||
right={props => <List.Icon {...props} icon={'chevron-right'}/>}
|
|
||||||
style={{
|
|
||||||
height: LIST_ITEM_HEIGHT,
|
|
||||||
justifyContent: 'center',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else
|
|
||||||
return <View/>;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const nav = this.props.navigation;
|
|
||||||
return (
|
|
||||||
<WebSectionList
|
|
||||||
createDataset={this.createDataset}
|
|
||||||
navigation={nav}
|
|
||||||
autoRefreshTime={0}
|
|
||||||
refreshOnFocus={false}
|
|
||||||
fetchUrl={DATA_URL}
|
|
||||||
renderItem={this.getRenderItem}/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withTheme(ProximoMainScreen);
|
export default withTheme(ProximoMainScreen);
|
||||||
|
|
Loading…
Reference in a new issue