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,20 +72,17 @@ 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;
const { item } = this.props;
var isFavorite = item.id === 0;
var isEmptyFavorite = isFavorite && props.favorites.length === 0; var isEmptyFavorite = isFavorite && props.favorites.length === 0;
return ( return (
<View>
<AnimatedAccordion <AnimatedAccordion
title={ title={
isEmptyFavorite isEmptyFavorite
? i18n.t('screens.planex.favorites.empty.title') ? i18n.t('screens.planex.favorites.empty.title')
: item.name.replace(REPLACE_REGEX, ' ') : props.item.name.replace(REPLACE_REGEX, ' ')
} }
subtitle={ subtitle={
isEmptyFavorite isEmptyFavorite
@ -113,7 +95,7 @@ class GroupListAccordion extends React.Component<PropsType> {
<List.Icon <List.Icon
style={iconProps.style} style={iconProps.style}
icon={'star'} icon={'star'}
color={props.theme.colors.tetrisScore} color={theme.colors.tetrisScore}
/> />
) : undefined ) : undefined
} }
@ -127,17 +109,21 @@ class GroupListAccordion extends React.Component<PropsType> {
<FlatList <FlatList
data={props.item.content} data={props.item.content}
extraData={props.currentSearchString + props.favorites.length} extraData={props.currentSearchString + props.favorites.length}
renderItem={this.getRenderItem} renderItem={getRenderItem}
keyExtractor={this.keyExtractor} keyExtractor={keyExtractor}
listKey={item.id.toString()} listKey={props.item.id.toString()}
// Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration // Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
getItemLayout={this.itemLayout} getItemLayout={itemLayout}
removeClippedSubviews removeClippedSubviews={true}
/> />
</AnimatedAccordion> </AnimatedAccordion>
</View>
); );
} }
}
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,55 +48,11 @@ 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) {
super(props);
this.starRef = React.createRef();
this.isFav = this.isGroupInFavorites(props.favorites);
}
shouldComponentUpdate(nextProps: PropsType): boolean {
const { favorites } = this.props;
const favChanged = favorites.length !== nextProps.favorites.length;
let newFavState = this.isFav;
if (favChanged) {
newFavState = this.isGroupInFavorites(nextProps.favorites);
}
const shouldUpdate = this.isFav !== newFavState;
this.isFav = newFavState;
return shouldUpdate;
}
onStarPress = () => {
const { props } = this;
const ref = this.starRef;
if (ref.current && ref.current.rubberBand && ref.current.swing) {
if (this.isFav) {
ref.current.rubberBand();
} else {
ref.current.swing();
}
}
props.onStarPress();
};
isGroupInFavorites(favorites: Array<PlanexGroupType>): boolean {
const { item } = this.props;
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 ( return (
<List.Item <List.Item
title={getPrettierPlanexGroupName(props.item.name)} title={getPrettierPlanexGroupName(props.item.name)}
@ -110,16 +65,20 @@ class GroupListItem extends React.Component<PropsType> {
/> />
)} )}
right={(iconProps) => ( right={(iconProps) => (
<Animatable.View ref={this.starRef} useNativeDriver> <Animatable.View
ref={starRef}
useNativeDriver={true}
animation={props.isFav ? 'rubberBand' : undefined}
>
<TouchableRipple <TouchableRipple
onPress={this.onStarPress} onPress={props.onStarPress}
style={styles.iconContainer} style={styles.iconContainer}
> >
<MaterialCommunityIcons <MaterialCommunityIcons
size={30} size={30}
style={styles.icon} style={styles.icon}
name="star" name="star"
color={this.isFav ? colors.tetrisScore : iconProps.color} color={props.isFav ? theme.colors.tetrisScore : iconProps.color}
/> />
</TouchableRipple> </TouchableRipple>
</Animatable.View> </Animatable.View>
@ -131,6 +90,9 @@ class GroupListItem extends React.Component<PropsType> {
/> />
); );
} }
}
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}
*/
const isGroupInFavorites = (group: PlanexGroupType): boolean => {
let isFav = false;
favoriteGroups.forEach((favGroup: PlanexGroupType) => {
if (group.id === favGroup.id) {
isFav = true;
}
});
return isFav;
}; };
/** if (favoriteGroups.some((f) => f.id === group.id)) {
* Adds or removes the given group to the favorites list, depending on whether it is already in it or not.
* Favorites are then saved in user preferences
*
* @param group The group to add/remove to favorites
*/
const updateGroupFavorites = (group: PlanexGroupType) => {
if (isGroupInFavorites(group)) {
removeGroupFromFavorites(group); removeGroupFromFavorites(group);
} else { } else {
addGroupToFavorites(group); addGroupToFavorites(group);
} }
}; },
[favoriteGroups]
);
useEffect(() => {
AsyncStorageManager.set(
AsyncStorageManager.PREFERENCES.planexFavoriteGroups.key,
favoriteGroups
);
}, [favoriteGroups]);
/** /**
* 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)}