Improve Services screen components to match linter

This commit is contained in:
Arnaud Vergnet 2020-08-05 18:39:44 +02:00
parent 3ce23726c2
commit fcbc70956b
10 changed files with 280 additions and 251 deletions

View file

@ -15,12 +15,12 @@ import i18n from 'i18n-js';
import * as Animatable from 'react-native-animatable';
import ProxiwashConstants from '../../../constants/ProxiwashConstants';
import AprilFoolsManager from '../../../managers/AprilFoolsManager';
import type {CustomTheme} from '../../../managers/ThemeManager';
import type {CustomThemeType} from '../../../managers/ThemeManager';
import type {ProxiwashMachineType} from '../../../screens/Proxiwash/ProxiwashScreen';
type PropsType = {
item: ProxiwashMachineType,
theme: CustomTheme,
theme: CustomThemeType,
onPress: (
title: string,
item: ProxiwashMachineType,

View file

@ -4,10 +4,10 @@ import * as React from 'react';
import {Avatar, Text, withTheme} from 'react-native-paper';
import {StyleSheet, View} from 'react-native';
import i18n from 'i18n-js';
import type {CustomTheme} from '../../../managers/ThemeManager';
import type {CustomThemeType} from '../../../managers/ThemeManager';
type PropsType = {
theme: CustomTheme,
theme: CustomThemeType,
title: string,
isDryer: boolean,
nbAvailable: number,

View file

@ -13,7 +13,7 @@ import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityI
import {withTheme} from 'react-native-paper';
import {StackNavigationProp} from '@react-navigation/stack';
import {Collapsible} from 'react-navigation-collapsible';
import type {CustomTheme} from '../../managers/ThemeManager';
import type {CustomThemeType} from '../../managers/ThemeManager';
import {withCollapsible} from '../../utils/withCollapsible';
import MaterialHeaderButtons, {Item} from '../Overrides/CustomHeaderButton';
import {ERROR_TYPE} from '../../utils/WebData';
@ -22,7 +22,7 @@ import BasicLoadingScreen from './BasicLoadingScreen';
type PropsType = {
navigation: StackNavigationProp,
theme: CustomTheme,
theme: CustomThemeType,
url: string,
collapsibleStack: Collapsible,
onMessage: (event: {nativeEvent: {data: string}}) => void,

View file

@ -2,6 +2,7 @@
import type {ProxiwashMachineType} from '../screens/Proxiwash/ProxiwashScreen';
import type {CustomThemeType} from './ThemeManager';
import type {RuFoodCategoryType} from '../screens/Services/SelfMenuScreen';
/**
* Singleton class used to manage april fools
@ -49,8 +50,8 @@ export default class AprilFoolsManager {
* @returns {Object}
*/
static getFakeMenuItem(
menu: Array<{dishes: Array<{name: string}>}>,
): Array<{dishes: Array<{name: string}>}> {
menu: Array<RuFoodCategoryType>,
): Array<RuFoodCategoryType> {
menu[1].dishes.splice(4, 0, {name: 'Coq au vin'});
menu[1].dishes.splice(2, 0, {name: "Bat'Soupe"});
menu[1].dishes.splice(1, 0, {name: 'Pave de loup'});

View file

@ -17,7 +17,7 @@ import MaterialHeaderButtons, {
Item,
} from '../../components/Overrides/CustomHeaderButton';
import ProxiwashSectionHeader from '../../components/Lists/Proxiwash/ProxiwashSectionHeader';
import type {CustomTheme} from '../../managers/ThemeManager';
import type {CustomThemeType} from '../../managers/ThemeManager';
import {
getCleanedMachineWatched,
getMachineEndDate,
@ -47,7 +47,7 @@ export type ProxiwashMachineType = {
type PropsType = {
navigation: StackNavigationProp,
theme: CustomTheme,
theme: CustomThemeType,
};
type StateType = {

View file

@ -19,7 +19,7 @@ import ProximoListItem from '../../../components/Lists/Proximo/ProximoListItem';
import MaterialHeaderButtons, {
Item,
} from '../../../components/Overrides/CustomHeaderButton';
import type {CustomTheme} from '../../../managers/ThemeManager';
import type {CustomThemeType} from '../../../managers/ThemeManager';
import CollapsibleFlatList from '../../../components/Collapsible/CollapsibleFlatList';
import type {ProximoArticleType} from './ProximoMainScreen';
@ -56,7 +56,7 @@ type PropsType = {
shouldFocusSearchBar: boolean,
},
},
theme: CustomTheme,
theme: CustomThemeType,
};
type StateType = {

View file

@ -8,7 +8,7 @@ import WebSectionList from '../../../components/Screens/WebSectionList';
import MaterialHeaderButtons, {
Item,
} from '../../../components/Overrides/CustomHeaderButton';
import type {CustomTheme} from '../../../managers/ThemeManager';
import type {CustomThemeType} from '../../../managers/ThemeManager';
import type {SectionListDataType} from '../../../components/Screens/WebSectionList';
const DATA_URL = 'https://etud.insa-toulouse.fr/~proximo/data/stock-v2.json';
@ -43,7 +43,7 @@ export type ProximoDataType = {
type PropsType = {
navigation: StackNavigationProp,
theme: CustomTheme,
theme: CustomThemeType,
};
/**

View file

@ -2,167 +2,188 @@
import * as React from 'react';
import {View} from 'react-native';
import DateManager from "../../managers/DateManager";
import WebSectionList from "../../components/Screens/WebSectionList";
import {Card, Text, withTheme} from 'react-native-paper';
import AprilFoolsManager from "../../managers/AprilFoolsManager";
import {StackNavigationProp} from "@react-navigation/stack";
import type {CustomTheme} from "../../managers/ThemeManager";
import {StackNavigationProp} from '@react-navigation/stack';
import i18n from 'i18n-js';
import DateManager from '../../managers/DateManager';
import WebSectionList from '../../components/Screens/WebSectionList';
import type {CustomThemeType} from '../../managers/ThemeManager';
import type {SectionListDataType} from '../../components/Screens/WebSectionList';
const DATA_URL = "https://etud.insa-toulouse.fr/~amicale_app/menu/menu_data.json";
const DATA_URL =
'https://etud.insa-toulouse.fr/~amicale_app/menu/menu_data.json';
type Props = {
navigation: StackNavigationProp,
theme: CustomTheme,
}
type PropsType = {
navigation: StackNavigationProp,
theme: CustomThemeType,
};
export type RuFoodCategoryType = {
name: string,
dishes: Array<{name: string}>,
};
type RuMealType = {
name: string,
foodcategory: Array<RuFoodCategoryType>,
};
type RawRuMenuType = {
restaurant_id: number,
id: number,
date: string,
meal: Array<RuMealType>,
};
/**
* Class defining the app's menu screen.
*/
class SelfMenuScreen extends React.Component<Props> {
class SelfMenuScreen extends React.Component<PropsType> {
/**
* Formats the given string to make sure it starts with a capital letter
*
* @param name The string to format
* @return {string} The formatted string
*/
static formatName(name: string): string {
return name.charAt(0) + name.substr(1).toLowerCase();
}
/**
* Extract a key for the given item
*
* @param item The item to extract the key from
* @return {*} The extracted key
*/
getKeyExtractor(item: Object) {
return item !== undefined ? item['name'] : undefined;
/**
* Creates the dataset to be used in the FlatList
*
* @param fetchedData
* @return {[]}
*/
createDataset = (
fetchedData: Array<RawRuMenuType>,
): SectionListDataType<RuFoodCategoryType> => {
let result = [];
if (fetchedData == null || fetchedData.length === 0) {
result = [
{
title: i18n.t('general.notAvailable'),
data: [],
keyExtractor: this.getKeyExtractor,
},
];
} else {
fetchedData.forEach((item: RawRuMenuType) => {
result.push({
title: DateManager.getInstance().getTranslatedDate(item.date),
data: item.meal[0].foodcategory,
keyExtractor: this.getKeyExtractor,
});
});
}
return result;
};
/**
* Creates the dataset to be used in the FlatList
*
* @param fetchedData
* @return {[]}
*/
createDataset = (fetchedData: Object) => {
let result = [];
if (fetchedData == null || Object.keys(fetchedData).length === 0) {
result = [
{
title: i18n.t("general.notAvailable"),
data: [],
keyExtractor: this.getKeyExtractor
}
];
} else {
if (AprilFoolsManager.getInstance().isAprilFoolsEnabled() && fetchedData.length > 0)
fetchedData[0].meal[0].foodcategory = AprilFoolsManager.getFakeMenuItem(fetchedData[0].meal[0].foodcategory);
// fetched data is an array here
for (let i = 0; i < fetchedData.length; i++) {
result.push(
{
title: DateManager.getInstance().getTranslatedDate(fetchedData[i].date),
data: fetchedData[i].meal[0].foodcategory,
keyExtractor: this.getKeyExtractor,
}
);
}
}
return result
};
/**
* Gets the render section header
*
* @param section The section to render the header from
* @return {*}
*/
getRenderSectionHeader = ({
section,
}: {
section: {title: string},
}): React.Node => {
return (
<Card
style={{
width: '95%',
marginLeft: 'auto',
marginRight: 'auto',
marginTop: 5,
marginBottom: 5,
elevation: 4,
}}>
<Card.Title
title={section.title}
titleStyle={{
textAlign: 'center',
}}
subtitleStyle={{
textAlign: 'center',
}}
style={{
paddingLeft: 0,
}}
/>
</Card>
);
};
/**
* Gets the render section header
*
* @param section The section to render the header from
* @return {*}
*/
getRenderSectionHeader = ({section}: Object) => {
return (
<Card style={{
width: '95%',
marginLeft: 'auto',
marginRight: 'auto',
marginTop: 5,
marginBottom: 5,
elevation: 4,
}}>
<Card.Title
title={section.title}
titleStyle={{
textAlign: 'center'
}}
subtitleStyle={{
textAlign: 'center'
}}
style={{
paddingLeft: 0,
}}
/>
</Card>
);
};
/**
* Gets a FlatList render item
*
* @param item The item to render
* @return {*}
*/
getRenderItem = ({item}: {item: RuFoodCategoryType}): React.Node => {
const {theme} = this.props;
return (
<Card
style={{
flex: 0,
marginHorizontal: 10,
marginVertical: 5,
}}>
<Card.Title style={{marginTop: 5}} title={item.name} />
<View
style={{
width: '80%',
marginLeft: 'auto',
marginRight: 'auto',
borderBottomWidth: 1,
borderBottomColor: theme.colors.primary,
marginTop: 5,
marginBottom: 5,
}}
/>
<Card.Content>
{item.dishes.map((object: {name: string}): React.Node =>
object.name !== '' ? (
<Text
style={{
marginTop: 5,
marginBottom: 5,
textAlign: 'center',
}}>
{SelfMenuScreen.formatName(object.name)}
</Text>
) : null,
)}
</Card.Content>
</Card>
);
};
/**
* Gets a FlatList render item
*
* @param item The item to render
* @return {*}
*/
getRenderItem = ({item}: Object) => {
return (
<Card style={{
flex: 0,
marginHorizontal: 10,
marginVertical: 5,
}}>
<Card.Title
style={{marginTop: 5}}
title={item.name}
/>
<View style={{
width: '80%',
marginLeft: 'auto',
marginRight: 'auto',
borderBottomWidth: 1,
borderBottomColor: this.props.theme.colors.primary,
marginTop: 5,
marginBottom: 5,
}}/>
<Card.Content>
{item.dishes.map((object) =>
<View>
{object.name !== "" ?
<Text style={{
marginTop: 5,
marginBottom: 5,
textAlign: 'center'
}}>{this.formatName(object.name)}</Text>
: <View/>}
</View>)}
</Card.Content>
</Card>
);
};
/**
* Extract a key for the given item
*
* @param item The item to extract the key from
* @return {*} The extracted key
*/
getKeyExtractor = (item: RuFoodCategoryType): string => item.name;
/**
* Formats the given string to make sure it starts with a capital letter
*
* @param name The string to format
* @return {string} The formatted string
*/
formatName(name: String) {
return name.charAt(0) + name.substr(1).toLowerCase();
}
render() {
const nav = this.props.navigation;
return (
<WebSectionList
createDataset={this.createDataset}
navigation={nav}
autoRefreshTime={0}
refreshOnFocus={false}
fetchUrl={DATA_URL}
renderItem={this.getRenderItem}
renderSectionHeader={this.getRenderSectionHeader}
stickyHeader={true}/>
);
}
render(): React.Node {
const {navigation} = this.props;
return (
<WebSectionList
createDataset={this.createDataset}
navigation={navigation}
autoRefreshTime={0}
refreshOnFocus={false}
fetchUrl={DATA_URL}
renderItem={this.getRenderItem}
renderSectionHeader={this.getRenderSectionHeader}
stickyHeader
/>
);
}
}
export default withTheme(SelfMenuScreen);

View file

@ -13,7 +13,7 @@ import {
import i18n from 'i18n-js';
import {StackNavigationProp} from '@react-navigation/stack';
import CardList from '../../components/Lists/CardList/CardList';
import type {CustomTheme} from '../../managers/ThemeManager';
import type {CustomThemeType} from '../../managers/ThemeManager';
import MaterialHeaderButtons, {
Item,
} from '../../components/Overrides/CustomHeaderButton';
@ -28,7 +28,7 @@ import type {ServiceCategoryType} from '../../managers/ServicesManager';
type PropsType = {
navigation: StackNavigationProp,
theme: CustomTheme,
theme: CustomThemeType,
};
class ServicesScreen extends React.Component<PropsType> {

View file

@ -1,109 +1,116 @@
// @flow
import * as React from 'react';
import {StackNavigationProp} from "@react-navigation/stack";
import WebViewScreen from "../../components/Screens/WebViewScreen";
import AvailableWebsites from "../../constants/AvailableWebsites";
import BasicLoadingScreen from "../../components/Screens/BasicLoadingScreen";
import {StackNavigationProp} from '@react-navigation/stack';
import WebViewScreen from '../../components/Screens/WebViewScreen';
import AvailableWebsites from '../../constants/AvailableWebsites';
import BasicLoadingScreen from '../../components/Screens/BasicLoadingScreen';
type Props = {
navigation: StackNavigationProp,
route: { params: { host: string, path: string | null, title: string } },
}
type PropsType = {
navigation: StackNavigationProp,
route: {params: {host: string, path: string | null, title: string}},
};
class WebsiteScreen extends React.Component<Props> {
const ENABLE_MOBILE_STRING = `<meta name="viewport" content="width=device-width, initial-scale=1.0">`;
fullUrl: string;
injectedJS: { [key: string]: string };
customPaddingFunctions: {[key: string]: (padding: string) => string}
const AVAILABLE_ROOMS_STYLE = `<style>body,body>.container2{padding-top:0;width:100%}b,body>.container2>h1,body>.container2>h3,br,header{display:none}.table-bordered td,.table-bordered th{border:none;border-right:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.table{padding:0;margin:0;width:200%;max-width:200%;display:block}tbody{display:block;width:100%}thead{display:block;width:100%}.table tbody tr,tbody tr[bgcolor],thead tr{width:100%;display:inline-flex}.table tbody td,.table thead td[colspan]{padding:0;flex:1;height:50px;margin:0}.table tbody td[bgcolor=white],.table thead td,.table>tbody>tr>td:nth-child(1){flex:0 0 150px;height:50px}</style>`;
const BIB_STYLE = `<style>.hero-unit,.navbar,footer{display:none}.hero-unit-form,.hero-unit2,.hero-unit3{background-color:#fff;box-shadow:none;padding:0;margin:0}.hero-unit-form h4{font-size:2rem;line-height:2rem}.btn{font-size:1.5rem;line-height:1.5rem;padding:20px}.btn-danger{background-image:none;background-color:#be1522}.table{font-size:.8rem}.table td{padding:0;height:18.2333px;border:none;border-bottom:1px solid #c1c1c1}.table td[style="max-width:55px;"]{max-width:110px!important}.table-bordered{min-width:50px}th{height:50px}.table-bordered{border-collapse:collapse}</style>`;
host: string;
const BIB_BACK_BUTTON =
`<div style='width: 100%; display: flex'>` +
`<a style='margin: auto' href='${AvailableWebsites.websites.BIB}'>` +
`<button id='customBackButton' class='btn btn-primary'>Retour</button>` +
`</a>` +
`</div>`;
constructor(props: Props) {
super(props);
this.props.navigation.addListener('focus', this.onScreenFocus);
this.injectedJS = {};
this.customPaddingFunctions = {};
this.injectedJS[AvailableWebsites.websites.AVAILABLE_ROOMS] =
'document.querySelector(\'head\').innerHTML += \'<meta name="viewport" content="width=device-width, initial-scale=1.0">\';' +
'document.querySelector(\'head\').innerHTML += \'<style>body,body>.container2{padding-top:0;width:100%}b,body>.container2>h1,body>.container2>h3,br,header{display:none}.table-bordered td,.table-bordered th{border:none;border-right:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.table{padding:0;margin:0;width:200%;max-width:200%;display:block}tbody{display:block;width:100%}thead{display:block;width:100%}.table tbody tr,tbody tr[bgcolor],thead tr{width:100%;display:inline-flex}.table tbody td,.table thead td[colspan]{padding:0;flex:1;height:50px;margin:0}.table tbody td[bgcolor=white],.table thead td,.table>tbody>tr>td:nth-child(1){flex:0 0 150px;height:50px}</style>\'; true;';
class WebsiteScreen extends React.Component<PropsType> {
fullUrl: string;
this.injectedJS[AvailableWebsites.websites.BIB] =
'document.querySelector(\'head\').innerHTML += \'<meta name="viewport" content="width=device-width, initial-scale=1.0">\';' +
'document.querySelector(\'head\').innerHTML += \'<style>.hero-unit,.navbar,footer{display:none}.hero-unit-form,.hero-unit2,.hero-unit3{background-color:#fff;box-shadow:none;padding:0;margin:0}.hero-unit-form h4{font-size:2rem;line-height:2rem}.btn{font-size:1.5rem;line-height:1.5rem;padding:20px}.btn-danger{background-image:none;background-color:#be1522}.table{font-size:.8rem}.table td{padding:0;height:18.2333px;border:none;border-bottom:1px solid #c1c1c1}.table td[style="max-width:55px;"]{max-width:110px!important}.table-bordered{min-width:50px}th{height:50px}.table-bordered{border-collapse:collapse}</style>\';' +
'if ($(".hero-unit-form").length > 0 && $("#customBackButton").length === 0)' +
'$(".hero-unit-form").append("' +
'<div style=\'width: 100%; display: flex\'>' +
'<a style=\'margin: auto\' href=\'' + AvailableWebsites.websites.BIB + '\'>' +
'<button id=\'customBackButton\' class=\'btn btn-primary\'>Retour</button>' +
'</a>' +
'</div>");true;';
injectedJS: {[key: string]: string};
this.customPaddingFunctions[AvailableWebsites.websites.BLUEMIND] = (padding: string) => {
return (
"$('head').append('<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">');" +
"$('.minwidth').css('top', " + padding + ");" +
"$('#mailview-bottom').css('min-height', 500);"
);
};
this.customPaddingFunctions[AvailableWebsites.websites.WIKETUD] = (padding: string) => {
return (
"$('#p-logo-text').css('top', 10 + " + padding + ");" +
"$('#site-navigation h2').css('top', 10 + " + padding + ");" +
"$('#site-tools h2').css('top', 10 + " + padding + ");" +
"$('#user-tools h2').css('top', 10 + " + padding + ");"
);
}
}
customPaddingFunctions: {[key: string]: (padding: string) => string};
onScreenFocus = () => {
this.handleNavigationParams();
host: string;
constructor(props: PropsType) {
super(props);
props.navigation.addListener('focus', this.onScreenFocus);
this.injectedJS = {};
this.customPaddingFunctions = {};
this.injectedJS[AvailableWebsites.websites.AVAILABLE_ROOMS] =
`document.querySelector('head').innerHTML += '${ENABLE_MOBILE_STRING}';` +
`document.querySelector('head').innerHTML += '${AVAILABLE_ROOMS_STYLE}'; true;`;
this.injectedJS[AvailableWebsites.websites.BIB] =
`document.querySelector('head').innerHTML += '${ENABLE_MOBILE_STRING}';` +
`document.querySelector('head').innerHTML += '${BIB_STYLE}';` +
`if ($(".hero-unit-form").length > 0 && $("#customBackButton").length === 0)` +
`$(".hero-unit-form").append("${BIB_BACK_BUTTON}");true;`;
this.customPaddingFunctions[AvailableWebsites.websites.BLUEMIND] = (
padding: string,
): string => {
return (
`$('head').append('${ENABLE_MOBILE_STRING}');` +
`$('.minwidth').css('top', ${padding}` +
`$('#mailview-bottom').css('min-height', 500);`
);
};
this.customPaddingFunctions[AvailableWebsites.websites.WIKETUD] = (
padding: string,
): string => {
return (
`$('#p-logo-text').css('top', 10 + ${padding});` +
`$('#site-navigation h2').css('top', 10 + ${padding});` +
`$('#site-tools h2').css('top', 10 + ${padding});` +
`$('#user-tools h2').css('top', 10 + ${padding});`
);
};
}
/**
*
*/
handleNavigationParams() {
if (this.props.route.params != null) {
this.host = this.props.route.params.host;
let path = this.props.route.params.path;
const title = this.props.route.params.title;
if (this.host != null && path != null) {
path = path.replace(this.host, "");
this.fullUrl = this.host + path;
}else
this.fullUrl = this.host;
onScreenFocus = () => {
this.handleNavigationParams();
};
if (title != null)
this.props.navigation.setOptions({title: title});
}
/**
*
*/
handleNavigationParams() {
const {route, navigation} = this.props;
if (route.params != null) {
this.host = route.params.host;
let {path} = route.params;
const {title} = route.params;
if (this.host != null && path != null) {
path = path.replace(this.host, '');
this.fullUrl = this.host + path;
} else this.fullUrl = this.host;
if (title != null) navigation.setOptions({title});
}
}
render() {
let injectedJavascript = "";
let customPadding = null;
if (this.host != null && this.injectedJS[this.host] != null)
injectedJavascript = this.injectedJS[this.host];
if (this.host != null && this.customPaddingFunctions[this.host] != null)
customPadding = this.customPaddingFunctions[this.host];
if (this.fullUrl != null) {
return (
<WebViewScreen
{...this.props}
url={this.fullUrl}
customJS={injectedJavascript}
customPaddingFunction={customPadding}
/>
);
} else {
return (
<BasicLoadingScreen/>
);
}
render(): React.Node {
const {navigation} = this.props;
let injectedJavascript = '';
let customPadding = null;
if (this.host != null && this.injectedJS[this.host] != null)
injectedJavascript = this.injectedJS[this.host];
if (this.host != null && this.customPaddingFunctions[this.host] != null)
customPadding = this.customPaddingFunctions[this.host];
if (this.fullUrl != null) {
return (
<WebViewScreen
navigation={navigation}
url={this.fullUrl}
customJS={injectedJavascript}
customPaddingFunction={customPadding}
/>
);
}
return <BasicLoadingScreen />;
}
}
export default WebsiteScreen;