@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||||
|
</state>
|
||||||
|
</component>
|
@ -1,7 +1,13 @@
|
|||||||
|
// App.tsx
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Navigation from "./navigation/Navigation";
|
import Navigation from "./navigation/Navigation";
|
||||||
|
import store from "./redux/store";
|
||||||
|
import { Provider } from "react-redux";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return <Navigation/>;
|
return (
|
||||||
// TODO Send to homescreen instead, and include a bottom bar to navigate to Moves, Pokemongs, Trainers
|
<Provider store={store}>
|
||||||
|
<Navigation/>
|
||||||
|
</Provider>);
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 79 KiB |
After Width: | Height: | Size: 54 KiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 31 KiB |
@ -0,0 +1,32 @@
|
|||||||
|
// components/MoveListItem.test.ts
|
||||||
|
|
||||||
|
import { Move } from "../entities/Move";
|
||||||
|
import React from "react";
|
||||||
|
import { StyleSheet, Text, TouchableOpacity } from "react-native";
|
||||||
|
|
||||||
|
type MoveListItemProps = {
|
||||||
|
move: Move;
|
||||||
|
onPress: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MoveListItem: React.FC<MoveListItemProps> = ({ move, onPress }) => (
|
||||||
|
<TouchableOpacity style={styles.listItem} onPress={onPress}>
|
||||||
|
<Text style={styles.listItemText}>{move.name}, {move.type.name}: {move.power}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
listItem: {
|
||||||
|
backgroundColor: '#DDD',
|
||||||
|
padding: 20,
|
||||||
|
marginVertical: 8,
|
||||||
|
marginHorizontal: 16,
|
||||||
|
borderRadius: 10,
|
||||||
|
},
|
||||||
|
listItemText: {
|
||||||
|
color: '#333',
|
||||||
|
fontSize: 18,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default MoveListItem;
|
@ -0,0 +1,2 @@
|
|||||||
|
// config.ts
|
||||||
|
export const API_BASE_URL = 'http://localhost:8080';
|
@ -1,22 +1,67 @@
|
|||||||
|
// navigation/Navigation.tsx
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {NavigationContainer} from '@react-navigation/native';
|
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||||
import {createStackNavigator} from '@react-navigation/stack';
|
import { NavigationContainer } from '@react-navigation/native';
|
||||||
import MoveListScreen from '../screens/MoveListScreen';
|
import MoveListScreen from '../screens/moves/MoveListScreen';
|
||||||
import MoveDetailScreen from '../screens/MoveDetailScreen';
|
import MoveDetailScreen from '../screens/moves/MoveDetailScreen';
|
||||||
import {RootStackParamList} from "./navigationTypes";
|
import HomeScreen from '../screens/HomeScreen';
|
||||||
|
import { createStackNavigator } from '@react-navigation/stack';
|
||||||
|
import { RootStackParamList, RootTabParamList } from "./navigationTypes";
|
||||||
|
import { Image, StyleSheet } from 'react-native';
|
||||||
|
|
||||||
const Stack = createStackNavigator<RootStackParamList>();
|
const Stack = createStackNavigator<RootStackParamList>();
|
||||||
|
const Tab = createBottomTabNavigator<RootTabParamList>();
|
||||||
|
|
||||||
const Navigation = () => {
|
const MoveStack = () => {
|
||||||
// TODO replace 'Move Detail' by the move name itself
|
|
||||||
return (
|
return (
|
||||||
<NavigationContainer>
|
|
||||||
<Stack.Navigator initialRouteName="MoveList">
|
<Stack.Navigator initialRouteName="MoveList">
|
||||||
<Stack.Screen name="MoveList" component={MoveListScreen} options={{title: 'Moves'}}/>
|
<Stack.Screen name="MoveList" component={MoveListScreen} options={{ title: 'Moves' }}/>
|
||||||
<Stack.Screen name="MoveDetail" component={MoveDetailScreen} options={{title: 'Move Detail'}}/>
|
<Stack.Screen
|
||||||
|
name="MoveDetail"
|
||||||
|
component={MoveDetailScreen}
|
||||||
|
options={({ route }) => ({ title: route.params.move.name })}
|
||||||
|
/>
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Navigation = () => {
|
||||||
|
return (
|
||||||
|
<NavigationContainer>
|
||||||
|
<Tab.Navigator initialRouteName="Home"
|
||||||
|
screenOptions={{ headerShown: false }}>
|
||||||
|
<Tab.Screen name="Home" component={HomeScreen} options={{
|
||||||
|
title: 'Home',
|
||||||
|
tabBarIcon: () => <Image source={require('../assets/home.png')}
|
||||||
|
style={styles.icon}/>
|
||||||
|
}}/>
|
||||||
|
<Tab.Screen name="Pokemongs" component={MoveStack} options={{
|
||||||
|
title: 'Pkmng',
|
||||||
|
tabBarIcon: () => <Image source={require('../assets/pokemongs.png')}
|
||||||
|
style={styles.icon}/>
|
||||||
|
}}/>
|
||||||
|
<Tab.Screen name="Moves" component={MoveStack} options={{
|
||||||
|
title: 'Moves',
|
||||||
|
tabBarIcon: () => <Image source={require('../assets/moves.png')}
|
||||||
|
style={styles.icon}/>
|
||||||
|
}}/>
|
||||||
|
<Tab.Screen name="Trainers" component={MoveStack} options={{
|
||||||
|
title: 'Trainers',
|
||||||
|
tabBarIcon: () => <Image source={require('../assets/trainers.png')}
|
||||||
|
style={styles.icon}/>
|
||||||
|
}}/>
|
||||||
|
</Tab.Navigator>
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
icon: {
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
padding: 8,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export default Navigation;
|
export default Navigation;
|
||||||
|
@ -1,6 +1,15 @@
|
|||||||
import {Move} from "../entities/Move";
|
// navigation/navigationTypes.ts
|
||||||
|
|
||||||
|
import { Move } from "../entities/Move";
|
||||||
|
|
||||||
export type RootStackParamList = {
|
export type RootStackParamList = {
|
||||||
MoveList: undefined;
|
MoveList: undefined;
|
||||||
MoveDetail: { move: Move };
|
MoveDetail: { move: Move };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type RootTabParamList = {
|
||||||
|
Home: undefined;
|
||||||
|
Pokemongs: undefined;
|
||||||
|
Moves: undefined;
|
||||||
|
Trainers: undefined;
|
||||||
|
};
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
// redux/actions/moveAction.ts
|
||||||
|
|
||||||
|
import { FETCH_MOVES } from '../constants';
|
||||||
|
import { Move } from "../../entities/Move";
|
||||||
|
import { Dispatch } from "redux";
|
||||||
|
import { API_BASE_URL } from "../../config";
|
||||||
|
|
||||||
|
export const setMoves = (moves: Move[]) => {
|
||||||
|
return {
|
||||||
|
type: FETCH_MOVES,
|
||||||
|
payload: moves,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getMoves = () => {
|
||||||
|
return async (dispatch: Dispatch) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/move`);
|
||||||
|
const data = await response.json();
|
||||||
|
dispatch(setMoves(data));
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1,3 @@
|
|||||||
|
// redux/constants.ts
|
||||||
|
|
||||||
|
export const FETCH_MOVES = 'FETCH_MOVES';
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
// redux/reducers/moveReducer.ts
|
||||||
|
import { FETCH_MOVES } from '../constants';
|
||||||
|
import { Move } from "../../entities/Move";
|
||||||
|
import { AnyAction } from "redux";
|
||||||
|
|
||||||
|
export type MoveState = {
|
||||||
|
moves: Move[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type MoveAction = AnyAction & {
|
||||||
|
type: typeof FETCH_MOVES;
|
||||||
|
payload?: Move[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialState: MoveState = {
|
||||||
|
moves: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function moveReducer(state = initialState, action: MoveAction): MoveState {
|
||||||
|
switch (action.type) {
|
||||||
|
case FETCH_MOVES:
|
||||||
|
return { ...state, moves: action.payload || [] };
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
|||||||
|
// redux/store.ts
|
||||||
|
|
||||||
|
import { configureStore } from '@reduxjs/toolkit'
|
||||||
|
import moveReducer from './reducers/moveReducer';
|
||||||
|
|
||||||
|
export type AppDispatch = typeof store.dispatch;
|
||||||
|
|
||||||
|
const store = configureStore({
|
||||||
|
reducer: {
|
||||||
|
move: moveReducer
|
||||||
|
},
|
||||||
|
middleware: (getDefaultMiddleware) => getDefaultMiddleware(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default store;
|
@ -0,0 +1,14 @@
|
|||||||
|
// screens/HomeScreen.tsx
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { Image, View } from 'react-native';
|
||||||
|
|
||||||
|
const HomeScreen = () => {
|
||||||
|
return (
|
||||||
|
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
||||||
|
<Image source={require('../assets/logo.png')} style={{ width: 500, height: 500 }}/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HomeScreen;
|
@ -1,54 +0,0 @@
|
|||||||
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;
|
|
@ -1,41 +0,0 @@
|
|||||||
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;
|
|
@ -0,0 +1,55 @@
|
|||||||
|
// screens/moves/MoveDetailScreen.tsx
|
||||||
|
|
||||||
|
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}>Name: {move.name}</Text>
|
||||||
|
<Text style={styles.detail}>Category: {move.category}</Text>
|
||||||
|
<Text style={styles.detail}>Power: {move.power}</Text>
|
||||||
|
<Text style={styles.detail}>Accuracy: {move.accuracy}</Text>
|
||||||
|
<Text style={styles.detail}>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',
|
||||||
|
marginBottom: 10,
|
||||||
|
},
|
||||||
|
detail: {
|
||||||
|
fontSize: 18,
|
||||||
|
marginBottom: 5,
|
||||||
|
},
|
||||||
|
typeListsContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-evenly',
|
||||||
|
marginTop: 10,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default MoveDetailScreen;
|
@ -0,0 +1,51 @@
|
|||||||
|
// screens/moves/MoveListScreen.tsx
|
||||||
|
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { FlatList, ScrollView, View } from 'react-native';
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
|
import { RootStackParamList } from "../../navigation/navigationTypes";
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { getMoves } from '../../redux/actions/moveActions';
|
||||||
|
import { MoveState } from "../../redux/reducers/moveReducer";
|
||||||
|
import { AppDispatch } from "../../redux/store";
|
||||||
|
import MoveListItem from "../../components/MoveListItem";
|
||||||
|
|
||||||
|
type MoveListScreenNavigationProp = StackNavigationProp<RootStackParamList, 'MoveList'>;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
navigation: MoveListScreenNavigationProp;
|
||||||
|
};
|
||||||
|
type RootState = {
|
||||||
|
move: MoveState;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MoveListScreen = ({ navigation }: Props) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const moves = useSelector((state: RootState) => state.move.moves);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadMoves = async () => {
|
||||||
|
await (dispatch as AppDispatch)(getMoves());
|
||||||
|
};
|
||||||
|
loadMoves();
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollView>
|
||||||
|
<View>
|
||||||
|
<FlatList
|
||||||
|
data={moves}
|
||||||
|
renderItem={({ item }) => (
|
||||||
|
<MoveListItem
|
||||||
|
move={item}
|
||||||
|
onPress={() => navigation.navigate('MoveDetail', { move: item })}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
keyExtractor={(item) => item.name}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MoveListScreen;
|