merge, les item on un lien avec le detail et le fav mais l'affichage des fav ne marche pas car item defini comme undefin

CardDetail
Pierre Ferreira 2 years ago
commit 33ce875c5a

@ -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>
---
&nbsp; ![Android](https://img.shields.io/badge/Android-3DDC84?style=for-the-badge&logo=android&logoColor=white)
&nbsp; ![Java](https://img.shields.io/badge/JavaScript-F7DF1E?style=for-the-badge&logo=javascript&logoColor=black)
---
</div>
<div align = center>
# **DesckStone**
</div>
# :bookmark_tabs: 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** : ![Sketchs](./assets/Sketchs.PNG)
## ```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_worker: **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_worker: **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``
* :warning: **Dark/Light mode switch** : Nous n'avons pas mis en place ce mode
* :construction_worker: **Sexy UI** : A vous de juger !
# :construction_worker: 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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

@ -15,4 +15,4 @@ export function ListItemComponent(props : ItemProps){
/> />
</View> </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,11 +1,7 @@
import {isValidNumber} from "react-native-gesture-handler/lib/typescript/web_hammer/utils";
import {CardSet} from "./CardSet";
import {Type} from "./Type";
import {Classe} from "./Classe";
export class Card { export class Card {
constructor(id: number,name :string,health : number, attack : number, manaCost : number, rarityId : number, flavorText : string, classId : number, multiClassIds : any, img : string, imgGold : string, cropImage : string, artistName : string){//,set : CardSet,type : Type,clas : Classe,rarity : string,, desc : string,flavor : string,artist : string,collectible : boolean,elite : boolean,race : string, cropImg :string) { constructor(id: number,name :string,health : number, attack : number, manaCost : number, rarityId : number, flavorText : string, classId : number, multiClassIds : any, img : string, imgGold : string, cropImage : string, artistName : string, fav : Boolean){//,set : CardSet,type : Type,clas : Classe,rarity : string,, desc : string,flavor : string,artist : string,collectible : boolean,elite : boolean,race : string, cropImg :string) {
this._id=id this._id=id
this._name=name this._name=name
//this._set=set //this._set=set
@ -26,11 +22,11 @@ export class Card {
this._img = img this._img = img
this._imgGold = imgGold this._imgGold = imgGold
this._cropImage = cropImage this._cropImage = cropImage
this._fav = fav
} }
// ID // // ID //
private _id : number; private _id : number;
get id(): number { get id(): number {
return this._id return this._id
@ -45,12 +41,6 @@ export class Card {
this._name = value; this._name = value;
} }
// private _set : CardSet;
// get set(): CardSet{
// return this._set
// }
// set set(value : CardSet){ // set set(value : CardSet){
// this._set = value // this._set = value
// } // }
@ -187,4 +177,9 @@ export class Card {
set cropImage(value: string) { set cropImage(value: string) {
this._cropImage = value; this._cropImage = value;
} }
//FAV//
public _fav;
get fav(): Boolean {
return this._fav
}
} }

@ -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) {
}
}

@ -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>
// )
// }

@ -1,11 +1,7 @@
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View, Button } from 'react-native';
import React, { useState } from "react"; import React, { useState } from "react";
import { NavigationContainer } from '@react-navigation/native'; import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import HomeScreen from '../screens/HomeScreen';
import ListScreen from '../screens/ListScreen'; import ListScreen from '../screens/ListScreen';
import ListFav from '../screens/ListFav'; import ListFav from '../screens/ListFav';
@ -13,9 +9,6 @@ import TabBarIcon from '../components/TabBarIcon';
import StackNavigation from './StackNavigation'; import StackNavigation from './StackNavigation';
import DetailStackNav from './DetailStackNav'; import DetailStackNav from './DetailStackNav';
export default function Navigation() { export default function Navigation() {
const BottomTabNavigator = createBottomTabNavigator(); const BottomTabNavigator = createBottomTabNavigator();
return ( return (

@ -11,12 +11,10 @@ import ListFav from '../screens/ListFav';
export default function StackNavigation() { export default function StackNavigation() {
const Stack = createStackNavigator(); const Stack = createStackNavigator();
return ( return (
//<NavigationContainer>
<Stack.Navigator initialRouteName="Home"> <Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen}/> <Stack.Screen name="Home" component={HomeScreen}/>
<Stack.Screen name="ListScreen" component={ListScreen}/> <Stack.Screen name="ListScreen" component={ListScreen}/>
<Stack.Screen name="ListFav" component={ListFav}/> <Stack.Screen name="ListFav" component={ListFav}/>
</Stack.Navigator> </Stack.Navigator>
//</NavigationContainer>
) )
} }

24745
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -6,16 +6,43 @@
"start": "expo start", "start": "expo start",
"android": "expo start --android", "android": "expo start --android",
"ios": "expo start --ios", "ios": "expo start --ios",
"web": "expo start --web" "web": "expo start --web",
"test": "jest"
},
"jest": {
"setupFiles": [
"./test/setup/jestSetupFile.js"
],
"preset": "jest-expo",
"verbose": true,
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg)"
],
"testMatch": [
"**.test.js"
],
"testEnvironment": "node",
"testEnvironmentOptions": {
"browsers": [
"chrome",
"firefox",
"safari"
]
}
}, },
"dependencies": { "dependencies": {
"@jest/globals": "^29.5.0",
"@react-navigation/bottom-tabs": "^6.5.5", "@react-navigation/bottom-tabs": "^6.5.5",
"@react-navigation/native": "^6.1.4", "@react-navigation/native": "^6.1.4",
"@react-navigation/native-stack": "^6.9.10", "@react-navigation/native-stack": "^6.9.10",
"@react-navigation/stack": "^6.3.14", "@react-navigation/stack": "^6.3.14",
"@reduxjs/toolkit": "^1.9.3", "@reduxjs/toolkit": "^1.9.3",
"@testing-library/jest-native": "^5.4.2",
"@testing-library/react-native": "^12.0.1",
"expo": "^48.0.0", "expo": "^48.0.0",
"expo-status-bar": "~1.4.4", "expo-status-bar": "~1.4.4",
"jest": "^29.2.1",
"jest-expo": "^48.0.2",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-native": "0.71.3", "react-native": "0.71.3",
@ -24,10 +51,13 @@
"react-native-table-component": "^1.2.2", "react-native-table-component": "^1.2.2",
"react-native-web": "~0.18.11", "react-native-web": "~0.18.11",
"react-redux": "^8.0.5", "react-redux": "^8.0.5",
"redux": "^4.2.1" "redux": "^4.2.1",
"@react-native-async-storage/async-storage": "1.17.11"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.0", "@babel/core": "^7.20.0",
"@react-native-async-storage/async-storage": "^1.18.1",
"@types/jest": "^29.5.0",
"@types/react": "~18.0.27", "@types/react": "~18.0.27",
"@types/react-native": "~0.70.6", "@types/react-native": "~0.70.6",
"@types/react-native-table-component": "^1.2.4", "@types/react-native-table-component": "^1.2.4",

@ -0,0 +1,8 @@
import { Card } from "../models/Card";
export interface CardProps{
route : {
card: Card;
bool: boolean;
}
}

@ -1,75 +1,28 @@
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: {
// 'content-length':'9505',
// 'content-type':'application/json; charset=utf-8',
// 'etag':'W/"74bb-QMT8DIj6saBS1wT4u5WWcEmZAdw"'
// }
//! Actualisation de l'API (16/03) :
headers: { headers: {
'content-length': '9508', 'content-length': '9508',
'content-type': 'application/json; charset=utf-8', 'content-type': 'application/json; charset=utf-8',
'etag': 'W/"74bb-d4gMlMNks7UGES3Jmn6wzUTXaLI"', 'etag': 'W/"74bb-d4gMlMNks7UGES3Jmn6wzUTXaLI"',
} }
//'pageSize':'100'
}; };
const CardsPromise = await fetch('https://us.api.blizzard.com/hearthstone/cards?locale=en_US&access_token=EUe6p4N9uLm8BbsHyYVZXIa4DDBP2hMR05', options); //! Actualisation de l'API (19/04) :
//const CardsPromise = await fetch('https://us.api.blizzard.com/hearthstone/cards?locale=en_US&access_token=EUZvGOfXsMKYrjqLJp5mE7IJlhQuELMiPk ', options); const CardsPromise = await fetch('https://us.api.blizzard.com/hearthstone/cards?locale=en_US&access_token=EUo8Snb09AfE3zQR4CoaB71gq1q3qvSmgL', options);
//const CardsPromise = await fetch('https://us.api.blizzard.com/hearthstone/cards?locale=en_US&access_token=EURTWhjBC2SRb4Ez42BT1kx8R2NcJc07kL', options);
//console.log("FETCH")
//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
@ -88,17 +41,13 @@ export const getAllCards = () => {
elt["artistName"] ? elt["artistName"] : "", 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,5 @@
import {ADD_FAVORITE_DATA, FETCH_DATA} from '../constants'; import { Card } from '../../models/Card';
import {FETCH_DATA} from '../constants';
//? Changer cette importe quand la classe sera definit dans un fichier correctement.
import {Card} from '../../models/Card'
export const setCardsList = (List: Card[]) => { export const setCardsList = (List: Card[]) => {
return { return {
type: FETCH_DATA, type: FETCH_DATA,

@ -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"

@ -1,22 +1,48 @@
import {FETCH_DATA, ADD_FAVORITE_DATA} from '../constants' import { CardProps } from '../../props/favProps'
import {FETCH_DATA, ADD_FAVORITE_DATA, SET_FAVS} from '../constants'
import StorageHeart from '../../service/AsyncStorage'
const initialState = { const initialState = {
cards: [], cards: [],
favoriteCards: [], favoriteCards: []
// cards: ["C_ace", "C_K", "C_Q", "C_J"],
// favoriteCards: [ "C_ace", "C_K"],
} }
// @ts-ignore // @ts-ignore
export default appReducer = (state = initialState, action) => { export default appReducer = (state = initialState, action) => {
switch (action.type) { switch (action.type) {
case ADD_FAVORITE_DATA: case ADD_FAVORITE_DATA: //Ajout d'une carte aux favoris
// @ts-ignore
return {...state, favoriteCards: state.favoriteCards.push(action.payload)}; const a : CardProps = action.payload
case FETCH_DATA: if(a.route.bool ==false){
// @ts-ignore if(state.favoriteCards == undefined){
const tab = [a.route.card]
StorageHeart.setItem("favoriteList",tab)
return {...state, favoriteCards : tab};
}
if( Array.from(state.favoriteCards).every((elem) => elem != a.route.card)){
//@ts-ignore
const tab = state.favoriteCards.concat([a.route.card])
StorageHeart.setItem("favoriteList",tab)
return {...state, favoriteCards : tab};
}
return {...state}
}
else{
const tab = state.favoriteCards.filter((item) => item!= a.route.card)
StorageHeart.setItem("favoriteList",tab)
return {...state, favoriteCards : tab };
}
case FETCH_DATA: //Récupération des données des cartes depuis l'API
return {...state, cards: action.payload}; return {...state, cards: action.payload};
case SET_FAVS: //Récupération des favoris depuis l'async storage
//@ts-ignore
return {...state, favoriteCards: action.payload}
default: default:
return state; return state;
} }

@ -1,7 +1,6 @@
import {configureStore} from '@reduxjs/toolkit' import {configureStore} from '@reduxjs/toolkit'
import appReducer from './reducers/appReducer'; import appReducer from './reducers/appReducer';
// Reference here all your application reducers
const reducer = { const reducer = {
appReducer: appReducer, appReducer: appReducer,
} }
@ -10,7 +9,6 @@ const reducer = {
const store = configureStore({ const store = configureStore({
// @ts-ignore // @ts-ignore
reducer, reducer,
// @ts-ignore // @ts-ignore
middleware: (getDefaultMiddleware) => getDefaultMiddleware({serializableCheck : false}), //desactive le check de la serialization (primitif) middleware: (getDefaultMiddleware) => getDefaultMiddleware({serializableCheck : false}), //desactive le check de la serialization (primitif)
},); },);

@ -1,36 +1,60 @@
import { StyleSheet, Text, View, TouchableNativeFeedback, Image } from 'react-native';
import { useDispatch } from 'react-redux';
import { useEffect } from 'react';
import { getAllCards } from "../redux/actions/actionSelection"
import StorageHeart from '../service/AsyncStorage';
import { setList } from '../redux/actions/action_setFavs';
import Navigation from '../navigation/Navigation';
import { StyleSheet, Text, View, TouchableNativeFeedback } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { NavigationContainer } from '@react-navigation/native';
import StackNavigation from '../navigation/StackNavigation'
import { Colors } from 'react-native/Libraries/NewAppScreen';
//import Button from 'react-bootstrap/Button'; // @ts-ignore //
// @ts-ignore //(ta gueule pour l'erreur sur navigation)
export default function HomeScreen({navigation}) { export default function HomeScreen({navigation}) {
const dispatch = useDispatch();
//chargement des listes
useEffect(() => {
const loadFavCards = async () => {
//@ts-ignore
const list = await StorageHeart.getItem("favoriteList")
//@ts-ignore
dispatch(setList(list))
};
loadFavCards();
}, [dispatch]);
useEffect(() => {
const loadCards = async () => {
//@ts-ignore
await dispatch(getAllCards());
};
loadCards();
}, [dispatch]);
const tabNav = Navigation()
// affichage de la homePage
return ( return (
<View style={styles.container}> <View style={styles.container}>
<View style={styles.centered}> <View style={styles.centered}>
<Text style={styles.title}>Mes super Nounours !</Text> <Text style={styles.title}>DeckStone</Text>
</View> </View>
<Text>Mon super texte ...</Text> <Text style={styles.txt}>Votre gestionnaire de cartes Hearthstone </Text>
{/* <MyCustomComponent /> */} {/* <MyCustomComponent /> */}
<View style={styles.MidArea}> <View style={styles.MidArea}>
<Text style={styles.textStyle}>Nous sommes actuellement dans l'écran d'accueil !</Text> <Image source={require("../assets/logo.png")} style={styles.ige} resizeMode='cover' ></Image>
</View> </View>
<View> <View style={styles.butContain}>
<Text style={styles.t3}> Vous cherchez une entités ? </Text>
<TouchableNativeFeedback onPress={() => navigation.navigate("ListScreen")}> <TouchableNativeFeedback onPress={() => navigation.navigate("ListScreen")}>
<Text style={styles.ButtonStyle}> Consulter la liste global !</Text> <Text style={styles.ButtonStyle}>List</Text>
</TouchableNativeFeedback> </TouchableNativeFeedback>
</View>
<View>
<Text style={styles.t3}> Vous avez des entités favorites ? </Text>
<TouchableNativeFeedback onPress={() => navigation.navigate("ListFav")}> <TouchableNativeFeedback onPress={() => navigation.navigate("ListFav")}>
<Text style={styles.ButtonStyle}>Aller sur la page de favoris !</Text> <Text style={styles.ButtonStyle}>Favoris</Text>
</TouchableNativeFeedback> </TouchableNativeFeedback>
</View> </View>
</View> </View>
@ -40,38 +64,43 @@ export default function HomeScreen({navigation}) {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
backgroundColor: "darksalmon", backgroundColor: "#ac9585",
alignItems: "center" alignItems: "center"
}, },
centered: { centered: {
alignItems: "center" alignItems: "center"
}, },
title: { title: {
fontSize: 20, fontSize: 56,
fontWeight: 'bold', fontWeight: 'bold',
}, },
MidArea: { MidArea: {
justifyContent: "center", justifyContent: "center",
backgroundColor: "white",
paddingTop: 50,
paddingBottom: 50,
margin: 40,
borderRadius: 15,
}, },
textStyle: { txt: {
textAlign: "center",
fontSize: 20, fontSize: 20,
}, },
butContain: {
flexDirection: 'row'
},
ige:{
maxWidth: "110%",
maxHeight: 400,
},
ButtonStyle :{ ButtonStyle :{
backgroundColor: "#2E8AE6", backgroundColor: "#F5F5F5",
borderRadius: 15, borderRadius: 15,
padding: 20, padding: 20,
color: "white", color: "black",
fontSize : 20, fontSize : 36,
width: "45%",
textAlign: 'center',
margin: 10,
fontWeight: 'bold', fontWeight: 'bold',
}, },
t3 :{
fontSize : 20,
fontWeight: 'bold',
}
}); });

@ -1,91 +1,52 @@
import { StyleSheet, Text, View, Button, FlatList } from 'react-native';
import { StatusBar } from 'expo-status-bar';
import React, { useState } from "react";
import {setFavList } from '../redux/actions/action_setFavList'; import { StyleSheet, View, TouchableHighlight, TextInput } from 'react-native';
import React, { useState} from "react";
import { FlatList } from 'react-native-gesture-handler';
import {useSelector} from 'react-redux';
//redux import { Card } from '../models/Card';
import {useDispatch, useSelector} from 'react-redux'; import Item from '../components/ListeFavComponent';
import {useEffect} from 'react';
export const Cardslist = [
{
id: '1',
title: "premier élément",
},
{
id: '2',
title: "second élément",
},
{
id: '3',
title: "élément",
},
{
id: '4',
title: "barman douteux",
},
{
id: '10',
title: "dernier élément",
}
];
//@ts-ignore //@ts-ignore
const Item = ({title}) => ( export default function ListScreen({navigation}){
<View style={styles.item}>
<Text style={styles.title}>{title}</Text>
</View>
);
//TODO //@ts-ignore
// export const getFavList = () => { var nList : Card[] = useSelector(state => state.appReducer.favoriteCards);
// //In order to use await your callback must be asynchronous using async keyword.
// return async dispatch => {
// //Then perform your asynchronous operations.
// try {
// //Have it first fetch data from our starwars url.
// //const nounoursPromise = await fetch('https://iut-weather-api.azurewebsites.net/nounours');
// //Then use the json method to get json data from api/
// //const nounoursListJson = await nounoursPromise.json();
// //const nounoursList: Nounours[] = nounoursListJson.map(elt => new Nounours(elt["name"], elt["age"], elt["nbPoils"], elt["image"]));
// dispatch(setFavList(Array{id,title}));
// } catch (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))
// }
// }
// }
const [searchValue, setSearchValue] = useState('');
const filteredList = nList.filter(item => item.name.toLowerCase().includes(searchValue.toLowerCase()));
export default function Main(){
const [count, setCount] = useState(0);
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Text>Maman, J4AI UNE LISTE DE FAVORIS ! !</Text>
<StatusBar style="auto" /> <TextInput
<Text>{count}</Text> style={styles.textInput}
<Button onPress={()=> setCount(count+1)} title="+1"/> value={searchValue}
<Button onPress={()=> setCount(count+2)} title="+2"/> onChangeText={text => setSearchValue(text)}
<Button onPress={()=> setCount(count+10)} title="+10"/> placeholder="Rechercher une carte..."
/>
<FlatList data={Cardslist}
renderItem={({item}) => <Item title={item.title} />} <FlatList
keyExtractor={item => item.id}/> numColumns={2}
data={filteredList}
renderItem={({item}) =>
<TouchableHighlight onPress={() => navigation.navigate("ListFav")}>
<Item route={{
card: item,
bool: true
}} />
</TouchableHighlight>
}
keyExtractor={(item: Card) => item.id}
/>
</View> </View>
); );
} }
@ -94,17 +55,18 @@ export default function Main(){
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
backgroundColor: '#fff', backgroundColor: '#ac9585',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'space-evenly',
}, },
item: { textInput: {
padding: 15,
margin: 5,
width:200,
backgroundColor: '#ffffff',
borderRadius : 15, borderRadius : 15,
backgroundColor: '#efefef', shadowColor: 'grey',
padding: 20, textAlign:'center'
margin : 10,
},
title: {
fontStyle: "italic",
} }
}); });

@ -1,61 +1,21 @@
import { StyleSheet, Text, View, Button, TouchableHighlight, TextInput } from 'react-native'; import { StyleSheet,View,TouchableHighlight, TextInput} from 'react-native';
import { StatusBar } from 'expo-status-bar'; import React, { useState} from "react";
import React, { useState, useEffect } from "react";
import { FlatList } from 'react-native-gesture-handler'; import { FlatList } from 'react-native-gesture-handler';
import {useDispatch, useSelector} from 'react-redux'; import {useSelector} from 'react-redux';
import { ThunkAction } from 'redux-thunk';
//? possiblement à supprimer
import { getAllCards } from "../redux/actions/actionSelection"
import { StubLib } from '../data/stub';
import { Card } from '../models/Card'; import { Card } from '../models/Card';
import { Image } from 'react-native'; import { FontAwesome } from '@expo/vector-icons';
import { ImageURISource } from 'react-native'; import { useDispatch } from "react-redux";
import {CardProps} from "../props/favProps";
//* Icons import { setFavList } from "../redux/actions/action_setFavList";
// import { BiSearchAlt } from 'react-icons';
//* Components //* Components
import {ListItemComponent} from '../components/ListItemComponent' import { ListItemComponent } from '../components/ListItemComponent';
//@ts-ignore //@ts-ignore
export default function ListScreen({navigation}){ export default function ListScreen({navigation}){
const [count, setCount] = useState(0);
// // Initialize the binding content with the application initial state
//@ts-ignore //@ts-ignore
const nList = useSelector(state => state.appReducer.cards); var nList = useSelector(state => state.appReducer.cards);
// Create a const that will hold the react-redux events dispatcher
const dispatch = useDispatch();
// Let's define a hook that will be used to update the rendered state after the return will be called
// You cannot perform side-effects outside of a useEffect hook
useEffect(() => {
console.log("USEEFFECT")
const loadCards = async () => {
//@ts-ignore
await dispatch(getAllCards());
};
loadCards();
}, [dispatch]);
//* Stub
// const {getCards} = new StubLib();
// const list: Card[] = getCards();
// const req = fetch('https://omgvamp-hearthstone-v1.p.rapidapi.com/cards')
//https://us.api.blizzard.com/hearthstone/cards/678?locale=en_US
//* Search : //* Search :
const [searchValue, setSearchValue] = useState(''); const [searchValue, setSearchValue] = useState('');
@ -63,6 +23,11 @@ export default function ListScreen({navigation}){
//@ts-ignore //@ts-ignore
const filteredList = nList.filter(item => item.name.toLowerCase().includes(searchValue.toLowerCase())); const filteredList = nList.filter(item => item.name.toLowerCase().includes(searchValue.toLowerCase()));
const dispatch = useDispatch()
const HandleAddFav = (props : CardProps) => {
dispatch(setFavList(props));
}
return ( return (
<View style={styles.container}> <View style={styles.container}>
@ -77,14 +42,18 @@ export default function ListScreen({navigation}){
numColumns={2} numColumns={2}
data={filteredList} data={filteredList}
renderItem={({item}) => renderItem={({item}) =>
// <TouchableHighlight onPress={() => ("DetailCard", {card :item})}> <View>
// <ListItemComponent url={item.img}/> <TouchableHighlight testID="button" style={item.fav?styles.favoriteButtonFav:styles.favoriteButtonNonFav}
// </TouchableHighlight> onPress={() => HandleAddFav({route: { card: item, bool: false }} as CardProps)} >
<TouchableHighlight onPress={() => navigation.navigate("DetailCard", {card :item, other : 'anything'})}> <FontAwesome name="heart-o" size={50} color="#fff" />
<ListItemComponent url={item.img}/> </TouchableHighlight>
</TouchableHighlight>
<TouchableHighlight style={styles.imageItem} onPress={() => navigation.navigate("DetailCard", {card :item, other : 'anything'})}>
<ListItemComponent url={item.img}/>
</TouchableHighlight>
</View>
} }
keyExtractor={(item: Card) => item.id.toString()} keyExtractor={(item: Card) => item.id}
/> />
</View> </View>
@ -93,8 +62,10 @@ export default function ListScreen({navigation}){
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
imageItem:{
zIndex : 0
},
container: { container: {
flex: 1, flex: 1,
backgroundColor: '#ac9585', backgroundColor: '#ac9585',
@ -102,25 +73,6 @@ const styles = StyleSheet.create({
justifyContent: 'space-evenly', justifyContent: 'space-evenly',
}, },
border: {
flex: 1,
backgroundColor: '#ff0000',
maxHeight : 100,
borderWidth : 15,
borderRadius : 15,
borderColor : '#00ffaa',
alignItems: 'center',
justifyContent: 'center',
},
item: {
borderRadius : 15,
backgroundColor: '#efefef',
padding: 20,
margin : 10,
},
title: {
fontStyle: "italic",
},
textInput: { textInput: {
padding: 15, padding: 15,
margin: 5, margin: 5,
@ -129,5 +81,24 @@ const styles = StyleSheet.create({
borderRadius : 15, borderRadius : 15,
shadowColor: 'grey', shadowColor: 'grey',
textAlign:'center' textAlign:'center'
},
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,
} }
}); });

@ -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);
}

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