Improve planex group favorite handling

This commit is contained in:
Arnaud Vergnet 2020-08-06 17:34:53 +02:00
parent eef6f75414
commit 327488a470
4 changed files with 87 additions and 95 deletions

View file

@ -136,8 +136,8 @@ android {
applicationId 'fr.amicaleinsat.application' applicationId 'fr.amicaleinsat.application'
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 32 versionCode 34
versionName "3.1.4" versionName "4.0.1"
missingDimensionStrategy 'react-native-camera', 'general' missingDimensionStrategy 'react-native-camera', 'general'
} }
splits { splits {

View file

@ -14,22 +14,23 @@ import type {CustomThemeType} from '../../../managers/ThemeManager';
type PropsType = { type PropsType = {
item: PlanexGroupCategoryType, item: PlanexGroupCategoryType,
favorites: Array<PlanexGroupType>,
onGroupPress: (PlanexGroupType) => void, onGroupPress: (PlanexGroupType) => void,
onFavoritePress: (PlanexGroupType) => void, onFavoritePress: (PlanexGroupType) => void,
currentSearchString: string, currentSearchString: string,
favoriteNumber: number,
height: number, height: number,
theme: CustomThemeType, theme: CustomThemeType,
}; };
const LIST_ITEM_HEIGHT = 64; const LIST_ITEM_HEIGHT = 64;
const REPLACE_REGEX = /_/g;
class GroupListAccordion extends React.Component<PropsType> { class GroupListAccordion extends React.Component<PropsType> {
shouldComponentUpdate(nextProps: PropsType): boolean { shouldComponentUpdate(nextProps: PropsType): boolean {
const {props} = this; const {props} = this;
return ( return (
nextProps.currentSearchString !== props.currentSearchString || nextProps.currentSearchString !== props.currentSearchString ||
nextProps.favoriteNumber !== props.favoriteNumber || nextProps.favorites.length !== props.favorites.length ||
nextProps.item.content.length !== props.item.content.length nextProps.item.content.length !== props.item.content.length
); );
} }
@ -46,6 +47,7 @@ class GroupListAccordion extends React.Component<PropsType> {
<GroupListItem <GroupListItem
height={LIST_ITEM_HEIGHT} height={LIST_ITEM_HEIGHT}
item={item} item={item}
favorites={props.favorites}
onPress={onPress} onPress={onPress}
onStarPress={onStarPress} onStarPress={onStarPress}
/> />
@ -80,7 +82,7 @@ class GroupListAccordion extends React.Component<PropsType> {
return ( return (
<View> <View>
<AnimatedAccordion <AnimatedAccordion
title={item.name} title={item.name.replace(REPLACE_REGEX, ' ')}
style={{ style={{
height: props.height, height: props.height,
justifyContent: 'center', justifyContent: 'center',
@ -94,11 +96,11 @@ class GroupListAccordion extends React.Component<PropsType> {
/> />
) : null ) : null
} }
unmountWhenCollapsed // Only render list if expanded for increased performance unmountWhenCollapsed={item.id !== 0} // Only render list if expanded for increased performance
opened={props.item.id === 0 || props.currentSearchString.length > 0}> opened={props.currentSearchString.length > 0}>
<FlatList <FlatList
data={this.getData()} data={this.getData()}
extraData={props.currentSearchString} extraData={props.currentSearchString + props.favorites.length}
renderItem={this.getRenderItem} renderItem={this.getRenderItem}
keyExtractor={this.keyExtractor} keyExtractor={this.keyExtractor}
listKey={item.id.toString()} listKey={item.id.toString()}

View file

@ -10,40 +10,44 @@ type PropsType = {
onPress: () => void, onPress: () => void,
onStarPress: () => void, onStarPress: () => void,
item: PlanexGroupType, item: PlanexGroupType,
favorites: Array<PlanexGroupType>,
height: number, height: number,
}; };
type StateType = { const REPLACE_REGEX = /_/g;
isFav: boolean,
}; class GroupListItem extends React.Component<PropsType> {
isFav: boolean;
class GroupListItem extends React.Component<PropsType, StateType> {
constructor(props: PropsType) { constructor(props: PropsType) {
super(props); super(props);
this.state = { this.isFav = this.isGroupInFavorites(props.favorites);
isFav: props.item.isFav !== undefined && props.item.isFav,
};
} }
shouldComponentUpdate(prevProps: PropsType, prevState: StateType): boolean { shouldComponentUpdate(nextProps: PropsType): boolean {
const {isFav} = this.state; const {favorites} = this.props;
return prevState.isFav !== isFav; 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 = () => { isGroupInFavorites(favorites: Array<PlanexGroupType>): boolean {
const {props} = this; const {item} = this.props;
this.setState((prevState: StateType): StateType => ({ for (let i = 0; i < favorites.length; i += 1) {
isFav: !prevState.isFav, if (favorites[i].id === item.id) return true;
})); }
props.onStarPress(); return false;
}; }
render(): React.Node { render(): React.Node {
const {props, state} = this; const {props} = this;
const {colors} = props.theme; const {colors} = props.theme;
return ( return (
<List.Item <List.Item
title={props.item.name} title={props.item.name.replace(REPLACE_REGEX, ' ')}
onPress={props.onPress} onPress={props.onPress}
left={({size}: {size: number}): React.Node => ( left={({size}: {size: number}): React.Node => (
<List.Icon size={size} icon="chevron-right" /> <List.Icon size={size} icon="chevron-right" />
@ -52,8 +56,8 @@ class GroupListItem extends React.Component<PropsType, StateType> {
<IconButton <IconButton
size={size} size={size}
icon="star" icon="star"
onPress={this.onStarPress} onPress={props.onStarPress}
color={state.isFav ? colors.tetrisScore : color} color={this.isFav ? colors.tetrisScore : color}
/> />
)} )}
style={{ style={{

View file

@ -15,7 +15,6 @@ const LIST_ITEM_HEIGHT = 70;
export type PlanexGroupType = { export type PlanexGroupType = {
name: string, name: string,
id: number, id: number,
isFav: boolean,
}; };
export type PlanexGroupCategoryType = { export type PlanexGroupCategoryType = {
@ -43,46 +42,11 @@ function sortName(
} }
const GROUPS_URL = 'http://planex.insa-toulouse.fr/wsAdeGrp.php?projectId=1'; const GROUPS_URL = 'http://planex.insa-toulouse.fr/wsAdeGrp.php?projectId=1';
const REPLACE_REGEX = /_/g;
/** /**
* Class defining planex group selection screen. * Class defining planex group selection screen.
*/ */
class GroupSelectionScreen extends React.Component<PropsType, StateType> { class GroupSelectionScreen extends React.Component<PropsType, StateType> {
/**
* Removes the given group from the given array
*
* @param favorites The array containing favorites groups
* @param group The group to remove from the array
*/
static removeGroupFromFavorites(
favorites: Array<PlanexGroupType>,
group: PlanexGroupType,
) {
for (let i = 0; i < favorites.length; i += 1) {
if (group.id === favorites[i].id) {
favorites.splice(i, 1);
break;
}
}
}
/**
* Adds the given group to the given array
*
* @param favorites The array containing favorites groups
* @param group The group to add to the array
*/
static addGroupToFavorites(
favorites: Array<PlanexGroupType>,
group: PlanexGroupType,
) {
const favGroup = {...group};
favGroup.isFav = true;
favorites.push(favGroup);
favorites.sort(sortName);
}
constructor(props: PropsType) { constructor(props: PropsType) {
super(props); super(props);
this.state = { this.state = {
@ -130,14 +94,17 @@ class GroupSelectionScreen extends React.Component<PropsType, StateType> {
*/ */
getRenderItem = ({item}: {item: PlanexGroupCategoryType}): React.Node => { getRenderItem = ({item}: {item: PlanexGroupCategoryType}): React.Node => {
const {currentSearchString, favoriteGroups} = this.state; const {currentSearchString, favoriteGroups} = this.state;
if (this.shouldDisplayAccordion(item)) { if (
this.shouldDisplayAccordion(item) ||
(item.id === 0 && item.content.length === 0)
) {
return ( return (
<GroupListAccordion <GroupListAccordion
item={item} item={item}
favorites={[...favoriteGroups]}
onGroupPress={this.onListItemPress} onGroupPress={this.onListItemPress}
onFavoritePress={this.onListFavoritePress} onFavoritePress={this.onListFavoritePress}
currentSearchString={currentSearchString} currentSearchString={currentSearchString}
favoriteNumber={favoriteGroups.length}
height={LIST_ITEM_HEIGHT} height={LIST_ITEM_HEIGHT}
/> />
); );
@ -145,21 +112,6 @@ class GroupSelectionScreen extends React.Component<PropsType, StateType> {
return null; return null;
}; };
/**
* Replaces underscore by spaces and sets the favorite state of every group in the given category
*
* @param groups The groups to format
* @return {Array<PlanexGroupType>}
*/
getFormattedGroups(groups: Array<PlanexGroupType>): Array<PlanexGroupType> {
return groups.map((group: PlanexGroupType): PlanexGroupType => {
const newGroup = {...group};
newGroup.name = group.name.replace(REPLACE_REGEX, ' ');
newGroup.isFav = this.isGroupInFavorites(group);
return newGroup;
});
}
/** /**
* Creates the dataset to be used in the FlatList * Creates the dataset to be used in the FlatList
* *
@ -231,16 +183,8 @@ class GroupSelectionScreen extends React.Component<PropsType, StateType> {
* @param group The group to add/remove to favorites * @param group The group to add/remove to favorites
*/ */
updateGroupFavorites(group: PlanexGroupType) { updateGroupFavorites(group: PlanexGroupType) {
const {favoriteGroups} = this.state; if (this.isGroupInFavorites(group)) this.removeGroupFromFavorites(group);
const newFavorites = [...favoriteGroups]; else this.addGroupToFavorites(group);
if (this.isGroupInFavorites(group))
GroupSelectionScreen.removeGroupFromFavorites(newFavorites, group);
else GroupSelectionScreen.addGroupToFavorites(newFavorites, group);
this.setState({favoriteGroups: newFavorites});
AsyncStorageManager.set(
AsyncStorageManager.PREFERENCES.planexFavoriteGroups.key,
newFavorites,
);
} }
/** /**
@ -276,9 +220,7 @@ class GroupSelectionScreen extends React.Component<PropsType, StateType> {
// eslint-disable-next-line flowtype/no-weak-types // eslint-disable-next-line flowtype/no-weak-types
(Object.values(fetchedData): Array<any>).forEach( (Object.values(fetchedData): Array<any>).forEach(
(category: PlanexGroupCategoryType) => { (category: PlanexGroupCategoryType) => {
const newCat = {...category}; data.push(category);
newCat.content = this.getFormattedGroups(category.content);
data.push(newCat);
}, },
); );
data.sort(sortName); data.sort(sortName);
@ -290,6 +232,50 @@ class GroupSelectionScreen extends React.Component<PropsType, StateType> {
return data; return data;
} }
/**
* Removes the given group from the favorites
*
* @param group The group to remove from the array
*/
removeGroupFromFavorites(group: PlanexGroupType) {
this.setState((prevState: StateType): {
favoriteGroups: Array<PlanexGroupType>,
} => {
const {favoriteGroups} = prevState;
for (let i = 0; i < favoriteGroups.length; i += 1) {
if (group.id === favoriteGroups[i].id) {
favoriteGroups.splice(i, 1);
break;
}
}
AsyncStorageManager.set(
AsyncStorageManager.PREFERENCES.planexFavoriteGroups.key,
favoriteGroups,
);
return {favoriteGroups};
});
}
/**
* Adds the given group to favorites
*
* @param group The group to add to the array
*/
addGroupToFavorites(group: PlanexGroupType) {
this.setState((prevState: StateType): {
favoriteGroups: Array<PlanexGroupType>,
} => {
const {favoriteGroups} = prevState;
favoriteGroups.push(group);
favoriteGroups.sort(sortName);
AsyncStorageManager.set(
AsyncStorageManager.PREFERENCES.planexFavoriteGroups.key,
favoriteGroups,
);
return {favoriteGroups};
});
}
render(): React.Node { render(): React.Node {
const {props, state} = this; const {props, state} = this;
return ( return (