Compare commits
32 Commits
DarkLightT
...
master
@ -1,32 +0,0 @@
|
|||||||
# React-Native BUT2 Notation
|
|
||||||
|
|
||||||
Duration : 10 * 2h
|
|
||||||
|
|
||||||
## Documentation (4 pts)
|
|
||||||
|
|
||||||
* Application sketches
|
|
||||||
|
|
||||||
## Basics (10 pts)
|
|
||||||
|
|
||||||
* Navigation (2 pts)
|
|
||||||
* Tab bottom navigation + at least one button
|
|
||||||
* Store (2 pts)
|
|
||||||
* Read data from redux store
|
|
||||||
* Actions (1 pts)
|
|
||||||
* Update data to redux store
|
|
||||||
* Display list of items (2 pts)
|
|
||||||
* FlatList, VirtualizedList or SectionList
|
|
||||||
* Display image (1 pts)
|
|
||||||
* Child props (1 pts)
|
|
||||||
* TextInput (1 pts)
|
|
||||||
|
|
||||||
## Application features (6 pts)
|
|
||||||
|
|
||||||
* Retrieve data using the Web API (2 pts)
|
|
||||||
* Store favorite data into phone storage (2 pts)
|
|
||||||
* Write Tests (2 pts)
|
|
||||||
|
|
||||||
## Bonus (only taken into account if the basics are all mastered)
|
|
||||||
|
|
||||||
* Dark/Light mode switch (2pts)
|
|
||||||
* Sexy UI (2 pts)
|
|
@ -1 +1,79 @@
|
|||||||
[Slides](https://iutsa01.blob.core.windows.net/react-native/ReactNative.pdf)
|
<div align = center>
|
||||||
|
|
||||||
|
<img src="assets/banner.png" width="1050" height="">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div align = center>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div align = center>
|
||||||
|
|
||||||
|
# **DesckStone**
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# :bookmark: Présentation
|
||||||
|
|
||||||
|
DeckStone est une application master detail créé grâce au framework React Native en TypeScript et en JavaScript.
|
||||||
|
Elle vous permet la gestion des cartes hearstone que vous posséder avec la consultation des informations des cartes et l'utilisation de favoris persistants.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Notation :white_check_mark:
|
||||||
|
|
||||||
|
|
||||||
|
## ``` Documentation ```
|
||||||
|
|
||||||
|
:white_check_mark: **ReadMe**
|
||||||
|
:white_check_mark: **Sketches** : 
|
||||||
|
|
||||||
|
## ```Bases```
|
||||||
|
|
||||||
|
:white_check_mark: **Navigation** : Nous avons une barre de navigation fonctionnel nous permettant de naviguer entre les trois écrans.
|
||||||
|
:white_check_mark: **Store** : Nous utilisons le store pour charger nos données et les stocker mais aussi pour gérer les favoris.
|
||||||
|
:white_check_mark: **Actions** : Nous utilisons les actions pour charger les données depuis le redux mais aussi pour ajouter et supprimer les favoris.
|
||||||
|
:white_check_mark: **Display List of Items** : Nous affichons la liste des cartes et celles des favoris dans 2 écrans différents.
|
||||||
|
:white_check_mark: **Display image** : Chaque cartes sont affiché par un component qui affiche l'image de la carte.
|
||||||
|
:white_check_mark: **Child props** : Nous utilisons un props pour passer la carte au component qui l'affiche.
|
||||||
|
:white_check_mark: **TextInput** : Une recherche par nom peut être effectué par dans les 2 listes.
|
||||||
|
|
||||||
|
## ```Application features```
|
||||||
|
|
||||||
|
:construction: **API** : Nous utilisons l'API officielle du jeu pour récupérer nos données, cependant nous n'avons pas pu gérer le changement de token, nous le changeons donc à la main.
|
||||||
|
***Source*** : https://develop.battle.net/documentation/hearthstone/game-data-apis
|
||||||
|
:white_check_mark: **Store favorite data into phone storage** : Nous utilison l'AsyncStorage pour sauvegarder la liste des favoris dans le téléphone.
|
||||||
|
:construction: **Write Tests** : Nous testons les actions, le reducer et les composants UI que nous avons implémenter, cependant les éléments utilisant indirectement l'AsyncStorage ne passent pas les tests à cause d'un problème d'utilisation du mockAsyncStorage.
|
||||||
|
|
||||||
|
## ``Bonus``
|
||||||
|
|
||||||
|
* :construction: **Dark/Light mode switch** : Nous n'avons mis en place cette gestion seulement dans la branche dédié par souci de visuel
|
||||||
|
* :construction: **Sexy UI** : A vous de juger !
|
||||||
|
|
||||||
|
|
||||||
|
# :construction: Développeurs
|
||||||
|
|
||||||
|
- Corentin RICHARD : corentin.richard@etu.uca.fr
|
||||||
|
- Pierre FERREIRA : pierre.ferreira@etu.uca.fr
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<a href = "https://codefirst.iut.uca.fr/git/corentin.richard">
|
||||||
|
<img src="https://codefirst.iut.uca.fr/git/avatars/4372364870f18ab9104f13222fa84d2e?size=870" width="50" >
|
||||||
|
</a>
|
||||||
|
<a href = "https://codefirst.iut.uca.fr/git/pierre.ferreira">
|
||||||
|
<img src="https://codefirst.iut.uca.fr/git/avatars/edbacace5f621ae77077f206ebdcee27?size=870" width="50" >
|
||||||
|
</a>
|
||||||
|
|
||||||
|
© IUT - Auvergne
|
||||||
|
</div>
|
@ -0,0 +1,9 @@
|
|||||||
|
// somewhere in your configuration files
|
||||||
|
import AsyncStorageMock from '@react-native-async-storage/async-storage/jest/async-storage-mock';
|
||||||
|
|
||||||
|
AsyncStorageMock.multiGet = jest.fn(([keys], callback) => {
|
||||||
|
// do something here to retrieve data
|
||||||
|
callback([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default AsyncStorageMock;
|
After Width: | Height: | Size: 144 KiB |
After Width: | Height: | Size: 286 KiB |
After Width: | Height: | Size: 281 KiB |
@ -0,0 +1,18 @@
|
|||||||
|
import { View } from 'react-native';
|
||||||
|
import { Image } from 'react-native';
|
||||||
|
|
||||||
|
|
||||||
|
type ItemProps = {
|
||||||
|
url : string //Image URL
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ListItemComponent(props : ItemProps){
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<Image
|
||||||
|
source={{uri: props.url}}
|
||||||
|
style={{flex:1, minHeight:250, minWidth:180}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { Card } from "../models/Card";
|
||||||
|
import { setFavList } from "../redux/actions/action_setFavList";
|
||||||
|
import { Image, TouchableHighlight, View } from "react-native";
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import {CardProps} from "../props/favProps"
|
||||||
|
import { FontAwesome } from '@expo/vector-icons';
|
||||||
|
import { StyleSheet} from 'react-native';
|
||||||
|
|
||||||
|
|
||||||
|
export default function Item(props: CardProps){ // a mettre dans components et definir une props pour passer le param
|
||||||
|
|
||||||
|
const {route} = props;
|
||||||
|
const item: Card = route.card;
|
||||||
|
const bool: boolean = route.bool;
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
|
const HandleAddFav = (props : CardProps) => {
|
||||||
|
dispatch(setFavList(props));
|
||||||
|
}
|
||||||
|
return(
|
||||||
|
|
||||||
|
/* dispatch , */
|
||||||
|
<View style={styles.item}>
|
||||||
|
<TouchableHighlight testID="button" style={item.fav?styles.favoriteButtonFav:styles.favoriteButtonNonFav} onPress={() => HandleAddFav(props)} >
|
||||||
|
<FontAwesome name="heart-o" size={50} color="#fff" />
|
||||||
|
</TouchableHighlight>
|
||||||
|
<Image
|
||||||
|
source={{uri:item.img}}
|
||||||
|
style={{flex:1, minHeight:250, minWidth:180}}/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
item: {
|
||||||
|
zIndex:0
|
||||||
|
},
|
||||||
|
favoriteButtonNonFav: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 10,
|
||||||
|
right: 10,
|
||||||
|
backgroundColor: 'red',
|
||||||
|
borderRadius: 50,
|
||||||
|
padding: 10,
|
||||||
|
zIndex:1
|
||||||
|
},
|
||||||
|
favoriteButtonFav: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 10,
|
||||||
|
right: 10,
|
||||||
|
backgroundColor: 'red',
|
||||||
|
borderRadius: 50,
|
||||||
|
padding: 10,
|
||||||
|
zIndex:1
|
||||||
|
},});
|
@ -1,41 +0,0 @@
|
|||||||
import { StatusBar } from 'expo-status-bar';
|
|
||||||
import { StyleSheet, Text, View, Button } from 'react-native';
|
|
||||||
import React, { useState } from "react";
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
export default function Main(props : mainProps){
|
|
||||||
const [count, setCount] = useState(0);
|
|
||||||
return (
|
|
||||||
<View style={styles.container}>
|
|
||||||
<View style={styles.border}>
|
|
||||||
<Text>Maman, prend la caméra ! !</Text>
|
|
||||||
<StatusBar style="auto" />
|
|
||||||
<Text>{count}</Text>
|
|
||||||
<Button onPress={()=> setCount(count+1)} title="+1"/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: '#ffffff',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
borderWidth: 5,
|
|
||||||
borderColor : "#ff00ff",
|
|
||||||
},
|
|
||||||
border: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: '#ff0000',
|
|
||||||
maxHeight : 100,
|
|
||||||
borderWidth : 15,
|
|
||||||
borderRadius : 15,
|
|
||||||
borderColor : '#00ffaa',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,14 +0,0 @@
|
|||||||
|
|
||||||
// export class ApiDataManager{
|
|
||||||
|
|
||||||
|
|
||||||
// public async getCards() : Promise<String[]> {
|
|
||||||
|
|
||||||
|
|
||||||
// const CardPromise = await fetch('https://omgvamp-hearthstone-v1.p.rapidapi.com/cards')
|
|
||||||
|
|
||||||
// const CardListJson = await CardPromise.json();
|
|
||||||
// const CardList: String[] = Array.of(CardListJson);
|
|
||||||
// return CardList;
|
|
||||||
// }
|
|
||||||
// }
|
|
@ -1,13 +0,0 @@
|
|||||||
import { Card } from "../models/Card"
|
|
||||||
|
|
||||||
export class StubLib {
|
|
||||||
|
|
||||||
public getCards(): Card[] {
|
|
||||||
const NOUNOURS_LIST : Card[] = [
|
|
||||||
|
|
||||||
|
|
||||||
]
|
|
||||||
return NOUNOURS_LIST
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
|||||||
|
|
||||||
export class CardSet {
|
|
||||||
|
|
||||||
constructor(id: number,name : string, type : string ) {
|
|
||||||
this._id = id
|
|
||||||
this._name =name
|
|
||||||
this._type = type
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private _id : number
|
|
||||||
|
|
||||||
get id(): number {
|
|
||||||
return this._id
|
|
||||||
}
|
|
||||||
|
|
||||||
// NAME //
|
|
||||||
private _name : string;
|
|
||||||
get name(): string {
|
|
||||||
return this._name;
|
|
||||||
}
|
|
||||||
set name(value: string) {
|
|
||||||
this._name = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _type : string;
|
|
||||||
get type(): string {
|
|
||||||
return this._type;
|
|
||||||
}
|
|
||||||
set type(value: string) {
|
|
||||||
this._type = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
export class Classe {
|
|
||||||
constructor(private id : number, private name : string) {
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
export class Type{
|
|
||||||
|
|
||||||
constructor(private id : number, private name : string) {
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
import { NavigationContainer } from '@react-navigation/native';
|
||||||
|
import { createStackNavigator } from '@react-navigation/stack';
|
||||||
|
import {createNativeStackNavigator} from '@react-navigation/native-stack';
|
||||||
|
|
||||||
|
import ListScreen from '../screens/ListScreen';
|
||||||
|
import DetailCard from '../screens/DetailCard';
|
||||||
|
|
||||||
|
export default function DetailStackNav() {
|
||||||
|
const Stack = createStackNavigator();
|
||||||
|
return (
|
||||||
|
<Stack.Navigator initialRouteName="CardList">
|
||||||
|
<Stack.Screen name="CardList" component={ListScreen}/>
|
||||||
|
<Stack.Screen name="DetailCard" component={DetailCard}/>
|
||||||
|
</Stack.Navigator>
|
||||||
|
)
|
||||||
|
}
|
@ -1,21 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
// import { NavigationContainer } from '@react-navigation/native';
|
|
||||||
// import { createStackNavigator } from '@react-navigation/stack';
|
|
||||||
|
|
||||||
// import HomeScreen from '../screens/HomeScreen';
|
|
||||||
// import ListScreen from '../screens/ListScreen';
|
|
||||||
// import ListFav from '../screens/ListFav';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// export default function ListNavigator() {
|
|
||||||
// const Stack = createStackNavigator();
|
|
||||||
// return (
|
|
||||||
// <Stack.Navigator initialRouteName="List">
|
|
||||||
// <Stack.Screen name="List" component={ListScreen}/>
|
|
||||||
// <Stack.Screen name="ListFav" component={ListFav}/>
|
|
||||||
// </Stack.Navigator>
|
|
||||||
// )
|
|
||||||
// }
|
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,8 @@
|
|||||||
|
import { Card } from "../models/Card";
|
||||||
|
|
||||||
|
export interface CardProps{
|
||||||
|
route : {
|
||||||
|
card: Card;
|
||||||
|
bool: boolean;
|
||||||
|
}
|
||||||
|
}
|
@ -1,85 +1,53 @@
|
|||||||
import { setCardsList } from "./action_setCardsList";
|
import { setCardsList } from "./action_setCardsList";
|
||||||
|
|
||||||
import { Card } from "../../models/Card";
|
import { Card } from "../../models/Card";
|
||||||
|
|
||||||
//! se fichier devra possiblement changer de dossier !!!
|
//Fonction chargé de l'appel à l'API
|
||||||
|
|
||||||
//! classe pour tester
|
|
||||||
// export class Card {
|
|
||||||
// cardId : String
|
|
||||||
// name : String
|
|
||||||
// manaCost : number
|
|
||||||
// attack : number
|
|
||||||
// health : number
|
|
||||||
// desc : String
|
|
||||||
|
|
||||||
// // constructor() {
|
|
||||||
// // this.cardId = "cardId";
|
|
||||||
// // this.name = "name";
|
|
||||||
// // this.manaCost = 0;
|
|
||||||
// // this.attack = 0;
|
|
||||||
// // this.health = 0;
|
|
||||||
// // this.desc = "desc";
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// constructor(cardId : String, name : String, manaCost : number, attack : number, health : number, desc : String){
|
|
||||||
// this.cardId = cardId;
|
|
||||||
// this.name = name;
|
|
||||||
// this.manaCost = manaCost;
|
|
||||||
// this.attack = attack;
|
|
||||||
// this.health = health;
|
|
||||||
// this.desc = desc;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
//Define your action creators that will be responsible for asynchronous operations
|
|
||||||
export const getAllCards = () => {
|
export const getAllCards = () => {
|
||||||
//In order to use await your callback must be asynchronous using async keyword.
|
|
||||||
console.log("getallcard")
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
//Then perform your asynchronous operations.
|
|
||||||
try {
|
try {
|
||||||
//Have it first fetch data from our starwars url.
|
|
||||||
const options = {
|
const options = {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'content-length':'9505',
|
'content-length': '9508',
|
||||||
'content-type':'application/json; charset=utf-8',
|
'content-type': 'application/json; charset=utf-8',
|
||||||
'etag':'W/"74bb-QMT8DIj6saBS1wT4u5WWcEmZAdw"'
|
'etag': 'W/"74bb-d4gMlMNks7UGES3Jmn6wzUTXaLI"',
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
const CardsPromise = await fetch('https://us.api.blizzard.com/hearthstone/cards?locale=en_US&access_token=EURTWhjBC2SRb4Ez42BT1kx8R2NcJc07kL', options);
|
//! Actualisation de l'API (19/04) :
|
||||||
//console.log("FETCH")
|
const CardsPromise = await fetch('https://us.api.blizzard.com/hearthstone/cards?locale=en_US&access_token=EUo8Snb09AfE3zQR4CoaB71gq1q3qvSmgL', options);
|
||||||
//console.log(CardsPromise)
|
|
||||||
|
|
||||||
//Then use the json method to get json data from api/
|
//Then use the json method to get json data from api/
|
||||||
const CardsListJson = await CardsPromise.json();
|
const CardsListJson = await CardsPromise.json();
|
||||||
|
|
||||||
//console.log(CardsListJson['cards'])
|
|
||||||
|
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const CardsList: Card[] = CardsListJson['cards'].map(elt => new Card(elt["id"] ? elt["id"] : 1,
|
const CardsList: Card[] = CardsListJson['cards'].map(elt => new Card(elt["id"] ? elt["id"] : 1,
|
||||||
elt["name"] ? elt["name"] : "",
|
elt["name"] ? elt["name"] : "",
|
||||||
|
elt["health"] ? elt["health"] : 0,
|
||||||
|
elt["attack"] ? elt["attack"] : 0,
|
||||||
|
elt["manaCost"] ? elt["manaCost"] : 0,
|
||||||
|
elt["rarityId"] ? elt["rarityId"] : 0,
|
||||||
|
elt["flavorText"] ? elt["flavorText"] : "",
|
||||||
|
elt["classId"] ? elt["classId"] : 0,
|
||||||
|
elt["multiClassIds"] ? elt["multiClassIds"] : "Nothing",
|
||||||
elt["image"] ? elt["image"] : "",
|
elt["image"] ? elt["image"] : "",
|
||||||
elt["imageGold"] ? elt["imageGold"] : "",
|
elt["imageGold"] ? elt["imageGold"] : "",
|
||||||
|
elt["cropImage"] ? elt["cropImage"] : "",
|
||||||
|
elt["artistName"] ? elt["artistName"] : "",
|
||||||
)); //, elt["cardSet"], elt["type"], elt["faction"], elt["rarity"], elt["cost"], elt["attack"], elt["health"],elt["text"], elt["flavor"], elt["artist"], elt["collectible"], elt["elite"], elt["race"], elt["img"], elt["imgGold"]
|
)); //, elt["cardSet"], elt["type"], elt["faction"], elt["rarity"], elt["cost"], elt["attack"], elt["health"],elt["text"], elt["flavor"], elt["artist"], elt["collectible"], elt["elite"], elt["race"], elt["img"], elt["imgGold"]
|
||||||
//elt["cardId"] == null ? elt["cardId"] : ""
|
//elt["cardId"] == null ? elt["cardId"] : ""
|
||||||
|
|
||||||
//console.log("TOTO")
|
|
||||||
|
|
||||||
//console.log(CardsList)
|
|
||||||
//call the action
|
//call the action
|
||||||
dispatch(setCardsList(CardsList));
|
dispatch(setCardsList(CardsList));
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Error---------', error);
|
console.log('Error---------', error);
|
||||||
//You can dispatch to another action if you want to display an error message in the application
|
|
||||||
//dispatch(fetchDataRejected(error))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,8 +1,10 @@
|
|||||||
import {ADD_FAVORITE_DATA, FETCH_DATA} from '../constants';
|
import { Card } from '../../models/Card';
|
||||||
|
import { CardProps } from '../../props/favProps';
|
||||||
|
import {ADD_FAVORITE_DATA} from '../constants';
|
||||||
|
|
||||||
export const setFavList = (List: String[]) => {
|
export const setFavList = (props : CardProps) => {
|
||||||
return {
|
return {
|
||||||
type: ADD_FAVORITE_DATA,
|
type: ADD_FAVORITE_DATA,
|
||||||
payload: List,
|
payload: props,
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
import { Card } from '../../models/Card';
|
||||||
|
import {SET_FAVS} from '../constants';
|
||||||
|
|
||||||
|
export const setList = (list: []) => {
|
||||||
|
return {
|
||||||
|
type: SET_FAVS,
|
||||||
|
payload: list
|
||||||
|
};
|
||||||
|
}
|
@ -1,15 +0,0 @@
|
|||||||
import {ADD_FAVORITE_DATA, FETCH_DATA} from '../constants';
|
|
||||||
|
|
||||||
export const setList = (List: String[]) => {
|
|
||||||
return {
|
|
||||||
type: FETCH_DATA,
|
|
||||||
payload: List,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// export const setFavList = (List: String[]) => {
|
|
||||||
// return {
|
|
||||||
// type: ADD_FAVORITE_DATA,
|
|
||||||
// payload: List,
|
|
||||||
// };
|
|
||||||
// }
|
|
@ -1,7 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
export const FETCH_DATA = "FETCH_DATA"
|
export const FETCH_DATA = "FETCH_DATA"
|
||||||
|
|
||||||
export const ADD_FAVORITE_DATA = "ADD_FAVORITE_DATA"
|
export const ADD_FAVORITE_DATA = "ADD_FAVORITE_DATA"
|
||||||
|
|
||||||
export const DISPLAY_ALL_CARD = "DISPLAY_ALL_CARD"
|
export const SET_FAVS = "SET_FAVS"
|
||||||
|
|
||||||
|
@ -0,0 +1,91 @@
|
|||||||
|
import { StyleSheet, Text, View, Button, FlatList } from 'react-native';
|
||||||
|
import { StatusBar } from 'expo-status-bar';
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { Card } from '../models/Card';
|
||||||
|
|
||||||
|
import { Table, Row, Rows } from 'react-native-table-component';
|
||||||
|
|
||||||
|
//* Components
|
||||||
|
import {ListItemComponent} from '../components/ListItemComponent'
|
||||||
|
import { ScrollView } from 'react-native-gesture-handler';
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
export default function DetailMain({ route }){
|
||||||
|
|
||||||
|
const { card, other } = route.params;
|
||||||
|
|
||||||
|
const tableHead = ['Stat', 'Value'];
|
||||||
|
const tableData = [
|
||||||
|
['Mana cost :', card.manaCost],
|
||||||
|
['Attack : ', card.attack],
|
||||||
|
['Health : ', card.health],
|
||||||
|
['Rarity : ', card.rarity],
|
||||||
|
['Artist : ', card.artistName],
|
||||||
|
['Class : ', card.classId],
|
||||||
|
]
|
||||||
|
|
||||||
|
let pressed : Boolean = false
|
||||||
|
const [titleText, setTitleText] = useState(card.name)
|
||||||
|
const onPressTitle = () => {
|
||||||
|
pressed ? setTitleText(titleText + '\n' + 'Id : ' + card.id) : setTitleText(card.name)
|
||||||
|
pressed ? pressed = false :pressed = true
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={styles.titleView}>
|
||||||
|
<Text style={styles.title} onPress={onPressTitle} >{titleText}</Text>
|
||||||
|
<Text style={styles.flavor}>{card.flavorText}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.item}>
|
||||||
|
<ListItemComponent url={card.img}/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<ScrollView>
|
||||||
|
<Table borderStyle={{borderWidth: 5, borderColor: '#c8e1ff'}}>
|
||||||
|
<Row data={tableHead} style={styles.head} textStyle={styles.text}/>
|
||||||
|
<Rows data={tableData} textStyle={styles.text}/>
|
||||||
|
</Table>
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
borderRadius : 15,
|
||||||
|
backgroundColor: '#efefef',
|
||||||
|
padding: 15,
|
||||||
|
maxHeight:300,
|
||||||
|
maxWidth:350,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontStyle: "italic",
|
||||||
|
fontWeight: "bold",
|
||||||
|
fontSize: 20,
|
||||||
|
},
|
||||||
|
flavor: {
|
||||||
|
fontStyle: "italic",
|
||||||
|
},
|
||||||
|
titleView: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingVertical: 0,
|
||||||
|
paddingHorizontal : 15,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius : 10,
|
||||||
|
borderWidth: 10,
|
||||||
|
borderColor: '#efefef',
|
||||||
|
maxHeight:100,
|
||||||
|
minHeight: 100
|
||||||
|
},
|
||||||
|
head: { height: 40, backgroundColor: '#f1f8ff', minWidth: '90%'},
|
||||||
|
text: { margin: 6 }
|
||||||
|
});
|
@ -0,0 +1,28 @@
|
|||||||
|
import { Card } from "../models/Card";
|
||||||
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
|
|
||||||
|
|
||||||
|
export default class StorageHeart {
|
||||||
|
|
||||||
|
static async getItem(key: string): Promise<any> {
|
||||||
|
try {
|
||||||
|
const value = await AsyncStorage.getItem(key);
|
||||||
|
|
||||||
|
if (value !== null) {
|
||||||
|
return JSON.parse(value);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`AsyncStorage getItem error: ${e}`);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async setItem(key: string, value: any): Promise<void> {
|
||||||
|
try {
|
||||||
|
await AsyncStorage.setItem(key, JSON.stringify(value));
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`AsyncStorage setItem error: ${e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
import { configureStore } from "@reduxjs/toolkit";
|
||||||
|
import testReducer from "../setup/testReducer";
|
||||||
|
import { Provider } from "react-redux";
|
||||||
|
import { render } from "react-dom";
|
||||||
|
import Item from "../../components/ListItemComponent";
|
||||||
|
|
||||||
|
jest.useFakeTimers();
|
||||||
|
|
||||||
|
const store = configureStore({
|
||||||
|
reducer: {
|
||||||
|
appReducer: testReducer,
|
||||||
|
},
|
||||||
|
middleware: (getDefaultMiddleWare) =>
|
||||||
|
getDefaultMiddleWare({
|
||||||
|
serializableCheck: false
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const Wrapper = ({children}) => (<Provider store={store}>{children}</Provider>);
|
||||||
|
|
||||||
|
describe('<Item/>', ()=> {
|
||||||
|
test('Assert displayed values', () =>{
|
||||||
|
const expectedCardInfos = store.getState().appReducer.cards[0];
|
||||||
|
|
||||||
|
render(<Wrapper>
|
||||||
|
<Item route={{
|
||||||
|
card: expectedCardInfos,
|
||||||
|
bool: false
|
||||||
|
}} ></Item>
|
||||||
|
</Wrapper>)
|
||||||
|
|
||||||
|
expect(screen.getByTestId('card-url')).toHaveProperty("source", {uri: expectedCardInfos.img})
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,44 @@
|
|||||||
|
import { configureStore } from "@reduxjs/toolkit";
|
||||||
|
import testReducer from "../setup/testReducer";
|
||||||
|
import { Provider } from "react-redux";
|
||||||
|
import { render } from "react-dom";
|
||||||
|
import Item from "../../components/ListItemComponent";
|
||||||
|
import { fireEvent } from "@testing-library/react-native";
|
||||||
|
import AsyncStorageMock from "../../__mocks__/@react-native-community/async-storage";
|
||||||
|
|
||||||
|
|
||||||
|
jest.useFakeTimers();
|
||||||
|
|
||||||
|
const store = configureStore({
|
||||||
|
reducer: {
|
||||||
|
appReducer: testReducer,
|
||||||
|
},
|
||||||
|
middleware: (getDefaultMiddleWare) =>
|
||||||
|
getDefaultMiddleWare({
|
||||||
|
serializableCheck: false
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const Wrapper = ({children}) => (<Provider store={store}>{children}</Provider>);
|
||||||
|
|
||||||
|
describe('<Item/>', ()=> {
|
||||||
|
test('Assert displayed values for fav list', () =>{
|
||||||
|
const expectedCardInfos = store.getState().appReducer.favoriteCards[0];
|
||||||
|
|
||||||
|
render(<Wrapper>
|
||||||
|
<Item route={{
|
||||||
|
card: expectedCardInfos,
|
||||||
|
bool: true,
|
||||||
|
}} ></Item>
|
||||||
|
</Wrapper>)
|
||||||
|
|
||||||
|
expect(screen.getByTestId('card-url')).toHaveProperty("source", {uri: expectedCardInfos.img})
|
||||||
|
|
||||||
|
let size = store.getState().appReducer.favoriteCards.length;
|
||||||
|
|
||||||
|
fireEvent.press(screen.getByTestId("button"))
|
||||||
|
|
||||||
|
expect(store.getState().appReducer.favoriteCards.length).toBe(size - 1)
|
||||||
|
|
||||||
|
})
|
||||||
|
});
|
@ -0,0 +1,14 @@
|
|||||||
|
import { Card } from "../../models/Card";
|
||||||
|
import { setCardsList } from "../../redux/actions/action_setCardsList";
|
||||||
|
|
||||||
|
describe('setCardLIst',() => {
|
||||||
|
it('should take the list', () => {
|
||||||
|
const payload = [new Card("1","test1","",""),new Card("2","test2","","",true)];
|
||||||
|
const expectation = {
|
||||||
|
type: "FETCH_DATA",
|
||||||
|
payload: payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(setCardsList(payload)).toEqual(expectation);
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,16 @@
|
|||||||
|
import { Card } from "../../models/Card";
|
||||||
|
import { setFavList } from "../../redux/actions/action_setFavList";
|
||||||
|
import { setList } from "../../redux/actions/action_setFavs";
|
||||||
|
|
||||||
|
|
||||||
|
describe('setFavs',() => {
|
||||||
|
it('should take the list', () => {
|
||||||
|
const payload = [new Card("1","test1","",""),new Card("2","test2","","",true)];
|
||||||
|
const expectation = {
|
||||||
|
type: "SET_FAVS",
|
||||||
|
payload: payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(setList(payload)).toEqual(expectation);
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,25 @@
|
|||||||
|
import appReducer from "../../redux/reducers/appReducer";
|
||||||
|
|
||||||
|
describe('Test Reducer', () => {
|
||||||
|
let initialState = {
|
||||||
|
cards: [],
|
||||||
|
favoriteCards: []
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should return initial state', () => {
|
||||||
|
expect(appReducer(undefined, {})).toEqual(initialState);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle FETCH_DATA', () => {
|
||||||
|
const payload = [new Card("1","test1","",""),new Card("2","test2","","",true)];
|
||||||
|
expect(
|
||||||
|
appReducer(initialState, {
|
||||||
|
type: "FETCH_DATA",
|
||||||
|
payload,
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
cards: payload,
|
||||||
|
favoriteCards: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,3 @@
|
|||||||
|
import mockAsyncStorage from '@react-native-async-storage/async-storage/jest/async-storage-mock';
|
||||||
|
|
||||||
|
jest.mock('@react-native-community/async-storage', () => mockAsyncStorage);
|
@ -0,0 +1,13 @@
|
|||||||
|
import { Card } from "../../models/Card";
|
||||||
|
import appReducer from "../../redux/reducers/appReducer"
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
cards: [new Card("1","test1","url1","urlGold2")],
|
||||||
|
favoriteCards: [new Card("1","test1","url1","urlGold2")]
|
||||||
|
}
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
export default testReducer = (state = initialState, action) => {
|
||||||
|
//@ts-ignore
|
||||||
|
return appReducer(initialState,action);
|
||||||
|
}
|
Loading…
Reference in new issue