Compare commits

..

No commits in common. 'master' and 'part5' have entirely different histories.

@ -1,17 +0,0 @@
kind: pipeline
type: docker
name: JokesApp
steps:
- name: sonar-analyses
image: hub.codefirst.iut.uca.fr/camille.petitalot/drone-sonarplugin-reactnative:latest
commands:
- cd src
- npm install
- sonar-scanner -Dsonar.projectKey=JokesAppReact_A-PE -Dsonar.sources=. -Dsonar.host.url=$${PLUGIN_SONAR_HOST}
-Dsonar.login=$${PLUGIN_SONAR_TOKEN}
secrets: [ SECRET_SONAR_LOGIN ]
settings:
sonar_host: https://codefirst.iut.uca.fr/sonar/
sonar_token:
from_secret: SECRET_SONAR_LOGIN

@ -64,11 +64,11 @@ Le projet de tp utilise un modèle de flux de travail Git pour organiser le dév
* [X] Partie 4 - Components * [X] Partie 4 - Components
* [X] Partie 5 - FlatList * [X] Partie 5 - FlatList
* [X] Partie 6 - Safe Area * [X] Partie 6 - Safe Area
* [X] Partie 7 - Navigation * [ ] Partie 7 - Navigation
* [X] Partie 8 - Hooks * [ ] Partie 8 - Hooks
* [X] Partie 9 - Redux Store * [ ] Partie 9 - Redux Store
* [X] Partie 10 - Async Storage * [ ] Partie 10 - Async Storage
* [X] Partie 11 - Theming * [ ] Partie 11 - Theming
* [ ] Partie 12 - Unit testing * [ ] Partie 12 - Unit testing
* [ ] Partie 13 - Resources * [ ] Partie 13 - Resources

@ -1,32 +1,5 @@
import {Theme} from "@react-navigation/native";
export const indigoColor = "rgba(14, 14, 44, 1)"; export const indigoColor = "rgba(14, 14, 44, 1)";
export const purpleColor = "rgba(74, 74, 104, 1)"; export const purpleColor = "rgba(74, 74, 104, 1)";
export const darksalmonColor = "rgba(233, 150, 122, 1)"; export const darksalmonColor = "rgba(233, 150, 122, 1)";
export const greyColor = "rgba(140, 140, 161, 1)"; export const greyColor = "rgba(140, 140, 161, 1)";
export const whiteColor = "rgba(239, 239, 253, 1)"; export const whiteColor = "rgba(239, 239, 253, 1)";
export const LightTheme: Theme = {
dark: false,
colors: {
primary: darksalmonColor,
background: whiteColor,
card: greyColor,
text: "black",
border: whiteColor,
notification: 'rgb(255, 59, 48)',
},
};
export const DarkTheme: Theme = {
dark: true,
colors: {
primary: darksalmonColor,
background: purpleColor,
card: indigoColor,
text: whiteColor,
border: greyColor,
notification: 'rgb(255, 69, 58)',
},
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 315 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 B

@ -1,26 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import Categ from './Categ';
import { Text, View } from 'react-native';
import {describe, expect, it} from "@jest/globals";
describe('Categ Component', () => {
it('renders correctly', () => {
const category = { name: 'Test Category' };
const wrapper = shallow(<Categ category={category} />);
expect(wrapper).toMatchSnapshot();
});
it('displays category name', () => {
const category = { name: 'Test Category' };
const wrapper = shallow(<Categ category={category} />);
expect(wrapper.find(Text).prop('children')).toEqual(category.name);
});
it('applies correct styles', () => {
const category = { name: 'Test Category' };
const wrapper = shallow(<Categ category={category} />);
const containerStyle = wrapper.find(View).prop('style');
expect(containerStyle).toEqual(expect.objectContaining(styles.bottomContainer));
});
});

@ -1,4 +1,6 @@
import {FlatList} from 'react-native'; import {FlatList} from 'react-native';
import {SampleJoke} from "../model/SampleJoke";
import {CustomJoke} from "../model/CustomJoke";
import Categ from "./Categ"; import Categ from "./Categ";
import {Category} from "../model/Category"; import {Category} from "../model/Category";

@ -1,36 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import Categs from './Categs';
import { FlatList } from 'react-native';
import Categ from './Categ';
import { Category } from '../model/Category';
import {describe, expect, it} from "@jest/globals";
describe('Categs Component', () => {
const categories: Category[] = [
new Category('Category 1', 10),
new Category('Category 2', 5),
new Category('Category 3', 15),
];
it('renders correctly', () => {
const wrapper = shallow(<Categs categories={categories} />);
expect(wrapper).toMatchSnapshot();
});
it('renders FlatList with correct props', () => {
const wrapper = shallow(<Categs categories={categories} />);
const flatList = wrapper.find(FlatList);
expect(flatList.exists()).toBeTruthy();
expect(flatList.prop('showsHorizontalScrollIndicator')).toBe(false);
expect(flatList.prop('horizontal')).toBe(true);
expect(flatList.prop('data')).toEqual(categories.sort((a, b) => b.number - a.number));
expect(flatList.prop('keyExtractor')).toBeInstanceOf(Function);
expect(flatList.prop('renderItem')).toBeInstanceOf(Function);
});
it('renders correct number of Categ components', () => {
const wrapper = shallow(<Categs categories={categories} />);
expect(wrapper.find(Categ)).toHaveLength(categories.length);
});
});

@ -1,29 +1,15 @@
import {StyleSheet, Text, View, Image, Button, TouchableOpacity} from 'react-native'; import {StyleSheet, Text, View, Image, Button, TouchableOpacity} from 'react-native';
import {SampleJoke} from "../model/SampleJoke"; import {SampleJoke} from "../model/SampleJoke";
import {darksalmonColor, whiteColor, greyColor, indigoColor, purpleColor} from "../assets/Theme"; import {darksalmonColor, whiteColor, greyColor, indigoColor, purpleColor} from "../assets/Theme";
import React, {useEffect, useState} from "react"; import React, {useState} from "react";
import {CustomJoke} from "../model/CustomJoke";
import {useDispatch, useSelector} from "react-redux";
import {getCustomJokeById, getJokeById} from "../redux/thunk/GetByThunk";
import {getCustomJokesList} from "../redux/thunk/GetThunk";
import {deleteCustomJoke} from "../redux/thunk/DeleteThunk";
import {useNavigation} from "@react-navigation/native";
import {AppDispatch} from "../redux/store";
type JokeItemProps = { type JokeItemProps = {
joke: SampleJoke | CustomJoke; joke: SampleJoke;
}; };
const eye = require("../assets/eye_icon.png")
const hideEye = require("../assets/eye_off_icon.png")
const heart = require("../assets/favorite_icon.png")
const bin = require("../assets/bin.png")
const heartPlain = require("../assets/plain_favorite_icon.png")
export default function JokeDetail(props: JokeItemProps) { export default function JokeDetail(props: JokeItemProps) {
const [showDescription, setShowDescription] = useState(false); const [showDescription, setShowDescription] = useState(false); // état local pour contrôler la visibilité de la description
const [showFavorite, setShowFavorite] = useState(false); const [showFavorite, setShowFavorite] = useState(false); // état local pour contrôler la visibilité du favori
const isCustom = props.joke instanceof CustomJoke;
const dispatch = useDispatch<AppDispatch>();
const navigation = useNavigation();
const toggleDescription = () => { const toggleDescription = () => {
setShowDescription(!showDescription); setShowDescription(!showDescription);
@ -33,11 +19,10 @@ export default function JokeDetail(props: JokeItemProps) {
setShowFavorite(!showFavorite); setShowFavorite(!showFavorite);
}; };
const deleteCustomJokes = async () => { const eye = require("../assets/eye_icon.png")
await dispatch(deleteCustomJoke(props.joke.id)); const hideEye = require("../assets/eye_off_icon.png")
await dispatch(getCustomJokesList()); const heart = require("../assets/favorite_icon.png")
navigation.goBack(); const heartPlain = require("../assets/plain_favorite_icon.png")
};
return( return(
<View style={styles.container}> <View style={styles.container}>
@ -47,15 +32,6 @@ export default function JokeDetail(props: JokeItemProps) {
</View> </View>
<Text style={styles.text}>Résumé de la blague</Text> <Text style={styles.text}>Résumé de la blague</Text>
<View style={styles.row}> <View style={styles.row}>
{isCustom && (<TouchableOpacity style={styles.favContainer} onPress={ deleteCustomJokes }>
<View>
<Image
source={bin}
style={styles.imageButton}
/>
</View>
</TouchableOpacity>)
}
<TouchableOpacity style={styles.favContainer} onPress={toggleFavorite}> <TouchableOpacity style={styles.favContainer} onPress={toggleFavorite}>
<View> <View>
<Image <Image
@ -172,7 +148,7 @@ const styles = StyleSheet.create({
marginLeft: 19, marginLeft: 19,
marginTop: 20, marginTop: 20,
borderRadius: 20, borderRadius: 20,
width : 150, width : 120,
alignItems : 'center' alignItems : 'center'
} }
}) })

@ -1,54 +0,0 @@
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import JokeDetail from '../path/to/JokeDetail';
import {describe} from "@jest/globals"; // Remplacez le chemin par le bon chemin
describe('JokeDetail Component', () => {
const sampleJoke = {
id: '1',
type: 'Sample',
image: 'sample-image-url',
description: () => 'Sample Joke Description'
};
const customJoke = {
id: '2',
type: 'Custom',
image: 'custom-image-url',
description: () => 'Custom Joke Description'
};
test('renders joke image', () => {
const { getByTestId } = render(<JokeDetail joke={sampleJoke} />);
const jokeImage = getByTestId('joke-image');
expect(jokeImage).toBeTruthy();
});
test('renders joke description when toggled', () => {
const { getByText } = render(<JokeDetail joke={sampleJoke} />);
const toggleButton = getByText('LA CHUTE');
fireEvent.press(toggleButton);
const jokeDescription = getByText('Sample Joke Description');
expect(jokeDescription).toBeTruthy();
});
test('toggles favorite icon when pressed', () => {
const { getByTestId } = render(<JokeDetail joke={sampleJoke} />);
const favoriteButton = getByTestId('favorite-button');
fireEvent.press(favoriteButton);
expect(favoriteButton.props.source).toEqual(require('../assets/plain_favorite_icon.png'));
});
test('calls deleteCustomJokes and updates list when delete button is pressed for custom joke', () => {
const deleteMock = jest.fn();
const dispatchMock = jest.fn();
jest.mock('../redux/thunk/DeleteThunk', () => ({ deleteCustomJoke: deleteMock }));
jest.mock('react-redux', () => ({ useDispatch: () => dispatchMock }));
const { getByTestId } = render(<JokeDetail joke={customJoke} />);
const deleteButton = getByTestId('delete-button');
fireEvent.press(deleteButton);
expect(deleteMock).toHaveBeenCalledWith(customJoke.id);
expect(dispatchMock).toHaveBeenCalledTimes(2); // Assuming one dispatch for delete and one for update list
});
});

@ -2,7 +2,6 @@ import {StyleSheet, Text, View, Image} from 'react-native';
import {SampleJoke} from "../model/SampleJoke"; import {SampleJoke} from "../model/SampleJoke";
import {CustomJoke} from "../model/CustomJoke"; import {CustomJoke} from "../model/CustomJoke";
import {darksalmonColor, whiteColor, greyColor, indigoColor} from "../assets/Theme"; import {darksalmonColor, whiteColor, greyColor, indigoColor} from "../assets/Theme";
import Categ from "./Categ";
type JokeListItemProps = { type JokeListItemProps = {
joke: (CustomJoke | SampleJoke); joke: (CustomJoke | SampleJoke);
@ -19,6 +18,7 @@ export default function JokeItem(prop: JokeListItemProps) {
<View style={styles.bottomContainer}> <View style={styles.bottomContainer}>
<Text style={{color:'white'}}>{prop.joke.type}</Text> <Text style={{color:'white'}}>{prop.joke.type}</Text>
</View> </View>
{/*<Categ category={prop.joke.type}/>*/}
</View> </View>
</View> </View>
); );

@ -1,10 +1,9 @@
import {FlatList, Image, StyleSheet, Text, TouchableHighlight, TouchableOpacity, View} from 'react-native'; import {FlatList, Text, TouchableHighlight} from 'react-native';
import {SampleJoke} from "../model/SampleJoke"; import {SampleJoke} from "../model/SampleJoke";
import {CustomJoke} from "../model/CustomJoke"; import {CustomJoke} from "../model/CustomJoke";
import JokeItem from "./JokeItem"; import JokeItem from "./JokeItem";
import React, {useState} from "react"; import React from "react";
import {useNavigation} from "@react-navigation/native"; import {useNavigation} from "@react-navigation/native";
import {darksalmonColor, greyColor, indigoColor, whiteColor} from "../assets/Theme";
type JokeListItemProps = { type JokeListItemProps = {
jokes: (CustomJoke | SampleJoke)[]; jokes: (CustomJoke | SampleJoke)[];
@ -12,7 +11,6 @@ type JokeListItemProps = {
export default function JokeItems(props: JokeListItemProps) { export default function JokeItems(props: JokeListItemProps) {
const navigation = useNavigation() const navigation = useNavigation()
return ( return (
<FlatList <FlatList
data={props.jokes} data={props.jokes}
@ -28,8 +26,3 @@ export default function JokeItems(props: JokeListItemProps) {
/> />
); );
} }
const styles = StyleSheet.create({
})

@ -1,25 +0,0 @@
import { Category } from './Category';
import {describe, expect, it} from "@jest/globals";
describe('Category Class Constructor', () => {
it('should create a new Category object', () => {
const category = new Category('name', 5);
expect(category).toBeDefined();
expect(category.name).toBe('name');
expect(category.number).toBe(5);
});
});
describe('Category Class Accessors', () => {
it('should set and get the name correctly', () => {
const category = new Category('name', 5);
category.name = 'newName';
expect(category.name).toBe('newName');
});
it('should set and get the number correctly', () => {
const category = new Category('name', 5);
category.number = 10;
expect(category.number).toBe(10);
});
});

@ -21,7 +21,7 @@ export class CustomJoke extends Joke {
* @param {string} punchline - La chute de la blague. * @param {string} punchline - La chute de la blague.
* @param {string} image - L'URL de l'image associée à la blague. * @param {string} image - L'URL de l'image associée à la blague.
*/ */
constructor(id: string, type: string, setup: string, image: string, punchline: string = "") { constructor(id: string, type: string, setup: string, punchline: string, image: string) {
super(type, setup, punchline, image); // Assuming Joke class has these properties super(type, setup, punchline, image); // Assuming Joke class has these properties
this._id = id; this._id = id;
} }

@ -1,35 +0,0 @@
import { CustomJoke } from './CustomJoke';
import {describe, expect, test} from "@jest/globals"; // Remplacez le chemin par le bon chemin
describe('CustomJoke Class', () => {
const id = '1';
const type = 'Custom';
const setup = 'Why did the developer go broke?';
const image = 'custom-image-url';
const punchline = 'Because he used up all his cache';
test('creates a new instance of CustomJoke', () => {
const customJoke = new CustomJoke(id, type, setup, image, punchline);
expect(customJoke).toBeInstanceOf(CustomJoke);
expect(customJoke.id).toEqual(id);
expect(customJoke.type).toEqual(type);
expect(customJoke.setup).toEqual(setup);
expect(customJoke.punchline).toEqual(punchline);
expect(customJoke.image).toEqual(image);
});
test('updates CustomJoke properties', () => {
const newSetup = 'Why do programmers prefer dark mode?';
const newImage = 'new-custom-image-url';
const newPunchline = 'Because light attracts bugs';
const customJoke = new CustomJoke(id, type, setup, image, punchline);
customJoke.setup = newSetup;
customJoke.image = newImage;
customJoke.punchline = newPunchline;
expect(customJoke.setup).toEqual(newSetup);
expect(customJoke.image).toEqual(newImage);
expect(customJoke.punchline).toEqual(newPunchline);
});
});

@ -1,61 +0,0 @@
const { Joke } = require('./Joke');
const {expect, it, beforeEach, describe} = require("@jest/globals");
// Mock class extending the abstract Joke class
class MockJoke extends Joke {
constructor(type, setup, punchline, image) {
super(type, setup, punchline, image);
}
}
// Test the Joke class
describe('Joke Class', () => {
let joke;
beforeEach(() => {
joke = new MockJoke('type', 'setup', 'punchline', 'image');
});
// Test the constructor
it('should create a new Joke object', () => {
expect(joke).toBeDefined();
expect(joke.type).toBe('type');
expect(joke.setup).toBe('setup');
expect(joke.punchline).toBe('punchline');
expect(joke.image).toBe('image');
});
// Test the summary() method
it('should return a summary of the joke', () => {
expect(joke.summary()).toBe('punchline');
});
// Test the description() method
it('should return a textual description of the joke', () => {
expect(joke.description()).toBe('type, punchline');
});
// Test setting and getting the type
it('should set and get the type correctly', () => {
joke.type = 'newType';
expect(joke.type).toBe('newType');
});
// Test setting and getting the setup
it('should set and get the setup correctly', () => {
joke.setup = 'newSetup';
expect(joke.setup).toBe('newSetup');
});
// Test setting and getting the punchline
it('should set and get the punchline correctly', () => {
joke.punchline = 'newPunchline';
expect(joke.punchline).toBe('newPunchline');
});
// Test setting and getting the image
it('should set and get the image correctly', () => {
joke.image = 'newImage';
expect(joke.image).toBe('newImage');
});
});

@ -1,4 +1,4 @@
import {DarkTheme, DefaultTheme, NavigationContainer, Theme} 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 {Image, View} from 'react-native'; import {Image, View} from 'react-native';
@ -9,8 +9,6 @@ import Settings from "../screens/Settings";
import {darksalmonColor, greyColor, indigoColor} from "../assets/Theme"; import {darksalmonColor, greyColor, indigoColor} from "../assets/Theme";
import StackNavigation from "./StackNavigation"; import StackNavigation from "./StackNavigation";
import {useEffect, useState} from "react";
import {getTheme} from "../redux/thunk/ThemeThunk";
export default function NavigationBar() { export default function NavigationBar() {
@ -22,23 +20,8 @@ export default function NavigationBar() {
const favoriteIcon = require("../assets/favorite_icon.png"); const favoriteIcon = require("../assets/favorite_icon.png");
const settingsIcon = require("../assets/settings_icon.png"); const settingsIcon = require("../assets/settings_icon.png");
const [themes, setTheme] = useState<Theme>(DefaultTheme);
useEffect(() => {
const fetchTheme = async () => {
const theme = await getTheme();
setTheme(theme);
};
fetchTheme();
});
if (themes == null) {
return null;
}
return ( return (
<NavigationContainer theme={ themes.dark === false ? DefaultTheme : DarkTheme}> <NavigationContainer>
<BottomTabNavigator.Navigator initialRouteName="Accueil" <BottomTabNavigator.Navigator initialRouteName="Accueil"
screenOptions={{ screenOptions={{
headerStyle: { headerStyle: {
@ -82,7 +65,7 @@ export default function NavigationBar() {
/> />
) )
}}/> }}/>
<BottomTabNavigator.Screen name="Ajout d'une blague" component={Add} <BottomTabNavigator.Screen name="Ajouter" component={Add}
options={{ options={{
tabBarIcon: ({focused}) => ( tabBarIcon: ({focused}) => (
<View style={{backgroundColor: greyColor, borderRadius: 5, padding: 10}}> <View style={{backgroundColor: greyColor, borderRadius: 5, padding: 10}}>

11987
src/package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -7,57 +7,26 @@
"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"
}, },
"dependencies": { "dependencies": {
"@jest/globals": "^29.7.0",
"@react-native-async-storage/async-storage": "^1.23.1",
"@react-navigation/bottom-tabs": "^6.5.11", "@react-navigation/bottom-tabs": "^6.5.11",
"@react-navigation/native": "^6.1.10", "@react-navigation/native": "^6.1.10",
"@react-navigation/stack": "^6.3.21", "@react-navigation/stack": "^6.3.21",
"@reduxjs/toolkit": "^2.2.1", "@reduxjs/toolkit": "^2.2.1",
"@testing-library/jest-native": "^5.4.3",
"@testing-library/react-native": "^12.4.5",
"@types/react": "~18.2.45", "@types/react": "~18.2.45",
"enzyme": "^3.11.0",
"expo": "~50.0.3", "expo": "~50.0.3",
"expo-status-bar": "~1.11.1", "expo-status-bar": "~1.11.1",
"jest": "^29.7.0",
"jest-expo": "^50.0.4",
"react": "18.2.0", "react": "18.2.0",
"react-native": "0.73.2", "react-native": "0.73.2",
"react-native-gesture-handler": "^2.15.0", "react-native-gesture-handler": "^2.15.0",
"react-native-safe-area-context": "^4.9.0", "react-native-safe-area-context": "^4.9.0",
"react-redux": "^9.1.0", "react-redux": "^9.1.0",
"redux": "^5.0.1", "redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"typescript": "^5.3.0" "typescript": "^5.3.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.0", "@babel/core": "^7.20.0"
"@types/fetch-mock": "^7.3.8",
"@types/redux-mock-store": "^1.0.6",
"fetch-mock": "^9.11.0",
"redux-mock-store": "^1.5.4"
},
"jest": {
"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"
]
}
}, },
"private": true "private": true
} }

@ -1,19 +0,0 @@
import {Category} from "../../model/Category";
export enum ActionType {
FETCH_CATEGORIES = 'FETCH_CATEGORIES',
}
type actionFetch = {
type: ActionType.FETCH_CATEGORIES;
payload: Category[];
}
export type Action = actionFetch;
export const setCategoriesList = (categoriesList: Category[]) => {
return {
type: ActionType.FETCH_CATEGORIES,
payload: categoriesList,
};
}

@ -1,23 +0,0 @@
import { ActionType, Action, setCategoriesList } from './CategoryAction';
import { Category } from '../../model/Category';
import {describe, expect, it} from "@jest/globals";
describe('Actions', () => {
describe('setCategoriesList', () => {
it('should create an action to set categories list', () => {
const categoriesList: Category[] = [
new Category('Category 1', 1),
new Category('Category 2', 2)
];
const expectedAction: Action = {
type: ActionType.FETCH_CATEGORIES,
payload: categoriesList,
};
const action = setCategoriesList(categoriesList);
expect(action).toEqual(expectedAction);
});
});
});

@ -1,53 +0,0 @@
import {CustomJoke} from "../../model/CustomJoke";
import {SampleJoke} from "../../model/SampleJoke";
export enum ActionType {
FETCH_CUSTOM_JOKES = 'FETCH_CUSTOM_JOKES',
POST_CUSTOM_JOKE = "POST_CUSTOM_JOKE",
FETCH_JOKES_BY_ID = "FETCH_JOKES_BY_ID",
DELETE_CUSTOM_JOKE = "DELETE_CUSTOM_JOKE"
}
type actionPostFetch = {
type: ActionType.POST_CUSTOM_JOKE;
payload: CustomJoke;
}
type actionGetFetch = {
type: ActionType.FETCH_CUSTOM_JOKES;
payload: CustomJoke[];
}
type actionGetByFetch = {
type: ActionType.FETCH_JOKES_BY_ID;
payload: CustomJoke;
}
type actionDeleteFetch = {
type: ActionType.DELETE_CUSTOM_JOKE;
payload: string;
}
export type Action = actionPostFetch | actionGetFetch | actionGetByFetch | actionDeleteFetch;
export const setPostJoke = (customJoke: CustomJoke) => {
return {
type: ActionType.POST_CUSTOM_JOKE,
payload: customJoke
}
}
export const setCustomJokesList = (customJokesList: CustomJoke[]) => {
return {
type: ActionType.FETCH_CUSTOM_JOKES,
payload: customJokesList
};
}
export const setCustomJokeById = (customJoke: CustomJoke) => {
return {
type: ActionType.FETCH_JOKES_BY_ID,
payload: customJoke,
};
}
export const setDeleteJoke = (jokeId: string) => {
return {
type: ActionType.DELETE_CUSTOM_JOKE,
payload: jokeId
}
}

@ -1,56 +0,0 @@
import { ActionType, Action, setPostJoke, setCustomJokesList, setCustomJokeById, setDeleteJoke } from './CustomJoke';
import { CustomJoke } from '../../model/CustomJoke';
import {describe, expect, it} from "@jest/globals";
describe('Actions', () => {
describe('setPostJoke', () => {
it('should create an action to post a custom joke', () => {
const customJoke: CustomJoke = new CustomJoke('1', 'pun', 'Setup', 'https://example.com/image.jpg', 'Punchline');
const expectedAction: Action = {
type: ActionType.POST_CUSTOM_JOKE,
payload: customJoke,
};
const action = setPostJoke(customJoke);
expect(action).toEqual(expectedAction);
});
});
describe('setCustomJokesList', () => {
it('should create an action to set custom jokes list', () => {
const customJokesList: CustomJoke[] = [
new CustomJoke('1', 'pun', 'Setup 1', 'https://example.com/image1.jpg', 'Punchline 1'),
new CustomJoke('2', 'pun', 'Setup 2', 'https://example.com/image2.jpg', 'Punchline 2')
];
const expectedAction: Action = {
type: ActionType.FETCH_CUSTOM_JOKES,
payload: customJokesList,
};
const action = setCustomJokesList(customJokesList);
expect(action).toEqual(expectedAction);
});
});
describe('setCustomJokeById', () => {
it('should create an action to set custom joke by ID', () => {
const customJoke: CustomJoke = new CustomJoke('1', 'pun', 'Setup', 'https://example.com/image.jpg', 'Punchline');
const expectedAction: Action = {
type: ActionType.FETCH_JOKES_BY_ID,
payload: customJoke,
};
const action = setCustomJokeById(customJoke);
expect(action).toEqual(expectedAction);
});
});
describe('setDeleteJoke', () => {
it('should create an action to delete a custom joke', () => {
const jokeId = '1';
const expectedAction: Action = {
type: ActionType.DELETE_CUSTOM_JOKE,
payload: jokeId,
};
const action = setDeleteJoke(jokeId);
expect(action).toEqual(expectedAction);
});
});
});

@ -1,18 +0,0 @@
export enum ActionType {
SET_THEME = 'SET_THEME',
}
type actionFetch = {
type: ActionType.SET_THEME;
payload: String;
}
export type Action = actionFetch;
export const setTheme = (theme) => {
return {
type: ActionType.SET_THEME,
payload: theme,
};
}

@ -1,28 +0,0 @@
import {CustomJoke} from "../../model/CustomJoke";
import {SampleJoke} from "../../model/SampleJoke";
export enum ActionType {
ADD_FAVORITE = 'ADD_CUSTOM_FAVORITE',
REMOVE_FAVORITE = "REMOVE_CUSTOM_FAVORITE"
}
type actionAddFetch = {
type: ActionType.ADD_FAVORITE;
payload: string;
}
type actionRemoveFetch = {
type: ActionType.REMOVE_FAVORITE;
payload: string;
}
export type Action = actionAddFetch | actionRemoveFetch;
export const addFavorite = (joke: CustomJoke | SampleJoke) => ({
type: ActionType.ADD_FAVORITE,
payload: joke,
});
export const removeFavorite = (joke: CustomJoke | SampleJoke) => ({
type: ActionType.REMOVE_FAVORITE,
payload: joke,
});

@ -1,20 +1,31 @@
import {Category} from "../../model/Category";
import {SampleJoke} from "../../model/SampleJoke"; import {SampleJoke} from "../../model/SampleJoke";
export enum ActionType { export enum ActionType {
FETCH_CATEGORIES = 'FETCH_CATEGORIES',
FETCH_JOKES = 'FETCH_JOKES', FETCH_JOKES = 'FETCH_JOKES',
FETCH_JOKES_BY_ID = 'FETCH_JOKES_BY_ID', FETCH_JOKES_BY_ID = 'FETCH_JOKES_BY_ID'
} }
type actionFetch = { type actionFetch = {
type: ActionType.FETCH_CATEGORIES;
payload: Category[];
} | {
type: ActionType.FETCH_JOKES; type: ActionType.FETCH_JOKES;
payload: SampleJoke[]; payload: SampleJoke[];
} } | {
type actionFetchById = {
type: ActionType.FETCH_JOKES_BY_ID; type: ActionType.FETCH_JOKES_BY_ID;
payload: SampleJoke; payload: SampleJoke;
} }
export type Action = actionFetch | actionFetchById;
export type Action = actionFetch;
export const setCategoriesList = (categoriesList: Category[]) => {
return {
type: ActionType.FETCH_CATEGORIES,
payload: categoriesList,
};
}
export const setJokesList = (jokesList: SampleJoke[]) => { export const setJokesList = (jokesList: SampleJoke[]) => {
return { return {
type: ActionType.FETCH_JOKES, type: ActionType.FETCH_JOKES,

@ -1,32 +0,0 @@
import { ActionType, Action, setJokesList, setJokeById } from './SampleAction';
import { SampleJoke } from '../../model/SampleJoke';
import {describe, expect, it} from "@jest/globals";
describe('Actions', () => {
describe('setJokesList', () => {
it('should create an action to set jokes list', () => {
const jokesList: SampleJoke[] = [
new SampleJoke(1, 'pun', 'Setup 1', 'https://example.com/image1.jpg', 'Punchline 1'),
new SampleJoke(2, 'pun', 'Setup 2', 'https://example.com/image2.jpg', 'Punchline 2')
];
const expectedAction: Action = {
type: ActionType.FETCH_JOKES,
payload: jokesList,
};
const action = setJokesList(jokesList);
expect(action).toEqual(expectedAction);
});
});
describe('setJokeById', () => {
it('should create an action to set joke by ID', () => {
const joke: SampleJoke = new SampleJoke(1, 'pun', 'Setup', 'https://example.com/image.jpg', 'Punchline');
const expectedAction: Action = {
type: ActionType.FETCH_JOKES_BY_ID,
payload: joke,
};
const action = setJokeById(joke);
expect(action).toEqual(expectedAction);
});
});
});

@ -1,24 +0,0 @@
import {CustomJoke} from "../../model/CustomJoke";
import {SampleJoke} from "../../model/SampleJoke";
import {Action, ActionType} from "../actions/CategoryAction";
import {Category} from "../../model/Category";
interface State {
categories: Category[];
}
const initialState = {
categories: []
}
const categoryReducer = (state: State = initialState, action: Action) => {
switch (action.type) {
case ActionType.FETCH_CATEGORIES:
// @ts-ignore
return {...state, categories: action.payload};
default:
return state;
}
}
export default categoryReducer;

@ -1,23 +0,0 @@
import categoryReducer from './CategoryReducer';
import { ActionType as CategoryActionType } from '../actions/CategoryAction';
import { Category } from '../../model/Category';
import {describe, expect, it} from "@jest/globals";
describe('Category Reducer', () => {
it('should handle FETCH_CATEGORIES', () => {
const categories = [
new Category('Category 1', 1),
new Category('Category 2', 2)
];
const action = {
type: CategoryActionType.FETCH_CATEGORIES,
payload: categories
};
expect(categoryReducer(undefined, action)).toEqual({
categories: categories
});
});
});

@ -1,31 +0,0 @@
import {CustomJoke} from "../../model/CustomJoke";
import {Action, ActionType} from "../actions/CustomJoke";
interface State {
customJokes: CustomJoke[];
customJoke?: CustomJoke;
jokeId: string;
}
const initialState = {
customJokes: [],
customJoke: null,
jokeId: ''
}
const customReducer = (state: State = initialState, action: Action) => {
switch (action.type) {
case ActionType.POST_CUSTOM_JOKE:
return {...state, customJoke: action.payload};
case ActionType.FETCH_CUSTOM_JOKES:
return {...state, customJokes: action.payload};
case ActionType.FETCH_JOKES_BY_ID:
return {...state, customJoke: action.payload};
case ActionType.DELETE_CUSTOM_JOKE:
return {...state, jokeId: action.payload};
default:
return state;
}
}
export default customReducer;

@ -1,29 +0,0 @@
import customReducer from './CustomReducer';
import { ActionType } from '../actions/CustomJoke';
import { CustomJoke } from '../../model/CustomJoke';
import {describe, expect, it} from "@jest/globals";
describe('Custom Reducer', () => {
it('should return the initial state', () => {
expect(customReducer(undefined, {})).toEqual({
customJokes: [],
customJoke: null,
jokeId: ''
});
});
it('should handle POST_CUSTOM_JOKE', () => {
const customJoke: CustomJoke = new CustomJoke('1', 'pun', 'Setup', 'https://example.com/image.jpg', 'Punchline');
const action = {
type: ActionType.POST_CUSTOM_JOKE,
payload: customJoke
};
expect(customReducer(undefined, action)).toEqual({
customJokes: [],
customJoke: customJoke,
jokeId: ''
});
});
});

@ -1,21 +0,0 @@
import {Action, ActionType} from "../actions/DarkModeAction";
interface State {
theme: String
}
const initialState = {
theme: 'dark'
}
const themeReducer = (state: State = initialState, action: Action) => {
switch (action.type) {
case ActionType.SET_THEME:
// @ts-ignore
return {...state, theme: action.payload};
default:
return state;
}
}
export default themeReducer;

@ -1,22 +0,0 @@
import themeReducer from './DarkModeReducer';
import { ActionType } from '../actions/DarkModeAction';
import {describe, expect, it} from "@jest/globals";
describe('Theme Reducer', () => {
// it('should return the initial state', () => {
// expect(themeReducer(undefined, {})).toEqual({
// theme: 'dark'
// });
// });
it('should handle SET_THEME', () => {
const action = {
type: ActionType.SET_THEME,
payload: 'light'
};
expect(themeReducer(undefined, action)).toEqual({
theme: 'light'
});
});
});

@ -1,26 +0,0 @@
import {Action, ActionType} from "../actions/FavoriteAction";
import {CustomJoke} from "../../model/CustomJoke";
import {SampleJoke} from "../../model/SampleJoke";
interface State {
joke: CustomJoke | SampleJoke
}
const initialState = {
joke: new CustomJoke('', '', '', '', '')
}
const favoriteReducer = (state: State = initialState, action: Action) => {
switch (action.type) {
case ActionType.ADD_FAVORITE:
// @ts-ignore
return {...state, joke: action.payload};
case ActionType.REMOVE_FAVORITE:
// @ts-ignore
return {...state, joke: action.payload};
default:
return state;
}
}
export default favoriteReducer;

@ -1,61 +0,0 @@
// Importez les réducteurs et les types d'action appropriés
import categoryReducer from './CategoryReducer';
import customReducer from './CustomReducer';
import themeReducer from './DarkModeReducer';
import sampleReducer from './SampleReducer';
import { ActionType as CategoryActionType } from '../actions/CategoryAction';
import { ActionType as CustomActionType } from '../actions/CustomJoke';
import { ActionType as ThemeActionType } from '../actions/DarkModeAction';
import { ActionType as SampleActionType } from '../actions/SampleAction';
import { Category } from '../../model/Category';
import { CustomJoke } from '../../model/CustomJoke';
import { SampleJoke } from '../../model/SampleJoke';
import {describe, expect, it} from "@jest/globals";
// Tester categoryReducer
describe('Category Reducer', () => {
it('should return the initial state', () => {
expect(categoryReducer(undefined, {})).toEqual({
categories: []
});
});
// Ajouter d'autres tests pour categoryReducer si nécessaire...
});
// Tester customReducer
describe('Custom Reducer', () => {
it('should return the initial state', () => {
expect(customReducer(undefined, {})).toEqual({
customJokes: [],
customJoke: null,
jokeId: ''
});
});
// Ajouter d'autres tests pour customReducer si nécessaire...
});
// Tester themeReducer
describe('Theme Reducer', () => {
it('should return the initial state', () => {
expect(themeReducer(undefined, {})).toEqual({
theme: 'dark'
});
});
// Ajouter d'autres tests pour themeReducer si nécessaire...
});
// Tester sampleReducer
describe('Sample Reducer', () => {
it('should return the initial state', () => {
expect(sampleReducer(undefined, {})).toEqual({
jokes: [],
favoriteJokes: [],
joke: null,
});
});
// Ajouter d'autres tests pour sampleReducer si nécessaire...
});

@ -1,25 +1,31 @@
import {CustomJoke} from "../../model/CustomJoke"; import {CustomJoke} from "../../model/CustomJoke";
import {SampleJoke} from "../../model/SampleJoke"; import {SampleJoke} from "../../model/SampleJoke";
import {Action, ActionType} from "../actions/SampleAction"; import {Action} from "redux";
import {ActionType} from "../actions/JokeAction";
import {Category} from "../../model/Category"; import {Category} from "../../model/Category";
interface State { interface State {
categories: Category[];
jokes: SampleJoke[]; jokes: SampleJoke[];
favoriteJokes: SampleJoke[]; favoriteJokes: SampleJoke[];
joke? : SampleJoke; joke : SampleJoke;
} }
const initialState = { const initialState = {
categories: [],
jokes: [], jokes: [],
favoriteJokes: [], favoriteJokes: [],
joke:null, joke: new SampleJoke(1, "", "", "", ""),
} }
const sampleReducer = (state: State = initialState, action: Action) => { const appReducer = (state: State = initialState, action: Action) => {
switch (action.type) { switch (action.type) {
case ActionType.FETCH_JOKES: case ActionType.FETCH_JOKES:
// @ts-ignore // @ts-ignore
return {...state, jokes: action.payload}; return {...state, jokes: action.payload};
case ActionType.FETCH_CATEGORIES:
// @ts-ignore
return {...state, categories: action.payload};
case ActionType.FETCH_JOKES_BY_ID: case ActionType.FETCH_JOKES_BY_ID:
// @ts-ignore // @ts-ignore
return {...state, joke: action.payload} return {...state, joke: action.payload}
@ -28,4 +34,4 @@ const sampleReducer = (state: State = initialState, action: Action) => {
} }
} }
export default sampleReducer; export default appReducer;

@ -1,17 +1,9 @@
import {configureStore} from '@reduxjs/toolkit' import {configureStore} from '@reduxjs/toolkit'
import sampleReducer from "./reducers/SampleReducer"; import appReducer from './reducers/jokeReducer';
import categoryReducer from "./reducers/CategoryReducer";
import customReducer from "./reducers/CustomReducer";
import themeReducer from "./reducers/DarkModeReducer"
import favoriteReducer from "./reducers/FavoriteReducer";
// Reference here all your application reducers // Reference here all your application reducers
const reducer = { const reducer = {
sampleReducer: sampleReducer, appReducer: appReducer,
categoryReducer: categoryReducer,
customReducer: customReducer,
theme: themeReducer,
favorite: favoriteReducer
} }
// @ts-ignore // @ts-ignore
@ -22,6 +14,5 @@ const store = configureStore({
serializableCheck: false serializableCheck: false
}) })
},); },);
export type AppState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
export default store; export default store;

@ -1,29 +0,0 @@
import store from './store';
import {describe, expect, it} from "@jest/globals";
describe('Redux Store Configuration', () => {
it('should have sampleReducer', () => {
expect(store.getState().sampleReducer).toBeDefined();
});
it('should have categoryReducer', () => {
expect(store.getState().categoryReducer).toBeDefined();
});
it('should have customReducer', () => {
expect(store.getState().customReducer).toBeDefined();
});
it('should have themeReducer', () => {
expect(store.getState().theme).toBeDefined();
});
it('should have favoriteReducer', () => {
expect(store.getState().favorite).toBeDefined();
});
it('should have a configured store', () => {
expect(store).toBeDefined();
expect(store.getState()).toBeDefined();
});
});

@ -1,28 +0,0 @@
import { setDeleteJoke } from "../actions/CustomJoke";
export const deleteItem = (id: string) => {
return async dispatch => {
try {
const response = await fetch(`https://iut-weather-api.azurewebsites.net/jokes/${id}`, {
method: 'DELETE',
headers: {
Accept: "application/json",
"Content-Type": 'application/json',
}
});
if (response.ok) {
dispatch(setDeleteJoke(id)); // Supprimer la blague dans le store Redux
console.log('Suppression de la blague réussie');
} else {
console.log('Erreur lors de la requête DELETE');
}
} catch (error) {
console.log('Erreur :', error);
}
};
};
export const deleteCustomJoke = (jokeId) => {
return deleteItem(jokeId)
}

@ -1,52 +0,0 @@
import fetchMock from 'fetch-mock';
import thunk from 'redux-thunk';
import configureMockStore from 'redux-mock-store';
import { deleteItem } from './DeleteThunk'; // Assurez-vous d'importer correctement votre thunk
import { setDeleteJoke } from '../actions/CustomJoke';
import {afterEach, describe, expect, it, jest} from "@jest/globals";
const middlewares = [thunk];
// @ts-ignore
const mockStore = configureMockStore(middlewares);
describe('deleteItem Thunk', () => {
afterEach(() => {
fetchMock.restore();
});
it('dispatches setDeleteJoke after successful DELETE request', async () => {
const jokeId = '123';
const expectedActions = [setDeleteJoke(jokeId)];
fetchMock.deleteOnce(`https://iut-weather-api.azurewebsites.net/jokes/${jokeId}`, {
status: 200,
});
const store = mockStore({});
// @ts-ignore
await store.dispatch(deleteItem(jokeId));
expect(store.getActions()).toEqual(expectedActions);
});
it('logs an error message if DELETE request fails', async () => {
const jokeId = '123';
fetchMock.deleteOnce(`https://iut-weather-api.azurewebsites.net/jokes/${jokeId}`, {
status: 500,
});
const consoleSpy = jest.spyOn(console, 'log');
consoleSpy.mockImplementation(() => {});
const store = mockStore({});
// @ts-ignore
await store.dispatch(deleteItem(jokeId));
expect(consoleSpy).toHaveBeenCalledWith('Erreur lors de la requête DELETE');
consoleSpy.mockRestore();
});
});

@ -1,47 +0,0 @@
// Fonction pour ajouter une blague aux favoris
import AsyncStorage from "@react-native-async-storage/async-storage";
import {SampleJoke} from "../../model/SampleJoke";
import {CustomJoke} from "../../model/CustomJoke";
const addFavorite = async (joke: SampleJoke | CustomJoke) => {
try {
let favorites: { sampleJokes: SampleJoke[], customJokes: CustomJoke[] } = await AsyncStorage.getItem('@favorites');
if (!favorites) {
favorites = { sampleJokes: [], customJokes: [] };
}
if (joke instanceof SampleJoke) {
favorites.sampleJokes.push(joke);
} else if (joke instanceof CustomJoke) {
favorites.customJokes.push(joke);
}
await AsyncStorage.setItem('@favorites', JSON.stringify(favorites));
} catch (error) {
console.error('Error adding favorite:', error);
}
};
const removeFavorite = async (jokeId: string | number) => {
try {
var favorites: { sampleJokes: SampleJoke[], customJokes: CustomJoke[] } = await AsyncStorage.getItem('@favorites');
if (!favorites) {
return;
}
favorites.sampleJokes = favorites.sampleJokes.filter(joke => joke.id !== jokeId);
favorites.customJokes = favorites.customJokes.filter(joke => joke.id !== jokeId);
await AsyncStorage.setItem('@favorites', JSON.stringify(favorites));
} catch (error) {
console.error('Error removing favorite:', error);
}
};
const getFavorites = async () => {
try {
const favoritesString = await AsyncStorage.getItem('@favorites');
if (favoritesString !== null) {
return JSON.parse(favoritesString);
}
} catch (error) {
console.error('Error getting favorites:', error);
}
return { sampleJokes: [], customJokes: [] };
};

@ -1,26 +0,0 @@
import {SampleJoke} from "../../model/SampleJoke";
import { setJokeById} from "../actions/SampleAction";
import {CustomJoke} from "../../model/CustomJoke";
import {setCustomJokeById} from "../actions/CustomJoke";
export const getItem = <TItem>(uri:string, constructor : (json:any) => TItem, setItem: (item: TItem) => any) => {
return async dispatch => {
try {
console.log(";;;;;;;;;;;;;;", uri, ";;;;;;;;;;;;;;;;;;;")
const promise = await fetch(uri);
const Json = await promise.json();
const Item: TItem = constructor(Json);
console.log("===========", Item , "===================");
dispatch(setItem(Item));
} catch (error) {
console.log('Error---------', error);
}
}
}
export const getJokeById = (id : number) => {
return getItem<SampleJoke>('https://iut-weather-api.azurewebsites.net/jokes/samples/' + id.toString() , (elt) => new SampleJoke(elt["id"], elt["type"], elt["setup"], elt["image"]), (item) => setJokeById(item))
}
export const getCustomJokeById = (id: string) => {
return getItem<CustomJoke>('https://iut-weather-api.azurewebsites.net/jokes/' + id , (elt) => new CustomJoke(elt["id"], elt["type"], elt["setup"], elt["image"]), (item) => setCustomJokeById(item))
}

@ -1,81 +0,0 @@
import fetchMock from 'fetch-mock';
import thunk from 'redux-thunk';
import configureMockStore from 'redux-mock-store';
import { getItem, getJokeById, getCustomJokeById } from './GetByThunk'; // Assurez-vous d'importer correctement vos thunks
import { setJokeById } from '../actions/SampleAction';
import {setCustomJokeById} from "../actions/CustomJoke";
import { SampleJoke } from '../../model/SampleJoke';
import { CustomJoke } from '../../model/CustomJoke';
import {afterEach, describe, expect, it, jest} from "@jest/globals";
const middlewares = [thunk];
// @ts-ignore
const mockStore = configureMockStore(middlewares);
describe('getItem Thunk', () => {
afterEach(() => {
fetchMock.restore();
});
it('dispatches setJokeById after successful GET request for SampleJoke', async () => {
const jokeId = 1;
const expectedJoke = new SampleJoke(jokeId, 'type', 'setup', 'image');
fetchMock.getOnce(`https://iut-weather-api.azurewebsites.net/jokes/samples/${jokeId}`, {
status: 200,
body: {
id: jokeId,
type: 'type',
setup: 'setup',
image: 'image',
},
});
const store = mockStore({});
// @ts-ignore
await store.dispatch(getJokeById(parseInt(jokeId)));
expect(store.getActions()).toEqual([setJokeById(expectedJoke)]);
});
it('dispatches setCustomJokeById after successful GET request for CustomJoke', async () => {
const jokeId = '1';
const expectedJoke = new CustomJoke(jokeId, 'type', 'setup', 'image');
fetchMock.getOnce(`https://iut-weather-api.azurewebsites.net/jokes/${jokeId}`, {
status: 200,
body: {
id: jokeId,
type: 'type',
setup: 'setup',
image: 'image',
},
});
const store = mockStore({});
// @ts-ignore
await store.dispatch(getCustomJokeById(jokeId));
expect(store.getActions()).toEqual([setCustomJokeById(expectedJoke)]);
});
it('logs an error message if GET request fails', async () => {
const jokeId = '1';
fetchMock.getOnce(`https://iut-weather-api.azurewebsites.net/jokes/samples/${jokeId}`, {
status: 500,
});
const consoleSpy = jest.spyOn(console, 'log');
consoleSpy.mockImplementation(() => {});
const store = mockStore({});
// @ts-ignore
await store.dispatch(getJokeById(parseInt(jokeId)));
expect(consoleSpy).toHaveBeenCalledWith('Error---------');
});
});

@ -1,35 +0,0 @@
import {SampleJoke} from "../../model/SampleJoke";
import {setJokesList} from "../actions/SampleAction";
import {Category} from "../../model/Category";
import {setCategoriesList} from "../actions/CategoryAction";
import {CustomJoke} from "../../model/CustomJoke";
import {setCustomJokesList} from "../actions/CustomJoke";
export const getList = <TList>(uri:string, constructor : (json:any) => TList, setList: (list: TList[]) => any) => {
return async dispatch => {
try {
const promise = await fetch(uri);
const listJson = await promise.json();
const List: TList[] = listJson.map(elt => constructor(elt));
dispatch(setList(List));
} catch (error) {
console.log(`Error In getList ${uri} ---------`, error);
}
}
}
export const getLastSampleJokesList = () => {
return getList('https://iut-weather-api.azurewebsites.net/jokes/lasts', (elt) => new SampleJoke(elt["id"], elt["type"], elt["setup"], elt["image"]), (list) => setJokesList(list))
}
export const getCategoriesList = () => {
return getList('https://iut-weather-api.azurewebsites.net/jokes/categories/top', (elt) => new Category(elt["name"], elt["number"]), (list) => setCategoriesList(list))
}
export const getSampleJokesList = () => {
return getList('https://iut-weather-api.azurewebsites.net/jokes/samples', (elt) => new SampleJoke(elt["id"], elt["type"], elt["setup"], elt["image"]), (list) => setJokesList(list))
}
export const getCustomJokesList = () => {
return getList('https://iut-weather-api.azurewebsites.net/jokes', (elt) => new CustomJoke(elt["id"], elt["type"], elt["setup"], elt["image"]), (list) => setCustomJokesList(list))
}

@ -1,84 +0,0 @@
import fetchMock from 'fetch-mock';
import configureMockStore from 'redux-mock-store';
import { getLastSampleJokesList, getCategoriesList } from './GetThunk';
import { setCategoriesList } from '../actions/CategoryAction';
import { SampleJoke } from '../../model/SampleJoke';
import { Category } from '../../model/Category';
import {afterEach, describe, expect, it, jest} from "@jest/globals";
import {setJokesList} from "../actions/SampleAction";
import thunk from "redux-thunk";
const middlewares = [thunk];
// @ts-ignore
const mockStore = configureMockStore(middlewares);
describe('getList Thunk', () => {
afterEach(() => {
fetchMock.restore();
});
it('dispatches setJokesList after successful GET request for LastSampleJokesList', async () => {
const expectedJokesList = [
new SampleJoke('1', 'type1', 'setup1', 'image1'),
new SampleJoke('2', 'type2', 'setup2', 'image2'),
];
fetchMock.getOnce('https://iut-weather-api.azurewebsites.net/jokes/lasts', {
status: 200,
body: expectedJokesList.map(joke => ({
id: joke.id,
type: joke.type,
setup: joke.setup,
image: joke.image,
})),
});
const store = mockStore({});
// @ts-ignore
await store.dispatch(getLastSampleJokesList());
expect(store.getActions()).toEqual([setJokesList(expectedJokesList)]);
});
it('dispatches setCategoriesList after successful GET request for CategoriesList', async () => {
const expectedCategoriesList = [
new Category('Category1', 1),
new Category('Category2', 2),
];
fetchMock.getOnce('https://iut-weather-api.azurewebsites.net/jokes/categories/top', {
status: 200,
body: expectedCategoriesList.map(category => ({
name: category.name,
number: category.number,
})),
});
const store = mockStore({});
// @ts-ignore
await store.dispatch(getCategoriesList());
expect(store.getActions()).toEqual([setCategoriesList(expectedCategoriesList)]);
});
// Tests similaires pour les autres fonctions thunks
it('logs an error message if GET request fails', async () => {
fetchMock.getOnce('https://iut-weather-api.azurewebsites.net/jokes/lasts', {
status: 500,
});
const consoleSpy = jest.spyOn(console, 'log');
consoleSpy.mockImplementation(() => {});
const store = mockStore({});
// @ts-ignore
await store.dispatch(getLastSampleJokesList());
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Error In getList'));
});
});

@ -1,41 +0,0 @@
import {setPostJoke} from "../actions/CustomJoke";
export const setItem = <TItem>(
uri: string,
type : string,
setup : string,
punchline : string
) => {
return async dispatch => {
try {
// @ts-ignore
const response = await fetch(uri, {
method: 'POST',
headers: {
Accept: "application/json",
"Content-Type": 'application/json',
},
body: JSON.stringify(
{
type: type,
setup: setup,
punchline: punchline
}
)
});
const data = await response.json();
dispatch(setPostJoke(data));
if (response.ok) {
console.log('Envoie ok de custom joke')
} else {
console.log('Erreur lors de la requête POST');
}
} catch (error) {
console.log('Erreur :', error);
}
};
};
export const postCustomJoke = (joke, downgrade, category) => {
return setItem('https://iut-weather-api.azurewebsites.net/jokes', joke, downgrade, category)
}

@ -1,67 +0,0 @@
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import fetchMock from 'fetch-mock';
import { setPostJoke } from '../actions/CustomJoke';
import { setItem, postCustomJoke } from './PostThunk';
import {afterEach, describe, expect, it, jest} from "@jest/globals";
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe('setItem Thunk', () => {
afterEach(() => {
fetchMock.restore();
});
it('dispatches setPostJoke after successful POST request', async () => {
const mockResponse = { id: '123', type: 'pun', setup: 'Why was the math book sad?', punchline: 'Because it had too many problems.' };
fetchMock.postOnce('https://iut-weather-api.azurewebsites.net/jokes', {
body: mockResponse,
headers: { 'content-type': 'application/json' }
});
const expectedActions = [
setPostJoke(mockResponse)
];
const store = mockStore({});
await store.dispatch(setItem('https://iut-weather-api.azurewebsites.net/jokes', 'pun', 'Why was the math book sad?', 'Because it had too many problems.'));
expect(store.getActions()).toEqual(expectedActions);
});
it('logs an error message if POST request fails', async () => {
fetchMock.postOnce('https://iut-weather-api.azurewebsites.net/jokes', 404);
const consoleSpy = jest.spyOn(console, 'log');
consoleSpy.mockImplementation(() => {});
const store = mockStore({});
await store.dispatch(setItem('https://iut-weather-api.azurewebsites.net/jokes', 'pun', 'Why was the math book sad?', 'Because it had too many problems.'));
expect(consoleSpy).toHaveBeenCalledWith('Erreur lors de la requête POST');
consoleSpy.mockRestore();
});
});
describe('postCustomJoke Thunk', () => {
afterEach(() => {
fetchMock.restore();
});
it('calls setItem with correct parameters', async () => {
const uri = 'https://iut-weather-api.azurewebsites.net/jokes';
const type = 'pun';
const setup = 'Why was the math book sad?';
const punchline = 'Because it had too many problems.';
const store = mockStore({});
const setItemSpy = jest.spyOn(global, 'setItem');
setItemSpy.mockResolvedValueOnce();
await store.dispatch(postCustomJoke(type, setup, punchline));
expect(setItemSpy).toHaveBeenCalledWith(uri, type, setup, punchline);
setItemSpy.mockRestore();
});
});

@ -0,0 +1,51 @@
import {SampleJoke} from "../../model/SampleJoke";
import {setCategoriesList, setJokeById, setJokesList} from "../actions/JokeAction";
import {Category} from "../../model/Category";
export const getList = <TList>(uri:string, constructor : (json:any) => TList, setList: (list: TList[]) => any) => {
//In order to use await your callback must be asynchronous using async keyword.
return async dispatch => {
//Then perform your asynchronous operations.
try {
const promise = await fetch(uri);
//Then use the json method to get json data from api/
const listJson = await promise.json();
const List: TList[] = listJson.map(elt => constructor(elt));
dispatch(setList(List));
} catch (error) {
console.log('Error---------', error);
}
}
}
export const getItem = <TItem>(uri:string, constructor : (json:any) => TItem, setItem: (item: TItem) => any) => {
//In order to use await your callback must be asynchronous using async keyword.
return async dispatch => {
//Then perform your asynchronous operations.
try {
const promise = await fetch(uri);
//Then use the json method to get json data from api/
const Json = await promise.json();
const Item: TItem = constructor(Json);
dispatch(setItem(Item));
} catch (error) {
console.log('Error---------', error);
}
}
}
export const getLastSampleJokesList = () => {
return getList('https://iut-weather-api.azurewebsites.net/jokes/lasts', (elt) => new SampleJoke(elt["id"], elt["type"], elt["setup"], elt["image"]), (list) => setJokesList(list))
}
export const getCategoriesList = () => {
return getList('https://iut-weather-api.azurewebsites.net/jokes/categories/top', (elt) => new Category(elt["name"], elt["number"]), (list) => setCategoriesList(list))
}
export const getSampleJokesList = () => {
return getList('https://iut-weather-api.azurewebsites.net/jokes/samples', (elt) => new SampleJoke(elt["id"], elt["type"], elt["setup"], elt["image"]), (list) => setJokesList(list))
}
export const getJokeById = (id) => {
return getItem<SampleJoke>('https://iut-weather-api.azurewebsites.net/jokes/samples/' + id , (elt) => new SampleJoke(elt["id"], elt["type"], elt["setup"], elt["image"]), (item) => setJokeById(item))
}

@ -1,21 +0,0 @@
import AsyncStorage from "@react-native-async-storage/async-storage";
import {Theme} from "@react-navigation/native";
export const storeTheme = async (theme) => {
try {
const jsonValue = JSON.stringify(theme)
await AsyncStorage.setItem('@theme', jsonValue)
console.log("theme stored")
} catch (e) {
console.log(e);
}
}
export const getTheme = async () => {
try {
const jsonValue = await AsyncStorage.getItem('@theme')
return jsonValue != null ? JSON.parse(jsonValue) as Theme : null;
} catch(e) {
console.log(e);
}
}

@ -1,59 +0,0 @@
import AsyncStorage from "@react-native-async-storage/async-storage";
import { storeTheme, getTheme } from "./ThemeThunk";
import {afterEach, describe, expect, it, jest} from "@jest/globals";
describe('storeTheme Function', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('stores theme in AsyncStorage', async () => {
const theme = { colors: { background: 'white', text: 'black' } };
const jsonValue = JSON.stringify(theme);
AsyncStorage.setItem = jest.fn().mockResolvedValueOnce();
console.log = jest.fn();
await storeTheme(theme);
expect(AsyncStorage.setItem).toHaveBeenCalledWith('@theme', jsonValue);
expect(console.log).toHaveBeenCalledWith("theme stored");
});
it('logs error if theme storage fails', async () => {
const theme = { colors: { background: 'white', text: 'black' } };
AsyncStorage.setItem = jest.fn().mockRejectedValueOnce('Storage error');
console.error = jest.fn();
await storeTheme(theme);
expect(console.error).toHaveBeenCalledWith('Storage error');
});
});
describe('getTheme Function', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('retrieves theme from AsyncStorage', async () => {
const theme = { colors: { background: 'white', text: 'black' } };
const jsonValue = JSON.stringify(theme);
AsyncStorage.getItem = jest.fn().mockResolvedValueOnce(jsonValue);
const result = await getTheme();
expect(result).toEqual(theme);
});
it('returns null if theme is not found in AsyncStorage', async () => {
AsyncStorage.getItem = jest.fn().mockResolvedValueOnce(null);
const result = await getTheme();
expect(result).toBeNull();
});
it('logs error if theme retrieval fails', async () => {
AsyncStorage.getItem = jest.fn().mockRejectedValueOnce('Retrieval error');
console.error = jest.fn();
await getTheme();
expect(console.error).toHaveBeenCalledWith('Retrieval error');
});
});

@ -3,153 +3,32 @@ import { sampleJokeStub } from '../data/stub/SampleJokeStub';
import JokeItems from "../components/JokeItems"; import JokeItems from "../components/JokeItems";
import '../types/extension'; import '../types/extension';
import {useDispatch, useSelector} from "react-redux"; import {useDispatch, useSelector} from "react-redux";
import {Image, StyleSheet, Text, TextInput, TouchableOpacity, View} from "react-native"; import {StyleSheet, View} from "react-native";
import {darksalmonColor, greyColor, indigoColor, purpleColor, whiteColor} from "../assets/Theme"; import {purpleColor} from "../assets/Theme";
import {getCustomJokesList, getSampleJokesList} from "../redux/thunk/GetThunk"; import {getSampleJokesList} from "../redux/thunk/RecentsJokesThunk";
import React, {useEffect} from "react"; import {useEffect} from "react";
import {postCustomJoke} from "../redux/thunk/PostThunk";
import {useTheme} from "@react-navigation/native";
export default function AddScreen() { export default function Catalogue() {
// @ts-ignore // @ts-ignore
const allJokes = useSelector(state => state.appReducer.jokes);
const dispatch = useDispatch(); const dispatch = useDispatch();
const MAX_LENGTH = 10;
const [joke, onChangeJoke] = React.useState('');
const [downgrade, onChangeDowngrade] = React.useState('');
const [category, onChangeCategory] = React.useState('');
const clearFields = () => { useEffect(() => {
onChangeCategory(''); const loadJokes = async () => {
onChangeJoke('');
onChangeDowngrade('');
}
const handleCreate = () => {
// @ts-ignore
dispatch(postCustomJoke(joke, downgrade, category));
// @ts-ignore // @ts-ignore
dispatch(getCustomJokesList()); await dispatch(getSampleJokesList());
clearFields();
}; };
loadJokes();
const styles = themeSettings() }, [dispatch]);
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Text style={styles.text}>Blague</Text> <JokeItems jokes={allJokes}/>
<TextInput
style={styles.textInput}
onChangeText={onChangeJoke}
value={joke}
placeholder="Inserez votre blague"
placeholderTextColor={whiteColor}
scrollEnabled={true}
autoCorrect={true}
multiline={true}
numberOfLines={10}
cursorColor={indigoColor}
/>
<Text style={styles.text}>Chute de la blague</Text>
<TextInput
style={styles.textInput}
onChangeText={onChangeDowngrade}
value={downgrade}
multiline={true}
placeholder="Inserez votre blague"
placeholderTextColor={whiteColor}
scrollEnabled={true}
autoCorrect={true}
numberOfLines={10}
cursorColor={indigoColor}
/>
<Text style={styles.text}>Catégorie</Text>
<TextInput
style={styles.textInput}
onChangeText={onChangeCategory}
value={category}
placeholder="Inserez votre blague"
placeholderTextColor={whiteColor}
scrollEnabled={true}
autoCorrect={true}
maxLength={10}
cursorColor={indigoColor}
/>
<View style={styles.viewCounter}>
<Text style={styles.textSize}>{category.length}/{MAX_LENGTH}</Text>
</View>
<TouchableOpacity style={styles.createButton} onPress={handleCreate}>
<Text style={styles.createTextButton} >CRÉER</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.eraseButton} onPress={clearFields}>
<Text style={styles.eraseTextButton} >EFFACER</Text>
</TouchableOpacity>
</View> </View>
) )
}; };
export function themeSettings() {
const theme = useTheme();
const colors = theme.colors;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
backgroundColor: colors.background, backgroundColor: purpleColor
width: "100%",
height: "100%",
},
textInput: {
backgroundColor: colors.card,
color: colors.text,
width: "90%",
alignSelf: "center",
minHeight: 100
},
eraseButton: {
borderRadius: 5,
alignItems: "center",
backgroundColor: colors.primary,
height: 50,
margin: 10
},
createButton: {
borderRadius: 5,
alignItems: "center",
backgroundColor: colors.primary,
height: 50,
margin: 10,
marginTop: 30
},
createTextButton: {
margin: 10,
textAlign: "center",
fontWeight: "700",
color: colors.text,
},
eraseTextButton: {
margin: 10,
textAlign: "center",
fontWeight: "700",
color: colors.text,
},
text: {
color: colors.text,
paddingBottom: 10,
paddingTop: 25,
marginLeft: 19,
fontSize: 20,
},
textSize: {
paddingTop: 15,
marginRight: 19,
fontSize: 12,
color: colors.text,
},
viewCounter: {
alignItems: 'flex-end',
bottom: 10,
right: 10,
}
})
return styles
} }
});

@ -1,114 +1,33 @@
import JokeItems from "../components/JokeItems"; import JokeItems from "../components/JokeItems";
import '../types/extension'; import '../types/extension';
import {Image, SafeAreaView, StyleSheet, Text, TouchableOpacity, View} from "react-native"; import {StyleSheet, View} from "react-native";
import {darksalmonColor, purpleColor, whiteColor} from "../assets/Theme"; import {purpleColor} from "../assets/Theme";
import {useDispatch, useSelector} from "react-redux"; import {useDispatch, useSelector} from "react-redux";
import React, {useEffect, useState} from "react"; import {useEffect} from "react";
import {getCustomJokesList, getSampleJokesList} from "../redux/thunk/GetThunk"; import { getSampleJokesList } from "../redux/thunk/RecentsJokesThunk";
import {useTheme} from "@react-navigation/native";
const eye = require("../assets/eye_icon.png")
const hideEye = require("../assets/eye_off_icon.png")
export default function Catalogue() { export default function Catalogue() {
// @ts-ignore // @ts-ignore
const sampleJokes = useSelector(state => state.sampleReducer.jokes); const allJokes = useSelector(state => state.appReducer.jokes);
// @ts-ignore
const customJokes = useSelector(state => state.customReducer.customJokes);
const [joke, setJoke] = useState([])
const dispatch = useDispatch(); const dispatch = useDispatch();
const [showCustomJoke, setCustomJoke] = useState(false);
const toggleDescription = () => {
setCustomJoke(!showCustomJoke);
};
useEffect(() => { useEffect(() => {
if(!showCustomJoke) { const loadJokes = async () => {
const loadSamplesJokes = async () => {
// @ts-ignore // @ts-ignore
await dispatch(getSampleJokesList()); await dispatch(getSampleJokesList());
}; };
loadSamplesJokes(); loadJokes();
setJoke(sampleJokes) }, [dispatch]);
} else {
const loadCustomJokes = async () => {
// @ts-ignore
await dispatch(getCustomJokesList());
};
loadCustomJokes();
setJoke(customJokes)
}
}, [dispatch, customJokes, sampleJokes]);
const styles = themeSettings()
return ( return (
<SafeAreaView style={styles.container}>
<View style={styles.columnContainer}>
<Text style={styles.TextButton}>Afficher les exemples</Text>
<TouchableOpacity style={styles.Button} onPress={toggleDescription}>
<View style={styles.jokeTypeContainer}>
<Image
source={showCustomJoke ? hideEye : eye}
style={styles.imageButton}
/>
</View>
</TouchableOpacity>
</View>
<View style={styles.container}> <View style={styles.container}>
<JokeItems jokes={joke}/> <JokeItems jokes={allJokes}/>
</View> </View>
</SafeAreaView>
) )
}; };
export function themeSettings() {
const theme = useTheme();
const colors = theme.colors;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
backgroundColor: colors.background, backgroundColor: purpleColor,
flex: 1,
},
Button: {
borderRadius: 5,
backgroundColor: colors.primary,
height: 40,
width: 60,
flexDirection: "row"
},
jokeTypeContainer: {
display: "flex",
flex:1, flex:1,
flexDirection: "row",
alignItems: "center"
},
imageButton: {
margin: 10,
width: 40,
height: 30,
top: 5,
alignSelf: "center",
backgroundColor: "none",
tintColor: colors.notification,
justifyContent: "center",
alignItems: "center",
},
TextButton: {
fontSize: 18,
color: colors.text,
textAlign: 'center',
fontWeight: 'bold',
margin: 10,
},
columnContainer: {
marginLeft: 20,
marginRight: 20,
width: '90%',
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
} }
}); });
return styles
}

@ -1,115 +1,21 @@
import { customJokeStub } from '../data/stub/CustomJokeStub';
import { sampleJokeStub } from '../data/stub/SampleJokeStub';
import JokeItems from "../components/JokeItems"; import JokeItems from "../components/JokeItems";
import '../types/extension'; import '../types/extension';
import {Image, SafeAreaView, StyleSheet, Text, TouchableOpacity, View} from "react-native"; import {StyleSheet, View} from "react-native";
import {darksalmonColor, purpleColor, whiteColor} from "../assets/Theme"; import {purpleColor} from "../assets/Theme";
import {useDispatch, useSelector} from "react-redux";
import React, {useEffect, useState} from "react";
import {getCustomJokesList, getSampleJokesList} from "../redux/thunk/GetThunk";
import {useTheme} from "@react-navigation/native";
export default function Favorites() {
// @ts-ignore
const sampleJokes = useSelector(state => state.sampleReducer.jokes);
// @ts-ignore
const customJokes = useSelector(state => state.customReducer.customJokes);
const [joke, setJoke] = useState([])
const dispatch = useDispatch();
const eye = require("../assets/eye_icon.png")
const hideEye = require("../assets/eye_off_icon.png")
const [showCustomJoke, setCustomJoke] = useState(false); // état local pour contrôler la visibilité de la description
const toggleDescription = () => {
setCustomJoke(!showCustomJoke);
};
useEffect(() => {
if(!showCustomJoke) {
const loadSamplesJokes = async () => {
// @ts-ignore
await dispatch(getFavorites());
};
loadSamplesJokes();
setJoke(sampleJokes)
} else {
const loadCustomJokes = async () => {
// @ts-ignore
await dispatch(getCustomJokesList());
};
loadCustomJokes();
setJoke(customJokes)
}
}, [dispatch, customJokes, sampleJokes]);
const styles = themeSettings()
export default function Catalogue() {
const allJokes = [...customJokeStub, ...sampleJokeStub];
return ( return (
<SafeAreaView style={styles.container}>
<View style={styles.columnContainer}>
<Text style={styles.TextButton}>Afficher les exemples</Text>
<TouchableOpacity style={styles.Button} onPress={toggleDescription}>
<View style={styles.jokeTypeContainer}>
<Image
source={showCustomJoke ? hideEye : eye}
style={styles.imageButton}
/>
</View>
</TouchableOpacity>
</View>
<View style={styles.container}> <View style={styles.container}>
<JokeItems jokes={joke}/> <JokeItems jokes={allJokes}/>
</View> </View>
</SafeAreaView>
) )
}; };
export function themeSettings() {
const theme = useTheme();
const colors = theme.colors;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
backgroundColor: colors.background, backgroundColor: purpleColor
flex: 1,
},
Button: {
borderRadius: 5,
backgroundColor: colors.background,
height: 40,
width: 60,
flexDirection: "row"
},
jokeTypeContainer: {
display: "flex",
flex: 1,
flexDirection: "row",
alignItems: "center"
},
imageButton: {
margin: 10,
width: 40,
height: 30,
top: 5,
alignSelf: "center",
backgroundColor: "none",
tintColor: colors.notification,
justifyContent: "center",
alignItems: "center",
},
TextButton: {
fontSize: 18,
color: colors.notification,
textAlign: 'center',
fontWeight: 'bold',
margin: 10,
},
columnContainer: {
marginLeft: 20,
marginRight: 20,
width: '90%',
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
} }
}); });
return styles
}

@ -5,15 +5,13 @@ import JokesHomeSquare from "../components/JokesHomeSquare";
import Categs from "../components/Categs"; import Categs from "../components/Categs";
import {useDispatch, useSelector} from "react-redux"; import {useDispatch, useSelector} from "react-redux";
import {useEffect} from "react"; import {useEffect} from "react";
import {getCategoriesList, getLastSampleJokesList} from "../redux/thunk/GetThunk"; import {getCategoriesList, getLastSampleJokesList} from "../redux/thunk/RecentsJokesThunk";
import {useTheme} from "@react-navigation/native";
import styleToBarStyle from "expo-status-bar/build/styleToBarStyle";
export default function Catalogue() { export default function Catalogue() {
// @ts-ignore // @ts-ignore
const allJokes = useSelector(state => state.sampleReducer.jokes); const allJokes = useSelector(state => state.appReducer.jokes);
// @ts-ignore // @ts-ignore
const allCategories = useSelector(state => state.categoryReducer.categories); const allCategories = useSelector(state => state.appReducer.categories)
const dispatch = useDispatch(); const dispatch = useDispatch();
useEffect(() => { useEffect(() => {
@ -25,9 +23,6 @@ export default function Catalogue() {
}; };
loadJokes(); loadJokes();
}, [dispatch]); }, [dispatch]);
const styles = themeSettings()
return ( return (
<> <>
<View style={styles.container}> <View style={styles.container}>
@ -53,12 +48,9 @@ export default function Catalogue() {
) )
}; };
export function themeSettings() {
const theme = useTheme();
const colors = theme.colors;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
backgroundColor: colors.background, backgroundColor: purpleColor,
flex:1, flex:1,
}, },
top: { top: {
@ -70,7 +62,7 @@ export function themeSettings() {
}, },
textAccueil: { textAccueil: {
fontSize: 25, fontSize: 25,
color: colors.text, color: darksalmonColor,
fontWeight: "bold", fontWeight: "bold",
}, },
Jokes: { Jokes: {
@ -83,7 +75,7 @@ export function themeSettings() {
alignItems: "center" alignItems: "center"
}, },
textLastJokes: { textLastJokes: {
color: colors.border, color: whiteColor,
fontSize: 20, fontSize: 20,
fontWeight: "bold", fontWeight: "bold",
}, },
@ -95,7 +87,7 @@ export function themeSettings() {
flexDirection: "row", flexDirection: "row",
}, },
textBestCateg: { textBestCateg: {
color: colors.text, color: whiteColor,
fontSize: 20, fontSize: 20,
fontWeight: "bold", fontWeight: "bold",
}, },
@ -104,5 +96,3 @@ export function themeSettings() {
marginTop: 30, marginTop: 30,
} }
}); });
return styles
}

@ -2,57 +2,33 @@ import '../types/extension';
import {StyleSheet, View} from "react-native"; import {StyleSheet, View} from "react-native";
import {purpleColor} from "../assets/Theme"; import {purpleColor} from "../assets/Theme";
import {useDispatch, useSelector} from "react-redux"; import {useDispatch, useSelector} from "react-redux";
import React, {useEffect, useState} from "react"; import React, {useEffect} from "react";
import {getCustomJokeById, getJokeById} from "../redux/thunk/GetByThunk"; import { getJokeById } from "../redux/thunk/RecentsJokesThunk";
import JokeDetail from "../components/JokeDetail"; import JokeDetail from "../components/JokeDetail";
import {AppDispatch, AppState} from "../redux/store";
import {CustomJoke} from "../model/CustomJoke";
import {SampleJoke} from "../model/SampleJoke";
import {useTheme} from "@react-navigation/native";
export default function JokeDetailsScreen({route}) { export default function JokeDetailsScreen({route}) {
const idJokeDetail = route.params.idJoke; const idJokeDetail = route.params.idJoke;
const joke = useSelector<AppState>(state => state.sampleReducer.joke); // @ts-ignore
const customJoke = useSelector<AppState>(state => state.customReducer.customJoke); const joke = useSelector(state => state.appReducer.joke);
const [isCustomJoke, setCustomJoke] = useState(false); const dispatch = useDispatch();
const dispatch = useDispatch<AppDispatch>();
const styles = themeSettings();
useEffect(() => { useEffect(() => {
if(typeof idJokeDetail == 'number') {
const loadJoke = async () => { const loadJoke = async () => {
// @ts-ignore
await dispatch(getJokeById(idJokeDetail)); await dispatch(getJokeById(idJokeDetail));
}; };
loadJoke(); loadJoke();
setCustomJoke(!isCustomJoke)
} else {
const loadCustomJoke = async () => {
await dispatch(getCustomJokeById(idJokeDetail));
};
loadCustomJoke();
}
}, [dispatch]); }, [dispatch]);
if(!joke && !customJoke) return null;
return ( return (
<View style={styles.container}> <View style={styles.container}>
{/*@ts-ignore}*/} <JokeDetail joke={joke}/>
<JokeDetail joke={isCustomJoke ? joke : customJoke}/>
</View> </View>
) )
}; };
export function themeSettings() {
const theme = useTheme();
const colors = theme.colors;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
backgroundColor: colors.background, backgroundColor: purpleColor,
flex:1, flex:1,
} }
}); });
return styles
}

@ -1,102 +1,21 @@
import { customJokeStub } from '../data/stub/CustomJokeStub';
import { sampleJokeStub } from '../data/stub/SampleJokeStub';
import JokeItems from "../components/JokeItems";
import '../types/extension'; import '../types/extension';
import {Image, StyleSheet, Switch, Text, View} from "react-native"; import {StyleSheet, View} from "react-native";
import { import {purpleColor} from "../assets/Theme";
darksalmonColor, DarkTheme,
whiteColor
} from "../assets/Theme";
import React from "react";
import {DefaultTheme, useTheme} from "@react-navigation/native";
import {storeTheme} from "../redux/thunk/ThemeThunk";
export default function Catalogue() { export default function Catalogue() {
const light_mode = require("../assets/light_mode.png") const allJokes = [...customJokeStub, ...sampleJokeStub];
const dark_mode = require("../assets/dark_mode.png")
const [isDark, setDark] = React.useState(false)
const toggleTheme = () => {
setDark(previousState => {
const theme = !previousState;
const newTheme = theme ? DarkTheme : DefaultTheme;
storeTheme(newTheme);
return theme;
});
};
const styles = themeSettings();
return ( return (
<View style={styles.container}> <View style={styles.container}>
<View style={styles.topText}> <JokeItems jokes={allJokes}/>
<Text style={styles.title}>Réglages</Text>
<View style={styles.switchMode}>
<View style={styles.textContainer}>
<Image
source={isDark? dark_mode: light_mode}
style={styles.imageButton} />
<Text style={styles.darkModeText}>Dark Mode</Text>
</View>
<Switch
value={isDark}
onValueChange={toggleTheme}
style={styles.switchMode}
trackColor={{false: darksalmonColor, true: darksalmonColor}}
thumbColor={whiteColor} />
</View> </View>
</View> )
</View>
);
}; };
export function themeSettings() {
const theme = useTheme();
const colors = theme.colors;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
paddingTop: 10, backgroundColor: purpleColor
paddingBottom: 10,
paddingLeft: 10,
paddingRight: 10,
flex: 1,
justifyContent: 'center',
backgroundColor: colors.background,
flexDirection: 'column',
},
topText: {
flex: 1,
},
title: {
padding: 10,
fontSize: 20,
color: colors.text,
fontWeight: 'bold'
},
imageButton : {
width: 30,
height:30,
alignSelf : "center",
tintColor: colors.primary,
},
switchMode: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
backgroundColor: colors.card,
padding: 20,
margin: 10,
},
darkModeText: {
color: colors.text,
fontSize: 20,
marginLeft: 10,
paddingTop: 5,
},
textContainer: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
} }
}); });
return styles;
}

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