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,72 +1,73 @@
// @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> {
static defaultProps = {
isHorizontal: false,
contentContainerStyle: null,
};
windowWidth: number;
export default class CardList extends React.Component<Props> { horizontalItemSize: number;
static defaultProps = { constructor(props: PropsType) {
isHorizontal: false, 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 = {};
if (props.isHorizontal) {
containerStyle = {
height: this.horizontalItemSize + 50,
justifyContent: 'space-around',
};
} }
return (
windowWidth: number; <Animated.FlatList
horizontalItemSize: number; data={props.dataset}
renderItem={this.getRenderItem}
constructor(props: Props) { keyExtractor={this.keyExtractor}
super(props); numColumns={props.isHorizontal ? undefined : 2}
this.windowWidth = Dimensions.get('window').width; horizontal={props.isHorizontal}
this.horizontalItemSize = this.windowWidth/4; // So that we can fit 3 items and a part of the 4th => user knows he can scroll contentContainerStyle={
} props.isHorizontal ? containerStyle : props.contentContainerStyle
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;
render() {
let containerStyle = {};
if (this.props.isHorizontal) {
containerStyle = {
height: this.horizontalItemSize + 50,
justifyContent: 'space-around',
};
} }
return ( pagingEnabled={props.isHorizontal}
<Animated.FlatList snapToInterval={
{...this.props} props.isHorizontal ? (this.horizontalItemSize + 5) * 3 : null
data={this.props.dataset} }
renderItem={this.renderItem} />
keyExtractor={this.keyExtractor} );
numColumns={this.props.isHorizontal ? undefined : 2} }
horizontal={this.props.isHorizontal}
contentContainerStyle={this.props.isHorizontal ? containerStyle : this.props.contentContainerStyle}
pagingEnabled={this.props.isHorizontal}
snapToInterval={this.props.isHorizontal ? (this.horizontalItemSize+5)*3 : null}
/>
);
}
} }

View file

@ -2,50 +2,41 @@
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(): React.Node {
render() { const {props} = this;
const props = this.props; const {item} = props;
const item = props.item; const source =
const source = typeof item.image === "number" typeof item.image === 'number' ? item.image : {uri: item.image};
? item.image return (
: {uri: item.image}; <Card
return ( style={{
<Card width: '40%',
style={{ margin: 5,
width: '40%', marginLeft: 'auto',
margin: 5, marginRight: 'auto',
marginLeft: 'auto', }}>
marginRight: 'auto', <TouchableRipple style={{flex: 1}} onPress={item.onPress}>
}} <View>
> <Card.Cover style={{height: 80}} source={source} />
<TouchableRipple <Card.Content>
style={{flex: 1}} <Paragraph>{item.title}</Paragraph>
onPress={item.onPress}> <Caption>{item.subtitle}</Caption>
<View> </Card.Content>
<Card.Cover </View>
style={{height: 80}} </TouchableRipple>
source={source} </Card>
/> );
<Card.Content> }
<Paragraph>{item.title}</Paragraph>
<Caption>{item.subtitle}</Caption>
</Card.Content>
</View>
</TouchableRipple>
</Card>
);
}
} }

View file

@ -3,53 +3,52 @@
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(): React.Node {
render() { const {props} = this;
const item = this.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 ( <TouchableRipple
<TouchableRipple style={{
style={{ width: props.width,
width: this.props.width, height: props.width + 40,
height: this.props.width + 40, margin: 5,
margin: 5, }}
}} onPress={item.onPress}>
onPress={item.onPress} <View>
> <Image
<View> style={{
<Image width: props.width - 20,
style={{ height: props.width - 20,
width: this.props.width - 20, marginLeft: 'auto',
height: this.props.width - 20, marginRight: 'auto',
marginLeft: '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>
</View> </TouchableRipple>
</TouchableRipple> );
); }
}
} }

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,378 +1,384 @@
// @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 = {
key: string,
title: string,
subtitle: string,
image: string,
onPress: () => void,
badgeFunction?: (dashboard: FullDashboardType) => number,
};
export type ServiceItem = { export type ServiceCategoryType = {
key: string, key: string,
title: string, title: string,
subtitle: string, subtitle: string,
image: string, image: string | number,
onPress: () => void, content: Array<ServiceItemType>,
badgeFunction?: (dashboard: fullDashboard) => number, };
}
export type ServiceCategory = {
key: string,
title: string,
subtitle: string,
image: string | number,
content: Array<ServiceItem>
}
export default class ServicesManager { export default class ServicesManager {
navigation: StackNavigationProp;
navigation: StackNavigationProp; amicaleDataset: Array<ServiceItemType>;
amicaleDataset: Array<ServiceItem>; studentsDataset: Array<ServiceItemType>;
studentsDataset: Array<ServiceItem>;
insaDataset: Array<ServiceItem>;
specialDataset: Array<ServiceItem>;
categoriesDataset: Array<ServiceCategory>; insaDataset: Array<ServiceItemType>;
constructor(nav: StackNavigationProp) { specialDataset: Array<ServiceItemType>;
this.navigation = nav;
this.amicaleDataset = [
{
key: SERVICES_KEY.CLUBS,
title: i18n.t('screens.clubs.title'),
subtitle: i18n.t('screens.services.descriptions.clubs'),
image: CLUBS_IMAGE,
onPress: () => this.onAmicaleServicePress("club-list"),
},
{
key: SERVICES_KEY.PROFILE,
title: i18n.t('screens.profile.title'),
subtitle: i18n.t('screens.services.descriptions.profile'),
image: PROFILE_IMAGE,
onPress: () => this.onAmicaleServicePress("profile"),
},
{
key: SERVICES_KEY.EQUIPMENT,
title: i18n.t('screens.equipment.title'),
subtitle: i18n.t('screens.services.descriptions.equipment'),
image: EQUIPMENT_IMAGE,
onPress: () => this.onAmicaleServicePress("equipment-list"),
},
{
key: SERVICES_KEY.AMICALE_WEBSITE,
title: i18n.t('screens.websites.amicale'),
subtitle: i18n.t('screens.services.descriptions.amicaleWebsite'),
image: AMICALE_IMAGE,
onPress: () => nav.navigate("website", {
host: AvailableWebsites.websites.AMICALE,
title: i18n.t('screens.websites.amicale')
}),
},
{
key: SERVICES_KEY.VOTE,
title: i18n.t('screens.vote.title'),
subtitle: i18n.t('screens.services.descriptions.vote'),
image: VOTE_IMAGE,
onPress: () => this.onAmicaleServicePress("vote"),
},
];
this.studentsDataset = [
{
key: SERVICES_KEY.PROXIMO,
title: i18n.t('screens.proximo.title'),
subtitle: i18n.t('screens.services.descriptions.proximo'),
image: PROXIMO_IMAGE,
onPress: () => nav.navigate("proximo"),
badgeFunction: (dashboard: fullDashboard) => dashboard.proximo_articles
},
{
key: SERVICES_KEY.WIKETUD,
title: "Wiketud",
subtitle: i18n.t('screens.services.descriptions.wiketud'),
image: WIKETUD_IMAGE,
onPress: () => nav.navigate("website", {host: AvailableWebsites.websites.WIKETUD, title: "Wiketud"}),
},
{
key: SERVICES_KEY.ELUS_ETUDIANTS,
title: "Élus Étudiants",
subtitle: i18n.t('screens.services.descriptions.elusEtudiants'),
image: EE_IMAGE,
onPress: () => nav.navigate("website", {
host: AvailableWebsites.websites.ELUS_ETUDIANTS,
title: "Élus Étudiants"
}),
},
{
key: SERVICES_KEY.TUTOR_INSA,
title: "Tutor'INSA",
subtitle: i18n.t('screens.services.descriptions.tutorInsa'),
image: TUTORINSA_IMAGE,
onPress: () => nav.navigate("website", {
host: AvailableWebsites.websites.TUTOR_INSA,
title: "Tutor'INSA"
}),
badgeFunction: (dashboard: fullDashboard) => dashboard.available_tutorials
},
];
this.insaDataset = [
{
key: SERVICES_KEY.RU,
title: i18n.t('screens.menu.title'),
subtitle: i18n.t('screens.services.descriptions.self'),
image: RU_IMAGE,
onPress: () => nav.navigate("self-menu"),
badgeFunction: (dashboard: fullDashboard) => dashboard.today_menu.length
},
{
key: SERVICES_KEY.AVAILABLE_ROOMS,
title: i18n.t('screens.websites.rooms'),
subtitle: i18n.t('screens.services.descriptions.availableRooms'),
image: ROOM_IMAGE,
onPress: () => nav.navigate("website", {
host: AvailableWebsites.websites.AVAILABLE_ROOMS,
title: i18n.t('screens.websites.rooms')
}),
},
{
key: SERVICES_KEY.BIB,
title: i18n.t('screens.websites.bib'),
subtitle: i18n.t('screens.services.descriptions.bib'),
image: BIB_IMAGE,
onPress: () => nav.navigate("website", {
host: AvailableWebsites.websites.BIB,
title: i18n.t('screens.websites.bib')
}),
},
{
key: SERVICES_KEY.EMAIL,
title: i18n.t('screens.websites.mails'),
subtitle: i18n.t('screens.services.descriptions.mails'),
image: EMAIL_IMAGE,
onPress: () => nav.navigate("website", {
host: AvailableWebsites.websites.BLUEMIND,
title: i18n.t('screens.websites.mails')
}),
},
{
key: SERVICES_KEY.ENT,
title: i18n.t('screens.websites.ent'),
subtitle: i18n.t('screens.services.descriptions.ent'),
image: ENT_IMAGE,
onPress: () => nav.navigate("website", {
host: AvailableWebsites.websites.ENT,
title: i18n.t('screens.websites.ent')
}),
},
{
key: SERVICES_KEY.INSA_ACCOUNT,
title: i18n.t('screens.insaAccount.title'),
subtitle: i18n.t('screens.services.descriptions.insaAccount'),
image: ACCOUNT_IMAGE,
onPress: () => nav.navigate("website", {
host: AvailableWebsites.websites.INSA_ACCOUNT,
title: i18n.t('screens.insaAccount.title')
}),
},
];
this.specialDataset = [
{
key: SERVICES_KEY.WASHERS,
title: i18n.t('screens.proxiwash.washers'),
subtitle: i18n.t('screens.services.descriptions.washers'),
image: WASHER_IMAGE,
onPress: () => nav.navigate("proxiwash"),
badgeFunction: (dashboard: fullDashboard) => dashboard.available_washers
},
{
key: SERVICES_KEY.DRYERS,
title: i18n.t('screens.proxiwash.dryers'),
subtitle: i18n.t('screens.services.descriptions.washers'),
image: DRYER_IMAGE,
onPress: () => nav.navigate("proxiwash"),
badgeFunction: (dashboard: fullDashboard) => dashboard.available_dryers
}
];
this.categoriesDataset = [
{
key: SERVICES_CATEGORIES_KEY.AMICALE,
title: i18n.t("screens.services.categories.amicale"),
subtitle: i18n.t("screens.services.more"),
image: AMICALE_LOGO,
content: this.amicaleDataset
},
{
key: SERVICES_CATEGORIES_KEY.STUDENTS,
title: i18n.t("screens.services.categories.students"),
subtitle: i18n.t("screens.services.more"),
image: 'account-group',
content: this.studentsDataset
},
{
key: SERVICES_CATEGORIES_KEY.INSA,
title: i18n.t("screens.services.categories.insa"),
subtitle: i18n.t("screens.services.more"),
image: 'school',
content: this.insaDataset
},
{
key: SERVICES_CATEGORIES_KEY.SPECIAL,
title: i18n.t("screens.services.categories.special"),
subtitle: i18n.t("screens.services.categories.special"),
image: 'star',
content: this.specialDataset
},
];
}
/** categoriesDataset: Array<ServiceCategoryType>;
* Redirects the user to the login screen if he is not logged in
*
* @param route
* @returns {null}
*/
onAmicaleServicePress(route: string) {
if (ConnectionManager.getInstance().isLoggedIn())
this.navigation.navigate(route);
else
this.navigation.navigate("login", {nextScreen: route});
}
/** constructor(nav: StackNavigationProp) {
* Gets the given services list without items of the given ids this.navigation = nav;
* this.amicaleDataset = [
* @param idList The ids of items to remove {
* @param sourceList The item list to use as source key: SERVICES_KEY.CLUBS,
* @returns {[]} title: i18n.t('screens.clubs.title'),
*/ subtitle: i18n.t('screens.services.descriptions.clubs'),
getStrippedList(idList: Array<string>, sourceList: Array<{key: string, [key: string]: any}>) { image: CLUBS_IMAGE,
let newArray = []; onPress: (): void => this.onAmicaleServicePress('club-list'),
for (let i = 0; i < sourceList.length; i++) { },
const item = sourceList[i]; {
if (!(idList.includes(item.key))) key: SERVICES_KEY.PROFILE,
newArray.push(item); title: i18n.t('screens.profile.title'),
} subtitle: i18n.t('screens.services.descriptions.profile'),
return newArray; image: PROFILE_IMAGE,
} onPress: (): void => this.onAmicaleServicePress('profile'),
},
{
key: SERVICES_KEY.EQUIPMENT,
title: i18n.t('screens.equipment.title'),
subtitle: i18n.t('screens.services.descriptions.equipment'),
image: EQUIPMENT_IMAGE,
onPress: (): void => this.onAmicaleServicePress('equipment-list'),
},
{
key: SERVICES_KEY.AMICALE_WEBSITE,
title: i18n.t('screens.websites.amicale'),
subtitle: i18n.t('screens.services.descriptions.amicaleWebsite'),
image: AMICALE_IMAGE,
onPress: (): void =>
nav.navigate('website', {
host: AvailableWebsites.websites.AMICALE,
title: i18n.t('screens.websites.amicale'),
}),
},
{
key: SERVICES_KEY.VOTE,
title: i18n.t('screens.vote.title'),
subtitle: i18n.t('screens.services.descriptions.vote'),
image: VOTE_IMAGE,
onPress: (): void => this.onAmicaleServicePress('vote'),
},
];
this.studentsDataset = [
{
key: SERVICES_KEY.PROXIMO,
title: i18n.t('screens.proximo.title'),
subtitle: i18n.t('screens.services.descriptions.proximo'),
image: PROXIMO_IMAGE,
onPress: (): void => nav.navigate('proximo'),
badgeFunction: (dashboard: FullDashboardType): number =>
dashboard.proximo_articles,
},
{
key: SERVICES_KEY.WIKETUD,
title: 'Wiketud',
subtitle: i18n.t('screens.services.descriptions.wiketud'),
image: WIKETUD_IMAGE,
onPress: (): void =>
nav.navigate('website', {
host: AvailableWebsites.websites.WIKETUD,
title: 'Wiketud',
}),
},
{
key: SERVICES_KEY.ELUS_ETUDIANTS,
title: 'Élus Étudiants',
subtitle: i18n.t('screens.services.descriptions.elusEtudiants'),
image: EE_IMAGE,
onPress: (): void =>
nav.navigate('website', {
host: AvailableWebsites.websites.ELUS_ETUDIANTS,
title: 'Élus Étudiants',
}),
},
{
key: SERVICES_KEY.TUTOR_INSA,
title: "Tutor'INSA",
subtitle: i18n.t('screens.services.descriptions.tutorInsa'),
image: TUTORINSA_IMAGE,
onPress: (): void =>
nav.navigate('website', {
host: AvailableWebsites.websites.TUTOR_INSA,
title: "Tutor'INSA",
}),
badgeFunction: (dashboard: FullDashboardType): number =>
dashboard.available_tutorials,
},
];
this.insaDataset = [
{
key: SERVICES_KEY.RU,
title: i18n.t('screens.menu.title'),
subtitle: i18n.t('screens.services.descriptions.self'),
image: RU_IMAGE,
onPress: (): void => nav.navigate('self-menu'),
badgeFunction: (dashboard: FullDashboardType): number =>
dashboard.today_menu.length,
},
{
key: SERVICES_KEY.AVAILABLE_ROOMS,
title: i18n.t('screens.websites.rooms'),
subtitle: i18n.t('screens.services.descriptions.availableRooms'),
image: ROOM_IMAGE,
onPress: (): void =>
nav.navigate('website', {
host: AvailableWebsites.websites.AVAILABLE_ROOMS,
title: i18n.t('screens.websites.rooms'),
}),
},
{
key: SERVICES_KEY.BIB,
title: i18n.t('screens.websites.bib'),
subtitle: i18n.t('screens.services.descriptions.bib'),
image: BIB_IMAGE,
onPress: (): void =>
nav.navigate('website', {
host: AvailableWebsites.websites.BIB,
title: i18n.t('screens.websites.bib'),
}),
},
{
key: SERVICES_KEY.EMAIL,
title: i18n.t('screens.websites.mails'),
subtitle: i18n.t('screens.services.descriptions.mails'),
image: EMAIL_IMAGE,
onPress: (): void =>
nav.navigate('website', {
host: AvailableWebsites.websites.BLUEMIND,
title: i18n.t('screens.websites.mails'),
}),
},
{
key: SERVICES_KEY.ENT,
title: i18n.t('screens.websites.ent'),
subtitle: i18n.t('screens.services.descriptions.ent'),
image: ENT_IMAGE,
onPress: (): void =>
nav.navigate('website', {
host: AvailableWebsites.websites.ENT,
title: i18n.t('screens.websites.ent'),
}),
},
{
key: SERVICES_KEY.INSA_ACCOUNT,
title: i18n.t('screens.insaAccount.title'),
subtitle: i18n.t('screens.services.descriptions.insaAccount'),
image: ACCOUNT_IMAGE,
onPress: (): void =>
nav.navigate('website', {
host: AvailableWebsites.websites.INSA_ACCOUNT,
title: i18n.t('screens.insaAccount.title'),
}),
},
];
this.specialDataset = [
{
key: SERVICES_KEY.WASHERS,
title: i18n.t('screens.proxiwash.washers'),
subtitle: i18n.t('screens.services.descriptions.washers'),
image: WASHER_IMAGE,
onPress: (): void => nav.navigate('proxiwash'),
badgeFunction: (dashboard: FullDashboardType): number =>
dashboard.available_washers,
},
{
key: SERVICES_KEY.DRYERS,
title: i18n.t('screens.proxiwash.dryers'),
subtitle: i18n.t('screens.services.descriptions.washers'),
image: DRYER_IMAGE,
onPress: (): void => nav.navigate('proxiwash'),
badgeFunction: (dashboard: FullDashboardType): number =>
dashboard.available_dryers,
},
];
this.categoriesDataset = [
{
key: SERVICES_CATEGORIES_KEY.AMICALE,
title: i18n.t('screens.services.categories.amicale'),
subtitle: i18n.t('screens.services.more'),
image: AMICALE_LOGO,
content: this.amicaleDataset,
},
{
key: SERVICES_CATEGORIES_KEY.STUDENTS,
title: i18n.t('screens.services.categories.students'),
subtitle: i18n.t('screens.services.more'),
image: 'account-group',
content: this.studentsDataset,
},
{
key: SERVICES_CATEGORIES_KEY.INSA,
title: i18n.t('screens.services.categories.insa'),
subtitle: i18n.t('screens.services.more'),
image: 'school',
content: this.insaDataset,
},
{
key: SERVICES_CATEGORIES_KEY.SPECIAL,
title: i18n.t('screens.services.categories.special'),
subtitle: i18n.t('screens.services.categories.special'),
image: 'star',
content: this.specialDataset,
},
];
}
/** /**
* Gets the list of amicale's services * Redirects the user to the login screen if he is not logged in
* *
* @param excludedItems Ids of items to exclude from the returned list * @param route
* @returns {Array<ServiceItem>} * @returns {null}
*/ */
getAmicaleServices(excludedItems?: Array<string>) { onAmicaleServicePress(route: string) {
if (excludedItems != null) if (ConnectionManager.getInstance().isLoggedIn())
return this.getStrippedList(excludedItems, this.amicaleDataset) this.navigation.navigate(route);
else else this.navigation.navigate('login', {nextScreen: route});
return this.amicaleDataset; }
}
/** /**
* Gets the list of students' 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>}
*/ */
getStudentServices(excludedItems?: Array<string>) { getAmicaleServices(excludedItems?: Array<string>): Array<ServiceItemType> {
if (excludedItems != null) if (excludedItems != null)
return this.getStrippedList(excludedItems, this.studentsDataset) return getStrippedServicesList(excludedItems, this.amicaleDataset);
else return this.amicaleDataset;
return this.studentsDataset; }
}
/** /**
* Gets the list of INSA's 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>}
*/ */
getINSAServices(excludedItems?: Array<string>) { getStudentServices(excludedItems?: Array<string>): Array<ServiceItemType> {
if (excludedItems != null) if (excludedItems != null)
return this.getStrippedList(excludedItems, this.insaDataset) return getStrippedServicesList(excludedItems, this.studentsDataset);
else return this.studentsDataset;
return this.insaDataset; }
}
/** /**
* Gets the list of special 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>}
*/ */
getSpecialServices(excludedItems?: Array<string>) { getINSAServices(excludedItems?: Array<string>): Array<ServiceItemType> {
if (excludedItems != null) if (excludedItems != null)
return this.getStrippedList(excludedItems, this.specialDataset) return getStrippedServicesList(excludedItems, this.insaDataset);
else return this.insaDataset;
return this.specialDataset; }
}
/** /**
* Gets all services sorted by category * Gets the list of special services
* *
* @param excludedItems Ids of categories to exclude from the returned list * @param excludedItems Ids of items to exclude from the returned list
* @returns {Array<ServiceCategory>} * @returns {Array<ServiceItemType>}
*/ */
getCategories(excludedItems?: Array<string>) { getSpecialServices(excludedItems?: Array<string>): Array<ServiceItemType> {
if (excludedItems != null) if (excludedItems != null)
return this.getStrippedList(excludedItems, this.categoriesDataset) return getStrippedServicesList(excludedItems, this.specialDataset);
else return this.specialDataset;
return this.categoriesDataset; }
}
/**
* Gets all services sorted by category
*
* @param excludedItems Ids of categories to exclude from the returned list
* @returns {Array<ServiceCategoryType>}
*/
getCategories(excludedItems?: Array<string>): Array<ServiceCategoryType> {
if (excludedItems != null)
return getStrippedServicesList(excludedItems, 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,149 +1,162 @@
// @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) {
super(props);
const services = new ServicesManager(props.navigation);
this.finalDataset = services.getCategories([
SERVICES_CATEGORIES_KEY.SPECIAL,
]);
}
class ServicesScreen extends React.Component<Props> { componentDidMount() {
const {props} = this;
props.navigation.setOptions({
headerRight: this.getAboutButton,
});
}
finalDataset: Array<listItem> getAboutButton = (): React.Node => (
<MaterialHeaderButtons>
<Item
title="information"
iconName="information"
onPress={this.onAboutPress}
/>
</MaterialHeaderButtons>
);
constructor(props) { onAboutPress = () => {
super(props); const {props} = this;
const services = new ServicesManager(props.navigation); props.navigation.navigate('amicale-contact');
this.finalDataset = services.getCategories([SERVICES_CATEGORIES_KEY.SPECIAL]) };
}
componentDidMount() { /**
this.props.navigation.setOptions({ * Gets the list title image for the list.
headerRight: this.getAboutButton, *
}); * If the source is a string, we are using an icon.
} * If the source is a number, we are using an internal image.
*
* @param source The source image to display. Can be a string for icons or a number for local images
* @returns {*}
*/
getListTitleImage(source: string | number): React.Node {
const {props} = this;
if (typeof source === 'number')
return (
<Image
size={48}
source={source}
style={{
width: 48,
height: 48,
}}
/>
);
return (
<Avatar.Icon
size={48}
icon={source}
color={props.theme.colors.primary}
style={{backgroundColor: 'transparent'}}
/>
);
}
getAboutButton = () => /**
<MaterialHeaderButtons> * A list item showing a list of available services for the current category
<Item title="information" iconName="information" onPress={this.onAboutPress}/> *
</MaterialHeaderButtons>; * @param item
* @returns {*}
*/
getRenderItem = ({item}: {item: ServiceCategoryType}): React.Node => {
const {props} = this;
return (
<TouchableRipple
style={{
margin: 5,
marginBottom: 20,
}}
onPress={() => {
props.navigation.navigate('services-section', {data: item});
}}>
<View>
<Card.Title
title={item.title}
subtitle={item.subtitle}
left={(): React.Node => this.getListTitleImage(item.image)}
right={({size}: {size: number}): React.Node => (
<List.Icon size={size} icon="chevron-right" />
)}
/>
<CardList dataset={item.content} isHorizontal />
</View>
</TouchableRipple>
);
};
onAboutPress = () => this.props.navigation.navigate('amicale-contact'); keyExtractor = (item: ServiceCategoryType): string => item.title;
/** render(): React.Node {
* Gets the list title image for the list. return (
* <View>
* If the source is a string, we are using an icon. <CollapsibleFlatList
* If the source is a number, we are using an internal image. data={this.finalDataset}
* renderItem={this.getRenderItem}
* @param props Props to pass to the component keyExtractor={this.keyExtractor}
* @param source The source image to display. Can be a string for icons or a number for local images ItemSeparatorComponent={(): React.Node => <Divider />}
* @returns {*} hasTab
*/ />
getListTitleImage(props, source: string | number) { <MascotPopup
if (typeof source === "number") prefKey={AsyncStorageManager.PREFERENCES.servicesShowBanner.key}
return <Image title={i18n.t('screens.services.mascotDialog.title')}
size={48} message={i18n.t('screens.services.mascotDialog.message')}
source={source} icon="cloud-question"
style={{ buttons={{
width: 48, action: null,
height: 48, cancel: {
}}/> message: i18n.t('screens.services.mascotDialog.button'),
else icon: 'check',
return <Avatar.Icon },
{...props} }}
size={48} emotion={MASCOT_STYLE.WINK}
icon={source} />
color={this.props.theme.colors.primary} </View>
style={{backgroundColor: 'transparent'}} );
/> }
}
/**
* A list item showing a list of available services for the current category
*
* @param item
* @returns {*}
*/
renderItem = ({item}: { item: listItem }) => {
return (
<TouchableRipple
style={{
margin: 5,
marginBottom: 20,
}}
onPress={() => this.props.navigation.navigate("services-section", {data: item})}
>
<View>
<Card.Title
title={item.title}
subtitle={item.description}
left={(props) => this.getListTitleImage(props, item.image)}
right={(props) => <List.Icon {...props} icon="chevron-right"/>}
/>
<CardList
dataset={item.content}
isHorizontal={true}
/>
</View>
</TouchableRipple>
);
};
keyExtractor = (item: listItem) => {
return item.title;
}
render() {
return (
<View>
<CollapsibleFlatList
data={this.finalDataset}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
ItemSeparatorComponent={() => <Divider/>}
hasTab={true}
/>
<MascotPopup
prefKey={AsyncStorageManager.PREFERENCES.servicesShowBanner.key}
title={i18n.t("screens.services.mascotDialog.title")}
message={i18n.t("screens.services.mascotDialog.message")}
icon={"cloud-question"}
buttons={{
action: null,
cancel: {
message: i18n.t("screens.services.mascotDialog.button"),
icon: "check",
onPress: this.onHideMascotDialog,
}
}}
emotion={MASCOT_STYLE.WINK}
/>
</View>
);
}
} }
export default withTheme(ServicesScreen); export default withTheme(ServicesScreen);

View file

@ -1,58 +1,65 @@
// @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) {
super(props);
this.handleNavigationParams();
}
constructor(props) { /**
super(props); * Recover the list to display from navigation parameters
this.handleNavigationParams(); */
handleNavigationParams() {
const {props} = this;
if (props.route.params != null) {
if (props.route.params.data != null) {
this.finalDataset = props.route.params.data;
// reset params to prevent infinite loop
props.navigation.dispatch(CommonActions.setParams({data: null}));
props.navigation.setOptions({
headerTitle: this.finalDataset.title,
});
}
} }
}
/** render(): React.Node {
* Recover the list to display from navigation parameters const {props} = this;
*/ const {
handleNavigationParams() { containerPaddingTop,
if (this.props.route.params != null) { scrollIndicatorInsetTop,
if (this.props.route.params.data != null) { onScroll,
this.finalDataset = this.props.route.params.data; } = props.collapsibleStack;
// reset params to prevent infinite loop return (
this.props.navigation.dispatch(CommonActions.setParams({data: null})); <CardList
this.props.navigation.setOptions({ dataset={this.finalDataset.content}
headerTitle: this.finalDataset.title, isHorizontal={false}
}); onScroll={onScroll}
} contentContainerStyle={{
} paddingTop: containerPaddingTop,
} paddingBottom: CustomTabBar.TAB_BAR_HEIGHT + 20,
}}
render() { scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}
const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack; />
return <CardList );
dataset={this.finalDataset.content} }
isHorizontal={false}
onScroll={onScroll}
contentContainerStyle={{
paddingTop: containerPaddingTop,
paddingBottom: CustomTabBar.TAB_BAR_HEIGHT + 20
}}
scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}
/>
}
} }
export default withCollapsible(ServicesSectionScreen); export default withCollapsible(ServicesSectionScreen);

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;
}