Improve Services components to match linter

This commit is contained in:
Arnaud Vergnet 2020-08-03 21:06:39 +02:00
parent 6b12b4cde2
commit 33d98b024b
9 changed files with 718 additions and 682 deletions

View file

@ -1,55 +1,53 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {Animated, Dimensions} from "react-native"; import {Animated, Dimensions} from 'react-native';
import ImageListItem from "./ImageListItem"; import type {ViewStyle} from 'react-native/Libraries/StyleSheet/StyleSheet';
import CardListItem from "./CardListItem"; import ImageListItem from './ImageListItem';
import type {ViewStyle} from "react-native/Libraries/StyleSheet/StyleSheet"; import CardListItem from './CardListItem';
import type {ServiceItemType} from '../../../managers/ServicesManager';
type Props = { type PropsType = {
dataset: Array<cardItem>, dataset: Array<ServiceItemType>,
isHorizontal: boolean, isHorizontal?: boolean,
contentContainerStyle?: ViewStyle, contentContainerStyle?: ViewStyle | null,
}
export type cardItem = {
key: string,
title: string,
subtitle: string,
image: string | number,
onPress: () => void,
}; };
export type cardList = Array<cardItem>; export default class CardList extends React.Component<PropsType> {
export default class CardList extends React.Component<Props> {
static defaultProps = { static defaultProps = {
isHorizontal: false, isHorizontal: false,
} contentContainerStyle: null,
windowWidth: number;
horizontalItemSize: number;
constructor(props: Props) {
super(props);
this.windowWidth = Dimensions.get('window').width;
this.horizontalItemSize = this.windowWidth/4; // So that we can fit 3 items and a part of the 4th => user knows he can scroll
}
renderItem = ({item}: { item: cardItem }) => {
if (this.props.isHorizontal)
return <ImageListItem item={item} key={item.title} width={this.horizontalItemSize}/>;
else
return <CardListItem item={item} key={item.title}/>;
}; };
keyExtractor = (item: cardItem) => item.key; windowWidth: number;
render() { horizontalItemSize: number;
constructor(props: PropsType) {
super(props);
this.windowWidth = Dimensions.get('window').width;
this.horizontalItemSize = this.windowWidth / 4; // So that we can fit 3 items and a part of the 4th => user knows he can scroll
}
getRenderItem = ({item}: {item: ServiceItemType}): React.Node => {
const {props} = this;
if (props.isHorizontal)
return (
<ImageListItem
item={item}
key={item.title}
width={this.horizontalItemSize}
/>
);
return <CardListItem item={item} key={item.title} />;
};
keyExtractor = (item: ServiceItemType): string => item.key;
render(): React.Node {
const {props} = this;
let containerStyle = {}; let containerStyle = {};
if (this.props.isHorizontal) { if (props.isHorizontal) {
containerStyle = { containerStyle = {
height: this.horizontalItemSize + 50, height: this.horizontalItemSize + 50,
justifyContent: 'space-around', justifyContent: 'space-around',
@ -57,15 +55,18 @@ export default class CardList extends React.Component<Props> {
} }
return ( return (
<Animated.FlatList <Animated.FlatList
{...this.props} data={props.dataset}
data={this.props.dataset} renderItem={this.getRenderItem}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor} keyExtractor={this.keyExtractor}
numColumns={this.props.isHorizontal ? undefined : 2} numColumns={props.isHorizontal ? undefined : 2}
horizontal={this.props.isHorizontal} horizontal={props.isHorizontal}
contentContainerStyle={this.props.isHorizontal ? containerStyle : this.props.contentContainerStyle} contentContainerStyle={
pagingEnabled={this.props.isHorizontal} props.isHorizontal ? containerStyle : props.contentContainerStyle
snapToInterval={this.props.isHorizontal ? (this.horizontalItemSize+5)*3 : null} }
pagingEnabled={props.isHorizontal}
snapToInterval={
props.isHorizontal ? (this.horizontalItemSize + 5) * 3 : null
}
/> />
); );
} }

View file

@ -2,25 +2,23 @@
import * as React from 'react'; import * as React from 'react';
import {Caption, Card, Paragraph, TouchableRipple} from 'react-native-paper'; import {Caption, Card, Paragraph, TouchableRipple} from 'react-native-paper';
import {View} from "react-native"; import {View} from 'react-native';
import type {cardItem} from "./CardList"; import type {ServiceItemType} from '../../../managers/ServicesManager';
type Props = { type PropsType = {
item: cardItem, item: ServiceItemType,
} };
export default class CardListItem extends React.Component<Props> { export default class CardListItem extends React.Component<PropsType> {
shouldComponentUpdate(): boolean {
shouldComponentUpdate() {
return false; return false;
} }
render() { render(): React.Node {
const props = this.props; const {props} = this;
const item = props.item; const {item} = props;
const source = typeof item.image === "number" const source =
? item.image typeof item.image === 'number' ? item.image : {uri: item.image};
: {uri: item.image};
return ( return (
<Card <Card
style={{ style={{
@ -28,23 +26,16 @@ export default class CardListItem extends React.Component<Props> {
margin: 5, margin: 5,
marginLeft: 'auto', marginLeft: 'auto',
marginRight: 'auto', marginRight: 'auto',
}} }}>
> <TouchableRipple style={{flex: 1}} onPress={item.onPress}>
<TouchableRipple
style={{flex: 1}}
onPress={item.onPress}>
<View> <View>
<Card.Cover <Card.Cover style={{height: 80}} source={source} />
style={{height: 80}}
source={source}
/>
<Card.Content> <Card.Content>
<Paragraph>{item.title}</Paragraph> <Paragraph>{item.title}</Paragraph>
<Caption>{item.subtitle}</Caption> <Caption>{item.subtitle}</Caption>
</Card.Content> </Card.Content>
</View> </View>
</TouchableRipple> </TouchableRipple>
</Card> </Card>
); );
} }

View file

@ -3,48 +3,47 @@
import * as React from 'react'; import * as React from 'react';
import {Text, TouchableRipple} from 'react-native-paper'; import {Text, TouchableRipple} from 'react-native-paper';
import {Image, View} from 'react-native'; import {Image, View} from 'react-native';
import type {cardItem} from "./CardList"; import type {ServiceItemType} from '../../../managers/ServicesManager';
type Props = { type PropsType = {
item: cardItem, item: ServiceItemType,
width: number, width: number,
} };
export default class ImageListItem extends React.Component<Props> { export default class ImageListItem extends React.Component<PropsType> {
shouldComponentUpdate(): boolean {
shouldComponentUpdate() {
return false; return false;
} }
render() { render(): React.Node {
const item = this.props.item; const {props} = this;
const source = typeof item.image === "number" const {item} = props;
? item.image const source =
: {uri: item.image}; typeof item.image === 'number' ? item.image : {uri: item.image};
return ( return (
<TouchableRipple <TouchableRipple
style={{ style={{
width: this.props.width, width: props.width,
height: this.props.width + 40, height: props.width + 40,
margin: 5, margin: 5,
}} }}
onPress={item.onPress} onPress={item.onPress}>
>
<View> <View>
<Image <Image
style={{ style={{
width: this.props.width - 20, width: props.width - 20,
height: this.props.width - 20, height: props.width - 20,
marginLeft: 'auto', marginLeft: 'auto',
marginRight: 'auto', marginRight: 'auto',
}} }}
source={source} source={source}
/> />
<Text style={{ <Text
style={{
marginTop: 5, marginTop: 5,
marginLeft: 'auto', marginLeft: 'auto',
marginRight: 'auto', marginRight: 'auto',
textAlign: 'center' textAlign: 'center',
}}> }}>
{item.title} {item.title}
</Text> </Text>

View file

@ -1,12 +1,12 @@
// @flow // @flow
import type {ServiceItem} from './ServicesManager'; import type {ServiceItemType} from './ServicesManager';
import ServicesManager from './ServicesManager'; import ServicesManager from './ServicesManager';
import {getSublistWithIds} from '../utils/Utils'; import {getSublistWithIds} from '../utils/Utils';
import AsyncStorageManager from './AsyncStorageManager'; import AsyncStorageManager from './AsyncStorageManager';
export default class DashboardManager extends ServicesManager { export default class DashboardManager extends ServicesManager {
getCurrentDashboard(): Array<ServiceItem | null> { getCurrentDashboard(): Array<ServiceItemType | null> {
const dashboardIdList = AsyncStorageManager.getObject( const dashboardIdList = AsyncStorageManager.getObject(
AsyncStorageManager.PREFERENCES.dashboardItems.key, AsyncStorageManager.PREFERENCES.dashboardItems.key,
); );

View file

@ -1,94 +1,107 @@
// @flow // @flow
import i18n from "i18n-js"; import i18n from 'i18n-js';
import AvailableWebsites from "../constants/AvailableWebsites"; import {StackNavigationProp} from '@react-navigation/stack';
import {StackNavigationProp} from "@react-navigation/stack"; import AvailableWebsites from '../constants/AvailableWebsites';
import ConnectionManager from "./ConnectionManager"; import ConnectionManager from './ConnectionManager';
import type {fullDashboard} from "../screens/Home/HomeScreen"; import type {FullDashboardType} from '../screens/Home/HomeScreen';
import getStrippedServicesList from '../utils/Services';
// AMICALE // AMICALE
const CLUBS_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Clubs.png"; const CLUBS_IMAGE =
const PROFILE_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/ProfilAmicaliste.png"; 'https://etud.insa-toulouse.fr/~amicale_app/images/Clubs.png';
const EQUIPMENT_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Materiel.png"; const PROFILE_IMAGE =
const VOTE_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Vote.png"; 'https://etud.insa-toulouse.fr/~amicale_app/images/ProfilAmicaliste.png';
const AMICALE_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/WebsiteAmicale.png"; const EQUIPMENT_IMAGE =
'https://etud.insa-toulouse.fr/~amicale_app/images/Materiel.png';
const VOTE_IMAGE = 'https://etud.insa-toulouse.fr/~amicale_app/images/Vote.png';
const AMICALE_IMAGE =
'https://etud.insa-toulouse.fr/~amicale_app/images/WebsiteAmicale.png';
// STUDENTS // STUDENTS
const PROXIMO_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Proximo.png" const PROXIMO_IMAGE =
const WIKETUD_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Wiketud.png"; 'https://etud.insa-toulouse.fr/~amicale_app/images/Proximo.png';
const EE_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/EEC.png"; const WIKETUD_IMAGE =
const TUTORINSA_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/TutorINSA.png"; 'https://etud.insa-toulouse.fr/~amicale_app/images/Wiketud.png';
const EE_IMAGE = 'https://etud.insa-toulouse.fr/~amicale_app/images/EEC.png';
const TUTORINSA_IMAGE =
'https://etud.insa-toulouse.fr/~amicale_app/images/TutorINSA.png';
// INSA // INSA
const BIB_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Bib.png"; const BIB_IMAGE = 'https://etud.insa-toulouse.fr/~amicale_app/images/Bib.png';
const RU_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/RU.png"; const RU_IMAGE = 'https://etud.insa-toulouse.fr/~amicale_app/images/RU.png';
const ROOM_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Salles.png"; const ROOM_IMAGE =
const EMAIL_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Bluemind.png"; 'https://etud.insa-toulouse.fr/~amicale_app/images/Salles.png';
const ENT_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/ENT.png"; const EMAIL_IMAGE =
const ACCOUNT_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Account.png"; 'https://etud.insa-toulouse.fr/~amicale_app/images/Bluemind.png';
const ENT_IMAGE = 'https://etud.insa-toulouse.fr/~amicale_app/images/ENT.png';
const ACCOUNT_IMAGE =
'https://etud.insa-toulouse.fr/~amicale_app/images/Account.png';
// SPECIAL // SPECIAL
const WASHER_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/ProxiwashLaveLinge.png"; const WASHER_IMAGE =
const DRYER_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/ProxiwashSecheLinge.png"; 'https://etud.insa-toulouse.fr/~amicale_app/images/ProxiwashLaveLinge.png';
const DRYER_IMAGE =
'https://etud.insa-toulouse.fr/~amicale_app/images/ProxiwashSecheLinge.png';
const AMICALE_LOGO = require("../../assets/amicale.png"); const AMICALE_LOGO = require('../../assets/amicale.png');
export const SERVICES_KEY = { export const SERVICES_KEY = {
CLUBS: "clubs", CLUBS: 'clubs',
PROFILE: "profile", PROFILE: 'profile',
EQUIPMENT: "equipment", EQUIPMENT: 'equipment',
AMICALE_WEBSITE: "amicale_website", AMICALE_WEBSITE: 'amicale_website',
VOTE: "vote", VOTE: 'vote',
PROXIMO: "proximo", PROXIMO: 'proximo',
WIKETUD: "wiketud", WIKETUD: 'wiketud',
ELUS_ETUDIANTS: "elus_etudiants", ELUS_ETUDIANTS: 'elus_etudiants',
TUTOR_INSA: "tutor_insa", TUTOR_INSA: 'tutor_insa',
RU: "ru", RU: 'ru',
AVAILABLE_ROOMS: "available_rooms", AVAILABLE_ROOMS: 'available_rooms',
BIB: "bib", BIB: 'bib',
EMAIL: "email", EMAIL: 'email',
ENT: "ent", ENT: 'ent',
INSA_ACCOUNT: "insa_account", INSA_ACCOUNT: 'insa_account',
WASHERS: "washers", WASHERS: 'washers',
DRYERS: "dryers", DRYERS: 'dryers',
} };
export const SERVICES_CATEGORIES_KEY = { export const SERVICES_CATEGORIES_KEY = {
AMICALE: "amicale", AMICALE: 'amicale',
STUDENTS: "students", STUDENTS: 'students',
INSA: "insa", INSA: 'insa',
SPECIAL: "special", SPECIAL: 'special',
} };
export type ServiceItemType = {
export type ServiceItem = {
key: string, key: string,
title: string, title: string,
subtitle: string, subtitle: string,
image: string, image: string,
onPress: () => void, onPress: () => void,
badgeFunction?: (dashboard: fullDashboard) => number, badgeFunction?: (dashboard: FullDashboardType) => number,
} };
export type ServiceCategory = { export type ServiceCategoryType = {
key: string, key: string,
title: string, title: string,
subtitle: string, subtitle: string,
image: string | number, image: string | number,
content: Array<ServiceItem> content: Array<ServiceItemType>,
} };
export default class ServicesManager { export default class ServicesManager {
navigation: StackNavigationProp; navigation: StackNavigationProp;
amicaleDataset: Array<ServiceItem>; amicaleDataset: Array<ServiceItemType>;
studentsDataset: Array<ServiceItem>;
insaDataset: Array<ServiceItem>;
specialDataset: Array<ServiceItem>;
categoriesDataset: Array<ServiceCategory>; studentsDataset: Array<ServiceItemType>;
insaDataset: Array<ServiceItemType>;
specialDataset: Array<ServiceItemType>;
categoriesDataset: Array<ServiceCategoryType>;
constructor(nav: StackNavigationProp) { constructor(nav: StackNavigationProp) {
this.navigation = nav; this.navigation = nav;
@ -98,30 +111,31 @@ export default class ServicesManager {
title: i18n.t('screens.clubs.title'), title: i18n.t('screens.clubs.title'),
subtitle: i18n.t('screens.services.descriptions.clubs'), subtitle: i18n.t('screens.services.descriptions.clubs'),
image: CLUBS_IMAGE, image: CLUBS_IMAGE,
onPress: () => this.onAmicaleServicePress("club-list"), onPress: (): void => this.onAmicaleServicePress('club-list'),
}, },
{ {
key: SERVICES_KEY.PROFILE, key: SERVICES_KEY.PROFILE,
title: i18n.t('screens.profile.title'), title: i18n.t('screens.profile.title'),
subtitle: i18n.t('screens.services.descriptions.profile'), subtitle: i18n.t('screens.services.descriptions.profile'),
image: PROFILE_IMAGE, image: PROFILE_IMAGE,
onPress: () => this.onAmicaleServicePress("profile"), onPress: (): void => this.onAmicaleServicePress('profile'),
}, },
{ {
key: SERVICES_KEY.EQUIPMENT, key: SERVICES_KEY.EQUIPMENT,
title: i18n.t('screens.equipment.title'), title: i18n.t('screens.equipment.title'),
subtitle: i18n.t('screens.services.descriptions.equipment'), subtitle: i18n.t('screens.services.descriptions.equipment'),
image: EQUIPMENT_IMAGE, image: EQUIPMENT_IMAGE,
onPress: () => this.onAmicaleServicePress("equipment-list"), onPress: (): void => this.onAmicaleServicePress('equipment-list'),
}, },
{ {
key: SERVICES_KEY.AMICALE_WEBSITE, key: SERVICES_KEY.AMICALE_WEBSITE,
title: i18n.t('screens.websites.amicale'), title: i18n.t('screens.websites.amicale'),
subtitle: i18n.t('screens.services.descriptions.amicaleWebsite'), subtitle: i18n.t('screens.services.descriptions.amicaleWebsite'),
image: AMICALE_IMAGE, image: AMICALE_IMAGE,
onPress: () => nav.navigate("website", { onPress: (): void =>
nav.navigate('website', {
host: AvailableWebsites.websites.AMICALE, host: AvailableWebsites.websites.AMICALE,
title: i18n.t('screens.websites.amicale') title: i18n.t('screens.websites.amicale'),
}), }),
}, },
{ {
@ -129,7 +143,7 @@ export default class ServicesManager {
title: i18n.t('screens.vote.title'), title: i18n.t('screens.vote.title'),
subtitle: i18n.t('screens.services.descriptions.vote'), subtitle: i18n.t('screens.services.descriptions.vote'),
image: VOTE_IMAGE, image: VOTE_IMAGE,
onPress: () => this.onAmicaleServicePress("vote"), onPress: (): void => this.onAmicaleServicePress('vote'),
}, },
]; ];
this.studentsDataset = [ this.studentsDataset = [
@ -138,24 +152,30 @@ export default class ServicesManager {
title: i18n.t('screens.proximo.title'), title: i18n.t('screens.proximo.title'),
subtitle: i18n.t('screens.services.descriptions.proximo'), subtitle: i18n.t('screens.services.descriptions.proximo'),
image: PROXIMO_IMAGE, image: PROXIMO_IMAGE,
onPress: () => nav.navigate("proximo"), onPress: (): void => nav.navigate('proximo'),
badgeFunction: (dashboard: fullDashboard) => dashboard.proximo_articles badgeFunction: (dashboard: FullDashboardType): number =>
dashboard.proximo_articles,
}, },
{ {
key: SERVICES_KEY.WIKETUD, key: SERVICES_KEY.WIKETUD,
title: "Wiketud", title: 'Wiketud',
subtitle: i18n.t('screens.services.descriptions.wiketud'), subtitle: i18n.t('screens.services.descriptions.wiketud'),
image: WIKETUD_IMAGE, image: WIKETUD_IMAGE,
onPress: () => nav.navigate("website", {host: AvailableWebsites.websites.WIKETUD, title: "Wiketud"}), onPress: (): void =>
nav.navigate('website', {
host: AvailableWebsites.websites.WIKETUD,
title: 'Wiketud',
}),
}, },
{ {
key: SERVICES_KEY.ELUS_ETUDIANTS, key: SERVICES_KEY.ELUS_ETUDIANTS,
title: "Élus Étudiants", title: 'Élus Étudiants',
subtitle: i18n.t('screens.services.descriptions.elusEtudiants'), subtitle: i18n.t('screens.services.descriptions.elusEtudiants'),
image: EE_IMAGE, image: EE_IMAGE,
onPress: () => nav.navigate("website", { onPress: (): void =>
nav.navigate('website', {
host: AvailableWebsites.websites.ELUS_ETUDIANTS, host: AvailableWebsites.websites.ELUS_ETUDIANTS,
title: "Élus Étudiants" title: 'Élus Étudiants',
}), }),
}, },
{ {
@ -163,11 +183,13 @@ export default class ServicesManager {
title: "Tutor'INSA", title: "Tutor'INSA",
subtitle: i18n.t('screens.services.descriptions.tutorInsa'), subtitle: i18n.t('screens.services.descriptions.tutorInsa'),
image: TUTORINSA_IMAGE, image: TUTORINSA_IMAGE,
onPress: () => nav.navigate("website", { onPress: (): void =>
nav.navigate('website', {
host: AvailableWebsites.websites.TUTOR_INSA, host: AvailableWebsites.websites.TUTOR_INSA,
title: "Tutor'INSA" title: "Tutor'INSA",
}), }),
badgeFunction: (dashboard: fullDashboard) => dashboard.available_tutorials badgeFunction: (dashboard: FullDashboardType): number =>
dashboard.available_tutorials,
}, },
]; ];
this.insaDataset = [ this.insaDataset = [
@ -176,17 +198,19 @@ export default class ServicesManager {
title: i18n.t('screens.menu.title'), title: i18n.t('screens.menu.title'),
subtitle: i18n.t('screens.services.descriptions.self'), subtitle: i18n.t('screens.services.descriptions.self'),
image: RU_IMAGE, image: RU_IMAGE,
onPress: () => nav.navigate("self-menu"), onPress: (): void => nav.navigate('self-menu'),
badgeFunction: (dashboard: fullDashboard) => dashboard.today_menu.length badgeFunction: (dashboard: FullDashboardType): number =>
dashboard.today_menu.length,
}, },
{ {
key: SERVICES_KEY.AVAILABLE_ROOMS, key: SERVICES_KEY.AVAILABLE_ROOMS,
title: i18n.t('screens.websites.rooms'), title: i18n.t('screens.websites.rooms'),
subtitle: i18n.t('screens.services.descriptions.availableRooms'), subtitle: i18n.t('screens.services.descriptions.availableRooms'),
image: ROOM_IMAGE, image: ROOM_IMAGE,
onPress: () => nav.navigate("website", { onPress: (): void =>
nav.navigate('website', {
host: AvailableWebsites.websites.AVAILABLE_ROOMS, host: AvailableWebsites.websites.AVAILABLE_ROOMS,
title: i18n.t('screens.websites.rooms') title: i18n.t('screens.websites.rooms'),
}), }),
}, },
{ {
@ -194,9 +218,10 @@ export default class ServicesManager {
title: i18n.t('screens.websites.bib'), title: i18n.t('screens.websites.bib'),
subtitle: i18n.t('screens.services.descriptions.bib'), subtitle: i18n.t('screens.services.descriptions.bib'),
image: BIB_IMAGE, image: BIB_IMAGE,
onPress: () => nav.navigate("website", { onPress: (): void =>
nav.navigate('website', {
host: AvailableWebsites.websites.BIB, host: AvailableWebsites.websites.BIB,
title: i18n.t('screens.websites.bib') title: i18n.t('screens.websites.bib'),
}), }),
}, },
{ {
@ -204,9 +229,10 @@ export default class ServicesManager {
title: i18n.t('screens.websites.mails'), title: i18n.t('screens.websites.mails'),
subtitle: i18n.t('screens.services.descriptions.mails'), subtitle: i18n.t('screens.services.descriptions.mails'),
image: EMAIL_IMAGE, image: EMAIL_IMAGE,
onPress: () => nav.navigate("website", { onPress: (): void =>
nav.navigate('website', {
host: AvailableWebsites.websites.BLUEMIND, host: AvailableWebsites.websites.BLUEMIND,
title: i18n.t('screens.websites.mails') title: i18n.t('screens.websites.mails'),
}), }),
}, },
{ {
@ -214,9 +240,10 @@ export default class ServicesManager {
title: i18n.t('screens.websites.ent'), title: i18n.t('screens.websites.ent'),
subtitle: i18n.t('screens.services.descriptions.ent'), subtitle: i18n.t('screens.services.descriptions.ent'),
image: ENT_IMAGE, image: ENT_IMAGE,
onPress: () => nav.navigate("website", { onPress: (): void =>
nav.navigate('website', {
host: AvailableWebsites.websites.ENT, host: AvailableWebsites.websites.ENT,
title: i18n.t('screens.websites.ent') title: i18n.t('screens.websites.ent'),
}), }),
}, },
{ {
@ -224,9 +251,10 @@ export default class ServicesManager {
title: i18n.t('screens.insaAccount.title'), title: i18n.t('screens.insaAccount.title'),
subtitle: i18n.t('screens.services.descriptions.insaAccount'), subtitle: i18n.t('screens.services.descriptions.insaAccount'),
image: ACCOUNT_IMAGE, image: ACCOUNT_IMAGE,
onPress: () => nav.navigate("website", { onPress: (): void =>
nav.navigate('website', {
host: AvailableWebsites.websites.INSA_ACCOUNT, host: AvailableWebsites.websites.INSA_ACCOUNT,
title: i18n.t('screens.insaAccount.title') title: i18n.t('screens.insaAccount.title'),
}), }),
}, },
]; ];
@ -236,46 +264,48 @@ export default class ServicesManager {
title: i18n.t('screens.proxiwash.washers'), title: i18n.t('screens.proxiwash.washers'),
subtitle: i18n.t('screens.services.descriptions.washers'), subtitle: i18n.t('screens.services.descriptions.washers'),
image: WASHER_IMAGE, image: WASHER_IMAGE,
onPress: () => nav.navigate("proxiwash"), onPress: (): void => nav.navigate('proxiwash'),
badgeFunction: (dashboard: fullDashboard) => dashboard.available_washers badgeFunction: (dashboard: FullDashboardType): number =>
dashboard.available_washers,
}, },
{ {
key: SERVICES_KEY.DRYERS, key: SERVICES_KEY.DRYERS,
title: i18n.t('screens.proxiwash.dryers'), title: i18n.t('screens.proxiwash.dryers'),
subtitle: i18n.t('screens.services.descriptions.washers'), subtitle: i18n.t('screens.services.descriptions.washers'),
image: DRYER_IMAGE, image: DRYER_IMAGE,
onPress: () => nav.navigate("proxiwash"), onPress: (): void => nav.navigate('proxiwash'),
badgeFunction: (dashboard: fullDashboard) => dashboard.available_dryers badgeFunction: (dashboard: FullDashboardType): number =>
} dashboard.available_dryers,
},
]; ];
this.categoriesDataset = [ this.categoriesDataset = [
{ {
key: SERVICES_CATEGORIES_KEY.AMICALE, key: SERVICES_CATEGORIES_KEY.AMICALE,
title: i18n.t("screens.services.categories.amicale"), title: i18n.t('screens.services.categories.amicale'),
subtitle: i18n.t("screens.services.more"), subtitle: i18n.t('screens.services.more'),
image: AMICALE_LOGO, image: AMICALE_LOGO,
content: this.amicaleDataset content: this.amicaleDataset,
}, },
{ {
key: SERVICES_CATEGORIES_KEY.STUDENTS, key: SERVICES_CATEGORIES_KEY.STUDENTS,
title: i18n.t("screens.services.categories.students"), title: i18n.t('screens.services.categories.students'),
subtitle: i18n.t("screens.services.more"), subtitle: i18n.t('screens.services.more'),
image: 'account-group', image: 'account-group',
content: this.studentsDataset content: this.studentsDataset,
}, },
{ {
key: SERVICES_CATEGORIES_KEY.INSA, key: SERVICES_CATEGORIES_KEY.INSA,
title: i18n.t("screens.services.categories.insa"), title: i18n.t('screens.services.categories.insa'),
subtitle: i18n.t("screens.services.more"), subtitle: i18n.t('screens.services.more'),
image: 'school', image: 'school',
content: this.insaDataset content: this.insaDataset,
}, },
{ {
key: SERVICES_CATEGORIES_KEY.SPECIAL, key: SERVICES_CATEGORIES_KEY.SPECIAL,
title: i18n.t("screens.services.categories.special"), title: i18n.t('screens.services.categories.special'),
subtitle: i18n.t("screens.services.categories.special"), subtitle: i18n.t('screens.services.categories.special'),
image: 'star', image: 'star',
content: this.specialDataset content: this.specialDataset,
}, },
]; ];
} }
@ -289,37 +319,18 @@ export default class ServicesManager {
onAmicaleServicePress(route: string) { onAmicaleServicePress(route: string) {
if (ConnectionManager.getInstance().isLoggedIn()) if (ConnectionManager.getInstance().isLoggedIn())
this.navigation.navigate(route); this.navigation.navigate(route);
else else this.navigation.navigate('login', {nextScreen: route});
this.navigation.navigate("login", {nextScreen: route});
}
/**
* Gets the given services list without items of the given ids
*
* @param idList The ids of items to remove
* @param sourceList The item list to use as source
* @returns {[]}
*/
getStrippedList(idList: Array<string>, sourceList: Array<{key: string, [key: string]: any}>) {
let newArray = [];
for (let i = 0; i < sourceList.length; i++) {
const item = sourceList[i];
if (!(idList.includes(item.key)))
newArray.push(item);
}
return newArray;
} }
/** /**
* Gets the list of amicale's services * Gets the list of amicale's services
* *
* @param excludedItems Ids of items to exclude from the returned list * @param excludedItems Ids of items to exclude from the returned list
* @returns {Array<ServiceItem>} * @returns {Array<ServiceItemType>}
*/ */
getAmicaleServices(excludedItems?: Array<string>) { getAmicaleServices(excludedItems?: Array<string>): Array<ServiceItemType> {
if (excludedItems != null) if (excludedItems != null)
return this.getStrippedList(excludedItems, this.amicaleDataset) return getStrippedServicesList(excludedItems, this.amicaleDataset);
else
return this.amicaleDataset; return this.amicaleDataset;
} }
@ -327,12 +338,11 @@ export default class ServicesManager {
* Gets the list of students' services * Gets the list of students' services
* *
* @param excludedItems Ids of items to exclude from the returned list * @param excludedItems Ids of items to exclude from the returned list
* @returns {Array<ServiceItem>} * @returns {Array<ServiceItemType>}
*/ */
getStudentServices(excludedItems?: Array<string>) { getStudentServices(excludedItems?: Array<string>): Array<ServiceItemType> {
if (excludedItems != null) if (excludedItems != null)
return this.getStrippedList(excludedItems, this.studentsDataset) return getStrippedServicesList(excludedItems, this.studentsDataset);
else
return this.studentsDataset; return this.studentsDataset;
} }
@ -340,12 +350,11 @@ export default class ServicesManager {
* Gets the list of INSA's services * Gets the list of INSA's services
* *
* @param excludedItems Ids of items to exclude from the returned list * @param excludedItems Ids of items to exclude from the returned list
* @returns {Array<ServiceItem>} * @returns {Array<ServiceItemType>}
*/ */
getINSAServices(excludedItems?: Array<string>) { getINSAServices(excludedItems?: Array<string>): Array<ServiceItemType> {
if (excludedItems != null) if (excludedItems != null)
return this.getStrippedList(excludedItems, this.insaDataset) return getStrippedServicesList(excludedItems, this.insaDataset);
else
return this.insaDataset; return this.insaDataset;
} }
@ -353,12 +362,11 @@ export default class ServicesManager {
* Gets the list of special services * Gets the list of special services
* *
* @param excludedItems Ids of items to exclude from the returned list * @param excludedItems Ids of items to exclude from the returned list
* @returns {Array<ServiceItem>} * @returns {Array<ServiceItemType>}
*/ */
getSpecialServices(excludedItems?: Array<string>) { getSpecialServices(excludedItems?: Array<string>): Array<ServiceItemType> {
if (excludedItems != null) if (excludedItems != null)
return this.getStrippedList(excludedItems, this.specialDataset) return getStrippedServicesList(excludedItems, this.specialDataset);
else
return this.specialDataset; return this.specialDataset;
} }
@ -366,13 +374,11 @@ export default class ServicesManager {
* Gets all services sorted by category * Gets all services sorted by category
* *
* @param excludedItems Ids of categories to exclude from the returned list * @param excludedItems Ids of categories to exclude from the returned list
* @returns {Array<ServiceCategory>} * @returns {Array<ServiceCategoryType>}
*/ */
getCategories(excludedItems?: Array<string>) { getCategories(excludedItems?: Array<string>): Array<ServiceCategoryType> {
if (excludedItems != null) if (excludedItems != null)
return this.getStrippedList(excludedItems, this.categoriesDataset) return getStrippedServicesList(excludedItems, this.categoriesDataset);
else
return this.categoriesDataset; return this.categoriesDataset;
} }
} }

View file

@ -26,7 +26,7 @@ import AsyncStorageManager from '../../managers/AsyncStorageManager';
import {MASCOT_STYLE} from '../../components/Mascot/Mascot'; import {MASCOT_STYLE} from '../../components/Mascot/Mascot';
import MascotPopup from '../../components/Mascot/MascotPopup'; import MascotPopup from '../../components/Mascot/MascotPopup';
import DashboardManager from '../../managers/DashboardManager'; import DashboardManager from '../../managers/DashboardManager';
import type {ServiceItem} from '../../managers/ServicesManager'; import type {ServiceItemType} from '../../managers/ServicesManager';
import {getDisplayEvent, getFutureEvents} from '../../utils/Home'; import {getDisplayEvent, getFutureEvents} from '../../utils/Home';
// import DATA from "../dashboard_data.json"; // import DATA from "../dashboard_data.json";
@ -230,7 +230,7 @@ class HomeScreen extends React.Component<PropsType, StateType> {
* @param content * @param content
* @return {*} * @return {*}
*/ */
getDashboardRow(content: Array<ServiceItem | null>): React.Node { getDashboardRow(content: Array<ServiceItemType | null>): React.Node {
return ( return (
// $FlowFixMe // $FlowFixMe
<FlatList <FlatList
@ -256,7 +256,7 @@ class HomeScreen extends React.Component<PropsType, StateType> {
getDashboardRowRenderItem = ({ getDashboardRowRenderItem = ({
item, item,
}: { }: {
item: ServiceItem | null, item: ServiceItemType | null,
}): React.Node => { }): React.Node => {
if (item != null) if (item != null)
return ( return (

View file

@ -1,55 +1,68 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import type {cardList} from "../../components/Lists/CardList/CardList"; import {Image, View} from 'react-native';
import CardList from "../../components/Lists/CardList/CardList"; import {
import {Image, View} from "react-native"; Avatar,
import {Avatar, Card, Divider, List, TouchableRipple, withTheme} from "react-native-paper"; Card,
import type {CustomTheme} from "../../managers/ThemeManager"; Divider,
List,
TouchableRipple,
withTheme,
} from 'react-native-paper';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton"; import {StackNavigationProp} from '@react-navigation/stack';
import {StackNavigationProp} from "@react-navigation/stack"; import CardList from '../../components/Lists/CardList/CardList';
import {MASCOT_STYLE} from "../../components/Mascot/Mascot"; import type {CustomTheme} from '../../managers/ThemeManager';
import MascotPopup from "../../components/Mascot/MascotPopup"; import MaterialHeaderButtons, {
import AsyncStorageManager from "../../managers/AsyncStorageManager"; Item,
import ServicesManager, {SERVICES_CATEGORIES_KEY} from "../../managers/ServicesManager"; } from '../../components/Overrides/CustomHeaderButton';
import CollapsibleFlatList from "../../components/Collapsible/CollapsibleFlatList"; import {MASCOT_STYLE} from '../../components/Mascot/Mascot';
import MascotPopup from '../../components/Mascot/MascotPopup';
import AsyncStorageManager from '../../managers/AsyncStorageManager';
import ServicesManager, {
SERVICES_CATEGORIES_KEY,
} from '../../managers/ServicesManager';
import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
import type {ServiceCategoryType} from '../../managers/ServicesManager';
type Props = { type PropsType = {
navigation: StackNavigationProp, navigation: StackNavigationProp,
theme: CustomTheme, theme: CustomTheme,
} };
export type listItem = { class ServicesScreen extends React.Component<PropsType> {
title: string, finalDataset: Array<ServiceCategoryType>;
description: string,
image: string | number,
content: cardList,
}
constructor(props: PropsType) {
class ServicesScreen extends React.Component<Props> {
finalDataset: Array<listItem>
constructor(props) {
super(props); super(props);
const services = new ServicesManager(props.navigation); const services = new ServicesManager(props.navigation);
this.finalDataset = services.getCategories([SERVICES_CATEGORIES_KEY.SPECIAL]) this.finalDataset = services.getCategories([
SERVICES_CATEGORIES_KEY.SPECIAL,
]);
} }
componentDidMount() { componentDidMount() {
this.props.navigation.setOptions({ const {props} = this;
props.navigation.setOptions({
headerRight: this.getAboutButton, headerRight: this.getAboutButton,
}); });
} }
getAboutButton = () => getAboutButton = (): React.Node => (
<MaterialHeaderButtons> <MaterialHeaderButtons>
<Item title="information" iconName="information" onPress={this.onAboutPress}/> <Item
</MaterialHeaderButtons>; title="information"
iconName="information"
onPress={this.onAboutPress}
/>
</MaterialHeaderButtons>
);
onAboutPress = () => this.props.navigation.navigate('amicale-contact'); onAboutPress = () => {
const {props} = this;
props.navigation.navigate('amicale-contact');
};
/** /**
* Gets the list title image for the list. * Gets the list title image for the list.
@ -57,27 +70,30 @@ class ServicesScreen extends React.Component<Props> {
* If the source is a string, we are using an icon. * If the source is a string, we are using an icon.
* If the source is a number, we are using an internal image. * If the source is a number, we are using an internal image.
* *
* @param props Props to pass to the component
* @param source The source image to display. Can be a string for icons or a number for local images * @param source The source image to display. Can be a string for icons or a number for local images
* @returns {*} * @returns {*}
*/ */
getListTitleImage(props, source: string | number) { getListTitleImage(source: string | number): React.Node {
if (typeof source === "number") const {props} = this;
return <Image if (typeof source === 'number')
return (
<Image
size={48} size={48}
source={source} source={source}
style={{ style={{
width: 48, width: 48,
height: 48, height: 48,
}}/> }}
else />
return <Avatar.Icon );
{...props} return (
<Avatar.Icon
size={48} size={48}
icon={source} icon={source}
color={this.props.theme.colors.primary} color={props.theme.colors.primary}
style={{backgroundColor: 'transparent'}} style={{backgroundColor: 'transparent'}}
/> />
);
} }
/** /**
@ -86,58 +102,55 @@ class ServicesScreen extends React.Component<Props> {
* @param item * @param item
* @returns {*} * @returns {*}
*/ */
renderItem = ({item}: { item: listItem }) => { getRenderItem = ({item}: {item: ServiceCategoryType}): React.Node => {
const {props} = this;
return ( return (
<TouchableRipple <TouchableRipple
style={{ style={{
margin: 5, margin: 5,
marginBottom: 20, marginBottom: 20,
}} }}
onPress={() => this.props.navigation.navigate("services-section", {data: item})} onPress={() => {
> props.navigation.navigate('services-section', {data: item});
}}>
<View> <View>
<Card.Title <Card.Title
title={item.title} title={item.title}
subtitle={item.description} subtitle={item.subtitle}
left={(props) => this.getListTitleImage(props, item.image)} left={(): React.Node => this.getListTitleImage(item.image)}
right={(props) => <List.Icon {...props} icon="chevron-right"/>} right={({size}: {size: number}): React.Node => (
/> <List.Icon size={size} icon="chevron-right" />
<CardList )}
dataset={item.content}
isHorizontal={true}
/> />
<CardList dataset={item.content} isHorizontal />
</View> </View>
</TouchableRipple> </TouchableRipple>
); );
}; };
keyExtractor = (item: listItem) => { keyExtractor = (item: ServiceCategoryType): string => item.title;
return item.title;
}
render() { render(): React.Node {
return ( return (
<View> <View>
<CollapsibleFlatList <CollapsibleFlatList
data={this.finalDataset} data={this.finalDataset}
renderItem={this.renderItem} renderItem={this.getRenderItem}
keyExtractor={this.keyExtractor} keyExtractor={this.keyExtractor}
ItemSeparatorComponent={() => <Divider/>} ItemSeparatorComponent={(): React.Node => <Divider />}
hasTab={true} hasTab
/> />
<MascotPopup <MascotPopup
prefKey={AsyncStorageManager.PREFERENCES.servicesShowBanner.key} prefKey={AsyncStorageManager.PREFERENCES.servicesShowBanner.key}
title={i18n.t("screens.services.mascotDialog.title")} title={i18n.t('screens.services.mascotDialog.title')}
message={i18n.t("screens.services.mascotDialog.message")} message={i18n.t('screens.services.mascotDialog.message')}
icon={"cloud-question"} icon="cloud-question"
buttons={{ buttons={{
action: null, action: null,
cancel: { cancel: {
message: i18n.t("screens.services.mascotDialog.button"), message: i18n.t('screens.services.mascotDialog.button'),
icon: "check", icon: 'check',
onPress: this.onHideMascotDialog, },
}
}} }}
emotion={MASCOT_STYLE.WINK} emotion={MASCOT_STYLE.WINK}
/> />

View file

@ -1,25 +1,24 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import CardList from "../../components/Lists/CardList/CardList"; import {Collapsible} from 'react-navigation-collapsible';
import CustomTabBar from "../../components/Tabbar/CustomTabBar"; import {CommonActions} from '@react-navigation/native';
import {withCollapsible} from "../../utils/withCollapsible"; import {StackNavigationProp} from '@react-navigation/stack';
import {Collapsible} from "react-navigation-collapsible"; import CardList from '../../components/Lists/CardList/CardList';
import {CommonActions} from "@react-navigation/native"; import CustomTabBar from '../../components/Tabbar/CustomTabBar';
import type {listItem} from "./ServicesScreen"; import {withCollapsible} from '../../utils/withCollapsible';
import {StackNavigationProp} from "@react-navigation/stack"; import type {ServiceCategoryType} from '../../managers/ServicesManager';
type Props = { type PropsType = {
navigation: StackNavigationProp, navigation: StackNavigationProp,
route: { params: { data: listItem | null } }, route: {params: {data: ServiceCategoryType | null}},
collapsibleStack: Collapsible, collapsibleStack: Collapsible,
} };
class ServicesSectionScreen extends React.Component<Props> { class ServicesSectionScreen extends React.Component<PropsType> {
finalDataset: ServiceCategoryType;
finalDataset: listItem; constructor(props: PropsType) {
constructor(props) {
super(props); super(props);
this.handleNavigationParams(); this.handleNavigationParams();
} }
@ -28,30 +27,38 @@ class ServicesSectionScreen extends React.Component<Props> {
* Recover the list to display from navigation parameters * Recover the list to display from navigation parameters
*/ */
handleNavigationParams() { handleNavigationParams() {
if (this.props.route.params != null) { const {props} = this;
if (this.props.route.params.data != null) { if (props.route.params != null) {
this.finalDataset = this.props.route.params.data; if (props.route.params.data != null) {
this.finalDataset = props.route.params.data;
// reset params to prevent infinite loop // reset params to prevent infinite loop
this.props.navigation.dispatch(CommonActions.setParams({data: null})); props.navigation.dispatch(CommonActions.setParams({data: null}));
this.props.navigation.setOptions({ props.navigation.setOptions({
headerTitle: this.finalDataset.title, headerTitle: this.finalDataset.title,
}); });
} }
} }
} }
render() { render(): React.Node {
const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack; const {props} = this;
return <CardList const {
containerPaddingTop,
scrollIndicatorInsetTop,
onScroll,
} = props.collapsibleStack;
return (
<CardList
dataset={this.finalDataset.content} dataset={this.finalDataset.content}
isHorizontal={false} isHorizontal={false}
onScroll={onScroll} onScroll={onScroll}
contentContainerStyle={{ contentContainerStyle={{
paddingTop: containerPaddingTop, paddingTop: containerPaddingTop,
paddingBottom: CustomTabBar.TAB_BAR_HEIGHT + 20 paddingBottom: CustomTabBar.TAB_BAR_HEIGHT + 20,
}} }}
scrollIndicatorInsets={{top: scrollIndicatorInsetTop}} scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}
/> />
);
} }
} }

19
src/utils/Services.js Normal file
View file

@ -0,0 +1,19 @@
// @flow
/**
* Gets the given services list without items of the given ids
*
* @param idList The ids of items to remove
* @param sourceList The item list to use as source
* @returns {[]}
*/
export default function getStrippedServicesList<T>(
idList: Array<string>,
sourceList: Array<{key: string, ...T}>,
): Array<{key: string, ...T}> {
const newArray = [];
sourceList.forEach((item: {key: string, ...T}) => {
if (!idList.includes(item.key)) newArray.push(item);
});
return newArray;
}