Compare commits

..

No commits in common. "5977ce257b458c4d5d2d955cdc4788c0790d1a0b" and "172b7e81877b9e9634e6759bfbaaa16b697f4de5" have entirely different histories.

8 changed files with 252 additions and 258 deletions

View file

@ -27,7 +27,7 @@ import type {
type PropsType = { type PropsType = {
onPress: () => void; onPress: () => void;
categoryTranslator: (id: number) => ClubCategoryType | null; categoryTranslator: (id: number) => ClubCategoryType;
item: ClubType; item: ClubType;
height: number; height: number;
theme: ReactNativePaper.Theme; theme: ReactNativePaper.Theme;
@ -50,16 +50,14 @@ class ClubListItem extends React.Component<PropsType> {
const final: Array<React.ReactNode> = []; const final: Array<React.ReactNode> = [];
categories.forEach((cat: number | null) => { categories.forEach((cat: number | null) => {
if (cat != null) { if (cat != null) {
const category = props.categoryTranslator(cat); const category: ClubCategoryType = props.categoryTranslator(cat);
if (category) { final.push(
final.push( <Chip
<Chip style={{marginRight: 5, marginBottom: 5}}
style={{marginRight: 5, marginBottom: 5}} key={`${props.item.id}:${category.id}`}>
key={`${props.item.id}:${category.id}`}> {category.name}
{category.name} </Chip>,
</Chip>, );
);
}
} }
}); });
return <View style={{flexDirection: 'row'}}>{final}</View>; return <View style={{flexDirection: 'row'}}>{final}</View>;

View file

@ -17,6 +17,8 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow
import * as React from 'react'; import * as React from 'react';
import {List} from 'react-native-paper'; import {List} from 'react-native-paper';
import {View} from 'react-native-animatable'; import {View} from 'react-native-animatable';
@ -24,8 +26,8 @@ import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatLis
import packageJson from '../../../package.json'; import packageJson from '../../../package.json';
type ListItemType = { type ListItemType = {
name: string; name: string,
version: string; version: string,
}; };
/** /**
@ -35,9 +37,9 @@ type ListItemType = {
* @return {Array<ListItemType>} * @return {Array<ListItemType>}
*/ */
function generateListFromObject(object: { function generateListFromObject(object: {
[key: string]: string; [key: string]: string,
}): Array<ListItemType> { }): Array<ListItemType> {
const list: Array<ListItemType> = []; const list = [];
const keys = Object.keys(object); const keys = Object.keys(object);
keys.forEach((key: string) => { keys.forEach((key: string) => {
list.push({name: key, version: object[key]}); list.push({name: key, version: object[key]});
@ -54,13 +56,13 @@ export default class AboutDependenciesScreen extends React.Component<null> {
data: Array<ListItemType>; data: Array<ListItemType>;
constructor() { constructor() {
super(null); super();
this.data = generateListFromObject(packageJson.dependencies); this.data = generateListFromObject(packageJson.dependencies);
} }
keyExtractor = (item: ListItemType): string => item.name; keyExtractor = (item: ListItemType): string => item.name;
getRenderItem = ({item}: {item: ListItemType}) => ( getRenderItem = ({item}: {item: ListItemType}): React.Node => (
<List.Item <List.Item
title={item.name} title={item.name}
description={item.version.replace('^', '').replace('~', '')} description={item.version.replace('^', '').replace('~', '')}
@ -69,15 +71,15 @@ export default class AboutDependenciesScreen extends React.Component<null> {
); );
getItemLayout = ( getItemLayout = (
data: Array<ListItemType> | null | undefined, data: ListItemType,
index: number, index: number,
): {length: number; offset: number; index: number} => ({ ): {length: number, offset: number, index: number} => ({
length: LIST_ITEM_HEIGHT, length: LIST_ITEM_HEIGHT,
offset: LIST_ITEM_HEIGHT * index, offset: LIST_ITEM_HEIGHT * index,
index, index,
}); });
render() { render(): React.Node {
return ( return (
<View> <View>
<CollapsibleFlatList <CollapsibleFlatList

View file

@ -17,32 +17,37 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow
import * as React from 'react'; import * as React from 'react';
import {FlatList, Linking, Platform, Image, View} from 'react-native'; import {FlatList, Linking, Platform, Image, View} from 'react-native';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import {Avatar, Card, List} from 'react-native-paper'; import {Avatar, Card, List, withTheme} from 'react-native-paper';
import {StackNavigationProp} from '@react-navigation/stack'; import {StackNavigationProp} from '@react-navigation/stack';
import packageJson from '../../../package.json'; import packageJson from '../../../package.json';
import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList'; import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
import APP_LOGO from '../../../assets/android.icon.round.png';
import type {
CardTitleIconPropsType,
ListIconPropsType,
} from '../../constants/PaperStyles';
import OptionsDialog from '../../components/Dialogs/OptionsDialog'; import OptionsDialog from '../../components/Dialogs/OptionsDialog';
import type {OptionsDialogButtonType} from '../../components/Dialogs/OptionsDialog'; import type {OptionsDialogButtonType} from '../../components/Dialogs/OptionsDialog';
const APP_LOGO = require('../../../assets/android.icon.round.png');
type ListItemType = { type ListItemType = {
onPressCallback: () => void; onPressCallback: () => void,
icon: string; icon: string,
text: string; text: string,
showChevron: boolean; showChevron: boolean,
}; };
type MemberItemType = { type MemberItemType = {
name: string; name: string,
message: string; message: string,
icon: string; icon: string,
trollLink?: string; trollLink?: string,
linkedin?: string; linkedin?: string,
mail?: string; mail?: string,
}; };
const links = { const links = {
@ -59,14 +64,14 @@ const links = {
}; };
type PropsType = { type PropsType = {
navigation: StackNavigationProp<any>; navigation: StackNavigationProp,
}; };
type StateType = { type StateType = {
dialogVisible: boolean; dialogVisible: boolean,
dialogTitle: string; dialogTitle: string,
dialogMessage: string; dialogMessage: string,
dialogButtons: Array<OptionsDialogButtonType>; dialogButtons: Array<OptionsDialogButtonType>,
}; };
/** /**
@ -81,6 +86,7 @@ function openWebLink(link: string) {
* Class defining an about screen. This screen shows the user information about the app and it's author. * Class defining an about screen. This screen shows the user information about the app and it's author.
*/ */
class AboutScreen extends React.Component<PropsType, StateType> { class AboutScreen extends React.Component<PropsType, StateType> {
/** /**
* Object containing data relative to major contributors * Object containing data relative to major contributors
*/ */
@ -103,8 +109,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
message: i18n.t('screens.about.user.yohan'), message: i18n.t('screens.about.user.yohan'),
icon: 'xml', icon: 'xml',
linkedin: 'https://www.linkedin.com/in/yohan-simard', linkedin: 'https://www.linkedin.com/in/yohan-simard',
mail: mail: 'mailto:ysimard@etud.insa-toulouse.fr?' +
'mailto:ysimard@etud.insa-toulouse.fr?' +
'subject=' + 'subject=' +
'Application Amicale INSA Toulouse' + 'Application Amicale INSA Toulouse' +
'&body=' + '&body=' +
@ -328,7 +333,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
* @param user The member to show information for * @param user The member to show information for
*/ */
onContributorListItemPress(user: MemberItemType) { onContributorListItemPress(user: MemberItemType) {
const dialogBtn: Array<OptionsDialogButtonType> = [ const dialogBtn = [
{ {
title: 'OK', title: 'OK',
onPress: this.onDialogDismiss, onPress: this.onDialogDismiss,
@ -374,14 +379,15 @@ class AboutScreen extends React.Component<PropsType, StateType> {
* *
* @return {*} * @return {*}
*/ */
getAppCard() { getAppCard(): React.Node {
return ( return (
<Card style={{marginBottom: 10}}> <Card style={{marginBottom: 10}}>
<Card.Title <Card.Title
title="Campus" title="Campus"
subtitle={packageJson.version} subtitle={packageJson.version}
left={(iconProps) => ( left={(iconProps: CardTitleIconPropsType): React.Node => (
<Image <Image
size={iconProps.size}
source={APP_LOGO} source={APP_LOGO}
style={{width: iconProps.size, height: iconProps.size}} style={{width: iconProps.size, height: iconProps.size}}
/> />
@ -403,12 +409,12 @@ class AboutScreen extends React.Component<PropsType, StateType> {
* *
* @return {*} * @return {*}
*/ */
getTeamCard() { getTeamCard(): React.Node {
return ( return (
<Card style={{marginBottom: 10}}> <Card style={{marginBottom: 10}}>
<Card.Title <Card.Title
title={i18n.t('screens.about.team')} title={i18n.t('screens.about.team')}
left={(iconProps) => ( left={(iconProps: CardTitleIconPropsType): React.Node => (
<Avatar.Icon size={iconProps.size} icon="account-multiple" /> <Avatar.Icon size={iconProps.size} icon="account-multiple" />
)} )}
/> />
@ -428,12 +434,12 @@ class AboutScreen extends React.Component<PropsType, StateType> {
* *
* @return {*} * @return {*}
*/ */
getThanksCard() { getThanksCard(): React.Node {
return ( return (
<Card style={{marginBottom: 10}}> <Card style={{marginBottom: 10}}>
<Card.Title <Card.Title
title={i18n.t('screens.about.thanks')} title={i18n.t('screens.about.thanks')}
left={(iconProps) => ( left={(iconProps: CardTitleIconPropsType): React.Node => (
<Avatar.Icon size={iconProps.size} icon="hand-heart" /> <Avatar.Icon size={iconProps.size} icon="hand-heart" />
)} )}
/> />
@ -453,12 +459,12 @@ class AboutScreen extends React.Component<PropsType, StateType> {
* *
* @return {*} * @return {*}
*/ */
getTechnoCard() { getTechnoCard(): React.Node {
return ( return (
<Card style={{marginBottom: 10}}> <Card style={{marginBottom: 10}}>
<Card.Title <Card.Title
title={i18n.t('screens.about.technologies')} title={i18n.t('screens.about.technologies')}
left={(iconProps) => ( left={(iconProps: CardTitleIconPropsType): React.Node => (
<Avatar.Icon size={iconProps.size} icon="wrench" /> <Avatar.Icon size={iconProps.size} icon="wrench" />
)} )}
/> />
@ -479,13 +485,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
* @param props * @param props
* @return {*} * @return {*}
*/ */
static getChevronIcon(props: { static getChevronIcon(props: ListIconPropsType): React.Node {
color: string;
style?: {
marginRight: number;
marginVertical?: number;
};
}) {
return ( return (
<List.Icon color={props.color} style={props.style} icon="chevron-right" /> <List.Icon color={props.color} style={props.style} icon="chevron-right" />
); );
@ -498,16 +498,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
* @param props * @param props
* @return {*} * @return {*}
*/ */
static getItemIcon( static getItemIcon(item: ListItemType, props: ListIconPropsType): React.Node {
item: ListItemType,
props: {
color: string;
style?: {
marginRight: number;
marginVertical?: number;
};
},
) {
return ( return (
<List.Icon color={props.color} style={props.style} icon={item.icon} /> <List.Icon color={props.color} style={props.style} icon={item.icon} />
); );
@ -518,14 +509,9 @@ class AboutScreen extends React.Component<PropsType, StateType> {
* *
* @returns {*} * @returns {*}
*/ */
getCardItem = ({item}: {item: ListItemType}) => { getCardItem = ({item}: {item: ListItemType}): React.Node => {
const getItemIcon = (props: { const getItemIcon = (props: ListIconPropsType): React.Node =>
color: string; AboutScreen.getItemIcon(item, props);
style?: {
marginRight: number;
marginVertical?: number;
};
}) => AboutScreen.getItemIcon(item, props);
if (item.showChevron) { if (item.showChevron) {
return ( return (
<List.Item <List.Item
@ -551,7 +537,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
* @param item The item to show * @param item The item to show
* @return {*} * @return {*}
*/ */
getMainCard = ({item}: {item: {id: string}}) => { getMainCard = ({item}: {item: {id: string}}): React.Node => {
switch (item.id) { switch (item.id) {
case 'app': case 'app':
return this.getAppCard(); return this.getAppCard();
@ -578,7 +564,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
*/ */
keyExtractor = (item: ListItemType): string => item.icon; keyExtractor = (item: ListItemType): string => item.icon;
render() { render(): React.Node {
const {state} = this; const {state} = this;
return ( return (
<View <View
@ -602,4 +588,4 @@ class AboutScreen extends React.Component<PropsType, StateType> {
} }
} }
export default AboutScreen; export default withTheme(AboutScreen);

View file

@ -17,6 +17,8 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow
import * as React from 'react'; import * as React from 'react';
import {View} from 'react-native'; import {View} from 'react-native';
import { import {
@ -30,21 +32,22 @@ import {
import {Modalize} from 'react-native-modalize'; import {Modalize} from 'react-native-modalize';
import CustomModal from '../../components/Overrides/CustomModal'; import CustomModal from '../../components/Overrides/CustomModal';
import AsyncStorageManager from '../../managers/AsyncStorageManager'; import AsyncStorageManager from '../../managers/AsyncStorageManager';
import type {CustomThemeType} from '../../managers/ThemeManager';
import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList'; import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
type PreferenceItemType = { type PreferenceItemType = {
key: string; key: string,
default: string; default: string,
current: string; current: string,
}; };
type PropsType = { type PropsType = {
theme: ReactNativePaper.Theme; theme: CustomThemeType,
}; };
type StateType = { type StateType = {
modalCurrentDisplayItem: PreferenceItemType | null; modalCurrentDisplayItem: PreferenceItemType,
currentPreferences: Array<PreferenceItemType>; currentPreferences: Array<PreferenceItemType>,
}; };
/** /**
@ -52,7 +55,7 @@ type StateType = {
* This screen allows the user to get and modify information on the app/device. * This screen allows the user to get and modify information on the app/device.
*/ */
class DebugScreen extends React.Component<PropsType, StateType> { class DebugScreen extends React.Component<PropsType, StateType> {
modalRef: Modalize | null; modalRef: Modalize;
modalInputValue: string; modalInputValue: string;
@ -63,16 +66,16 @@ class DebugScreen extends React.Component<PropsType, StateType> {
*/ */
constructor(props: PropsType) { constructor(props: PropsType) {
super(props); super(props);
this.modalRef = null;
this.modalInputValue = ''; this.modalInputValue = '';
const currentPreferences: Array<PreferenceItemType> = []; const currentPreferences: Array<PreferenceItemType> = [];
// eslint-disable-next-line flowtype/no-weak-types
Object.values(AsyncStorageManager.PREFERENCES).forEach((object: any) => { Object.values(AsyncStorageManager.PREFERENCES).forEach((object: any) => {
const newObject: PreferenceItemType = {...object}; const newObject: PreferenceItemType = {...object};
newObject.current = AsyncStorageManager.getString(newObject.key); newObject.current = AsyncStorageManager.getString(newObject.key);
currentPreferences.push(newObject); currentPreferences.push(newObject);
}); });
this.state = { this.state = {
modalCurrentDisplayItem: null, modalCurrentDisplayItem: {},
currentPreferences, currentPreferences,
}; };
} }
@ -82,27 +85,21 @@ class DebugScreen extends React.Component<PropsType, StateType> {
* *
* @return {*} * @return {*}
*/ */
getModalContent() { getModalContent(): React.Node {
const {props, state} = this; const {props, state} = this;
let key = '';
let defaultValue = '';
let current = '';
if (state.modalCurrentDisplayItem) {
key = state.modalCurrentDisplayItem.key;
defaultValue = state.modalCurrentDisplayItem.default;
defaultValue = state.modalCurrentDisplayItem.default;
current = state.modalCurrentDisplayItem.current;
}
return ( return (
<View <View
style={{ style={{
flex: 1, flex: 1,
padding: 20, padding: 20,
}}> }}>
<Title>{key}</Title> <Title>{state.modalCurrentDisplayItem.key}</Title>
<Subheading>Default: {defaultValue}</Subheading> <Subheading>
<Subheading>Current: {current}</Subheading> Default: {state.modalCurrentDisplayItem.default}
</Subheading>
<Subheading>
Current: {state.modalCurrentDisplayItem.current}
</Subheading>
<TextInput <TextInput
label="New Value" label="New Value"
onChangeText={(text: string) => { onChangeText={(text: string) => {
@ -119,7 +116,10 @@ class DebugScreen extends React.Component<PropsType, StateType> {
dark dark
color={props.theme.colors.success} color={props.theme.colors.success}
onPress={() => { onPress={() => {
this.saveNewPrefs(key, this.modalInputValue); this.saveNewPrefs(
state.modalCurrentDisplayItem.key,
this.modalInputValue,
);
}}> }}>
Save new value Save new value
</Button> </Button>
@ -128,7 +128,10 @@ class DebugScreen extends React.Component<PropsType, StateType> {
dark dark
color={props.theme.colors.danger} color={props.theme.colors.danger}
onPress={() => { onPress={() => {
this.saveNewPrefs(key, defaultValue); this.saveNewPrefs(
state.modalCurrentDisplayItem.key,
state.modalCurrentDisplayItem.default,
);
}}> }}>
Reset to default Reset to default
</Button> </Button>
@ -137,7 +140,7 @@ class DebugScreen extends React.Component<PropsType, StateType> {
); );
} }
getRenderItem = ({item}: {item: PreferenceItemType}) => { getRenderItem = ({item}: {item: PreferenceItemType}): React.Node => {
return ( return (
<List.Item <List.Item
title={item.key} title={item.key}
@ -167,9 +170,7 @@ class DebugScreen extends React.Component<PropsType, StateType> {
this.setState({ this.setState({
modalCurrentDisplayItem: item, modalCurrentDisplayItem: item,
}); });
if (this.modalRef) { if (this.modalRef) this.modalRef.open();
this.modalRef.open();
}
} }
/** /**
@ -198,25 +199,24 @@ class DebugScreen extends React.Component<PropsType, StateType> {
*/ */
saveNewPrefs(key: string, value: string) { saveNewPrefs(key: string, value: string) {
this.setState((prevState: StateType): { this.setState((prevState: StateType): {
currentPreferences: Array<PreferenceItemType>; currentPreferences: Array<PreferenceItemType>,
} => { } => {
const currentPreferences = [...prevState.currentPreferences]; const currentPreferences = [...prevState.currentPreferences];
currentPreferences[this.findIndexOfKey(key)].current = value; currentPreferences[this.findIndexOfKey(key)].current = value;
return {currentPreferences}; return {currentPreferences};
}); });
AsyncStorageManager.set(key, value); AsyncStorageManager.set(key, value);
if (this.modalRef) { this.modalRef.close();
this.modalRef.close();
}
} }
render() { render(): React.Node {
const {state} = this; const {state} = this;
return ( return (
<View> <View>
<CustomModal onRef={this.onModalRef}> <CustomModal onRef={this.onModalRef}>
{this.getModalContent()} {this.getModalContent()}
</CustomModal> </CustomModal>
{/* $FlowFixMe */}
<CollapsibleFlatList <CollapsibleFlatList
data={state.currentPreferences} data={state.currentPreferences}
extraData={state.currentPreferences} extraData={state.currentPreferences}

View file

@ -0,0 +1,72 @@
/*
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
*
* This file is part of Campus INSAT.
*
* Campus INSAT is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Campus INSAT is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @flow
import * as React from 'react';
import {Image, View} from 'react-native';
import {Card, Avatar, Text, withTheme} from 'react-native-paper';
import i18n from 'i18n-js';
import Autolink from 'react-native-autolink';
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
import AMICALE_ICON from '../../../../assets/amicale.png';
import type {CardTitleIconPropsType} from '../../../constants/PaperStyles';
const CONTACT_LINK = 'clubs@amicale-insat.fr';
// eslint-disable-next-line react/prefer-stateless-function
class ClubAboutScreen extends React.Component<null> {
render(): React.Node {
return (
<CollapsibleScrollView style={{padding: 5}}>
<View
style={{
width: '100%',
height: 100,
marginTop: 20,
marginBottom: 20,
justifyContent: 'center',
alignItems: 'center',
}}>
<Image
source={AMICALE_ICON}
style={{flex: 1, resizeMode: 'contain'}}
resizeMode="contain"
/>
</View>
<Text>{i18n.t('screens.clubs.about.text')}</Text>
<Card style={{margin: 5}}>
<Card.Title
title={i18n.t('screens.clubs.about.title')}
subtitle={i18n.t('screens.clubs.about.subtitle')}
left={(iconProps: CardTitleIconPropsType): React.Node => (
<Avatar.Icon size={iconProps.size} icon="information" />
)}
/>
<Card.Content>
<Text>{i18n.t('screens.clubs.about.message')}</Text>
<Autolink text={CONTACT_LINK} component={Text} />
</Card.Content>
</Card>
</CollapsibleScrollView>
);
}
}
export default withTheme(ClubAboutScreen);

View file

@ -1,66 +0,0 @@
/*
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
*
* This file is part of Campus INSAT.
*
* Campus INSAT is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Campus INSAT is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
import * as React from 'react';
import {Image, View} from 'react-native';
import {Card, Avatar, Text} from 'react-native-paper';
import i18n from 'i18n-js';
import Autolink from 'react-native-autolink';
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
const AMICALE_ICON = require('../../../../assets/amicale.png');
const CONTACT_LINK = 'clubs@amicale-insat.fr';
function ClubAboutScreen() {
return (
<CollapsibleScrollView style={{padding: 5}}>
<View
style={{
width: '100%',
height: 100,
marginTop: 20,
marginBottom: 20,
justifyContent: 'center',
alignItems: 'center',
}}>
<Image
source={AMICALE_ICON}
style={{flex: 1, resizeMode: 'contain'}}
resizeMode="contain"
/>
</View>
<Text>{i18n.t('screens.clubs.about.text')}</Text>
<Card style={{margin: 5}}>
<Card.Title
title={i18n.t('screens.clubs.about.title')}
subtitle={i18n.t('screens.clubs.about.subtitle')}
left={(iconProps) => (
<Avatar.Icon size={iconProps.size} icon="information" />
)}
/>
<Card.Content>
<Text>{i18n.t('screens.clubs.about.message')}</Text>
<Autolink<typeof Text> text={CONTACT_LINK} component={Text} />
</Card.Content>
</Card>
</CollapsibleScrollView>
);
}
export default ClubAboutScreen;

View file

@ -17,6 +17,8 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow
import * as React from 'react'; import * as React from 'react';
import {Linking, View} from 'react-native'; import {Linking, View} from 'react-native';
import { import {
@ -33,20 +35,24 @@ import AuthenticatedScreen from '../../../components/Amicale/AuthenticatedScreen
import CustomHTML from '../../../components/Overrides/CustomHTML'; import CustomHTML from '../../../components/Overrides/CustomHTML';
import CustomTabBar from '../../../components/Tabbar/CustomTabBar'; import CustomTabBar from '../../../components/Tabbar/CustomTabBar';
import type {ClubCategoryType, ClubType} from './ClubListScreen'; import type {ClubCategoryType, ClubType} from './ClubListScreen';
import type {CustomThemeType} from '../../../managers/ThemeManager';
import {ERROR_TYPE} from '../../../utils/WebData'; import {ERROR_TYPE} from '../../../utils/WebData';
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView'; import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
import type {ApiGenericDataType} from '../../../utils/WebData';
import type {CardTitleIconPropsType} from '../../../constants/PaperStyles';
import ImageGalleryButton from '../../../components/Media/ImageGalleryButton'; import ImageGalleryButton from '../../../components/Media/ImageGalleryButton';
type PropsType = { type PropsType = {
navigation: StackNavigationProp<any>; navigation: StackNavigationProp,
route: { route: {
params?: { params?: {
data?: ClubType; data?: ClubType,
categories?: Array<ClubCategoryType>; categories?: Array<ClubCategoryType>,
clubId?: number; clubId?: number,
}; },
}; ...
theme: ReactNativePaper.Theme; },
theme: CustomThemeType,
}; };
const AMICALE_MAIL = 'clubs@amicale-insat.fr'; const AMICALE_MAIL = 'clubs@amicale-insat.fr';
@ -67,20 +73,21 @@ class ClubDisplayScreen extends React.Component<PropsType> {
constructor(props: PropsType) { constructor(props: PropsType) {
super(props); super(props);
this.displayData = null; if (props.route.params != null) {
this.categories = null; if (
this.clubId = props.route.params?.clubId ? props.route.params.clubId : 0; props.route.params.data != null &&
this.shouldFetchData = true; props.route.params.categories != null
) {
if ( this.displayData = props.route.params.data;
props.route.params && this.categories = props.route.params.categories;
props.route.params.data && this.clubId = props.route.params.data.id;
props.route.params.categories this.shouldFetchData = false;
) { } else if (props.route.params.clubId != null) {
this.displayData = props.route.params.data; this.displayData = null;
this.categories = props.route.params.categories; this.categories = null;
this.clubId = props.route.params.data.id; this.clubId = props.route.params.clubId;
this.shouldFetchData = false; this.shouldFetchData = true;
}
} }
} }
@ -94,9 +101,7 @@ class ClubDisplayScreen extends React.Component<PropsType> {
let categoryName = ''; let categoryName = '';
if (this.categories !== null) { if (this.categories !== null) {
this.categories.forEach((item: ClubCategoryType) => { this.categories.forEach((item: ClubCategoryType) => {
if (id === item.id) { if (id === item.id) categoryName = item.name;
categoryName = item.name;
}
}); });
} }
return categoryName; return categoryName;
@ -108,12 +113,10 @@ class ClubDisplayScreen extends React.Component<PropsType> {
* @param categories The categories to display (max 2) * @param categories The categories to display (max 2)
* @returns {null|*} * @returns {null|*}
*/ */
getCategoriesRender(categories: Array<number | null>) { getCategoriesRender(categories: Array<number | null>): React.Node {
if (this.categories == null) { if (this.categories == null) return null;
return null;
}
const final: Array<React.ReactNode> = []; const final = [];
categories.forEach((cat: number | null) => { categories.forEach((cat: number | null) => {
if (cat != null) { if (cat != null) {
final.push( final.push(
@ -133,9 +136,9 @@ class ClubDisplayScreen extends React.Component<PropsType> {
* @param email The club contact email * @param email The club contact email
* @returns {*} * @returns {*}
*/ */
getManagersRender(managers: Array<string>, email: string | null) { getManagersRender(managers: Array<string>, email: string | null): React.Node {
const {props} = this; const {props} = this;
const managersListView: Array<React.ReactNode> = []; const managersListView = [];
managers.forEach((item: string) => { managers.forEach((item: string) => {
managersListView.push(<Paragraph key={item}>{item}</Paragraph>); managersListView.push(<Paragraph key={item}>{item}</Paragraph>);
}); });
@ -150,7 +153,7 @@ class ClubDisplayScreen extends React.Component<PropsType> {
? i18n.t('screens.clubs.managersSubtitle') ? i18n.t('screens.clubs.managersSubtitle')
: i18n.t('screens.clubs.managersUnavailable') : i18n.t('screens.clubs.managersUnavailable')
} }
left={(iconProps) => ( left={(iconProps: CardTitleIconPropsType): React.Node => (
<Avatar.Icon <Avatar.Icon
size={iconProps.size} size={iconProps.size}
style={{backgroundColor: 'transparent'}} style={{backgroundColor: 'transparent'}}
@ -178,7 +181,10 @@ class ClubDisplayScreen extends React.Component<PropsType> {
* @param hasManagers True if the club has managers * @param hasManagers True if the club has managers
* @returns {*} * @returns {*}
*/ */
static getEmailButton(email: string | null, hasManagers: boolean) { static getEmailButton(
email: string | null,
hasManagers: boolean,
): React.Node {
const destinationEmail = const destinationEmail =
email != null && hasManagers ? email : AMICALE_MAIL; email != null && hasManagers ? email : AMICALE_MAIL;
const text = const text =
@ -200,15 +206,20 @@ class ClubDisplayScreen extends React.Component<PropsType> {
); );
} }
getScreen = (response: Array<ClubType | null>) => { getScreen = (response: Array<ApiGenericDataType | null>): React.Node => {
let data: ClubType | null = response[0]; const {navigation} = this.props;
if (data != null) { let data: ClubType | null = null;
if (response[0] != null) {
[data] = response;
this.updateHeaderTitle(data); this.updateHeaderTitle(data);
}
if (data != null) {
return ( return (
<CollapsibleScrollView style={{paddingLeft: 5, paddingRight: 5}} hasTab> <CollapsibleScrollView style={{paddingLeft: 5, paddingRight: 5}} hasTab>
{this.getCategoriesRender(data.category)} {this.getCategoriesRender(data.category)}
{data.logo !== null ? ( {data.logo !== null ? (
<ImageGalleryButton <ImageGalleryButton
navigation={navigation}
images={[{url: data.logo}]} images={[{url: data.logo}]}
style={{ style={{
width: 300, width: 300,
@ -248,9 +259,9 @@ class ClubDisplayScreen extends React.Component<PropsType> {
props.navigation.setOptions({title: data.name}); props.navigation.setOptions({title: data.name});
} }
render() { render(): React.Node {
const {props} = this; const {props} = this;
if (this.shouldFetchData) { if (this.shouldFetchData)
return ( return (
<AuthenticatedScreen <AuthenticatedScreen
navigation={props.navigation} navigation={props.navigation}
@ -272,7 +283,6 @@ class ClubDisplayScreen extends React.Component<PropsType> {
]} ]}
/> />
); );
}
return this.getScreen([this.displayData]); return this.getScreen([this.displayData]);
} }
} }

View file

@ -17,6 +17,8 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow
import * as React from 'react'; import * as React from 'react';
import {Platform} from 'react-native'; import {Platform} from 'react-native';
import {Searchbar} from 'react-native-paper'; import {Searchbar} from 'react-native-paper';
@ -32,27 +34,27 @@ import MaterialHeaderButtons, {
import CollapsibleFlatList from '../../../components/Collapsible/CollapsibleFlatList'; import CollapsibleFlatList from '../../../components/Collapsible/CollapsibleFlatList';
export type ClubCategoryType = { export type ClubCategoryType = {
id: number; id: number,
name: string; name: string,
}; };
export type ClubType = { export type ClubType = {
id: number; id: number,
name: string; name: string,
description: string; description: string,
logo: string; logo: string,
email: string | null; email: string | null,
category: Array<number | null>; category: Array<number | null>,
responsibles: Array<string>; responsibles: Array<string>,
}; };
type PropsType = { type PropsType = {
navigation: StackNavigationProp<any>; navigation: StackNavigationProp,
}; };
type StateType = { type StateType = {
currentlySelectedCategories: Array<number>; currentlySelectedCategories: Array<number>,
currentSearchString: string; currentSearchString: string,
}; };
const LIST_ITEM_HEIGHT = 96; const LIST_ITEM_HEIGHT = 96;
@ -60,9 +62,8 @@ const LIST_ITEM_HEIGHT = 96;
class ClubListScreen extends React.Component<PropsType, StateType> { class ClubListScreen extends React.Component<PropsType, StateType> {
categories: Array<ClubCategoryType>; categories: Array<ClubCategoryType>;
constructor(props: PropsType) { constructor() {
super(props); super();
this.categories = [];
this.state = { this.state = {
currentlySelectedCategories: [], currentlySelectedCategories: [],
currentSearchString: '', currentSearchString: '',
@ -113,7 +114,7 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
* *
* @return {*} * @return {*}
*/ */
getSearchBar = () => { getSearchBar = (): React.Node => {
return ( return (
<Searchbar <Searchbar
placeholder={i18n.t('screens.proximo.search')} placeholder={i18n.t('screens.proximo.search')}
@ -130,7 +131,7 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
* Gets the header button * Gets the header button
* @return {*} * @return {*}
*/ */
getHeaderButtons = () => { getHeaderButtons = (): React.Node => {
const onPress = () => { const onPress = () => {
const {props} = this; const {props} = this;
props.navigation.navigate('club-about'); props.navigation.navigate('club-about');
@ -144,12 +145,12 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
getScreen = ( getScreen = (
data: Array<{ data: Array<{
categories: Array<ClubCategoryType>; categories: Array<ClubCategoryType>,
clubs: Array<ClubType>; clubs: Array<ClubType>,
} | null>, } | null>,
) => { ): React.Node => {
let categoryList: Array<ClubCategoryType> = []; let categoryList = [];
let clubList: Array<ClubType> = []; let clubList = [];
if (data[0] != null) { if (data[0] != null) {
categoryList = data[0].categories; categoryList = data[0].categories;
clubList = data[0].clubs; clubList = data[0].clubs;
@ -173,7 +174,7 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
* *
* @returns {*} * @returns {*}
*/ */
getListHeader() { getListHeader(): React.Node {
const {state} = this; const {state} = this;
return ( return (
<ClubListHeader <ClubListHeader
@ -193,14 +194,12 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
getCategoryOfId = (id: number): ClubCategoryType | null => { getCategoryOfId = (id: number): ClubCategoryType | null => {
let cat = null; let cat = null;
this.categories.forEach((item: ClubCategoryType) => { this.categories.forEach((item: ClubCategoryType) => {
if (id === item.id) { if (id === item.id) cat = item;
cat = item;
}
}); });
return cat; return cat;
}; };
getRenderItem = ({item}: {item: ClubType}) => { getRenderItem = ({item}: {item: ClubType}): React.Node => {
const onPress = () => { const onPress = () => {
this.onListItemPress(item); this.onListItemPress(item);
}; };
@ -220,9 +219,9 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
keyExtractor = (item: ClubType): string => item.id.toString(); keyExtractor = (item: ClubType): string => item.id.toString();
itemLayout = ( itemLayout = (
data: Array<ClubType> | null | undefined, data: {...},
index: number, index: number,
): {length: number; offset: number; index: number} => ({ ): {length: number, offset: number, index: number} => ({
length: LIST_ITEM_HEIGHT, length: LIST_ITEM_HEIGHT,
offset: LIST_ITEM_HEIGHT * index, offset: LIST_ITEM_HEIGHT * index,
index, index,
@ -241,23 +240,17 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
const {state} = this; const {state} = this;
const newCategoriesState = [...state.currentlySelectedCategories]; const newCategoriesState = [...state.currentlySelectedCategories];
let newStrState = state.currentSearchString; let newStrState = state.currentSearchString;
if (filterStr !== null) { if (filterStr !== null) newStrState = filterStr;
newStrState = filterStr;
}
if (categoryId !== null) { if (categoryId !== null) {
const index = newCategoriesState.indexOf(categoryId); const index = newCategoriesState.indexOf(categoryId);
if (index === -1) { if (index === -1) newCategoriesState.push(categoryId);
newCategoriesState.push(categoryId); else newCategoriesState.splice(index, 1);
} else {
newCategoriesState.splice(index, 1);
}
} }
if (filterStr !== null || categoryId !== null) { if (filterStr !== null || categoryId !== null)
this.setState({ this.setState({
currentSearchString: newStrState, currentSearchString: newStrState,
currentlySelectedCategories: newCategoriesState, currentlySelectedCategories: newCategoriesState,
}); });
}
} }
/** /**
@ -271,13 +264,12 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
let shouldRender = let shouldRender =
state.currentlySelectedCategories.length === 0 || state.currentlySelectedCategories.length === 0 ||
isItemInCategoryFilter(state.currentlySelectedCategories, item.category); isItemInCategoryFilter(state.currentlySelectedCategories, item.category);
if (shouldRender) { if (shouldRender)
shouldRender = stringMatchQuery(item.name, state.currentSearchString); shouldRender = stringMatchQuery(item.name, state.currentSearchString);
}
return shouldRender; return shouldRender;
} }
render() { render(): React.Node {
const {props} = this; const {props} = this;
return ( return (
<AuthenticatedScreen <AuthenticatedScreen