forked from vergnet/application-amicale
Improved accordion performance
This commit is contained in:
parent
1e0cc867b8
commit
070d6beb83
5 changed files with 72 additions and 65 deletions
|
@ -12,8 +12,7 @@ type Props = {
|
|||
title: string,
|
||||
subtitle?: string,
|
||||
left?: (props: { [keys: string]: any }) => React.Node,
|
||||
startOpen: boolean,
|
||||
keepOpen: boolean,
|
||||
opened: boolean,
|
||||
unmountWhenCollapsed: boolean,
|
||||
children?: React.Node,
|
||||
}
|
||||
|
@ -24,36 +23,51 @@ type State = {
|
|||
|
||||
const AnimatedListIcon = Animatable.createAnimatableComponent(List.Icon);
|
||||
|
||||
class AnimatedAccordion extends React.PureComponent<Props, State> {
|
||||
class AnimatedAccordion extends React.Component<Props, State> {
|
||||
|
||||
static defaultProps = {
|
||||
startOpen: false,
|
||||
keepOpen: false,
|
||||
opened: false,
|
||||
unmountWhenCollapsed: false,
|
||||
}
|
||||
chevronRef: { current: null | AnimatedListIcon };
|
||||
chevronIcon: string;
|
||||
animStart: string;
|
||||
animEnd: string;
|
||||
|
||||
state = {
|
||||
expanded: false,
|
||||
expanded: this.props.opened,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.chevronRef = React.createRef();
|
||||
this.setupChevron();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.startOpen)
|
||||
this.toggleAccordion();
|
||||
setupChevron() {
|
||||
if (this.state.expanded) {
|
||||
this.chevronIcon = "chevron-up";
|
||||
this.animStart = "180deg";
|
||||
this.animEnd = "0deg";
|
||||
} else {
|
||||
this.chevronIcon = "chevron-down";
|
||||
this.animStart = "0deg";
|
||||
this.animEnd = "180deg";
|
||||
}
|
||||
}
|
||||
|
||||
toggleAccordion = () => {
|
||||
if (!this.props.keepOpen) {
|
||||
if (this.chevronRef.current != null)
|
||||
this.chevronRef.current.transitionTo({rotate: this.state.expanded ? '0deg' : '180deg'});
|
||||
this.setState({expanded: !this.state.expanded})
|
||||
}
|
||||
if (this.chevronRef.current != null)
|
||||
this.chevronRef.current.transitionTo({rotate: this.state.expanded ? this.animStart : this.animEnd});
|
||||
this.setState({expanded: !this.state.expanded})
|
||||
};
|
||||
|
||||
shouldComponentUpdate(nextProps: Props) {
|
||||
this.state.expanded = nextProps.opened;
|
||||
this.setupChevron();
|
||||
return true;
|
||||
}
|
||||
|
||||
render() {
|
||||
const colors = this.props.theme.colors;
|
||||
return (
|
||||
|
@ -67,13 +81,13 @@ class AnimatedAccordion extends React.PureComponent<Props, State> {
|
|||
right={(props) => <AnimatedListIcon
|
||||
ref={this.chevronRef}
|
||||
{...props}
|
||||
icon={"chevron-down"}
|
||||
icon={this.chevronIcon}
|
||||
color={this.state.expanded ? colors.primary : undefined}
|
||||
useNativeDriver
|
||||
/>}
|
||||
left={this.props.left}
|
||||
/>
|
||||
<Collapsible collapsed={!this.props.keepOpen && !this.state.expanded}>
|
||||
<Collapsible collapsed={!this.state.expanded}>
|
||||
{!this.props.unmountWhenCollapsed || (this.props.unmountWhenCollapsed && this.state.expanded)
|
||||
? this.props.children
|
||||
: null}
|
||||
|
|
|
@ -1,22 +1,43 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Card, List, Text} from 'react-native-paper';
|
||||
import {Card, Chip, List, Text} from 'react-native-paper';
|
||||
import {StyleSheet, View} from "react-native";
|
||||
import i18n from 'i18n-js';
|
||||
import AnimatedAccordion from "../../Animations/AnimatedAccordion";
|
||||
import {isItemInCategoryFilter} from "../../../utils/Search";
|
||||
import type {category} from "../../../screens/Amicale/Clubs/ClubListScreen";
|
||||
|
||||
type Props = {
|
||||
categoryRender: Function,
|
||||
categories: Array<Object>,
|
||||
categories: Array<category>,
|
||||
onChipSelect: (id: number) => void,
|
||||
selectedCategories: Array<number>,
|
||||
}
|
||||
|
||||
class ClubListHeader extends React.Component<Props> {
|
||||
|
||||
shouldComponentUpdate(nextProps: Props) {
|
||||
return nextProps.selectedCategories.length !== this.props.selectedCategories.length;
|
||||
}
|
||||
|
||||
getChipRender = (category: category, key: string) => {
|
||||
const onPress = () => this.props.onChipSelect(category.id);
|
||||
return <Chip
|
||||
selected={isItemInCategoryFilter(this.props.selectedCategories, [category.id])}
|
||||
mode={'outlined'}
|
||||
onPress={onPress}
|
||||
style={{marginRight: 5, marginBottom: 5}}
|
||||
key={key}
|
||||
>
|
||||
{category.name}
|
||||
</Chip>;
|
||||
};
|
||||
|
||||
|
||||
getCategoriesRender() {
|
||||
let final = [];
|
||||
for (let i = 0; i < this.props.categories.length; i++) {
|
||||
final.push(this.props.categoryRender(this.props.categories[i], this.props.categories[i].id));
|
||||
final.push(this.getChipRender(this.props.categories[i], this.props.categories[i].id.toString()));
|
||||
}
|
||||
return final;
|
||||
}
|
||||
|
@ -27,7 +48,7 @@ class ClubListHeader extends React.Component<Props> {
|
|||
<AnimatedAccordion
|
||||
title={i18n.t("clubs.categories")}
|
||||
left={props => <List.Icon {...props} icon="star"/>}
|
||||
startOpen={true}
|
||||
opened={true}
|
||||
>
|
||||
<Text style={styles.text}>{i18n.t("clubs.categoriesFilterMessage")}</Text>
|
||||
<View style={styles.chipContainer}>
|
||||
|
|
|
@ -19,27 +19,16 @@ type Props = {
|
|||
theme: CustomTheme,
|
||||
}
|
||||
|
||||
type State = {
|
||||
expanded: boolean,
|
||||
}
|
||||
|
||||
const LIST_ITEM_HEIGHT = 64;
|
||||
|
||||
class GroupListAccordion extends React.Component<Props, State> {
|
||||
class GroupListAccordion extends React.Component<Props> {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
expanded: props.item.id === "0",
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps: Props, nextSate: State) {
|
||||
if (nextProps.currentSearchString !== this.props.currentSearchString)
|
||||
this.state.expanded = nextProps.currentSearchString.length > 0;
|
||||
|
||||
shouldComponentUpdate(nextProps: Props) {
|
||||
return (nextProps.currentSearchString !== this.props.currentSearchString)
|
||||
|| (nextSate.expanded !== this.state.expanded)
|
||||
|| (nextProps.favoriteNumber !== this.props.favoriteNumber)
|
||||
|| (nextProps.item.content.length !== this.props.item.content.length);
|
||||
}
|
||||
|
@ -61,8 +50,6 @@ class GroupListAccordion extends React.Component<Props, State> {
|
|||
return null;
|
||||
}
|
||||
|
||||
itemLayout = (data, index) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index});
|
||||
|
||||
render() {
|
||||
const item = this.props.item;
|
||||
return (
|
||||
|
@ -82,6 +69,7 @@ class GroupListAccordion extends React.Component<Props, State> {
|
|||
/>
|
||||
: null}
|
||||
unmountWhenCollapsed={true}// Only render list if expanded for increased performance
|
||||
opened={this.props.item.id === 0 || this.props.currentSearchString.length > 0}
|
||||
>
|
||||
{/*$FlowFixMe*/}
|
||||
<FlatList
|
||||
|
@ -91,8 +79,8 @@ class GroupListAccordion extends React.Component<Props, State> {
|
|||
keyExtractor={this.keyExtractor}
|
||||
listKey={item.id.toString()}
|
||||
// Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
|
||||
getItemLayout={this.itemLayout} // Broken with search
|
||||
removeClippedSubviews={true}
|
||||
// getItemLayout={this.itemLayout} // Broken with search
|
||||
// removeClippedSubviews={true}
|
||||
/>
|
||||
</AnimatedAccordion>
|
||||
</View>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import * as React from 'react';
|
||||
import {Animated, Platform} from "react-native";
|
||||
import {Chip, Searchbar} from 'react-native-paper';
|
||||
import {Searchbar} from 'react-native-paper';
|
||||
import AuthenticatedScreen from "../../../components/Amicale/AuthenticatedScreen";
|
||||
import i18n from "i18n-js";
|
||||
import ClubListItem from "../../../components/Lists/Clubs/ClubListItem";
|
||||
|
@ -36,7 +36,7 @@ type Props = {
|
|||
}
|
||||
|
||||
type State = {
|
||||
currentlySelectedCategories: Array<string>,
|
||||
currentlySelectedCategories: Array<number>,
|
||||
currentSearchString: string,
|
||||
}
|
||||
|
||||
|
@ -99,13 +99,11 @@ class ClubListScreen extends React.Component<Props, State> {
|
|||
this.updateFilteredData(str, null);
|
||||
};
|
||||
|
||||
keyExtractor = (item: club) => {
|
||||
return item.id.toString();
|
||||
};
|
||||
keyExtractor = (item: club) => item.id.toString();
|
||||
|
||||
itemLayout = (data, index) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index});
|
||||
|
||||
getScreen = (data: Array<{categories: Array<category>, clubs: Array<club>} | null>) => {
|
||||
getScreen = (data: Array<{ categories: Array<category>, clubs: Array<club> } | null>) => {
|
||||
let categoryList = [];
|
||||
let clubList = [];
|
||||
if (data[0] != null) {
|
||||
|
@ -131,11 +129,9 @@ class ClubListScreen extends React.Component<Props, State> {
|
|||
)
|
||||
};
|
||||
|
||||
onChipSelect(id: string) {
|
||||
this.updateFilteredData(null, id);
|
||||
}
|
||||
onChipSelect = (id: number) => this.updateFilteredData(null, id);
|
||||
|
||||
updateFilteredData(filterStr: string | null, categoryId: string | null) {
|
||||
updateFilteredData(filterStr: string | null, categoryId: number | null) {
|
||||
let newCategoriesState = [...this.state.currentlySelectedCategories];
|
||||
let newStrState = this.state.currentSearchString;
|
||||
if (filterStr !== null)
|
||||
|
@ -154,23 +150,11 @@ class ClubListScreen extends React.Component<Props, State> {
|
|||
})
|
||||
}
|
||||
|
||||
getChipRender = (category: category, key: string) => {
|
||||
const onPress = this.onChipSelect.bind(this, category.id);
|
||||
return <Chip
|
||||
selected={isItemInCategoryFilter(this.state.currentlySelectedCategories, [category.id])}
|
||||
mode={'outlined'}
|
||||
onPress={onPress}
|
||||
style={{marginRight: 5, marginBottom: 5}}
|
||||
key={key}
|
||||
>
|
||||
{category.name}
|
||||
</Chip>;
|
||||
};
|
||||
|
||||
getListHeader() {
|
||||
return <ClubListHeader
|
||||
categories={this.categories}
|
||||
categoryRender={this.getChipRender}
|
||||
selectedCategories={this.state.currentlySelectedCategories}
|
||||
onChipSelect={this.onChipSelect}
|
||||
/>;
|
||||
}
|
||||
|
||||
|
@ -189,7 +173,7 @@ class ClubListScreen extends React.Component<Props, State> {
|
|||
return shouldRender;
|
||||
}
|
||||
|
||||
getRenderItem = ({item}: {item: club}) => {
|
||||
getRenderItem = ({item}: { item: club }) => {
|
||||
const onPress = this.onListItemPress.bind(this, item);
|
||||
if (this.shouldRenderItem(item)) {
|
||||
return (
|
||||
|
|
|
@ -37,8 +37,8 @@ const emailRegex = /^.+@.+\..+$/;
|
|||
class LoginScreen extends React.Component<Props, State> {
|
||||
|
||||
state = {
|
||||
email: '',
|
||||
password: '',
|
||||
email: 'vergnet@etud.insa-toulouse.fr',
|
||||
password: 'LHSdf32ù43',
|
||||
isEmailValidated: false,
|
||||
isPasswordValidated: false,
|
||||
loading: false,
|
||||
|
|
Loading…
Reference in a new issue