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

@ -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 {
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._name=name
//this._set=set
@ -26,11 +22,11 @@ export class Card {
this._img = img
this._imgGold = imgGold
this._cropImage = cropImage
this._fav = fav
}
// ID //
private _id : number;
get id(): number {
return this._id
@ -45,12 +41,6 @@ export class Card {
this._name = value;
}
// private _set : CardSet;
// get set(): CardSet{
// return this._set
// }
// set set(value : CardSet){
// this._set = value
// }
@ -187,4 +177,9 @@ export class Card {
set cropImage(value: string) {
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 { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import HomeScreen from '../screens/HomeScreen';
import ListScreen from '../screens/ListScreen';
import ListFav from '../screens/ListFav';
@ -13,9 +9,6 @@ import TabBarIcon from '../components/TabBarIcon';
import StackNavigation from './StackNavigation';
import DetailStackNav from './DetailStackNav';
export default function Navigation() {
const BottomTabNavigator = createBottomTabNavigator();
return (

@ -11,12 +11,10 @@ import ListFav from '../screens/ListFav';
export default function StackNavigation() {
const Stack = createStackNavigator();
return (
//<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen}/>
<Stack.Screen name="ListScreen" component={ListScreen}/>
<Stack.Screen name="ListFav" component={ListFav}/>
</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",
"android": "expo start --android",
"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": {
"@jest/globals": "^29.5.0",
"@react-navigation/bottom-tabs": "^6.5.5",
"@react-navigation/native": "^6.1.4",
"@react-navigation/native-stack": "^6.9.10",
"@react-navigation/stack": "^6.3.14",
"@reduxjs/toolkit": "^1.9.3",
"@testing-library/jest-native": "^5.4.2",
"@testing-library/react-native": "^12.0.1",
"expo": "^48.0.0",
"expo-status-bar": "~1.4.4",
"jest": "^29.2.1",
"jest-expo": "^48.0.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-native": "0.71.3",
@ -24,10 +51,13 @@
"react-native-table-component": "^1.2.2",
"react-native-web": "~0.18.11",
"react-redux": "^8.0.5",
"redux": "^4.2.1"
"redux": "^4.2.1",
"@react-native-async-storage/async-storage": "1.17.11"
},
"devDependencies": {
"@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-native": "~0.70.6",
"@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 { Card } from "../../models/Card";
//! se fichier devra possiblement changer de dossier !!!
//! 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
//Fonction chargé de l'appel à l'API
export const getAllCards = () => {
//In order to use await your callback must be asynchronous using async keyword.
console.log("getallcard")
//@ts-ignore
return async dispatch => {
//Then perform your asynchronous operations.
try {
//Have it first fetch data from our starwars url.
const options = {
method: 'GET',
// headers: {
// 'content-length':'9505',
// 'content-type':'application/json; charset=utf-8',
// 'etag':'W/"74bb-QMT8DIj6saBS1wT4u5WWcEmZAdw"'
// }
//! Actualisation de l'API (16/03) :
headers: {
'content-length': '9508',
'content-type': 'application/json; charset=utf-8',
'etag': 'W/"74bb-d4gMlMNks7UGES3Jmn6wzUTXaLI"',
}
//'pageSize':'100'
};
const CardsPromise = await fetch('https://us.api.blizzard.com/hearthstone/cards?locale=en_US&access_token=EUe6p4N9uLm8BbsHyYVZXIa4DDBP2hMR05', options);
//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=EURTWhjBC2SRb4Ez42BT1kx8R2NcJc07kL', options);
//console.log("FETCH")
//console.log(CardsPromise)
//! Actualisation de l'API (19/04) :
const CardsPromise = await fetch('https://us.api.blizzard.com/hearthstone/cards?locale=en_US&access_token=EUo8Snb09AfE3zQR4CoaB71gq1q3qvSmgL', options);
//Then use the json method to get json data from api/
const CardsListJson = await CardsPromise.json();
//console.log(CardsListJson['cards'])
//@ts-ignore
@ -89,16 +42,12 @@ export const getAllCards = () => {
)); //, 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"] : ""
//console.log("TOTO")
//console.log(CardsList)
//call the action
dispatch(setCardsList(CardsList));
} 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))
}
}
}

@ -1,8 +1,5 @@
import {ADD_FAVORITE_DATA, FETCH_DATA} from '../constants';
//? Changer cette importe quand la classe sera definit dans un fichier correctement.
import {Card} from '../../models/Card'
import { Card } from '../../models/Card';
import {FETCH_DATA} from '../constants';
export const setCardsList = (List: Card[]) => {
return {
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 {
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 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 = {
cards: [],
favoriteCards: [],
// cards: ["C_ace", "C_K", "C_Q", "C_J"],
// favoriteCards: [ "C_ace", "C_K"],
favoriteCards: []
}
// @ts-ignore
export default appReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_FAVORITE_DATA:
// @ts-ignore
return {...state, favoriteCards: state.favoriteCards.push(action.payload)};
case FETCH_DATA:
case ADD_FAVORITE_DATA: //Ajout d'une carte aux favoris
const a : CardProps = action.payload
if(a.route.bool ==false){
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};
case SET_FAVS: //Récupération des favoris depuis l'async storage
//@ts-ignore
return {...state, favoriteCards: action.payload}
default:
return state;
}

@ -1,7 +1,6 @@
import {configureStore} from '@reduxjs/toolkit'
import appReducer from './reducers/appReducer';
// Reference here all your application reducers
const reducer = {
appReducer: appReducer,
}
@ -10,7 +9,6 @@ const reducer = {
const store = configureStore({
// @ts-ignore
reducer,
// @ts-ignore
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 //(ta gueule pour l'erreur sur navigation)
// @ts-ignore //
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 (
<View style={styles.container}>
<View style={styles.centered}>
<Text style={styles.title}>Mes super Nounours !</Text>
<Text style={styles.title}>DeckStone</Text>
</View>
<Text>Mon super texte ...</Text>
<Text style={styles.txt}>Votre gestionnaire de cartes Hearthstone </Text>
{/* <MyCustomComponent /> */}
<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>
<Text style={styles.t3}> Vous cherchez une entités ? </Text>
<View style={styles.butContain}>
<TouchableNativeFeedback onPress={() => navigation.navigate("ListScreen")}>
<Text style={styles.ButtonStyle}> Consulter la liste global !</Text>
<Text style={styles.ButtonStyle}>List</Text>
</TouchableNativeFeedback>
</View>
<View>
<Text style={styles.t3}> Vous avez des entités favorites ? </Text>
<TouchableNativeFeedback onPress={() => navigation.navigate("ListFav")}>
<Text style={styles.ButtonStyle}>Aller sur la page de favoris !</Text>
<Text style={styles.ButtonStyle}>Favoris</Text>
</TouchableNativeFeedback>
</View>
</View>
@ -40,38 +64,43 @@ export default function HomeScreen({navigation}) {
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "darksalmon",
backgroundColor: "#ac9585",
alignItems: "center"
},
centered: {
alignItems: "center"
},
title: {
fontSize: 20,
fontSize: 56,
fontWeight: 'bold',
},
MidArea: {
justifyContent: "center",
backgroundColor: "white",
paddingTop: 50,
paddingBottom: 50,
margin: 40,
borderRadius: 15,
},
textStyle: {
textAlign: "center",
txt: {
fontSize: 20,
},
butContain: {
flexDirection: 'row'
},
ige:{
maxWidth: "110%",
maxHeight: 400,
},
ButtonStyle :{
backgroundColor: "#2E8AE6",
backgroundColor: "#F5F5F5",
borderRadius: 15,
padding: 20,
color: "white",
fontSize : 20,
color: "black",
fontSize : 36,
width: "45%",
textAlign: 'center',
margin: 10,
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';
//redux
import {useDispatch, useSelector} from 'react-redux';
import {useEffect} from 'react';
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';
import { Card } from '../models/Card';
import Item from '../components/ListeFavComponent';
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
const Item = ({title}) => (
<View style={styles.item}>
<Text style={styles.title}>{title}</Text>
</View>
);
//TODO
// export const getFavList = () => {
// //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))
// }
// }
// }
export default function ListScreen({navigation}){
//@ts-ignore
var nList : Card[] = useSelector(state => state.appReducer.favoriteCards);
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 (
<View style={styles.container}>
<Text>Maman, J4AI UNE LISTE DE FAVORIS ! !</Text>
<StatusBar style="auto" />
<Text>{count}</Text>
<Button onPress={()=> setCount(count+1)} title="+1"/>
<Button onPress={()=> setCount(count+2)} title="+2"/>
<Button onPress={()=> setCount(count+10)} title="+10"/>
<FlatList data={Cardslist}
renderItem={({item}) => <Item title={item.title} />}
keyExtractor={item => item.id}/>
<TextInput
style={styles.textInput}
value={searchValue}
onChangeText={text => setSearchValue(text)}
placeholder="Rechercher une carte..."
/>
<FlatList
numColumns={2}
data={filteredList}
renderItem={({item}) =>
<TouchableHighlight onPress={() => navigation.navigate("ListFav")}>
<Item route={{
card: item,
bool: true
}} />
</TouchableHighlight>
}
keyExtractor={(item: Card) => item.id}
/>
</View>
);
}
@ -94,17 +55,18 @@ export default function Main(){
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
backgroundColor: '#ac9585',
alignItems: 'center',
justifyContent: 'center',
justifyContent: 'space-evenly',
},
item: {
textInput: {
padding: 15,
margin: 5,
width:200,
backgroundColor: '#ffffff',
borderRadius : 15,
backgroundColor: '#efefef',
padding: 20,
margin : 10,
},
title: {
fontStyle: "italic",
shadowColor: 'grey',
textAlign:'center'
}
});

@ -1,61 +1,21 @@
import { StyleSheet, Text, View, Button, TouchableHighlight, TextInput } from 'react-native';
import { StatusBar } from 'expo-status-bar';
import React, { useState, useEffect } from "react";
import { StyleSheet,View,TouchableHighlight, TextInput} from 'react-native';
import React, { useState} from "react";
import { FlatList } from 'react-native-gesture-handler';
import {useDispatch, useSelector} from 'react-redux';
import { ThunkAction } from 'redux-thunk';
//? possiblement à supprimer
import { getAllCards } from "../redux/actions/actionSelection"
import { StubLib } from '../data/stub';
import {useSelector} from 'react-redux';
import { Card } from '../models/Card';
import { Image } from 'react-native';
import { ImageURISource } from 'react-native';
//* Icons
// import { BiSearchAlt } from 'react-icons';
import { FontAwesome } from '@expo/vector-icons';
import { useDispatch } from "react-redux";
import {CardProps} from "../props/favProps";
import { setFavList } from "../redux/actions/action_setFavList";
//* Components
import {ListItemComponent} from '../components/ListItemComponent'
import { ListItemComponent } from '../components/ListItemComponent';
//@ts-ignore
export default function ListScreen({navigation}){
const [count, setCount] = useState(0);
// // Initialize the binding content with the application initial state
//@ts-ignore
const 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
var nList = useSelector(state => state.appReducer.cards);
//* Search :
const [searchValue, setSearchValue] = useState('');
@ -63,6 +23,11 @@ export default function ListScreen({navigation}){
//@ts-ignore
const filteredList = nList.filter(item => item.name.toLowerCase().includes(searchValue.toLowerCase()));
const dispatch = useDispatch()
const HandleAddFav = (props : CardProps) => {
dispatch(setFavList(props));
}
return (
<View style={styles.container}>
@ -77,14 +42,18 @@ export default function ListScreen({navigation}){
numColumns={2}
data={filteredList}
renderItem={({item}) =>
// <TouchableHighlight onPress={() => ("DetailCard", {card :item})}>
// <ListItemComponent url={item.img}/>
// </TouchableHighlight>
<TouchableHighlight onPress={() => navigation.navigate("DetailCard", {card :item, other : 'anything'})}>
<View>
<TouchableHighlight testID="button" style={item.fav?styles.favoriteButtonFav:styles.favoriteButtonNonFav}
onPress={() => HandleAddFav({route: { card: item, bool: false }} as CardProps)} >
<FontAwesome name="heart-o" size={50} color="#fff" />
</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>
@ -93,8 +62,10 @@ export default function ListScreen({navigation}){
}
const styles = StyleSheet.create({
imageItem:{
zIndex : 0
},
container: {
flex: 1,
backgroundColor: '#ac9585',
@ -102,25 +73,6 @@ const styles = StyleSheet.create({
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: {
padding: 15,
margin: 5,
@ -129,5 +81,24 @@ const styles = StyleSheet.create({
borderRadius : 15,
shadowColor: 'grey',
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