Convert planex group components to functional
This commit is contained in:
parent
9675d329cc
commit
52651ecf85
3 changed files with 136 additions and 216 deletions
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
);
|
||||||
|
|
|
@ -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)}
|
||||||
|
|
Loading…
Reference in a new issue