diff --git a/JokesApp/App.tsx b/JokesApp/App.tsx index 2e66510..6cfeeaa 100644 --- a/JokesApp/App.tsx +++ b/JokesApp/App.tsx @@ -3,14 +3,18 @@ import React from "react"; import {ListJokeScreen} from "./screens/ListJokeScreen"; import {Navigation} from "./navigation/Navigation"; import {darksalmonColor, indigo, purpleColor} from "./Theme"; +import {Provider} from "react-redux"; +import store from "./redux/store"; export default function App() { return ( + + ); } diff --git a/JokesApp/api/fetch.ts b/JokesApp/api/fetch.ts new file mode 100644 index 0000000..e69de29 diff --git a/JokesApp/components/ListAllCategories.tsx b/JokesApp/components/ListAllCategories.tsx index c081443..cb56ec0 100644 --- a/JokesApp/components/ListAllCategories.tsx +++ b/JokesApp/components/ListAllCategories.tsx @@ -1,20 +1,21 @@ import {Joke} from "../model/Joke"; import {Image, StyleSheet, Text, View} from "react-native"; import React from "react"; -import {indigo} from "../Theme"; +import {greyColor, indigo} from "../Theme"; +import {Categorie} from "../model/Categorie"; -type JokeListItemProps = { - item: String; +type ListAllCategories = { + item: Categorie; } -export function ListAllCategories(props: JokeListItemProps) { +export function ListAllCategories(props: ListAllCategories) { return ( - {props.item} + {props.item.name} ); @@ -30,7 +31,7 @@ const styles = StyleSheet.create({ }, chip: { borderRadius: 16, - backgroundColor: 'rgba(140, 140, 161, 1)', + backgroundColor: greyColor, padding: 5, marginTop: 5, alignSelf: 'flex-start', diff --git a/JokesApp/model/Categorie.ts b/JokesApp/model/Categorie.ts new file mode 100644 index 0000000..a960a92 --- /dev/null +++ b/JokesApp/model/Categorie.ts @@ -0,0 +1,24 @@ +import {Joke} from "./Joke"; + +export class Categorie{ + + private _name : string; + + private _number : number; + + + get name(): string { + return this._name; + } + + get number(): number { + return this._number; + } + + public constructor(name :string, number :number) { + this._name = name; + this._number = number; + } + + +} \ No newline at end of file diff --git a/JokesApp/model/CategorieFactory.ts b/JokesApp/model/CategorieFactory.ts new file mode 100644 index 0000000..1b6ec86 --- /dev/null +++ b/JokesApp/model/CategorieFactory.ts @@ -0,0 +1,12 @@ +import {Categorie} from "./Categorie"; + +export class CategorieFactory { + public static createCategories(jsonArray: string): Categorie[] { + let array = []; + let json = JSON.parse(jsonArray); + json.forEach(function (categorie) { + array.push(new Categorie(categorie.name, categorie.number)); + }); + return array; + } +} \ No newline at end of file diff --git a/JokesApp/model/JokeFactory.ts b/JokesApp/model/JokeFactory.ts index 57597a3..1e4b563 100644 --- a/JokesApp/model/JokeFactory.ts +++ b/JokesApp/model/JokeFactory.ts @@ -1,5 +1,6 @@ import { CustomJoke } from "./CustomJoke"; import { SampleJoke } from "./SampleJoke"; +import {Categorie} from "./Categorie"; export class JokeFactory { @@ -25,6 +26,4 @@ export class JokeFactory { return array; } - - } \ No newline at end of file diff --git a/JokesApp/package-lock.json b/JokesApp/package-lock.json index 0ef945d..be1dc40 100644 --- a/JokesApp/package-lock.json +++ b/JokesApp/package-lock.json @@ -12,12 +12,15 @@ "@react-navigation/bottom-tabs": "^6.5.11", "@react-navigation/native": "^6.1.9", "@react-navigation/stack": "^6.3.20", + "@reduxjs/toolkit": "^2.2.1", "@types/react": "~18.2.45", "expo": "~50.0.3", "expo-status-bar": "~1.11.1", "react": "18.2.0", "react-native": "0.73.2", "react-native-gesture-handler": "^2.15.0", + "react-redux": "^9.1.0", + "redux": "^5.0.1", "typescript": "^5.3.0" }, "devDependencies": { @@ -6244,6 +6247,29 @@ "react-native-screens": ">= 3.0.0" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.1.tgz", + "integrity": "sha512-8CREoqJovQW/5I4yvvijm/emUiCCmcs4Ev4XPWd4mizSO+dD3g5G6w34QK5AGeNrSH7qM8Fl66j4vuV7dpOdkw==", + "dependencies": { + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.0.1" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@segment/loosely-validate-event": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz", @@ -6411,6 +6437,11 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", @@ -8839,6 +8870,15 @@ "node": ">=16.x" } }, + "node_modules/immer": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.3.tgz", + "integrity": "sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", @@ -12062,6 +12102,32 @@ "async-limiter": "~1.0.0" } }, + "node_modules/react-redux": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.0.tgz", + "integrity": "sha512-6qoDzIO+gbrza8h3hjMA9aq4nwVFCKFtY2iLxCtVT38Swyy2C/dJCGBXHeHLtx6qlg/8qzc2MrhOeduf5K32wQ==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25", + "react": "^18.0", + "react-native": ">=0.69", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -12123,6 +12189,19 @@ "node": ">=0.10.0" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -12234,6 +12313,11 @@ "path-parse": "^1.0.5" } }, + "node_modules/reselect": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.0.tgz", + "integrity": "sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg==" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -13361,6 +13445,14 @@ "react": ">=16.8" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/JokesApp/package.json b/JokesApp/package.json index 5b6e36d..e1c4c48 100644 --- a/JokesApp/package.json +++ b/JokesApp/package.json @@ -13,12 +13,15 @@ "@react-navigation/bottom-tabs": "^6.5.11", "@react-navigation/native": "^6.1.9", "@react-navigation/stack": "^6.3.20", + "@reduxjs/toolkit": "^2.2.1", "@types/react": "~18.2.45", "expo": "~50.0.3", "expo-status-bar": "~1.11.1", "react": "18.2.0", "react-native": "0.73.2", "react-native-gesture-handler": "^2.15.0", + "react-redux": "^9.1.0", + "redux": "^5.0.1", "typescript": "^5.3.0" }, "devDependencies": { diff --git a/JokesApp/redux/actions/categoriesAction.ts b/JokesApp/redux/actions/categoriesAction.ts new file mode 100644 index 0000000..178d976 --- /dev/null +++ b/JokesApp/redux/actions/categoriesAction.ts @@ -0,0 +1,33 @@ +import {Categorie} from "../../model/Categorie"; +import {CategorieFactory} from "../../model/CategorieFactory"; + + +export enum CategoriesActionType { + FETCH_CATEGORIES = 'FETCH_CATEGORIES', +} + +export interface CategoriesAction { + type: CategoriesActionType; + payload: Categorie[]; +} + +export type Action = CategoriesAction; + +export const setCategories = (categories: Categorie[]): CategoriesAction => { + return { + type: CategoriesActionType.FETCH_CATEGORIES, + payload: categories + } +} + +export const getCategorie = async() : Promise => { + try { + const categories = await fetch('https://iut-weather-api.azurewebsites.net/jokes/categories/top'); + const categorieJson = await categories.text(); + return CategorieFactory.createCategories(categorieJson); + + } + catch (error) { + console.log('Error---------', error); + } +} \ No newline at end of file diff --git a/JokesApp/redux/actions/sampleAction.ts b/JokesApp/redux/actions/sampleAction.ts new file mode 100644 index 0000000..8e81c1b --- /dev/null +++ b/JokesApp/redux/actions/sampleAction.ts @@ -0,0 +1,52 @@ +import {SampleJoke} from "../../model/SampleJoke"; +import {JokeFactory} from "../../model/JokeFactory"; + +export enum SampleActionType { + FETCH_SAMPLE = 'FETCH_SAMPLE', + FECTH_LAST_JOKES = 'FECTH_LAST_JOKES' +} + +export interface SampleAction { + type: SampleActionType; + payload: SampleJoke[]; +} + +export type Action = SampleAction; + + +export const setSample = (sample: SampleJoke[]): SampleAction => { + return { + type: SampleActionType.FETCH_SAMPLE, + payload: sample + } +} + +export const setRecentJokes = (recentJokes: SampleJoke[]): SampleAction => { + return { + type: SampleActionType.FECTH_LAST_JOKES, + payload: recentJokes + } +} + +export const getSampleJoke = async() : Promise => { + try { + const sample = await fetch('https://iut-weather-api.azurewebsites.net/jokes/samples'); + const sampleJson = await sample.text(); + return JokeFactory.createSampleJokes(sampleJson); + } + catch (error) { + console.log('Error---------', error); + } +} + +export const getLatestJokes = async() : Promise => { + try { + const sample = await fetch('https://iut-weather-api.azurewebsites.net/jokes/lasts'); + const sampleJson = await sample.text(); + return JokeFactory.createSampleJokes(sampleJson); + } + catch (error) { + console.log('Error---------', error); + } + +} diff --git a/JokesApp/redux/constants.ts b/JokesApp/redux/constants.ts new file mode 100644 index 0000000..e69de29 diff --git a/JokesApp/redux/reducers/categoryReducer.ts b/JokesApp/redux/reducers/categoryReducer.ts new file mode 100644 index 0000000..04ffb17 --- /dev/null +++ b/JokesApp/redux/reducers/categoryReducer.ts @@ -0,0 +1,22 @@ +import {Action, CategoriesActionType} from "../actions/categoriesAction"; +import {Categorie} from "../../model/Categorie"; + +interface State { + categories: Categorie[]; +} + +const initialState: State = { + categories: [], +} +// @ts-ignore +export default appReducer = (state = initialState, action: Action) => { + switch (action.type) { + case CategoriesActionType.FETCH_CATEGORIES: + return { + ...state, + categories: action.payload + } + default: + return state; + } +} \ No newline at end of file diff --git a/JokesApp/redux/reducers/sampleJokeReducer.ts b/JokesApp/redux/reducers/sampleJokeReducer.ts new file mode 100644 index 0000000..af2bf1a --- /dev/null +++ b/JokesApp/redux/reducers/sampleJokeReducer.ts @@ -0,0 +1,32 @@ +import {SampleJoke} from "../../model/SampleJoke"; +import {Action, SampleActionType} from "../actions/sampleAction"; + + +interface state { + sampleJoke: SampleJoke[]; + recentJokes: SampleJoke[]; +} + +const initialState: state = { + sampleJoke: [], + recentJokes: [], +} + +// @ts-ignore +export default appReducer = (state = initialState, action: Action) => { + switch (action.type) { + case SampleActionType.FETCH_SAMPLE: + return { + ...state, + sampleJoke: action.payload + } + case SampleActionType.FECTH_LAST_JOKES: + return { + ...state, + recentJokes: action.payload + } + + default: + return state; + } +} \ No newline at end of file diff --git a/JokesApp/redux/store.ts b/JokesApp/redux/store.ts new file mode 100644 index 0000000..28eabdb --- /dev/null +++ b/JokesApp/redux/store.ts @@ -0,0 +1,19 @@ +import { configureStore } from '@reduxjs/toolkit'; +import categorieReducer from './reducers/categoryReducer'; +import sampleReducer from './reducers/sampleJokeReducer'; + +const reducer = { + categorieReducer: categorieReducer, + sampleReducer: sampleReducer +}; + +// @ts-ignore +const store = configureStore({ + reducer: reducer, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware({ + serializableCheck: false + }) +}); + +export default store; diff --git a/JokesApp/screens/AccueilScreen.tsx b/JokesApp/screens/AccueilScreen.tsx index a05eb2b..339e8b3 100644 --- a/JokesApp/screens/AccueilScreen.tsx +++ b/JokesApp/screens/AccueilScreen.tsx @@ -1,28 +1,35 @@ import {FlatList, Image, SafeAreaView, ScrollView, SectionListComponent, StyleSheet, Text, View} from "react-native"; import {indigo, purpleColor} from "../Theme"; import {Joke} from "../model/Joke"; -import {DataGen, ListJokeScreen} from "./ListJokeScreen"; -import React from "react"; +import React, {useEffect} from "react"; import {HorizontalListJokeComponent} from "../components/HorizontalListJokeComponent"; import {ListAllCategories} from "../components/ListAllCategories"; import {CustomJoke} from "../model/CustomJoke"; +import {JokeFactory} from "../model/JokeFactory"; +import {JokeStub} from "../model/JokeStub"; +import {useDispatch, useSelector} from "react-redux"; +import {getLatestJokes, getSampleJoke, setRecentJokes, setSample} from "../redux/actions/sampleAction"; +import {getCategorie, setCategories} from "../redux/actions/categoriesAction"; +import {Categorie} from "../model/Categorie"; -let taille = DataGen.length; -let LastJokes = DataGen.slice(taille - 20, taille); -function filterCategory(jokes: Joke[]): String[] { - let categories: String[] = []; - jokes.forEach(joke => { - if (!categories.includes(joke.type())) { - categories.push(joke.type()); - } - }); - return categories; -} export function AccueilScreen() { - // Permet de filtrer les types des blagues pour les afficher dans la liste des categories - const FiltereData = filterCategory(LastJokes); + const DataGen = useSelector((state: any) => state.sampleReducer.recentJokes); + const DataCate = useSelector((state: any) => state.categorieReducer.categories); + const dispatch = useDispatch(); + useEffect(() => { + + const getJokes = async () => { + dispatch(setRecentJokes(await getLatestJokes())); + }; + getJokes(); + + const getTopCategories = async () => { + dispatch(setCategories(await getCategorie())); + }; + getTopCategories(); + }, [dispatch]); return ( @@ -33,7 +40,7 @@ export function AccueilScreen() { Dernieres Blagues item.id.toString()} /> @@ -42,10 +49,9 @@ export function AccueilScreen() { item.toString()} - /> + keyExtractor={(item : Categorie) => item.name}/> ); } @@ -88,4 +94,4 @@ categories: { } -}); +}); \ No newline at end of file diff --git a/JokesApp/screens/ListJokeScreen.tsx b/JokesApp/screens/ListJokeScreen.tsx index 4caac33..b12bbc4 100644 --- a/JokesApp/screens/ListJokeScreen.tsx +++ b/JokesApp/screens/ListJokeScreen.tsx @@ -1,20 +1,22 @@ import {FlatList, SafeAreaView, StyleSheet, Text, View} from "react-native"; -import React from "react"; +import React, {useEffect} from "react"; import {JokeListItems} from "../components/ListeJokeComponent"; -import {Joke} from "../model/Joke"; -import {JokeFactory} from "../model/JokeFactory"; -import {JokeStub} from "../model/JokeStub"; import {indigo, purpleColor} from "../Theme"; import {CustomJoke} from "../model/CustomJoke"; +import {useDispatch, useSelector} from 'react-redux'; +import {getLatestJokes, getSampleJoke, setSample} from "../redux/actions/sampleAction"; -const DATACUSTOM = JokeFactory.createCustomJokes(JokeStub.customJokes) -const DATASAMPLE = JokeFactory.createSampleJokes(JokeStub.sampleJokes) - -//@ts-ignore -export let DataGen: Joke[] = DATACUSTOM.concat(DATASAMPLE); export function ListJokeScreen() { + const DataGen = useSelector((state: any) => state.sampleReducer.sampleJoke); + const dispatch = useDispatch(); + useEffect(() => { + const getJokes = async () => { + dispatch(setSample(await getSampleJoke())); + }; + getJokes(); + }, [dispatch]); return (