application-amicale/src/screens/Amicale/Clubs/ClubListScreen.tsx
2021-05-13 13:19:28 +02:00

287 lines
7.5 KiB
TypeScript

/*
* 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 { Platform } from 'react-native';
import { Searchbar } from 'react-native-paper';
import i18n from 'i18n-js';
import { StackNavigationProp } from '@react-navigation/stack';
import ClubListItem from '../../../components/Lists/Clubs/ClubListItem';
import {
isItemInCategoryFilter,
stringMatchQuery,
} from '../../../utils/Search';
import ClubListHeader from '../../../components/Lists/Clubs/ClubListHeader';
import MaterialHeaderButtons, {
Item,
} from '../../../components/Overrides/CustomHeaderButton';
import ConnectionManager from '../../../managers/ConnectionManager';
import WebSectionList from '../../../components/Screens/WebSectionList';
export type ClubCategoryType = {
id: number;
name: string;
};
export type ClubType = {
id: number;
name: string;
description: string;
logo: string;
email: string | null;
category: Array<number | null>;
responsibles: Array<string>;
};
type PropsType = {
navigation: StackNavigationProp<any>;
};
type StateType = {
currentlySelectedCategories: Array<number>;
currentSearchString: string;
};
type ResponseType = {
categories: Array<ClubCategoryType>;
clubs: Array<ClubType>;
};
const LIST_ITEM_HEIGHT = 96;
class ClubListScreen extends React.Component<PropsType, StateType> {
categories: Array<ClubCategoryType>;
constructor(props: PropsType) {
super(props);
this.categories = [];
this.state = {
currentlySelectedCategories: [],
currentSearchString: '',
};
}
/**
* Creates the header content
*/
componentDidMount() {
const { props } = this;
props.navigation.setOptions({
headerTitle: this.getSearchBar,
headerRight: this.getHeaderButtons,
headerBackTitleVisible: false,
headerTitleContainerStyle:
Platform.OS === 'ios'
? { marginHorizontal: 0, width: '70%' }
: { marginHorizontal: 0, right: 50, left: 50 },
});
}
/**
* Callback used when clicking an article in the list.
* It opens the modal to show detailed information about the article
*
* @param item The article pressed
*/
onListItemPress(item: ClubType) {
const { props } = this;
props.navigation.navigate('club-information', {
data: item,
categories: this.categories,
});
}
/**
* Callback used when the search changes
*
* @param str The new search string
*/
onSearchStringChange = (str: string) => {
this.updateFilteredData(str, null);
};
/**
* Gets the header search bar
*
* @return {*}
*/
getSearchBar = () => {
return (
// @ts-ignore
<Searchbar
placeholder={i18n.t('screens.proximo.search')}
onChangeText={this.onSearchStringChange}
/>
);
};
onChipSelect = (id: number) => {
this.updateFilteredData(null, id);
};
/**
* Gets the header button
* @return {*}
*/
getHeaderButtons = () => {
const onPress = () => {
const { props } = this;
props.navigation.navigate('club-about');
};
return (
<MaterialHeaderButtons>
<Item title="main" iconName="information" onPress={onPress} />
</MaterialHeaderButtons>
);
};
createDataset = (data: ResponseType | undefined) => {
if (data) {
this.categories = data?.categories;
return [{ title: '', data: data.clubs }];
} else {
return [{ title: '', data: [] }];
}
};
/**
* Gets the list header, with controls to change the categories filter
*
* @returns {*}
*/
getListHeader(data: ResponseType | undefined) {
const { state } = this;
if (data) {
return (
<ClubListHeader
categories={this.categories}
selectedCategories={state.currentlySelectedCategories}
onChipSelect={this.onChipSelect}
/>
);
} else {
return null;
}
}
/**
* Gets the category object of the given ID
*
* @param id The ID of the category to find
* @returns {*}
*/
getCategoryOfId = (id: number): ClubCategoryType | null => {
let cat = null;
this.categories.forEach((item: ClubCategoryType) => {
if (id === item.id) {
cat = item;
}
});
return cat;
};
getRenderItem = ({ item }: { item: ClubType }) => {
const onPress = () => {
this.onListItemPress(item);
};
if (this.shouldRenderItem(item)) {
return (
<ClubListItem
categoryTranslator={this.getCategoryOfId}
item={item}
onPress={onPress}
height={LIST_ITEM_HEIGHT}
/>
);
}
return null;
};
keyExtractor = (item: ClubType): string => item.id.toString();
/**
* Updates the search string and category filter, saving them to the State.
*
* If the given category is already in the filter, it removes it.
* Otherwise it adds it to the filter.
*
* @param filterStr The new filter string to use
* @param categoryId The category to add/remove from the filter
*/
updateFilteredData(filterStr: string | null, categoryId: number | null) {
const { state } = this;
const newCategoriesState = [...state.currentlySelectedCategories];
let newStrState = state.currentSearchString;
if (filterStr !== null) {
newStrState = filterStr;
}
if (categoryId !== null) {
const index = newCategoriesState.indexOf(categoryId);
if (index === -1) {
newCategoriesState.push(categoryId);
} else {
newCategoriesState.splice(index, 1);
}
}
if (filterStr !== null || categoryId !== null) {
this.setState({
currentSearchString: newStrState,
currentlySelectedCategories: newCategoriesState,
});
}
}
/**
* Checks if the given item should be rendered according to current name and category filters
*
* @param item The club to check
* @returns {boolean}
*/
shouldRenderItem(item: ClubType): boolean {
const { state } = this;
let shouldRender =
state.currentlySelectedCategories.length === 0 ||
isItemInCategoryFilter(state.currentlySelectedCategories, item.category);
if (shouldRender) {
shouldRender = stringMatchQuery(item.name, state.currentSearchString);
}
return shouldRender;
}
render() {
return (
<WebSectionList
request={() =>
ConnectionManager.getInstance().authenticatedRequest<ResponseType>(
'clubs/list'
)
}
createDataset={this.createDataset}
keyExtractor={this.keyExtractor}
renderItem={this.getRenderItem}
renderListHeaderComponent={(data) => this.getListHeader(data)}
// Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
removeClippedSubviews={true}
itemHeight={LIST_ITEM_HEIGHT}
/>
);
}
}
export default ClubListScreen;