💄 Create Move screens (#3)
continuous-integration/drone/push Build is passing Details

... and 📝 add sketches to Readme

Co-authored-by: Alexis DRAI <alexis.drai@etu.uca.fr>
Reviewed-on: alexis.drai/AD_ReactNative#3
pull/11/head
Alexis Drai 2 years ago
parent 110d930b50
commit bf1ecb518d

@ -1,20 +1,7 @@
import {StatusBar} from 'expo-status-bar'; import React from 'react';
import {StyleSheet, Text, View} from 'react-native'; import Navigation from "./navigation/Navigation";
export default function App() { export default function App() {
return ( return <Navigation/>;
<View style={styles.container}> // TODO Send to homescreen instead, and include a bottom bar to navigate to Moves, Pokemongs, Trainers
<Text>Open up App.js to start working on your app!</Text>
<StatusBar style="auto"/>
</View>
);
} }
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#FFFFFF',
alignItems: 'center',
justifyContent: 'center',
},
});

@ -17,4 +17,26 @@
# AD_ReactNative # AD_ReactNative
A React Native app for education purposes. Refer to [instructions here](https://react-native-courses.clubinfo-clermont.fr/docs/notation). A React Native app for education purposes. Refer
to [instructions here](https://react-native-courses.clubinfo-clermont.fr/docs/notation).
## Sketches
This app will contain several "master/detail" tabs. They are as follows.
### Trainers
<img src="./docs/trainers.jpg" width="540" style="margin:20px">
### Pokemongs
<img src="./docs/pokemongs.jpg" width="540" style="margin:20px">
### Moves
<img src="./docs/moves.jpg" width="540" style="margin:20px">
## Using the app
This app is linked to a backend that is set up to accept CORS from [`http://localhost:19006`](http://localhost:19006), so please make sure you're
not overriding that default port number when running it.

@ -1,6 +1,6 @@
module.exports = function(api) { module.exports = function (api) {
api.cache(true); api.cache(true);
return { return {
presets: ['babel-preset-expo'], presets: ['babel-preset-expo'],
}; };
}; };

@ -1,10 +0,0 @@
import React from 'react';
import {render} from '@testing-library/react-native';
import Greeting from './Greeting';
describe('Greeting component', () => {
it('renders Hello, World! text', () => {
const {getByText} = render(<Greeting/>);
expect(getByText('Hello, World!')).toBeTruthy();
});
});

@ -1,6 +0,0 @@
import React from 'react';
import {Text} from 'react-native';
export default function Greeting() {
return (<Text>Hello, World!</Text>);
}

@ -0,0 +1,25 @@
import React from 'react';
import {render} from '@testing-library/react-native';
import TypeTacticsInfoList from './TypeTacticsInfoList';
describe('TypeTacticsInfoList component', () => {
it('renders types correctly', () => {
const types = ['FIRE', 'WATER', 'GRASS'];
const {getByText} = render(<TypeTacticsInfoList isWeakness={true} types={types}/>);
types.forEach(type => {
expect(getByText(type)).toBeTruthy();
});
});
it('renders "Nothing" when types array is empty', () => {
const {getByText} = render(<TypeTacticsInfoList isWeakness={false} types={[]}/>);
expect(getByText('Nothing')).toBeTruthy();
});
it('renders "Nothing" when types is undefined', () => {
// @ts-ignore
const {getByText} = render(<TypeTacticsInfoList isWeakness={true} types={undefined}/>);
expect(getByText('Nothing')).toBeTruthy();
});
});

@ -0,0 +1,40 @@
import React from "react";
import {StyleSheet, View, Text, ScrollView} from "react-native";
type TypeListProps = {
isWeakness: boolean;
types: string[];
};
const TypeTacticsInfoList = ({isWeakness, types}: TypeListProps) => {
if (!types || types.length === 0) {
types = ['Nothing'];
}
return (
<ScrollView style={isWeakness ? styles.weakAgainst : styles.effectiveAgainst}>
<View style={styles.list}>
<Text>{isWeakness ? 'weak against' : 'effective against'}:</Text>
{types.map((type, index) => (
<Text key={index}>{type}</Text>
))}
</View>
</ScrollView>
);
};
const styles = StyleSheet.create({
list: {
borderRadius: 5,
padding: 10,
marginBottom: 10,
},
weakAgainst: {
backgroundColor: '#FF6961',
},
effectiveAgainst: {
backgroundColor: '#77DD77',
},
});
export default TypeTacticsInfoList;

Binary file not shown.

After

Width:  |  Height:  |  Size: 836 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

@ -0,0 +1,9 @@
import {Type} from "./Type";
export interface Move {
name: string;
category: string;
power: number;
accuracy: number;
type: Type;
}

@ -0,0 +1,5 @@
export interface Type {
name: string;
weakAgainst: string[];
effectiveAgainst: string[];
}

@ -0,0 +1,22 @@
import React from 'react';
import {NavigationContainer} from '@react-navigation/native';
import {createStackNavigator} from '@react-navigation/stack';
import MoveListScreen from '../screens/MoveListScreen';
import MoveDetailScreen from '../screens/MoveDetailScreen';
import {RootStackParamList} from "./navigationTypes";
const Stack = createStackNavigator<RootStackParamList>();
const Navigation = () => {
// TODO replace 'Move Detail' by the move name itself
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="MoveList">
<Stack.Screen name="MoveList" component={MoveListScreen} options={{title: 'Moves'}}/>
<Stack.Screen name="MoveDetail" component={MoveDetailScreen} options={{title: 'Move Detail'}}/>
</Stack.Navigator>
</NavigationContainer>
);
};
export default Navigation;

@ -0,0 +1,6 @@
import {Move} from "../entities/Move";
export type RootStackParamList = {
MoveList: undefined;
MoveDetail: { move: Move };
};

@ -30,6 +30,9 @@
}, },
"dependencies": { "dependencies": {
"@expo/webpack-config": "^18.0.1", "@expo/webpack-config": "^18.0.1",
"@react-navigation/bottom-tabs": "^6.5.7",
"@react-navigation/native": "^6.1.6",
"@react-navigation/stack": "^6.3.16",
"@testing-library/react-native": "^12.1.2", "@testing-library/react-native": "^12.1.2",
"@types/react": "~18.0.27", "@types/react": "~18.0.27",
"expo": "^48.0.0", "expo": "^48.0.0",
@ -37,6 +40,9 @@
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-native": "0.71.8", "react-native": "0.71.8",
"react-native-gesture-handler": "~2.9.0",
"react-native-safe-area-context": "4.5.0",
"react-native-screens": "~3.20.0",
"react-native-web": "~0.18.11", "react-native-web": "~0.18.11",
"typescript": "^4.9.4" "typescript": "^4.9.4"
}, },

@ -0,0 +1,54 @@
import React from 'react';
import {ScrollView, StyleSheet, Text, View} from 'react-native';
import {RouteProp} from '@react-navigation/native';
import {RootStackParamList} from "../navigation/navigationTypes";
import TypeTacticsInfoList from "../components/TypeTacticsInfoList"
type MoveDetailScreenRouteProp = RouteProp<RootStackParamList, 'MoveDetail'>;
type Props = {
route: MoveDetailScreenRouteProp;
};
const MoveDetailScreen = ({route}: Props) => {
const {move} = route.params;
return (
<ScrollView style={styles.container}>
<Text style={styles.title}>{move.name}</Text>
<Text>Name: {move.name}</Text>
<Text>Category: {move.category}</Text>
<Text>Power: {move.power}</Text>
<Text>Accuracy: {move.accuracy}</Text>
<Text>Type: {move.type.name}</Text>
<View style={styles.typeListsContainer}>
<TypeTacticsInfoList isWeakness={true} types={move.type.weakAgainst}/>
<TypeTacticsInfoList isWeakness={false} types={move.type.effectiveAgainst}/>
</View>
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#FFFFFF',
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
},
list: {
backgroundColor: '#F0F0F0',
borderRadius: 5,
padding: 10,
marginBottom: 10,
},
typeListsContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
},
});
export default MoveDetailScreen;

@ -0,0 +1,41 @@
import React, {useEffect, useState} from 'react';
import {FlatList, ScrollView, Text, TouchableOpacity, View} from 'react-native';
import {StackNavigationProp} from '@react-navigation/stack';
import {RootStackParamList} from "../navigation/navigationTypes";
import {Move} from "../entities/Move";
type MoveListScreenNavigationProp = StackNavigationProp<RootStackParamList, 'MoveList'>;
type Props = {
navigation: MoveListScreenNavigationProp;
};
const MoveListScreen = ({navigation}: Props) => {
const [moves, setMoves] = useState<Move[]>([]);
// FIXME LATER use a redux store to fetch the data
useEffect(() => {
fetch('http://localhost:8080/move')
.then(response => response.json())
.then(data => setMoves(data))
.catch(error => console.error(error));
}, []);
return (
<ScrollView>
<View>
<FlatList
data={moves}
renderItem={({item}) => (
<TouchableOpacity onPress={() => navigation.navigate('MoveDetail', {move: item})}>
<Text>{item.name}, {item.type.name}: {item.power}</Text>
</TouchableOpacity>
)}
keyExtractor={(item) => item.name}
/>
</View>
</ScrollView>
);
};
export default MoveListScreen;

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save