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
import * as React from 'react';
import {Animated, Dimensions} from "react-native";
import ImageListItem from "./ImageListItem";
import CardListItem from "./CardListItem";
import type {ViewStyle} from "react-native/Libraries/StyleSheet/StyleSheet";
import {Animated, Dimensions} from 'react-native';
import type {ViewStyle} from 'react-native/Libraries/StyleSheet/StyleSheet';
import ImageListItem from './ImageListItem';
import CardListItem from './CardListItem';
import type {ServiceItemType} from '../../../managers/ServicesManager';
type Props = {
dataset: Array<cardItem>,
isHorizontal: boolean,
contentContainerStyle?: ViewStyle,
}
export type cardItem = {
key: string,
title: string,
subtitle: string,
image: string | number,
onPress: () => void,
type PropsType = {
dataset: Array<ServiceItemType>,
isHorizontal?: boolean,
contentContainerStyle?: ViewStyle | null,
};
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 = {
isHorizontal: false,
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 = {};
if (props.isHorizontal) {
containerStyle = {
height: this.horizontalItemSize + 50,
justifyContent: 'space-around',
};
}
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;
render() {
let containerStyle = {};
if (this.props.isHorizontal) {
containerStyle = {
height: this.horizontalItemSize + 50,
justifyContent: 'space-around',
};
return (
<Animated.FlatList
data={props.dataset}
renderItem={this.getRenderItem}
keyExtractor={this.keyExtractor}
numColumns={props.isHorizontal ? undefined : 2}
horizontal={props.isHorizontal}
contentContainerStyle={
props.isHorizontal ? containerStyle : props.contentContainerStyle
}
return (
<Animated.FlatList
{...this.props}
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}
/>
);
}
pagingEnabled={props.isHorizontal}
snapToInterval={
props.isHorizontal ? (this.horizontalItemSize + 5) * 3 : null
}
/>
);
}
}

View file

@ -2,50 +2,41 @@
import * as React from 'react';
import {Caption, Card, Paragraph, TouchableRipple} from 'react-native-paper';
import {View} from "react-native";
import type {cardItem} from "./CardList";
import {View} from 'react-native';
import type {ServiceItemType} from '../../../managers/ServicesManager';
type Props = {
item: cardItem,
}
export default class CardListItem extends React.Component<Props> {
shouldComponentUpdate() {
return false;
}
render() {
const props = this.props;
const item = props.item;
const source = typeof item.image === "number"
? item.image
: {uri: item.image};
return (
<Card
style={{
width: '40%',
margin: 5,
marginLeft: 'auto',
marginRight: 'auto',
}}
>
<TouchableRipple
style={{flex: 1}}
onPress={item.onPress}>
<View>
<Card.Cover
style={{height: 80}}
source={source}
/>
<Card.Content>
<Paragraph>{item.title}</Paragraph>
<Caption>{item.subtitle}</Caption>
</Card.Content>
</View>
</TouchableRipple>
</Card>
);
}
type PropsType = {
item: ServiceItemType,
};
export default class CardListItem extends React.Component<PropsType> {
shouldComponentUpdate(): boolean {
return false;
}
render(): React.Node {
const {props} = this;
const {item} = props;
const source =
typeof item.image === 'number' ? item.image : {uri: item.image};
return (
<Card
style={{
width: '40%',
margin: 5,
marginLeft: 'auto',
marginRight: 'auto',
}}>
<TouchableRipple style={{flex: 1}} onPress={item.onPress}>
<View>
<Card.Cover style={{height: 80}} source={source} />
<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 {Text, TouchableRipple} from 'react-native-paper';
import {Image, View} from 'react-native';
import type {cardItem} from "./CardList";
import type {ServiceItemType} from '../../../managers/ServicesManager';
type Props = {
item: cardItem,
width: number,
}
export default class ImageListItem extends React.Component<Props> {
shouldComponentUpdate() {
return false;
}
render() {
const item = this.props.item;
const source = typeof item.image === "number"
? item.image
: {uri: item.image};
return (
<TouchableRipple
style={{
width: this.props.width,
height: this.props.width + 40,
margin: 5,
}}
onPress={item.onPress}
>
<View>
<Image
style={{
width: this.props.width - 20,
height: this.props.width - 20,
marginLeft: 'auto',
marginRight: 'auto',
}}
source={source}
/>
<Text style={{
marginTop: 5,
marginLeft: 'auto',
marginRight: 'auto',
textAlign: 'center'
}}>
{item.title}
</Text>
</View>
</TouchableRipple>
);
}
type PropsType = {
item: ServiceItemType,
width: number,
};
export default class ImageListItem extends React.Component<PropsType> {
shouldComponentUpdate(): boolean {
return false;
}
render(): React.Node {
const {props} = this;
const {item} = props;
const source =
typeof item.image === 'number' ? item.image : {uri: item.image};
return (
<TouchableRipple
style={{
width: props.width,
height: props.width + 40,
margin: 5,
}}
onPress={item.onPress}>
<View>
<Image
style={{
width: props.width - 20,
height: props.width - 20,
marginLeft: 'auto',
marginRight: 'auto',
}}
source={source}
/>
<Text
style={{
marginTop: 5,
marginLeft: 'auto',
marginRight: 'auto',
textAlign: 'center',
}}>
{item.title}
</Text>
</View>
</TouchableRipple>
);
}
}

View file

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

View file

@ -1,378 +1,384 @@
// @flow
import i18n from "i18n-js";
import AvailableWebsites from "../constants/AvailableWebsites";
import {StackNavigationProp} from "@react-navigation/stack";
import ConnectionManager from "./ConnectionManager";
import type {fullDashboard} from "../screens/Home/HomeScreen";
import i18n from 'i18n-js';
import {StackNavigationProp} from '@react-navigation/stack';
import AvailableWebsites from '../constants/AvailableWebsites';
import ConnectionManager from './ConnectionManager';
import type {FullDashboardType} from '../screens/Home/HomeScreen';
import getStrippedServicesList from '../utils/Services';
// AMICALE
const CLUBS_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Clubs.png";
const PROFILE_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/ProfilAmicaliste.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";
const CLUBS_IMAGE =
'https://etud.insa-toulouse.fr/~amicale_app/images/Clubs.png';
const PROFILE_IMAGE =
'https://etud.insa-toulouse.fr/~amicale_app/images/ProfilAmicaliste.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
const PROXIMO_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Proximo.png"
const WIKETUD_IMAGE = "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";
const PROXIMO_IMAGE =
'https://etud.insa-toulouse.fr/~amicale_app/images/Proximo.png';
const WIKETUD_IMAGE =
'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
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 ROOM_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Salles.png";
const EMAIL_IMAGE = "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";
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 ROOM_IMAGE =
'https://etud.insa-toulouse.fr/~amicale_app/images/Salles.png';
const EMAIL_IMAGE =
'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
const WASHER_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/ProxiwashLaveLinge.png";
const DRYER_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/ProxiwashSecheLinge.png";
const WASHER_IMAGE =
'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 = {
CLUBS: "clubs",
PROFILE: "profile",
EQUIPMENT: "equipment",
AMICALE_WEBSITE: "amicale_website",
VOTE: "vote",
PROXIMO: "proximo",
WIKETUD: "wiketud",
ELUS_ETUDIANTS: "elus_etudiants",
TUTOR_INSA: "tutor_insa",
RU: "ru",
AVAILABLE_ROOMS: "available_rooms",
BIB: "bib",
EMAIL: "email",
ENT: "ent",
INSA_ACCOUNT: "insa_account",
WASHERS: "washers",
DRYERS: "dryers",
}
CLUBS: 'clubs',
PROFILE: 'profile',
EQUIPMENT: 'equipment',
AMICALE_WEBSITE: 'amicale_website',
VOTE: 'vote',
PROXIMO: 'proximo',
WIKETUD: 'wiketud',
ELUS_ETUDIANTS: 'elus_etudiants',
TUTOR_INSA: 'tutor_insa',
RU: 'ru',
AVAILABLE_ROOMS: 'available_rooms',
BIB: 'bib',
EMAIL: 'email',
ENT: 'ent',
INSA_ACCOUNT: 'insa_account',
WASHERS: 'washers',
DRYERS: 'dryers',
};
export const SERVICES_CATEGORIES_KEY = {
AMICALE: "amicale",
STUDENTS: "students",
INSA: "insa",
SPECIAL: "special",
}
AMICALE: 'amicale',
STUDENTS: 'students',
INSA: 'insa',
SPECIAL: 'special',
};
export type ServiceItemType = {
key: string,
title: string,
subtitle: string,
image: string,
onPress: () => void,
badgeFunction?: (dashboard: FullDashboardType) => number,
};
export type ServiceItem = {
key: string,
title: string,
subtitle: string,
image: string,
onPress: () => void,
badgeFunction?: (dashboard: fullDashboard) => number,
}
export type ServiceCategory = {
key: string,
title: string,
subtitle: string,
image: string | number,
content: Array<ServiceItem>
}
export type ServiceCategoryType = {
key: string,
title: string,
subtitle: string,
image: string | number,
content: Array<ServiceItemType>,
};
export default class ServicesManager {
navigation: StackNavigationProp;
navigation: StackNavigationProp;
amicaleDataset: Array<ServiceItemType>;
amicaleDataset: Array<ServiceItem>;
studentsDataset: Array<ServiceItem>;
insaDataset: Array<ServiceItem>;
specialDataset: Array<ServiceItem>;
studentsDataset: Array<ServiceItemType>;
categoriesDataset: Array<ServiceCategory>;
insaDataset: Array<ServiceItemType>;
constructor(nav: StackNavigationProp) {
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
},
];
}
specialDataset: Array<ServiceItemType>;
/**
* 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});
}
categoriesDataset: Array<ServiceCategoryType>;
/**
* 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;
}
constructor(nav: StackNavigationProp) {
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: (): void => 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: (): 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
*
* @param excludedItems Ids of items to exclude from the returned list
* @returns {Array<ServiceItem>}
*/
getAmicaleServices(excludedItems?: Array<string>) {
if (excludedItems != null)
return this.getStrippedList(excludedItems, this.amicaleDataset)
else
return this.amicaleDataset;
}
/**
* 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});
}
/**
* Gets the list of students' services
*
* @param excludedItems Ids of items to exclude from the returned list
* @returns {Array<ServiceItem>}
*/
getStudentServices(excludedItems?: Array<string>) {
if (excludedItems != null)
return this.getStrippedList(excludedItems, this.studentsDataset)
else
return this.studentsDataset;
}
/**
* Gets the list of amicale's services
*
* @param excludedItems Ids of items to exclude from the returned list
* @returns {Array<ServiceItemType>}
*/
getAmicaleServices(excludedItems?: Array<string>): Array<ServiceItemType> {
if (excludedItems != null)
return getStrippedServicesList(excludedItems, this.amicaleDataset);
return this.amicaleDataset;
}
/**
* Gets the list of INSA's services
*
* @param excludedItems Ids of items to exclude from the returned list
* @returns {Array<ServiceItem>}
*/
getINSAServices(excludedItems?: Array<string>) {
if (excludedItems != null)
return this.getStrippedList(excludedItems, this.insaDataset)
else
return this.insaDataset;
}
/**
* Gets the list of students' services
*
* @param excludedItems Ids of items to exclude from the returned list
* @returns {Array<ServiceItemType>}
*/
getStudentServices(excludedItems?: Array<string>): Array<ServiceItemType> {
if (excludedItems != null)
return getStrippedServicesList(excludedItems, this.studentsDataset);
return this.studentsDataset;
}
/**
* Gets the list of special services
*
* @param excludedItems Ids of items to exclude from the returned list
* @returns {Array<ServiceItem>}
*/
getSpecialServices(excludedItems?: Array<string>) {
if (excludedItems != null)
return this.getStrippedList(excludedItems, this.specialDataset)
else
return this.specialDataset;
}
/**
* Gets the list of INSA's services
*
* @param excludedItems Ids of items to exclude from the returned list
* @returns {Array<ServiceItemType>}
*/
getINSAServices(excludedItems?: Array<string>): Array<ServiceItemType> {
if (excludedItems != null)
return getStrippedServicesList(excludedItems, this.insaDataset);
return this.insaDataset;
}
/**
* Gets all services sorted by category
*
* @param excludedItems Ids of categories to exclude from the returned list
* @returns {Array<ServiceCategory>}
*/
getCategories(excludedItems?: Array<string>) {
if (excludedItems != null)
return this.getStrippedList(excludedItems, this.categoriesDataset)
else
return this.categoriesDataset;
}
/**
* Gets the list of special services
*
* @param excludedItems Ids of items to exclude from the returned list
* @returns {Array<ServiceItemType>}
*/
getSpecialServices(excludedItems?: Array<string>): Array<ServiceItemType> {
if (excludedItems != null)
return getStrippedServicesList(excludedItems, this.specialDataset);
return this.specialDataset;
}
/**
* 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 MascotPopup from '../../components/Mascot/MascotPopup';
import DashboardManager from '../../managers/DashboardManager';
import type {ServiceItem} from '../../managers/ServicesManager';
import type {ServiceItemType} from '../../managers/ServicesManager';
import {getDisplayEvent, getFutureEvents} from '../../utils/Home';
// import DATA from "../dashboard_data.json";
@ -230,7 +230,7 @@ class HomeScreen extends React.Component<PropsType, StateType> {
* @param content
* @return {*}
*/
getDashboardRow(content: Array<ServiceItem | null>): React.Node {
getDashboardRow(content: Array<ServiceItemType | null>): React.Node {
return (
// $FlowFixMe
<FlatList
@ -256,7 +256,7 @@ class HomeScreen extends React.Component<PropsType, StateType> {
getDashboardRowRenderItem = ({
item,
}: {
item: ServiceItem | null,
item: ServiceItemType | null,
}): React.Node => {
if (item != null)
return (

View file

@ -1,149 +1,162 @@
// @flow
import * as React from 'react';
import type {cardList} from "../../components/Lists/CardList/CardList";
import CardList from "../../components/Lists/CardList/CardList";
import {Image, View} from "react-native";
import {Avatar, Card, Divider, List, TouchableRipple, withTheme} from "react-native-paper";
import type {CustomTheme} from "../../managers/ThemeManager";
import {Image, View} from 'react-native';
import {
Avatar,
Card,
Divider,
List,
TouchableRipple,
withTheme,
} from 'react-native-paper';
import i18n from 'i18n-js';
import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton";
import {StackNavigationProp} from "@react-navigation/stack";
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 {StackNavigationProp} from '@react-navigation/stack';
import CardList from '../../components/Lists/CardList/CardList';
import type {CustomTheme} from '../../managers/ThemeManager';
import MaterialHeaderButtons, {
Item,
} from '../../components/Overrides/CustomHeaderButton';
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 = {
navigation: StackNavigationProp,
theme: CustomTheme,
}
type PropsType = {
navigation: StackNavigationProp,
theme: CustomTheme,
};
export type listItem = {
title: string,
description: string,
image: string | number,
content: cardList,
}
class ServicesScreen extends React.Component<PropsType> {
finalDataset: Array<ServiceCategoryType>;
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) {
super(props);
const services = new ServicesManager(props.navigation);
this.finalDataset = services.getCategories([SERVICES_CATEGORIES_KEY.SPECIAL])
}
onAboutPress = () => {
const {props} = this;
props.navigation.navigate('amicale-contact');
};
componentDidMount() {
this.props.navigation.setOptions({
headerRight: this.getAboutButton,
});
}
/**
* Gets the list title image for the list.
*
* 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>
<Item title="information" iconName="information" onPress={this.onAboutPress}/>
</MaterialHeaderButtons>;
/**
* A list item showing a list of available services for the current category
*
* @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;
/**
* Gets the list title image for the list.
*
* If the source is a string, we are using an icon.
* 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
* @returns {*}
*/
getListTitleImage(props, source: string | number) {
if (typeof source === "number")
return <Image
size={48}
source={source}
style={{
width: 48,
height: 48,
}}/>
else
return <Avatar.Icon
{...props}
size={48}
icon={source}
color={this.props.theme.colors.primary}
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>
);
}
render(): React.Node {
return (
<View>
<CollapsibleFlatList
data={this.finalDataset}
renderItem={this.getRenderItem}
keyExtractor={this.keyExtractor}
ItemSeparatorComponent={(): React.Node => <Divider />}
hasTab
/>
<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',
},
}}
emotion={MASCOT_STYLE.WINK}
/>
</View>
);
}
}
export default withTheme(ServicesScreen);

View file

@ -1,58 +1,65 @@
// @flow
import * as React from 'react';
import CardList from "../../components/Lists/CardList/CardList";
import CustomTabBar from "../../components/Tabbar/CustomTabBar";
import {withCollapsible} from "../../utils/withCollapsible";
import {Collapsible} from "react-navigation-collapsible";
import {CommonActions} from "@react-navigation/native";
import type {listItem} from "./ServicesScreen";
import {StackNavigationProp} from "@react-navigation/stack";
import {Collapsible} from 'react-navigation-collapsible';
import {CommonActions} from '@react-navigation/native';
import {StackNavigationProp} from '@react-navigation/stack';
import CardList from '../../components/Lists/CardList/CardList';
import CustomTabBar from '../../components/Tabbar/CustomTabBar';
import {withCollapsible} from '../../utils/withCollapsible';
import type {ServiceCategoryType} from '../../managers/ServicesManager';
type Props = {
navigation: StackNavigationProp,
route: { params: { data: listItem | null } },
collapsibleStack: Collapsible,
}
type PropsType = {
navigation: StackNavigationProp,
route: {params: {data: ServiceCategoryType | null}},
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);
this.handleNavigationParams();
/**
* Recover the list to display from navigation parameters
*/
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,
});
}
}
}
/**
* Recover the list to display from navigation parameters
*/
handleNavigationParams() {
if (this.props.route.params != null) {
if (this.props.route.params.data != null) {
this.finalDataset = this.props.route.params.data;
// reset params to prevent infinite loop
this.props.navigation.dispatch(CommonActions.setParams({data: null}));
this.props.navigation.setOptions({
headerTitle: this.finalDataset.title,
});
}
}
}
render() {
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}}
/>
}
render(): React.Node {
const {props} = this;
const {
containerPaddingTop,
scrollIndicatorInsetTop,
onScroll,
} = 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);

19
src/utils/Services.js Normaal bestaand
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;
}