Improved flow typing

This commit is contained in:
Arnaud Vergnet 2020-04-20 09:29:21 +02:00
parent 7f24eb77ac
commit dc3aed8bda
6 changed files with 103 additions and 83 deletions

View file

@ -1,7 +1,7 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {Card, List, Text, withTheme} from 'react-native-paper'; import {Card, List, Text} from 'react-native-paper';
import {StyleSheet, View} from "react-native"; import {StyleSheet, View} from "react-native";
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import AnimatedAccordion from "../../Animations/AnimatedAccordion"; import AnimatedAccordion from "../../Animations/AnimatedAccordion";
@ -13,14 +13,6 @@ type Props = {
class ClubListHeader extends React.Component<Props> { class ClubListHeader extends React.Component<Props> {
colors: Object;
constructor(props) {
super(props);
this.colors = props.theme.colors;
}
getCategoriesRender() { getCategoriesRender() {
let final = []; let final = [];
for (let i = 0; i < this.props.categories.length; i++) { for (let i = 0; i < this.props.categories.length; i++) {
@ -67,4 +59,4 @@ const styles = StyleSheet.create({
}, },
}); });
export default withTheme(ClubListHeader); export default ClubListHeader;

View file

@ -3,22 +3,23 @@
import * as React from 'react'; import * as React from 'react';
import {Avatar, Chip, List, withTheme} from 'react-native-paper'; import {Avatar, Chip, List, withTheme} from 'react-native-paper';
import {View} from "react-native"; import {View} from "react-native";
import type {category, club} from "../../../screens/Amicale/Clubs/ClubListScreen";
import type {CustomTheme} from "../../../managers/ThemeManager";
type Props = { type Props = {
onPress: Function, onPress: () => void,
categoryTranslator: Function, categoryTranslator: (id: number) => category,
item: Object, item: club,
height: number, height: number,
theme: CustomTheme,
} }
class ClubListItem extends React.Component<Props> { class ClubListItem extends React.Component<Props> {
colors: Object;
hasManagers: boolean; hasManagers: boolean;
constructor(props) { constructor(props) {
super(props); super(props);
this.colors = props.theme.colors;
this.hasManagers = props.item.responsibles.length > 0; this.hasManagers = props.item.responsibles.length > 0;
} }
@ -30,7 +31,7 @@ class ClubListItem extends React.Component<Props> {
let final = []; let final = [];
for (let i = 0; i < categories.length; i++) { for (let i = 0; i < categories.length; i++) {
if (categories[i] !== null) { if (categories[i] !== null) {
const category = this.props.categoryTranslator(categories[i]); const category: category = this.props.categoryTranslator(categories[i]);
final.push( final.push(
<Chip <Chip
style={{marginRight: 5, marginBottom: 5}} style={{marginRight: 5, marginBottom: 5}}
@ -46,6 +47,7 @@ class ClubListItem extends React.Component<Props> {
render() { render() {
const categoriesRender = this.getCategoriesRender.bind(this, this.props.item.category); const categoriesRender = this.getCategoriesRender.bind(this, this.props.item.category);
const colors = this.props.theme.colors;
return ( return (
<List.Item <List.Item
title={this.props.item.name} title={this.props.item.name}
@ -65,7 +67,7 @@ class ClubListItem extends React.Component<Props> {
}} }}
size={48} size={48}
icon={this.hasManagers ? "check-circle-outline" : "alert-circle-outline"} icon={this.hasManagers ? "check-circle-outline" : "alert-circle-outline"}
color={this.hasManagers ? this.colors.success : this.colors.primary} color={this.hasManagers ? colors.success : colors.primary}
/>} />}
style={{ style={{
height: this.props.height, height: this.props.height,

View file

@ -4,18 +4,19 @@ import * as React from 'react';
import {List, withTheme} from 'react-native-paper'; import {List, withTheme} from 'react-native-paper';
import {FlatList, View} from "react-native"; import {FlatList, View} from "react-native";
import {stringMatchQuery} from "../../../utils/Search"; import {stringMatchQuery} from "../../../utils/Search";
import * as Animatable from "react-native-animatable";
import GroupListItem from "./GroupListItem"; import GroupListItem from "./GroupListItem";
import AnimatedAccordion from "../../Animations/AnimatedAccordion"; import AnimatedAccordion from "../../Animations/AnimatedAccordion";
import type {group, groupCategory} from "../../../screens/Planex/GroupSelectionScreen";
import type {CustomTheme} from "../../../managers/ThemeManager";
type Props = { type Props = {
item: Object, item: groupCategory,
onGroupPress: Function, onGroupPress: (group) => void,
onFavoritePress: Function, onFavoritePress: (group) => void,
currentSearchString: string, currentSearchString: string,
favoriteNumber: number, favoriteNumber: number,
height: number, height: number,
theme: Object, theme: CustomTheme,
} }
type State = { type State = {
@ -23,18 +24,14 @@ type State = {
} }
const LIST_ITEM_HEIGHT = 64; const LIST_ITEM_HEIGHT = 64;
const AnimatedListIcon = Animatable.createAnimatableComponent(List.Icon);
class GroupListAccordion extends React.Component<Props, State> { class GroupListAccordion extends React.Component<Props, State> {
chevronRef: Object;
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
expanded: props.item.id === "0", expanded: props.item.id === "0",
} }
this.chevronRef = React.createRef();
} }
shouldComponentUpdate(nextProps: Props, nextSate: State) { shouldComponentUpdate(nextProps: Props, nextSate: State) {
@ -47,24 +44,24 @@ class GroupListAccordion extends React.Component<Props, State> {
|| (nextProps.item.content.length !== this.props.item.content.length); || (nextProps.item.content.length !== this.props.item.content.length);
} }
keyExtractor = (item: Object) => item.id.toString(); keyExtractor = (item: group) => item.id.toString();
renderItem = ({item}: Object) => { renderItem = ({item}: {item: group}) => {
if (stringMatchQuery(item.name, this.props.currentSearchString)) { if (stringMatchQuery(item.name, this.props.currentSearchString)) {
const onPress = () => this.props.onGroupPress(item); const onPress = () => this.props.onGroupPress(item);
const onStartPress = () => this.props.onFavoritePress(item); const onStarPress = () => this.props.onFavoritePress(item);
return ( return (
<GroupListItem <GroupListItem
height={LIST_ITEM_HEIGHT} height={LIST_ITEM_HEIGHT}
item={item} item={item}
onPress={onPress} onPress={onPress}
onStartPress={onStartPress}/> onStarPress={onStarPress}/>
); );
} else } else
return null; return null;
} }
itemLayout = (data: Object, index: number) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index}); itemLayout = (data, index) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index});
render() { render() {
const item = this.props.item; const item = this.props.item;
@ -86,12 +83,13 @@ class GroupListAccordion extends React.Component<Props, State> {
: null} : null}
unmountWhenCollapsed={true}// Only render list if expanded for increased performance unmountWhenCollapsed={true}// Only render list if expanded for increased performance
> >
{/*$FlowFixMe*/}
<FlatList <FlatList
data={item.content} data={item.content}
extraData={this.props.currentSearchString} extraData={this.props.currentSearchString}
renderItem={this.renderItem} renderItem={this.renderItem}
keyExtractor={this.keyExtractor} keyExtractor={this.keyExtractor}
listKey={item.id} listKey={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} // Broken with search getItemLayout={this.itemLayout} // Broken with search
removeClippedSubviews={true} removeClippedSubviews={true}

View file

@ -2,12 +2,14 @@
import * as React from 'react'; import * as React from 'react';
import {IconButton, List, withTheme} from 'react-native-paper'; import {IconButton, List, withTheme} from 'react-native-paper';
import type {CustomTheme} from "../../../managers/ThemeManager";
import type {group} from "../../../screens/Planex/GroupSelectionScreen";
type Props = { type Props = {
theme: Object, theme: CustomTheme,
onPress: Function, onPress: () => void,
onStartPress: Function, onStarPress: () => void,
item: Object, item: group,
height: number, height: number,
} }
@ -17,11 +19,8 @@ type State = {
class GroupListItem extends React.Component<Props, State> { class GroupListItem extends React.Component<Props, State> {
colors: Object;
constructor(props) { constructor(props) {
super(props); super(props);
this.colors = props.theme.colors;
this.state = { this.state = {
isFav: (props.item.isFav !== undefined && props.item.isFav), isFav: (props.item.isFav !== undefined && props.item.isFav),
} }
@ -33,10 +32,11 @@ class GroupListItem extends React.Component<Props, State> {
onStarPress = () => { onStarPress = () => {
this.setState({isFav: !this.state.isFav}); this.setState({isFav: !this.state.isFav});
this.props.onStartPress(); this.props.onStarPress();
} }
render() { render() {
const colors = this.props.theme.colors;
return ( return (
<List.Item <List.Item
title={this.props.item.name} title={this.props.item.name}
@ -51,7 +51,7 @@ class GroupListItem extends React.Component<Props, State> {
icon={"star"} icon={"star"}
onPress={this.onStarPress} onPress={this.onStarPress}
color={this.state.isFav color={this.state.isFav
? this.props.theme.colors.tetrisScore ? colors.tetrisScore
: props.color} : props.color}
/>} />}
style={{ style={{

View file

@ -2,7 +2,7 @@
import * as React from 'react'; import * as React from 'react';
import {Animated, Platform} from "react-native"; import {Animated, Platform} from "react-native";
import {Chip, Searchbar, withTheme} from 'react-native-paper'; import {Chip, Searchbar} from 'react-native-paper';
import AuthenticatedScreen from "../../../components/Amicale/AuthenticatedScreen"; import AuthenticatedScreen from "../../../components/Amicale/AuthenticatedScreen";
import i18n from "i18n-js"; import i18n from "i18n-js";
import ClubListItem from "../../../components/Lists/Clubs/ClubListItem"; import ClubListItem from "../../../components/Lists/Clubs/ClubListItem";
@ -10,11 +10,29 @@ import {isItemInCategoryFilter, stringMatchQuery} from "../../../utils/Search";
import ClubListHeader from "../../../components/Lists/Clubs/ClubListHeader"; import ClubListHeader from "../../../components/Lists/Clubs/ClubListHeader";
import MaterialHeaderButtons, {Item} from "../../../components/Overrides/CustomHeaderButton"; import MaterialHeaderButtons, {Item} from "../../../components/Overrides/CustomHeaderButton";
import {withCollapsible} from "../../../utils/withCollapsible"; import {withCollapsible} from "../../../utils/withCollapsible";
import {StackNavigationProp} from "@react-navigation/stack";
import type {CustomTheme} from "../../../managers/ThemeManager";
import {Collapsible} from "react-navigation-collapsible";
export type category = {
id: number,
name: string,
};
export type club = {
id: number,
name: string,
description: string,
logo: string,
email:string,
category: [number, number],
responsibles: Array<string>,
};
type Props = { type Props = {
navigation: Object, navigation: StackNavigationProp,
theme: Object, theme: CustomTheme,
collapsibleStack: Object, collapsibleStack: Collapsible,
} }
type State = { type State = {
@ -31,14 +49,7 @@ class ClubListScreen extends React.Component<Props, State> {
currentSearchString: '', currentSearchString: '',
}; };
colors: Object; categories: Array<category>;
categories: Array<Object>;
constructor(props) {
super(props);
this.colors = props.theme.colors;
}
/** /**
* Creates the header content * Creates the header content
@ -88,19 +99,25 @@ class ClubListScreen extends React.Component<Props, State> {
this.updateFilteredData(str, null); this.updateFilteredData(str, null);
}; };
keyExtractor = (item: Object) => { keyExtractor = (item: club) => {
return item.id.toString(); return item.id.toString();
}; };
itemLayout = (data, index) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index}); itemLayout = (data, index) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index});
getScreen = (data: Object) => { getScreen = (data: Array<{categories: Array<category>, clubs: Array<club>} | null>) => {
this.categories = data[0].categories; let categoryList = [];
let clubList = [];
if (data[0] != null) {
categoryList = data[0].categories;
clubList = data[0].clubs;
}
this.categories = categoryList;
const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack; const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack;
return ( return (
//$FlowFixMe //$FlowFixMe
<Animated.FlatList <Animated.FlatList
data={data[0].clubs} data={clubList}
keyExtractor={this.keyExtractor} keyExtractor={this.keyExtractor}
renderItem={this.getRenderItem} renderItem={this.getRenderItem}
ListHeaderComponent={this.getListHeader()} ListHeaderComponent={this.getListHeader()}
@ -138,7 +155,7 @@ class ClubListScreen extends React.Component<Props, State> {
}) })
} }
getChipRender = (category: Object, key: string) => { getChipRender = (category: category, key: string) => {
const onPress = this.onChipSelect.bind(this, category.id); const onPress = this.onChipSelect.bind(this, category.id);
return <Chip return <Chip
selected={isItemInCategoryFilter(this.state.currentlySelectedCategories, [category.id])} selected={isItemInCategoryFilter(this.state.currentlySelectedCategories, [category.id])}
@ -165,7 +182,7 @@ class ClubListScreen extends React.Component<Props, State> {
} }
}; };
shouldRenderItem(item) { shouldRenderItem(item: club) {
let shouldRender = this.state.currentlySelectedCategories.length === 0 let shouldRender = this.state.currentlySelectedCategories.length === 0
|| isItemInCategoryFilter(this.state.currentlySelectedCategories, item.category); || isItemInCategoryFilter(this.state.currentlySelectedCategories, item.category);
if (shouldRender) if (shouldRender)
@ -173,7 +190,7 @@ class ClubListScreen extends React.Component<Props, State> {
return shouldRender; return shouldRender;
} }
getRenderItem = ({item}: Object) => { getRenderItem = ({item}: {item: club}) => {
const onPress = this.onListItemPress.bind(this, item); const onPress = this.onListItemPress.bind(this, item);
if (this.shouldRenderItem(item)) { if (this.shouldRenderItem(item)) {
return ( return (
@ -194,7 +211,7 @@ class ClubListScreen extends React.Component<Props, State> {
* *
* @param item The article pressed * @param item The article pressed
*/ */
onListItemPress(item: Object) { onListItemPress(item: club) {
this.props.navigation.navigate("club-information", {data: item, categories: this.categories}); this.props.navigation.navigate("club-information", {data: item, categories: this.categories});
} }
@ -215,4 +232,4 @@ class ClubListScreen extends React.Component<Props, State> {
} }
} }
export default withCollapsible(withTheme(ClubListScreen)); export default withCollapsible(ClubListScreen);

View file

@ -3,26 +3,37 @@
import * as React from 'react'; import * as React from 'react';
import {Platform} from "react-native"; import {Platform} from "react-native";
import i18n from "i18n-js"; import i18n from "i18n-js";
import {Searchbar, withTheme} from "react-native-paper"; import {Searchbar} from "react-native-paper";
import {stringMatchQuery} from "../../utils/Search"; import {stringMatchQuery} from "../../utils/Search";
import WebSectionList from "../../components/Screens/WebSectionList"; import WebSectionList from "../../components/Screens/WebSectionList";
import GroupListAccordion from "../../components/Lists/PlanexGroups/GroupListAccordion"; import GroupListAccordion from "../../components/Lists/PlanexGroups/GroupListAccordion";
import AsyncStorageManager from "../../managers/AsyncStorageManager"; import AsyncStorageManager from "../../managers/AsyncStorageManager";
import {StackNavigationProp} from "@react-navigation/stack";
const LIST_ITEM_HEIGHT = 70; const LIST_ITEM_HEIGHT = 70;
export type group = {
name: string,
id: number,
isFav: boolean,
};
export type groupCategory = {
name: string,
id: number,
content: Array<group>,
};
type Props = { type Props = {
navigation: Object, navigation: StackNavigationProp,
route: Object,
theme: Object,
} }
type State = { type State = {
currentSearchString: string, currentSearchString: string,
favoriteGroups: Array<Object>, favoriteGroups: Array<group>,
}; };
function sortName(a, b) { function sortName(a: group | groupCategory, b: group | groupCategory) {
if (a.name.toLowerCase() < b.name.toLowerCase()) if (a.name.toLowerCase() < b.name.toLowerCase())
return -1; return -1;
if (a.name.toLowerCase() > b.name.toLowerCase()) if (a.name.toLowerCase() > b.name.toLowerCase())
@ -38,7 +49,7 @@ const REPLACE_REGEX = /_/g;
*/ */
class GroupSelectionScreen extends React.Component<Props, State> { class GroupSelectionScreen extends React.Component<Props, State> {
constructor(props) { constructor(props: Props) {
super(props); super(props);
this.state = { this.state = {
currentSearchString: '', currentSearchString: '',
@ -88,18 +99,18 @@ class GroupSelectionScreen extends React.Component<Props, State> {
* *
* @param item The article pressed * @param item The article pressed
*/ */
onListItemPress = (item: Object) => { onListItemPress = (item: group) => {
this.props.navigation.navigate("planex", { this.props.navigation.navigate("planex", {
screen: "index", screen: "index",
params: {group: item} params: {group: item}
}); });
}; };
onListFavoritePress = (item: Object) => { onListFavoritePress = (item: group) => {
this.updateGroupFavorites(item); this.updateGroupFavorites(item);
}; };
isGroupInFavorites(group: Object) { isGroupInFavorites(group: group) {
let isFav = false; let isFav = false;
for (let i = 0; i < this.state.favoriteGroups.length; i++) { for (let i = 0; i < this.state.favoriteGroups.length; i++) {
if (group.id === this.state.favoriteGroups[i].id) { if (group.id === this.state.favoriteGroups[i].id) {
@ -110,7 +121,7 @@ class GroupSelectionScreen extends React.Component<Props, State> {
return isFav; return isFav;
} }
removeGroupFromFavorites(favorites: Array<Object>, group: Object) { removeGroupFromFavorites(favorites: Array<group>, group: group) {
for (let i = 0; i < favorites.length; i++) { for (let i = 0; i < favorites.length; i++) {
if (group.id === favorites[i].id) { if (group.id === favorites[i].id) {
favorites.splice(i, 1); favorites.splice(i, 1);
@ -119,13 +130,13 @@ class GroupSelectionScreen extends React.Component<Props, State> {
} }
} }
addGroupToFavorites(favorites: Array<Object>, group: Object) { addGroupToFavorites(favorites: Array<group>, group: group) {
group.isFav = true; group.isFav = true;
favorites.push(group); favorites.push(group);
favorites.sort(sortName); favorites.sort(sortName);
} }
updateGroupFavorites(group: Object) { updateGroupFavorites(group: group) {
let newFavorites = [...this.state.favoriteGroups] let newFavorites = [...this.state.favoriteGroups]
if (this.isGroupInFavorites(group)) if (this.isGroupInFavorites(group))
this.removeGroupFromFavorites(newFavorites, group); this.removeGroupFromFavorites(newFavorites, group);
@ -137,7 +148,7 @@ class GroupSelectionScreen extends React.Component<Props, State> {
JSON.stringify(newFavorites)); JSON.stringify(newFavorites));
} }
shouldDisplayAccordion(item: Object) { shouldDisplayAccordion(item: groupCategory) {
let shouldDisplay = false; let shouldDisplay = false;
for (let i = 0; i < item.content.length; i++) { for (let i = 0; i < item.content.length; i++) {
if (stringMatchQuery(item.content[i].name, this.state.currentSearchString)) { if (stringMatchQuery(item.content[i].name, this.state.currentSearchString)) {
@ -154,7 +165,7 @@ class GroupSelectionScreen extends React.Component<Props, State> {
* @param item The article to render * @param item The article to render
* @return {*} * @return {*}
*/ */
renderItem = ({item}: Object) => { renderItem = ({item}: { item: groupCategory }) => {
if (this.shouldDisplayAccordion(item)) { if (this.shouldDisplayAccordion(item)) {
return ( return (
<GroupListAccordion <GroupListAccordion
@ -170,18 +181,18 @@ class GroupSelectionScreen extends React.Component<Props, State> {
return null; return null;
}; };
generateData(fetchedData: Object) { generateData(fetchedData: { [key: string]: groupCategory }) {
let data = []; let data = [];
for (let key in fetchedData) { for (let key in fetchedData) {
this.formatGroups(fetchedData[key]); this.formatGroups(fetchedData[key]);
data.push(fetchedData[key]); data.push(fetchedData[key]);
} }
data.sort(sortName); data.sort(sortName);
data.unshift({name: "FAVORITES", id: "0", content: this.state.favoriteGroups}); data.unshift({name: "FAVORITES", id: 0, content: this.state.favoriteGroups});
return data; return data;
} }
formatGroups(item: Object) { formatGroups(item: groupCategory) {
for (let i = 0; i < item.content.length; i++) { for (let i = 0; i < item.content.length; i++) {
item.content[i].name = item.content[i].name.replace(REPLACE_REGEX, " ") item.content[i].name = item.content[i].name.replace(REPLACE_REGEX, " ")
item.content[i].isFav = this.isGroupInFavorites(item.content[i]); item.content[i].isFav = this.isGroupInFavorites(item.content[i]);
@ -194,7 +205,7 @@ class GroupSelectionScreen extends React.Component<Props, State> {
* @param fetchedData * @param fetchedData
* @return {*} * @return {*}
* */ * */
createDataset = (fetchedData: Object) => { createDataset = (fetchedData: { [key: string]: groupCategory }) => {
return [ return [
{ {
title: '', title: '',
@ -219,4 +230,4 @@ class GroupSelectionScreen extends React.Component<Props, State> {
} }
} }
export default withTheme(GroupSelectionScreen); export default GroupSelectionScreen;