🐛 Fix Android display bug

by not inserting MultiSelect (which uses a VirtualizedList)
into a ScrollView anymore
pull/21/head
Alexis Drai 2 years ago
parent 1b41495222
commit 866b3406bd

@ -86,6 +86,22 @@ The updating screen provides a form for updating an existing `Move`.
<img src="./docs/update.png" width="410" style="margin:20px" alt="">> <img src="./docs/update.png" width="410" style="margin:20px" alt="">>
## Some business logic
While the back end doesn't forbid a `Move` from being both weak against and effective against the same one type,
it should. That's one of the flaws of that API.
In this app, we did implement the business logic described above in our callbacks for our `MultiSelect` elements, like
so:
```typescript
const handleSelectType = (selectedTypes: string[], setTypes: React.Dispatch<React.SetStateAction<string[]>>, otherSelectedTypes: string[]) => {
const uniqueSelectedTypes = Array.from(new Set(selectedTypes));
const withoutDuplicatesFromOtherColumn = uniqueSelectedTypes.filter(type => !otherSelectedTypes.includes(type));
setTypes(withoutDuplicatesFromOtherColumn);
};
```
## Using the app ## Using the app
### Running the backend ### Running the backend
@ -94,7 +110,7 @@ In order to use this app, you will need to run the dedicated backend. A `README`
with [instructions](https://github.com/draialexis/pokemong_api#user-content-prep-steps) is provided with [instructions](https://github.com/draialexis/pokemong_api#user-content-prep-steps) is provided
for that purpose. for that purpose.
### Connecting to the backend locally ### Connecting to the backend locally
First, please find the `config.ts` file at the root of this project, and replace ~~`192.168.0.15`~~ First, please find the `config.ts` file at the root of this project, and replace ~~`192.168.0.15`~~
with the IPv4 address associated with your own Wi-Fi adapter. with the IPv4 address associated with your own Wi-Fi adapter.

@ -1,23 +1,32 @@
// screens/moves/MoveFormScreen.tsx // screens/moves/MoveFormScreen.tsx
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Button, StyleSheet, Text, TextInput } from 'react-native'; import {
import { StackNavigationProp } from '@react-navigation/stack'; Button,
import { RootStackParamList } from "../../navigation/navigationTypes"; KeyboardAvoidingView,
import { useDispatch, useSelector } from 'react-redux'; Modal,
import { createMove, updateMove } from '../../redux/actions/moveActions'; Platform,
import { AppDispatch, RootState } from "../../redux/store"; ScrollView,
import { Move } from "../../entities/Move"; StyleSheet,
import { RouteProp } from "@react-navigation/native"; Text,
import { MOVE_FORM } from "../../navigation/constants"; TextInput,
import { Picker } from "@react-native-community/picker"; View
import { ItemValue } from "@react-native-community/picker/typings/Picker"; } from 'react-native';
import { MoveCategoryName } from "../../entities/MoveCategoryName"; import { StackNavigationProp } from '@react-navigation/stack';
import { TypeName } from "../../entities/TypeName"; import { RootStackParamList } from "../../navigation/navigationTypes";
import MultiSelect from "react-native-multiple-select"; import { useDispatch, useSelector } from 'react-redux';
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view"; import { createMove, updateMove } from '../../redux/actions/moveActions';
import AlertModal from "../../components/AlertModal"; import { AppDispatch, RootState } from "../../redux/store";
import { MOVE_ERROR } from "../../redux/constants"; import { Move } from "../../entities/Move";
import { RouteProp } from "@react-navigation/native";
import { MOVE_FORM } from "../../navigation/constants";
import { Picker } from "@react-native-community/picker";
import { ItemValue } from "@react-native-community/picker/typings/Picker";
import { MoveCategoryName } from "../../entities/MoveCategoryName";
import { TypeName } from "../../entities/TypeName";
import MultiSelect from "react-native-multiple-select";
import AlertModal from "../../components/AlertModal";
import { MOVE_ERROR } from "../../redux/constants";
type MoveFormScreenNavigationProp = StackNavigationProp<RootStackParamList, typeof MOVE_FORM>; type MoveFormScreenNavigationProp = StackNavigationProp<RootStackParamList, typeof MOVE_FORM>;
type MoveFormScreenRouteProp = RouteProp<RootStackParamList, typeof MOVE_FORM>; type MoveFormScreenRouteProp = RouteProp<RootStackParamList, typeof MOVE_FORM>;
@ -50,6 +59,14 @@ const MoveFormScreen = ({ navigation, route }: Props) => {
}); });
}, [navigation, route.params?.move]); }, [navigation, route.params?.move]);
const [isModalVisible, setModalVisible] = useState(false);
const [currentMultiSelect, setCurrentMultiSelect] = useState<'weakAgainst' | 'effectiveAgainst'>('weakAgainst');
const handleOpenModal = (multiSelect: 'weakAgainst' | 'effectiveAgainst') => {
setCurrentMultiSelect(multiSelect);
setModalVisible(true);
};
const [selectedWeakAgainst, setSelectedWeakAgainst] = useState<string[]>(move.type.weakAgainst); const [selectedWeakAgainst, setSelectedWeakAgainst] = useState<string[]>(move.type.weakAgainst);
const [selectedEffectiveAgainst, setSelectedEffectiveAgainst] = useState<string[]>(move.type.effectiveAgainst); const [selectedEffectiveAgainst, setSelectedEffectiveAgainst] = useState<string[]>(move.type.effectiveAgainst);
@ -76,89 +93,113 @@ const MoveFormScreen = ({ navigation, route }: Props) => {
}; };
return ( return (
<KeyboardAwareScrollView style={styles.container}> <KeyboardAvoidingView
<AlertModal style={styles.container}
visible={!!error} behavior={Platform.OS === "ios" ? "padding" : "height"}
message={error || ''} >
onClose={() => dispatch({ type: MOVE_ERROR, payload: null })} <ScrollView contentContainerStyle={{ paddingBottom: 20 }}>
/> <AlertModal
<Text style={styles.label}>Name: </Text> visible={!!error}
<TextInput message={error || ''}
value={move.name} onClose={() => dispatch({ type: MOVE_ERROR, payload: null })}
onChangeText={(text) => setMove({ ...move, name: text })} />
style={styles.input} <View style={styles.row}>
/> <Text style={styles.label}>Name: </Text>
<Text style={styles.label}>Category: </Text> <TextInput
<Picker value={move.name}
selectedValue={move.category} onChangeText={(text) => setMove({ ...move, name: text })}
style={styles.input} style={styles.input}
onValueChange={(itemValue: ItemValue) => />
setMove({ ...move, category: itemValue as MoveCategoryName }) </View>
}> <View style={styles.row}>
{Object.values(MoveCategoryName).map((value) => <Text style={styles.label}>Category: </Text>
<Picker.Item key={value} label={value} value={value}/> <Picker
)} selectedValue={move.category}
</Picker> style={styles.input}
<Text style={styles.label}>Power: </Text> onValueChange={(itemValue: ItemValue) =>
<TextInput setMove({ ...move, category: itemValue as MoveCategoryName })
value={move.power.toString()} }>
onChangeText={(text) => { {Object.values(MoveCategoryName).map((value) =>
if (!isNaN(Number(text))) { <Picker.Item key={value} label={value} value={value}/>
setMove({ ...move, power: Number(text) }); )}
} </Picker>
}} </View>
style={styles.input} <View style={styles.row}>
keyboardType="numeric" <Text style={styles.label}>Power: </Text>
/> <TextInput
<Text style={styles.label}>Accuracy: </Text> value={move.power.toString()}
<TextInput onChangeText={(text) => {
value={move.accuracy.toString()} if (!isNaN(Number(text))) {
onChangeText={(text) => { setMove({ ...move, power: Number(text) });
if (!isNaN(Number(text))) { }
setMove({ ...move, accuracy: Number(text) }); }}
} style={styles.input}
}} keyboardType="numeric"
style={styles.input} />
keyboardType="numeric" </View>
/> <View style={styles.row}>
<Text style={styles.label}>Type: </Text> <Text style={styles.label}>Accuracy: </Text>
<Picker <TextInput
selectedValue={move.type.name} value={move.accuracy.toString()}
style={styles.input} onChangeText={(text) => {
onValueChange={(itemValue: ItemValue) => if (!isNaN(Number(text))) {
setMove({ ...move, type: { ...move.type, name: itemValue as TypeName } }) setMove({ ...move, accuracy: Number(text) });
}> }
{Object.values(TypeName).map((value) => }}
<Picker.Item key={value} label={value} value={value}/> style={styles.input}
)} keyboardType="numeric"
</Picker> />
<Text style={styles.label}>Weak Against: </Text> </View>
<MultiSelect <View style={styles.row}>
items={ <Text style={styles.label}>Type: </Text>
Object.values(TypeName).map((value) => ({ id: value, name: value })) <Picker
} selectedValue={move.type.name}
uniqueKey="id" style={styles.input}
onSelectedItemsChange={ onValueChange={(itemValue: ItemValue) =>
(selectedItems) => handleSelectType(selectedItems, setSelectedWeakAgainst, selectedEffectiveAgainst) setMove({ ...move, type: { ...move.type, name: itemValue as TypeName } })
} }>
selectedItems={selectedWeakAgainst} {Object.values(TypeName).map((value) =>
displayKey="name" <Picker.Item key={value} label={value} value={value}/>
/> )}
<Text style={styles.label}>Effective Against: </Text> </Picker>
<MultiSelect </View>
items={ <Text>Weak Against: {selectedWeakAgainst.join(', ')}</Text>
Object.values(TypeName).map((value) => ({ id: value, name: value })) <View style={styles.buttonContainer}>
} <Button title="Select Weak Against" onPress={() => handleOpenModal('weakAgainst')}/>
uniqueKey="id" </View>
onSelectedItemsChange={ <Text>Effective Against: {selectedEffectiveAgainst.join(', ')}</Text>
(selectedItems) => handleSelectType(selectedItems, setSelectedEffectiveAgainst, selectedWeakAgainst) <View style={styles.buttonContainer}>
} <Button title="Select Effective Against" onPress={() => handleOpenModal('effectiveAgainst')}/>
selectedItems={selectedEffectiveAgainst} </View>
displayKey="name" <View style={styles.buttonContainer}>
/> <Button title="Save" onPress={handleSave} color={styles.saveButton.backgroundColor}/>
<Button title="Save" onPress={handleSave}/> </View>
</ScrollView>
</KeyboardAwareScrollView> <Modal
animationType="slide"
transparent={true}
visible={isModalVisible}
onRequestClose={() => {
setModalVisible(!isModalVisible);
}}>
<View style={styles.centeredView}>
<View style={styles.modalView}>
<MultiSelect
items={
Object.values(TypeName).map((value) => ({ id: value, name: value }))
}
uniqueKey="id"
onSelectedItemsChange={
(selectedItems) => handleSelectType(selectedItems, currentMultiSelect === 'weakAgainst' ? setSelectedWeakAgainst : setSelectedEffectiveAgainst, currentMultiSelect === 'weakAgainst' ? selectedEffectiveAgainst : selectedWeakAgainst)
}
selectedItems={currentMultiSelect === 'weakAgainst' ? selectedWeakAgainst : selectedEffectiveAgainst}
displayKey="name"
/>
<Button title="Close" onPress={() => setModalVisible(false)}/>
</View>
</View>
</Modal>
</KeyboardAvoidingView>
); );
}; };
@ -167,14 +208,49 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
padding: 16, padding: 16,
}, },
label: {
marginRight: 8,
},
input: { input: {
backgroundColor: '#CEC', backgroundColor: '#CEC',
borderRadius: 8, borderRadius: 8,
height: 32, minHeight: 32,
flex: 1,
}, },
label: { row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
margin: 8, margin: 8,
} flexWrap: 'wrap',
},
centeredView: {
flex: 1,
justifyContent: "center",
alignItems: "center",
marginTop: 22
},
modalView: {
margin: 20,
backgroundColor: "white",
borderRadius: 20,
padding: 35,
alignItems: "center",
shadowColor: "#000",
shadowOffset: {
width: 0,
height: 2
},
shadowOpacity: 0.25,
shadowRadius: 4,
elevation: 5
},
buttonContainer: {
margin: 8,
},
saveButton: {
backgroundColor: '#BADA55',
},
}); });
export default MoveFormScreen; export default MoveFormScreen;

Loading…
Cancel
Save