Convert planex group components to functional

This commit is contained in:
Arnaud Vergnet 2021-05-13 17:19:43 +02:00
parent 9675d329cc
commit 52651ecf85
3 changed files with 136 additions and 216 deletions

View file

@ -18,8 +18,8 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import { List, withTheme } from 'react-native-paper'; import { List, useTheme } from 'react-native-paper';
import { FlatList, StyleSheet, View } from 'react-native'; import { FlatList, StyleSheet } from 'react-native';
import GroupListItem from './GroupListItem'; import GroupListItem from './GroupListItem';
import AnimatedAccordion from '../../Animations/AnimatedAccordion'; import AnimatedAccordion from '../../Animations/AnimatedAccordion';
import type { import type {
@ -34,7 +34,6 @@ type PropsType = {
onGroupPress: (data: PlanexGroupType) => void; onGroupPress: (data: PlanexGroupType) => void;
onFavoritePress: (data: PlanexGroupType) => void; onFavoritePress: (data: PlanexGroupType) => void;
currentSearchString: string; currentSearchString: string;
theme: ReactNativePaper.Theme;
}; };
const LIST_ITEM_HEIGHT = 64; const LIST_ITEM_HEIGHT = 64;
@ -49,36 +48,22 @@ const styles = StyleSheet.create({
}, },
}); });
class GroupListAccordion extends React.Component<PropsType> { function GroupListAccordion(props: PropsType) {
shouldComponentUpdate(nextProps: PropsType): boolean { const theme = useTheme();
const { props } = this;
return (
nextProps.currentSearchString !== props.currentSearchString ||
nextProps.favorites.length !== props.favorites.length ||
nextProps.item.content.length !== props.item.content.length
);
}
getRenderItem = ({ item }: { item: PlanexGroupType }) => { const getRenderItem = ({ item }: { item: PlanexGroupType }) => {
const { props } = this;
const onPress = () => {
props.onGroupPress(item);
};
const onStarPress = () => {
props.onFavoritePress(item);
};
return ( return (
<GroupListItem <GroupListItem
height={LIST_ITEM_HEIGHT} height={LIST_ITEM_HEIGHT}
item={item} item={item}
favorites={props.favorites} isFav={props.favorites.some((f) => f.id === item.id)}
onPress={onPress} onPress={() => props.onGroupPress(item)}
onStarPress={onStarPress} onStarPress={() => props.onFavoritePress(item)}
/> />
); );
}; };
itemLayout = ( const itemLayout = (
_data: Array<PlanexGroupType> | null | undefined, _data: Array<PlanexGroupType> | null | undefined,
index: number index: number
): { length: number; offset: number; index: number } => ({ ): { length: number; offset: number; index: number } => ({
@ -87,57 +72,58 @@ class GroupListAccordion extends React.Component<PropsType> {
index, index,
}); });
keyExtractor = (item: PlanexGroupType): string => item.id.toString(); const keyExtractor = (item: PlanexGroupType): string => item.id.toString();
render() { var isFavorite = props.item.id === 0;
const { props } = this; var isEmptyFavorite = isFavorite && props.favorites.length === 0;
const { item } = this.props;
var isFavorite = item.id === 0; return (
var isEmptyFavorite = isFavorite && props.favorites.length === 0; <AnimatedAccordion
return ( title={
<View> isEmptyFavorite
<AnimatedAccordion ? i18n.t('screens.planex.favorites.empty.title')
title={ : props.item.name.replace(REPLACE_REGEX, ' ')
isEmptyFavorite }
? i18n.t('screens.planex.favorites.empty.title') subtitle={
: item.name.replace(REPLACE_REGEX, ' ') isEmptyFavorite
} ? i18n.t('screens.planex.favorites.empty.subtitle')
subtitle={ : undefined
isEmptyFavorite }
? i18n.t('screens.planex.favorites.empty.subtitle') style={styles.container}
: undefined left={(iconProps) =>
} isFavorite ? (
style={styles.container} <List.Icon
left={(iconProps) => style={iconProps.style}
isFavorite ? ( icon={'star'}
<List.Icon color={theme.colors.tetrisScore}
style={iconProps.style}
icon={'star'}
color={props.theme.colors.tetrisScore}
/>
) : undefined
}
unmountWhenCollapsed={!isFavorite} // Only render list if expanded for increased performance
opened={
props.currentSearchString.length >= MIN_SEARCH_SIZE_EXPAND ||
(isFavorite && !isEmptyFavorite)
}
enabled={!isEmptyFavorite}
>
<FlatList
data={props.item.content}
extraData={props.currentSearchString + props.favorites.length}
renderItem={this.getRenderItem}
keyExtractor={this.keyExtractor}
listKey={item.id.toString()}
// Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
getItemLayout={this.itemLayout}
removeClippedSubviews
/> />
</AnimatedAccordion> ) : undefined
</View> }
); unmountWhenCollapsed={!isFavorite} // Only render list if expanded for increased performance
} opened={
props.currentSearchString.length >= MIN_SEARCH_SIZE_EXPAND ||
(isFavorite && !isEmptyFavorite)
}
enabled={!isEmptyFavorite}
>
<FlatList
data={props.item.content}
extraData={props.currentSearchString + props.favorites.length}
renderItem={getRenderItem}
keyExtractor={keyExtractor}
listKey={props.item.id.toString()}
// Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
getItemLayout={itemLayout}
removeClippedSubviews={true}
/>
</AnimatedAccordion>
);
} }
export default withTheme(GroupListAccordion); const propsEqual = (pp: PropsType, np: PropsType) =>
pp.currentSearchString === np.currentSearchString &&
pp.favorites.length === np.favorites.length &&
pp.item.content.length === np.item.content.length &&
pp.onFavoritePress === np.onFavoritePress;
export default React.memo(GroupListAccordion, propsEqual);

View file

@ -17,20 +17,19 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
import * as React from 'react'; import React, { useRef } from 'react';
import { List, TouchableRipple, withTheme } from 'react-native-paper'; import { List, TouchableRipple, useTheme } from 'react-native-paper';
import * as Animatable from 'react-native-animatable'; import * as Animatable from 'react-native-animatable';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import type { PlanexGroupType } from '../../../screens/Planex/GroupSelectionScreen'; import type { PlanexGroupType } from '../../../screens/Planex/GroupSelectionScreen';
import { StyleSheet, View } from 'react-native'; import { StyleSheet, View } from 'react-native';
import { getPrettierPlanexGroupName } from '../../../utils/Utils'; import { getPrettierPlanexGroupName } from '../../../utils/Utils';
type PropsType = { type Props = {
theme: ReactNativePaper.Theme;
onPress: () => void; onPress: () => void;
onStarPress: () => void; onStarPress: () => void;
item: PlanexGroupType; item: PlanexGroupType;
favorites: Array<PlanexGroupType>; isFav: boolean;
height: number; height: number;
}; };
@ -49,88 +48,51 @@ const styles = StyleSheet.create({
}, },
}); });
class GroupListItem extends React.Component<PropsType> { function GroupListItem(props: Props) {
isFav: boolean; const theme = useTheme();
starRef: { current: null | (Animatable.View & View) }; const starRef = useRef<Animatable.View & View>(null);
constructor(props: PropsType) { return (
super(props); <List.Item
this.starRef = React.createRef(); title={getPrettierPlanexGroupName(props.item.name)}
this.isFav = this.isGroupInFavorites(props.favorites); onPress={props.onPress}
} left={(iconProps) => (
<List.Icon
shouldComponentUpdate(nextProps: PropsType): boolean { color={iconProps.color}
const { favorites } = this.props; style={iconProps.style}
const favChanged = favorites.length !== nextProps.favorites.length; icon={'chevron-right'}
let newFavState = this.isFav; />
if (favChanged) { )}
newFavState = this.isGroupInFavorites(nextProps.favorites); right={(iconProps) => (
} <Animatable.View
const shouldUpdate = this.isFav !== newFavState; ref={starRef}
this.isFav = newFavState; useNativeDriver={true}
return shouldUpdate; animation={props.isFav ? 'rubberBand' : undefined}
} >
<TouchableRipple
onStarPress = () => { onPress={props.onStarPress}
const { props } = this; style={styles.iconContainer}
const ref = this.starRef; >
if (ref.current && ref.current.rubberBand && ref.current.swing) { <MaterialCommunityIcons
if (this.isFav) { size={30}
ref.current.rubberBand(); style={styles.icon}
} else { name="star"
ref.current.swing(); color={props.isFav ? theme.colors.tetrisScore : iconProps.color}
} />
} </TouchableRipple>
props.onStarPress(); </Animatable.View>
}; )}
style={{
isGroupInFavorites(favorites: Array<PlanexGroupType>): boolean { height: props.height,
const { item } = this.props; ...styles.item,
for (let i = 0; i < favorites.length; i += 1) { }}
if (favorites[i].id === item.id) { />
return true; );
}
}
return false;
}
render() {
const { props } = this;
const { colors } = props.theme;
return (
<List.Item
title={getPrettierPlanexGroupName(props.item.name)}
onPress={props.onPress}
left={(iconProps) => (
<List.Icon
color={iconProps.color}
style={iconProps.style}
icon={'chevron-right'}
/>
)}
right={(iconProps) => (
<Animatable.View ref={this.starRef} useNativeDriver>
<TouchableRipple
onPress={this.onStarPress}
style={styles.iconContainer}
>
<MaterialCommunityIcons
size={30}
style={styles.icon}
name="star"
color={this.isFav ? colors.tetrisScore : iconProps.color}
/>
</TouchableRipple>
</Animatable.View>
)}
style={{
height: props.height,
...styles.item,
}}
/>
);
}
} }
export default withTheme(GroupListItem); export default React.memo(
GroupListItem,
(pp: Props, np: Props) =>
pp.isFav === np.isFav && pp.onStarPress === np.onStarPress
);

View file

@ -17,7 +17,12 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
import React, { useEffect, useLayoutEffect, useState } from 'react'; import React, {
useCallback,
useEffect,
useLayoutEffect,
useState,
} from 'react';
import { Platform } from 'react-native'; import { Platform } from 'react-native';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import { Searchbar } from 'react-native-paper'; import { Searchbar } from 'react-native-paper';
@ -142,39 +147,31 @@ function GroupSelectionScreen() {
* *
* @param item The item to add/remove from favorites * @param item The item to add/remove from favorites
*/ */
const onListFavoritePress = (item: PlanexGroupType) => { const onListFavoritePress = useCallback(
updateGroupFavorites(item); (group: PlanexGroupType) => {
}; const removeGroupFromFavorites = (g: PlanexGroupType) => {
setFavoriteGroups(favoriteGroups.filter((f) => f.id !== g.id));
};
/** const addGroupToFavorites = (g: PlanexGroupType) => {
* Checks if the given group is in the favorites list setFavoriteGroups([...favoriteGroups, g].sort(sortName));
* };
* @param group The group to check
* @returns {boolean} if (favoriteGroups.some((f) => f.id === group.id)) {
*/ removeGroupFromFavorites(group);
const isGroupInFavorites = (group: PlanexGroupType): boolean => { } else {
let isFav = false; addGroupToFavorites(group);
favoriteGroups.forEach((favGroup: PlanexGroupType) => {
if (group.id === favGroup.id) {
isFav = true;
} }
}); },
return isFav; [favoriteGroups]
}; );
/** useEffect(() => {
* Adds or removes the given group to the favorites list, depending on whether it is already in it or not. AsyncStorageManager.set(
* Favorites are then saved in user preferences AsyncStorageManager.PREFERENCES.planexFavoriteGroups.key,
* favoriteGroups
* @param group The group to add/remove to favorites );
*/ }, [favoriteGroups]);
const updateGroupFavorites = (group: PlanexGroupType) => {
if (isGroupInFavorites(group)) {
removeGroupFromFavorites(group);
} else {
addGroupToFavorites(group);
}
};
/** /**
* Generates the dataset to be used in the FlatList. * Generates the dataset to be used in the FlatList.
@ -220,31 +217,6 @@ function GroupSelectionScreen() {
return data; return data;
}; };
/**
* Removes the given group from the favorites
*
* @param group The group to remove from the array
*/
const removeGroupFromFavorites = (group: PlanexGroupType) => {
setFavoriteGroups(favoriteGroups.filter((g) => g.id !== group.id));
};
useEffect(() => {
AsyncStorageManager.set(
AsyncStorageManager.PREFERENCES.planexFavoriteGroups.key,
favoriteGroups
);
}, [favoriteGroups]);
/**
* Adds the given group to favorites
*
* @param group The group to add to the array
*/
const addGroupToFavorites = (group: PlanexGroupType) => {
setFavoriteGroups([...favoriteGroups, group].sort(sortName));
};
return ( return (
<WebSectionList <WebSectionList
request={() => readData<PlanexGroupsType>(Urls.planex.groups)} request={() => readData<PlanexGroupsType>(Urls.planex.groups)}