Compare commits

...

48 Commits

Author SHA1 Message Date
Tony Fages 5aa949ae0f Mise à jour de 'README.md'
continuous-integration/drone/push Build is passing Details
1 year ago
Tony Fages c905cbb7fa add tests
continuous-integration/drone/push Build is passing Details
1 year ago
Tony Fages fe5d58285e first tests
continuous-integration/drone/push Build is passing Details
1 year ago
Tony Fages d187888198 first tests
continuous-integration/drone/push Build is passing Details
1 year ago
Tony Fages a20e3d13af Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is passing Details
1 year ago
Tony Fages 99d6e6cdac Mise à jour de '.drone.yml'
continuous-integration/drone/push Build encountered an error Details
1 year ago
Tony Fages 5ec03de121 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is failing Details
1 year ago
Tony Fages 5bce0b3d7e Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is failing Details
1 year ago
Tony Fages 9f6e3c1a07 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is failing Details
1 year ago
Tony Fages d169c5aa8c Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is failing Details
1 year ago
Tony Fages a9cba28673 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is failing Details
1 year ago
Tony Fages 4a89f463ca Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is failing Details
1 year ago
Tony Fages 7e24172efe Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is passing Details
1 year ago
Tony Fages f6f0f142e9 Drone
continuous-integration/drone/push Build is passing Details
1 year ago
Tony Fages 4fc7b3ddb5 Drone
continuous-integration/drone/push Build is failing Details
1 year ago
Tony Fages e53ea4f06b Drone
continuous-integration/drone/push Build encountered an error Details
1 year ago
Tony Fages a83661eb5e Drone
continuous-integration/drone/push Build encountered an error Details
1 year ago
Tony Fages 2383e13a6c Drone
continuous-integration/drone Build encountered an error Details
1 year ago
Tony Fages 2ff3cb5d34 Drone
1 year ago
Tony Fages da7fe0d80d Merge branch 'Tp8'
1 year ago
Tony Fages 7220483540 tp8
1 year ago
Tony Fages 2dee48564e tp8
1 year ago
Tony Fages 51ceb3511f tp8
1 year ago
Tony Fages 296c8d66e8 addFav
1 year ago
Tony Fages a525cb40e7 tp8
1 year ago
Tony Fages 747f190096 tp8
1 year ago
Tony Fages 194f68e83c Merge remote-tracking branch 'origin/tp7'
1 year ago
Tony Fages 8156251771 Merge remote-tracking branch 'origin/tp6'
1 year ago
Tony Fages 0036ac877b Merge remote-tracking branch 'origin/part5'
1 year ago
Tony Fages 6da31b6cbc tp8
1 year ago
Tony Fages 9bbb4d508b tp7
1 year ago
Tony Fages f09a8fcc8f finission tp
1 year ago
Tony Fages ecd70bd7e9 passurdupush
1 year ago
Tony Fages dd8de95ed6 tp6
1 year ago
Tony Fages 02cbc2a196 ajout addPage + Post Joke method
1 year ago
Tony Fages afa662c60e tp5
1 year ago
Tony Fages 530d6153ec tp5 :fix:
1 year ago
Tony Fages 5040bd6c46 tp5
1 year ago
Tony Fages 01e7c49a3b tp4
1 year ago
Tony Fages e122ae186c part4
1 year ago
Tony Fages 83196ed2be part4
1 year ago
Tony Fages 90fa0557b0 tp3
1 year ago
Tony Fages 5c2c5ee7fe tp3
1 year ago
Tony Fages 419b6eb88a tp3
1 year ago
Tony Fages 0c3a3537b6 tp3
1 year ago
Tony Fages d6c86e76ce Merge branch 'part2'
1 year ago
Tony Fages f81d694742 Mise à jour de 'README.md'
1 year ago
Tony Fages 265c7e9075 Mise à jour de 'README.md'
1 year ago

@ -0,0 +1,22 @@
kind: pipeline
type: docker
name: default
steps:
- name: build
image: hub.codefirst.iut.uca.fr/camille.petitalot/drone-sonarplugin-reactnative:latest
commands:
- cd JokesApp
- npm install
- name : sonar
image : hub.codefirst.iut.uca.fr/camille.petitalot/drone-sonarplugin-reactnative:latest
environment:
sonar_host: https://codefirst.iut.uca.fr/sonar/
sonar_token:
from_secret: SECRET_SONAR_LOGIN
project_key: JokesAppSonar
commands:
- cd JokesApp
- npm install
- npm run test
- sonar-scanner -Dsonar.projectKey=$${project_key} -Dsonar.sources=. -Dsonar.host.url=$${sonar_host} -Dsonar.login=$${sonar_token} -Dsonar.javascript.lcov.reportPaths=/lcov.info -Dsonar.exclusions=/coverage//*,/tests/*/

4
.gitignore vendored

@ -419,7 +419,8 @@ FodyWeavers.xsd
.LSOverride
# Icon must end with two \r
Icon
Icon
# Thumbnails
._*
@ -440,3 +441,4 @@ Network Trash Folder
Temporary Items
.apdisk
coverage

5
.idea/.gitignore vendored

@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Tp_ReactNative.iml" filepath="$PROJECT_DIR$/.idea/Tp_ReactNative.iml" />
</modules>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

@ -3,3 +3,5 @@
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# GitHub Copilot persisted chat sessions
/copilot/chatSessions

@ -1,14 +1,22 @@
import {SafeAreaView, StatusBar, StyleSheet} from 'react-native';
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";
import AsyncStorage from "@react-native-async-storage/async-storage";
export default function App() {
//AsyncStorage.clear()
return (
<Provider store={store}>
<SafeAreaView style={styles.container}>
<StatusBar barStyle="light-content" />
<ListJokeScreen/>
<Navigation/>
</SafeAreaView>
</Provider>
);
}
@ -19,7 +27,7 @@ const styles = StyleSheet.create({
title: {
fontSize: 24,
color: 'darksalmon',
color: darksalmonColor,
textAlign: 'center',
marginVertical: 20,
},
@ -30,7 +38,7 @@ const styles = StyleSheet.create({
},
container: {
flex: 1,
backgroundColor: 'rgba(14, 14, 44, 1)',
backgroundColor: indigo,
},
});

@ -1 +1,75 @@
export const indigo = "rgba(74, 74, 104, 1)"
import {DarkTheme, Theme, useTheme} from "@react-navigation/native";
import React, {useEffect, useState} from "react";
import DefaultTheme from "@react-navigation/native/src/theming/DefaultTheme";
import {useColorScheme} from "react-native";
export const indigo = "rgba(14, 14, 44, 1)";
export const purpleColor = "rgba(74, 74, 104, 1)";
export const darksalmonColor = "rgba(233, 150, 122, 1)";
export const greyColor = "rgba(140, 140, 161, 1)";
export const whiteColor = "rgba(239, 239, 253, 1)";
export const blackColor = "rgba(0, 0, 0, 1)";
export default function usePersonalTheme() {
useTheme()
const theme = React.useContext(ThemeContext);
const [isDark, setIsDark] = useState(false);
if (isDark) {
theme.dark = true;
theme.colors = MyDarkTheme.colors;
}
else {
theme.dark = false;
theme.colors = MyLightTheme.colors;
}
return theme;
}
//const isDark = false;
const MyDarkTheme: Theme = {
dark: true,
colors: {
primary: indigo,
background: purpleColor,
card: darksalmonColor,
text: whiteColor,
border: whiteColor,
notification: whiteColor,
},
};
const MyLightTheme: Theme = {
dark: false,
colors: {
primary: whiteColor,
background: greyColor,
card: '',
text: blackColor,
border: blackColor,
notification: blackColor,
},
};
const ThemeContext = React.createContext<Theme>(MyDarkTheme);

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 705 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 805 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@ -0,0 +1,163 @@
import {Button, Image, StyleSheet, Text, TouchableOpacity, View} from "react-native";
import {darksalmonColor, greyColor, indigo, purpleColor, whiteColor} from "../Theme";
import React, {useState} from "react";
import {SampleJoke} from "../model/SampleJoke";
import {Joke} from "../model/Joke";
import {CustomJoke} from "../model/CustomJoke";
import {removeFavoriteJoke, storeFavoriteJoke, useAppDispatch, useAppSelector} from "../redux/store";
type DetailJokeProps = {
item: CustomJoke;
}
export function DetailJoke(props: DetailJokeProps) {
const favoriteJokes = useAppSelector(state => state.customReducer.favoriteJokes) as [CustomJoke, SampleJoke];
const dispatch = useAppDispatch();
const isFav : boolean = favoriteJokes.some(it => it.id == props.item.id )
const [isActivated, setIsActivated] = useState(isFav);
const [isActivated2, setIsActivated2] = useState(false);
function toggleActivation() {
setIsActivated(!isActivated);
if (isActivated) {
console.log("Joke retirée des favoris");
dispatch(removeFavoriteJoke(props.item));
}
else {
dispatch(storeFavoriteJoke(props.item));
console.log("Joke ajoutée aux favoris");
}
}
function toggleActivation2() {
setIsActivated2(!isActivated2);
}
return (
<View style={styles.listItem}>
<Image style={styles.imageSettings} source={{
uri: props.item.image}}/>
<View style={styles.chip}>
<Text >Type : {props.item.type}</Text>
</View>
<View style={styles.contentList}>
<Text style={styles.contentSummary}>{props.item.description}</Text>
</View >
<View style={styles.buttons}>
<TouchableOpacity style={styles.button1} onPress={toggleActivation}>
<Image source={isActivated ? require('../assets/plain_favorite_icon.png') : require('../assets/favorite_icon.png')} style={styles.img1} />
</TouchableOpacity>
<TouchableOpacity style={styles.button2} onPress={toggleActivation2}>
<Image source={isActivated2 ? require('../assets/eye_icon.png') : require('../assets/eye_off_icon.png')} style={styles.img2} />
<Text style={styles.bt2text}>LA CHUTE</Text>
</TouchableOpacity>
</View>
{isActivated2 ? <Text style={styles.contentSummary}>{props.item.punchline}</Text> : null}
</View>
)
}
const styles = StyleSheet.create({
titleResume: {
fontSize: 15,
fontWeight: 'bold',
marginBottom: 20,
color: whiteColor,
},
listItem: {
flexDirection: 'column',
margin: 10,
height : '80%',
borderRadius: 20,
borderColor: whiteColor,
backgroundColor : indigo,
borderWidth: 1,
},
imageSettings: {
flex: 1,
width: '85%',
height: 100,
margin :30,
justifyContent: 'center',
alignItems: 'center',
},
contentList :{
margin: 10,
},
chip: {
borderRadius: 16,
backgroundColor: whiteColor,
padding: 5,
margin: 5,
marginLeft: 30,
alignSelf: 'flex-start',
},
contentSummary: {
color: greyColor,
fontWeight: 'bold',
fontSize: 16,
textAlign: 'left',
margin: 10,
marginLeft: 20,
},
buttons:
{
flexDirection: "row",
justifyContent: "flex-end",
alignItems: "flex-end",
marginBottom: 20,
},
button1:{
marginRight: 20,
borderRadius: 10,
width: 100,
height: 50,
alignItems: "center",
borderColor: '#ffffff',
borderWidth: 1
},
button2:{
flexDirection: "row",
justifyContent: "center",
marginRight: 30,
borderRadius: 10,
width: 140,
height: 50,
alignItems: "center",
borderColor: whiteColor,
borderWidth: 1,
backgroundColor: darksalmonColor,
},
img1:{
justifyContent: "center",
tintColor: darksalmonColor,
alignItems: "center",
marginTop: 2,
},
img2: {
tintColor: whiteColor,
justifyContent: "center",
alignItems: "center",
alignSelf: "center",
marginRight: 10,
},
bt2text: {
fontSize: 16,
color: whiteColor,
textAlign: 'center',
fontWeight: 'bold',
},
});

@ -0,0 +1,77 @@
import {Image, ImageBackground, StyleSheet, Text, View} from "react-native";
import React from "react";
import {Joke} from "../model/Joke";
import * as url from "url";
import {greyColor, indigo, purpleColor} from "../Theme";
type JokeListItemProps = {
item: Joke;
}
export function HorizontalListJokeComponent(props: JokeListItemProps) {
return (
<View style={styles.listItem}>
<View style={styles.rectangle}></View>
<Image style={styles.imageSettings} source={{
uri: props.item.image}}/>
<View style={styles.contentList}>
<Text style={styles.titleResume}>Résumé de la blague</Text>
<Text style={styles.contentSummary}>{props.item.summary}</Text>
</View>
</View>
)
}
const styles = StyleSheet.create({
titleResume: {
fontSize: 15,
fontWeight: 'bold',
marginBottom: 20,
color: "white",
textAlign: 'center',
},
listItem: {
flexDirection: 'column',
alignItems: 'center',
backgroundColor: indigo,
margin: 10,
height: 270,
width: 250,
borderRadius: 5,
},
imageSettings: {
width: '75%',
height: 150,
position: 'absolute',
margin: 5,
},
contentList :{
flex: 1,
justifyContent: 'flex-end',
alignItems: 'center',
margin: 30,
},
chip: {
borderRadius: 16,
backgroundColor: greyColor,
padding: 5,
marginTop: 5,
alignSelf: 'center',
},
rectangle: {
borderRadius: 4,
flexShrink: 0,
width: "100%",
height: "20%",
backgroundColor: 'darksalmon',
},
contentSummary: {
textAlign: 'center',
color: "white",
}
});

@ -0,0 +1,41 @@
import {Joke} from "../model/Joke";
import {Image, StyleSheet, Text, View} from "react-native";
import React from "react";
import {greyColor, indigo} from "../Theme";
import {Categorie} from "../model/Categorie";
type ListAllCategories = {
item: Categorie;
}
export function ListAllCategories(props: ListAllCategories) {
return (
<View style={styles.listItem}>
<View style={styles.chip}>
<Text>{props.item.name}</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
listItem: {
flexDirection: 'column',
alignItems: 'flex-start',
margin: 10,
},
chip: {
borderRadius: 16,
backgroundColor: greyColor,
padding: 5,
marginTop: 5,
alignSelf: 'flex-start',
},
});

@ -2,6 +2,7 @@ import {Image, ImageBackground, StyleSheet, Text, View} from "react-native";
import React from "react";
import {Joke} from "../model/Joke";
import * as url from "url";
import {indigo} from "../Theme";
type JokeListItemProps = {
item: Joke;
@ -14,9 +15,9 @@ export function JokeListItems(props: JokeListItemProps) {
uri: props.item.image}}/>
<View style={styles.contentList}>
<Text style={styles.titleResume}>Résumé de la blague</Text>
<Text style={styles.contentSummary}>{props.item.summary()}</Text>
<Text style={styles.contentSummary}>{props.item.summary}</Text>
<View style={styles.chip}>
<Text >Type : {props.item.type()}</Text>
<Text >Type : {props.item.type}</Text>
</View>
</View>
</View>
@ -36,7 +37,7 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
margin: 10,
backgroundColor : "rgba(14, 14, 44, 1)",
backgroundColor : indigo,
},
imageSettings: {
width: '40%',

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

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

@ -12,7 +12,7 @@ export abstract class Joke{
this._punchline = punchline
this._image = image
}
public type(): string {
public get type(): string {
return this._type;
}
@ -29,13 +29,13 @@ export abstract class Joke{
}
// Permet d'afficher les 25 premiers caractères du contexte de la blague suivis de ...
public summary():string{
public get summary():string{
return this.setup.substring(0,25) + ' ...'
}
// Permet de retourner le type d'une blague + sont contexte
public description():string{
return this.type() + ' - ' +this.summary()
public get description():string{
return this.type + ' : ' + this.setup
}

@ -1,5 +1,6 @@
import { CustomJoke } from "./CustomJoke";
import { SampleJoke } from "./SampleJoke";
import {Categorie} from "./Categorie";
export class JokeFactory {
@ -26,5 +27,19 @@ export class JokeFactory {
return array;
}
static createSampleJokeById(jsonArray: string): SampleJoke {
let array = [];
let json = JSON.parse(jsonArray);
return new SampleJoke(json.type, json.setup, json.punchline,json.image, json.id)
//json.forEach(function (joke) {
// array.push(new SampleJoke(joke.type, joke.setup, joke.image,joke.punchline, joke.id));
//})
}
static createCustomJokeById(jsonArray: string): CustomJoke {
let array = [];
let json = JSON.parse(jsonArray);
return new CustomJoke(json.type, json.setup, json.punchline,json.image, json.id)
}
}

@ -3,9 +3,8 @@ import {SampleJoke} from "./SampleJoke";
// Stub permettant de stocker les différentes blagues de différents types
export class JokeStub {
// Données JSON pour les CustomJokes
public static customJokes = '[{"type":"custom", "setup":"Quel jour les poules ont-elles l anus dilaté au maximum ?", "punchline":"Le jour où elles passent du coq à l âne.", "image":"http://placekitten.com/200/300", "id":"id1"}, {"type":"custom", "setup":"Savez-vous comment on appelle le sexe de Michael Jackson ???", "punchline":"Vérité ! Car la vérité sort toujours de la bouche des enfants .", "image":"http://placekitten.com/200/300", "id":"id2"}]';
public static customJokes = `[{"type":"custom", "setup":"Comment est-ce qu'on appelle un boomerang qui ne revient pas ?", "punchline":"Un chat disparu.", "image":"http://placekitten.com/200/300", "id":"id1"}, {"type":"custom", "setup":"Que dit un aveugle lorsqu'on lui donne du papier de verre ?", "punchline":"C'est écrit tout petit.", "image":"http://placekitten.com/200/300", "id":"id4"}, {"type":"custom", "setup":"Pourquoi la petite fille tombe-t-elle de la balançoire ?", "punchline":"Parce qu'elle n'a pas de bras.", "image":"http://placekitten.com/200/300", "id":"id5"}, {"type":"custom", "setup":"Qu'est-ce qui est pire qu'un bébé dans une poubelle ?", "punchline":"Un bébé dans deux poubelles.", "image":"http://placekitten.com/200/300", "id":"id6"}, {"type":"custom", "setup":"Grâce à quoi peut-on enlever le chewing-gum dans les cheveux ?", "punchline":"Le cancer.", "image":"http://placekitten.com/200/300", "id":"id7"}, {"type":"custom", "setup":"Qu'est-ce qui est mieux que gagner une médaille d'or aux Jeux Paralympiques ?", "punchline":"Marcher.", "image":"http://placekitten.com/200/300", "id":"id8"}, {"type":"custom", "setup":"Quelle partie du légume ne passe pas dans le mixeur ?", "punchline":"La chaise roulante.", "image":"http://placekitten.com/200/300", "id":"id9"}, {"type":"custom", "setup":"Comment reconnaît-on une lettre envoyée par un lépreux ?", "punchline":"La langue est collée au timbre.", "image":"http://placekitten.com/200/300", "id":"id10"}, {"type":"custom", "setup":"Que faire quand on trouve un épileptique en crise dans une baignoire ?", "punchline":"Ajouter de la lessive et y jeter son linge sale.", "image":"http://placekitten.com/200/300", "id":"id11"}, {"type":"custom", "setup":"Comment sortir un bébé d'un mixeur ?", "punchline":"Avec une paille.", "image":"http://placekitten.com/200/300", "id":"id12"}, {"type":"custom", "setup":"Qu'est-ce qui a deux pattes et qui saigne ?", "punchline":"Un demi-chien.", "image":"http://placekitten.com/200/300", "id":"id13"}]`;
// Données JSON pour les SampleJokes
public static sampleJokes = '[{"type":"sample", "setup":"Que dit un escargot quand il croise une limace ?", "punchline":"Oh la belle décapotable ", "image":"http://placekitten.com/200/300", "id":"id1"}, {"type":"sample", "setup":"Qu est ce qui n est pas un steak ?", "punchline":"Une pastèque.", "image":"http://placekitten.com/200/300", "id":"id2"}]';
}
public static sampleJokes = `[{"type":"DavidJTM", "setup":"Que dit un escargot quand il croise une limace ?", "punchline":"Oh la belle décapotable", "image":"http://placekitten.com/200/300", "id":"id1"}, {"type":"sample", "setup":"Qu'est-ce qui est vert et qui porte une cape ?", "punchline":"Un concombre imitant Super Tomate.", "image":"http://placekitten.com/200/300", "id":"id2"}, {"type":"sample", "setup":"Vous connaissez lhistoire du petit-déjeuner ?", "punchline":"Pas de bol !", "image":"http://placekitten.com/200/300", "id":"id3"}, {"type":"sample", "setup":"Connaissez-vous lhistoire du pingouin qui respirait par les fesses ?", "punchline":"Un beau jour, il sassoit sur une chaise et meurt.", "image":"http://placekitten.com/200/300", "id":"id4"}, {"type":"sample", "setup":"Est-ce que vous connaissez lhistoire du poil ?", "punchline":"Avant, il était bien. Maintenant, il est pubien.", "image":"http://placekitten.com/200/300", "id":"id5"}, {"type":"sample", "setup":"Comment appelle-t-on un bébé éléphant né prématurément ?", "punchline":"Un éléphant tôt.", "image":"http://placekitten.com/200/300", "id":"id6"}, {"type":"sample", "setup":"Les vaches ferment les yeux pendant la traite de lait. Pourquoi ?", "punchline":"Pour faire du lait concentré.", "image":"http://placekitten.com/200/300", "id":"id7"}, {"type":"sample", "setup":"Quelle est lhistoire du lit superposé ?", "punchline":"Cest une histoire à dormir debout.", "image":"http://placekitten.com/200/300", "id":"id8"}, {"type":"sample", "setup":"Quest-ce qui est jaune et qui attend ?", "punchline":"Jonathan.", "image":"http://placekitten.com/200/300", "id":"id9"}, {"type":"sample", "setup":"Connais-tu lhistoire dun panda qui en a eu marre de la vie ?", "punchline":"Il se panda.", "image":"http://placekitten.com/200/300", "id":"id10"}, {"type":"sample", "setup":"Quest-ce qui est vert et qui pousse dans le jardin ?", "punchline":"Un extraterrestre qui fait caca.", "image":"http://placekitten.com/200/300", "id":"id11"}, {"type":"sample", "setup":"Avez-vous déjà entendu lhistoire de lhomme qui a giflé un aveugle ?", "punchline":"Vous ne lavez pas vu venir celle-là.", "image":"http://placekitten.com/200/300", "id":"id12"}, {"type":"sample", "setup":"Que dit un chien après avoir fait caca dans la maison ?", "punchline":"Le chat fait la même chose, mais vous ne vous fâchez jamais.", "image":"http://placekitten.com/200/300", "id":"id13"}]`;
}

@ -0,0 +1,154 @@
import React, {useContext, useEffect, useState} from "react";
import {DarkTheme, DefaultTheme, NavigationContainer, Theme, useTheme} from "@react-navigation/native";
import {createBottomTabNavigator} from "@react-navigation/bottom-tabs";
import {ListJokeScreen} from "../screens/ListJokeScreen";
import {Image, StyleSheet, useColorScheme, View} from "react-native";
import usePersonalTheme, {darksalmonColor, greyColor, indigo, purpleColor} from "../Theme";
import {AccueilScreen} from "../screens/AccueilScreen";
import {AddJokeScreen} from "../screens/AddJokeScreen";
import {SettingsScreen} from "../screens/SettingsScreen";
import {JokeListItems} from "../components/ListeJokeComponent";
import {CatalogueScreen, FavoriteScreen} from "./StackNavigation";
const homeIcon = require("../assets/home_icon.png");
const listIcon = require("../assets/list_icon.png");
const addIcon = require("../assets/add_icon.png");
const favIcon = require("../assets/favorite_icon.png");
const setIcon = require("../assets/settings_icon.png");
import store, {getTheme, storeTheme} from "../redux/store";
export function Navigation(){
const BottomTabNavigator = createBottomTabNavigator();
const [themes, setThemes] = useState<Theme>(DefaultTheme);
useEffect(() => {
const fetchTheme = async () => {
const theme = await getTheme();
setThemes(theme);
};
fetchTheme();
});
if (themes == null) {
return null;
}
return (
<NavigationContainer theme={ themes.dark === false ? DefaultTheme : DarkTheme} >
<BottomTabNavigator.Navigator initialRouteName="Home" screenOptions={{
headerTitleStyle: {
fontSize: 24,
fontWeight: 'bold',
color: themes.colors.text,
},
headerStyle: {
backgroundColor: indigo,
},
tabBarShowLabel: false,
tabBarStyle: styles.top,
}}>
<BottomTabNavigator.Screen name="Accueil" component={AccueilScreen}
options={{
tabBarIcon: ({focused}) => (
<Image
source={homeIcon}
style={{ tintColor: focused ? darksalmonColor : purpleColor }}
/>
)
}}/>
<BottomTabNavigator.Screen name="Catalogue" component={CatalogueScreen}
options={{
tabBarIcon: ({focused}) => (
<Image source={listIcon}
style={{ tintColor: focused ? darksalmonColor : purpleColor }}
/>
),
headerShown: false,
}}/>
<BottomTabNavigator.Screen name="Ajouter" component={AddJokeScreen}
options={{
tabBarIcon: ({focused}) => (
<View style={styles.addJoke}>
<Image source={addIcon}
style={{ tintColor: focused ? darksalmonColor : purpleColor }}
/>
</View>
),
}}/>
<BottomTabNavigator.Screen name="Favoris" component={FavoriteScreen}
options={{
tabBarIcon: ({focused}) => (
<Image source={favIcon}
style={{ tintColor: focused ? darksalmonColor : purpleColor }}
/>
),
headerShown: false,
}}/>
<BottomTabNavigator.Screen name="Paramètres" component={SettingsScreen}
options={{
tabBarIcon: ({focused}) => (
<Image source={setIcon}
style={{ tintColor: focused ? darksalmonColor : purpleColor }}
/>
)
}}/>
</BottomTabNavigator.Navigator>
</NavigationContainer>
)
}
const styles = StyleSheet.create({
title: {
fontSize: 24,
color: 'darksalmon',
textAlign: 'center',
fontWeight: 'bold',
marginVertical: 20,
},
top: {
backgroundColor : indigo
},
addJoke: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: greyColor,
width: '70%',
height: '100%',
borderRadius: 4,
marginTop: 4,
},
});
const stylesDark = StyleSheet.create({
title: {
fontSize: 24,
color: 'darksalmon',
textAlign: 'center',
fontWeight: 'bold',
marginVertical: 20,
},
top: {
backgroundColor : indigo
},
addJoke: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: greyColor,
width: '70%',
height: '100%',
borderRadius: 4,
marginTop: 4,
},
});

@ -0,0 +1,65 @@
import {createStackNavigator} from "@react-navigation/stack";
import {NavigationContainer} from "@react-navigation/native";
import {AccueilScreen} from "../screens/AccueilScreen";
import {JokeListItems} from "../components/ListeJokeComponent";
import {DetailJoke} from "../components/DetailJoke";
import JokeDetailScreen from "../screens/JokeDetailScreen";
import {ListJokeScreen} from "../screens/ListJokeScreen";
import {darksalmonColor, indigo, purpleColor} from "../Theme";
import {ListFavoriteJokeScreen} from "../screens/ListFavoriteJokeScreen";
export function CatalogueScreen(){
const Stack = createStackNavigator();
return(
<Stack.Navigator initialRouteName="CatalogueStack" screenOptions={
{
headerTitleStyle: {
fontSize: 24,
fontWeight: 'bold',
color: darksalmonColor,
},
headerStyle: {
backgroundColor: indigo,
},
headerTitle : "Catalogue",
headerBackTitleVisible: false,
headerTintColor: darksalmonColor,
}
}>
<Stack.Screen name="CatalogueStack" component={ListJokeScreen} options={{ headerTitle: 'Catalogue' }}/>
<Stack.Screen name="JokeDetail" component={JokeDetailScreen} options={{ headerTitle: 'Detail d une blague' }
} />
</Stack.Navigator>
)}
export function FavoriteScreen(){
const Stack = createStackNavigator();
return(
<Stack.Navigator initialRouteName="FavoriteStack" screenOptions={
{
headerTitleStyle: {
fontSize: 24,
fontWeight: 'bold',
color: darksalmonColor,
},
headerStyle: {
backgroundColor: indigo,
},
headerTitle : "Favoris",
headerBackTitleVisible: false,
headerTintColor: darksalmonColor,
}
}>
<Stack.Screen name="FavoriteStack" component={ListFavoriteJokeScreen} options={{ headerTitle: 'Favoris' }}/>
<Stack.Screen name="JokeDetail" component={JokeDetailScreen} options={{ headerTitle: 'Detail d une blague' }
} />
</Stack.Navigator>
)
}

File diff suppressed because it is too large Load Diff

@ -6,20 +6,52 @@
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web"
"web": "expo start --web",
"test": "jest --coverage "
},
"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",
"**.test.ts",
"**.test.tsx"
],
"testEnvironment": "node",
"testEnvironmentOptions": {
"browsers": [
"chrome",
"firefox",
"safari"
]
}
},
"dependencies": {
"@expo/ngrok": "^2.5.0",
"@react-native-async-storage/async-storage": "^1.23.1",
"@react-navigation/bottom-tabs": "^6.5.11",
"@react-navigation/native": "^6.1.10",
"@react-navigation/stack": "^6.3.21",
"@reduxjs/toolkit": "^2.2.1",
"@types/react": "~18.2.45",
"expo": "~50.0.3",
"expo": "^50.0.14",
"expo-status-bar": "~1.11.1",
"react": "18.2.0",
"react-native": "0.73.2",
"react-native": "^0.73.2",
"react-native-gesture-handler": "^2.15.0",
"react-native-safe-area-context": "4.8.2",
"react-native-screens": "~3.29.0",
"react-redux": "^9.1.0",
"redux": "^5.0.1",
"typescript": "^5.3.0"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@expo/ngrok": "^4.1.0"
"@expo/ngrok": "^4.1.0",
"jest-expo": "^50.0.4"
},
"private": true
}

@ -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<Categorie[]> => {
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);
}
}

@ -0,0 +1,138 @@
import {CustomJoke} from "../../model/CustomJoke";
import {JokeFactory} from "../../model/JokeFactory";
export enum CustomActionType {
POST_CUSTOM_JOKE = 'POST_CUSTOM_JOKE',
FETCH_CUSTOMS_JOKE = 'FETCH_CUSTOMS_JOKE',
FETCH_CUSTOMS_JOKE_BY_ID = 'FETCH_CUSTOMS_JOKE_BY_ID',
DELETE_CUSTOM_JOKE = 'DELETE_CUSTOM_JOKE',
FETCH_FAVORITE_JOKE = 'FETCH_FAVORITE_JOKE'
}
export interface CustomAction {
type: CustomActionType;
payload: [];
}
export interface postCustomAction {
type: CustomActionType;
payload: CustomJoke;
}
export interface CustomsAction {
type: CustomActionType;
payload: CustomJoke[];
}
export type Action = CustomAction;
export const setPostJoke = (postJoke: CustomJoke): postCustomAction => {
return {
type: CustomActionType.POST_CUSTOM_JOKE,
payload: postJoke
}
}
export const setCustomsJoke = (customJokes: CustomJoke[]): CustomsAction => {
return {
type: CustomActionType.FETCH_CUSTOMS_JOKE,
payload: customJokes
}
}
export const setCustomsJokeById = (completCustomJoke: CustomJoke): postCustomAction => {
return {
type: CustomActionType.FETCH_CUSTOMS_JOKE_BY_ID,
payload: completCustomJoke
}
}
export const setDeleteCustomJoke = (deleteCustomJoke: CustomJoke): postCustomAction => {
return {
type: CustomActionType.DELETE_CUSTOM_JOKE,
payload: deleteCustomJoke
}
}
export const setFavoriteJoke = (favoriteJokes: CustomJoke[]): CustomsAction => {
return {
type: CustomActionType.FETCH_FAVORITE_JOKE,
payload: favoriteJokes
}
}
export const postJoke = (type : string, setup : string, punchline : string) => {
return async dispatch => {
try {
console.log('type', type, 'setup', setup, 'punchline', punchline);
const reponse = await fetch('https://iut-weather-api.azurewebsites.net/jokes/', {
method: 'POST',
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(
{
type: type,
setup: setup,
punchline: punchline
}
)
});
const data = await reponse.json();
dispatch(setPostJoke(data));
} catch (error) {
console.log('Error---------', error);
}
}
}
export const getJokesCustoms = () => {
return async dispatch => {
try {
const custom = await fetch('https://iut-weather-api.azurewebsites.net/jokes/');
const customsJson = await custom.text();
const customJokes = JokeFactory.createCustomJokes(customsJson);
dispatch(setCustomsJoke(customJokes));
} catch (error) {
console.log('Error---------', error);
}
}
}
export const getJokesCustomsById = (id : number) => {
return async dispatch => {
try {
const custom = await fetch('https://iut-weather-api.azurewebsites.net/jokes/' + id);
const customsJson = await custom.text();
const customJokes = JokeFactory.createCustomJokeById(customsJson);
console.log('customJokes', customJokes);
dispatch(setCustomsJokeById(customJokes));
} catch (error) {
console.log('Error---------', error);
}
}
}
export const deleteJoke = (id : number) => {
return async dispatch => {
try {
const custom = await fetch('https://iut-weather-api.azurewebsites.net/jokes/' + id, {
method: 'DELETE',
headers: {
Accept: "application/json",
"Content-Type": "application/json",
}
});
console.log('Suppression');
dispatch(setDeleteCustomJoke({} as CustomJoke));
} catch (error) {
console.log('Error---------', error);
}
}
}

@ -0,0 +1,87 @@
import {SampleJoke} from "../../model/SampleJoke";
import {JokeFactory} from "../../model/JokeFactory";
export enum SampleActionType {
FETCH_SAMPLE = 'FETCH_SAMPLE',
FECTH_LAST_JOKES = 'FECTH_LAST_JOKES',
FECTH_COMPLET_JOKE = 'FECTH_COMPLET_JOKE',
}
export interface SampleAction {
type: SampleActionType;
payload: SampleJoke[];
}
export interface SampleActionComplet {
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 setCompletJokes = (completJoke: SampleJoke): SampleActionComplet => {
return {
type: SampleActionType.FECTH_COMPLET_JOKE,
payload: completJoke
}
}
export const getSampleJoke = () => {
return async dispatch => {
try {
const sample = await fetch('https://iut-weather-api.azurewebsites.net/jokes/samples');
const sampleJson = await sample.text();
const joke = JokeFactory.createSampleJokes(sampleJson);
dispatch(setSample(joke));
} catch (error) {
console.log('Error---------', error);
}
}
}
export const getLatestJokes = () => {
return async dispatch => {
try {
const sample = await fetch('https://iut-weather-api.azurewebsites.net/jokes/lasts');
const sampleJson = await sample.text();
const latestJoke = JokeFactory.createSampleJokes(sampleJson);
dispatch(setRecentJokes(latestJoke));
} catch (error) {
console.log('Error---------', error);
}
}
}
export const getCompletJokes = (id : number) => {
return async dispatch => {
try {
const sample = await fetch('https://iut-weather-api.azurewebsites.net/jokes/samples/' + id);
const sampleJson = await sample.text();
const jokeSelect = JokeFactory.createSampleJokeById(sampleJson);
dispatch(setCompletJokes(jokeSelect))
} catch (error) {
console.log('Error---------', error);
}
}
}

@ -0,0 +1,26 @@
import {Action, CategoriesActionType} from "../actions/categoriesAction";
import {Categorie} from "../../model/Categorie";
interface State {
categories: Categorie[];
}
// initial state for categories
const initialState: State = {
categories: [],
}
// app reducer for 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;
}
}

@ -0,0 +1,56 @@
import {SampleJoke} from "../../model/SampleJoke";
import {CustomJoke} from "../../model/CustomJoke";
import {CustomActionType} from "../actions/customAction";
interface state {
postJoke: CustomJoke;
customJokes: CustomJoke[];
completCustomJoke: CustomJoke;
deleteCustomJoke: CustomJoke;
favoriteJokes: CustomJoke[];
}
// initial state for sampleJokes
const initialState: state = {
postJoke: {} as CustomJoke,
customJokes: [],
completCustomJoke: {} as CustomJoke,
deleteCustomJoke: {} as CustomJoke,
favoriteJokes: []
}
// app reducer for sampleJokes
// @ts-ignore
export default appReducer = (state = initialState, action: Action) => {
switch (action.type) {
case CustomActionType.POST_CUSTOM_JOKE:
return {
...state,
postJoke: action.payload,
}
case CustomActionType.FETCH_CUSTOMS_JOKE:
return {
...state,
customJokes: action.payload,
}
case CustomActionType.FETCH_CUSTOMS_JOKE_BY_ID:
return {
...state,
completCustomJoke: action.payload,
}
case CustomActionType.DELETE_CUSTOM_JOKE:
return {
...state,
deleteCustomJoke: action.payload,
}
case CustomActionType.FETCH_FAVORITE_JOKE:
return {
...state,
favoriteJokes: action.payload,
}
default:
return state;
}
}

@ -0,0 +1,43 @@
import {SampleJoke} from "../../model/SampleJoke";
import {Action, SampleActionType} from "../actions/sampleAction";
import {CustomJoke} from "../../model/CustomJoke";
interface state {
sampleJoke: SampleJoke[];
recentJokes: SampleJoke[];
completJoke: SampleJoke;
}
// initial state for sampleJokes
const initialState: state = {
completJoke: {} as SampleJoke,
sampleJoke: [],
recentJokes: [],
}
// app reducer for sampleJokes
// @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
}
case SampleActionType.FECTH_COMPLET_JOKE:
return {
...state,
completJoke: action.payload,
}
default:
return state;
}
}

@ -0,0 +1,102 @@
import { configureStore } from '@reduxjs/toolkit';
import categorieReducer from './reducers/categoryReducer';
import sampleReducer from './reducers/sampleJokeReducer';
import customReducer from "./reducers/customJokeReducer";
import AsyncStorage from '@react-native-async-storage/async-storage';
import {DefaultTheme, Theme} from "@react-navigation/native";
import {CustomJoke} from "../model/CustomJoke";
import {setFavoriteJoke} from "./actions/customAction";
import {TypedUseSelectorHook, useDispatch, useSelector} from "react-redux";
import {SampleJoke} from "../model/SampleJoke";
import {Joke} from "../model/Joke";
import {JokeFactory} from "../model/JokeFactory";
const reducer = {
categorieReducer: categorieReducer,
sampleReducer: sampleReducer,
customReducer: customReducer
};
const store = configureStore({
// @ts-ignore
reducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false
})
});
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 : DefaultTheme;
} catch(e) {
console.log(e);
}
}
export const storeFavoriteJoke = (joke : CustomJoke) => {
return async dispatch => {
try {
const favoriteJokes = await AsyncStorage.getItem('favorites');
const favoriteJokesList: CustomJoke[] = favoriteJokes != null ? JokeFactory.createCustomJokes(favoriteJokes) : [];
favoriteJokesList.push(joke);
await AsyncStorage.setItem('favorites', JSON.stringify(favoriteJokesList.map(j => ({type: j.type, setup: j.setup, punchline: j.punchline, image: j.image, id: j.id}))));
dispatch(setFavoriteJoke(favoriteJokesList));
} catch (e) {
console.log(e);
}
}
}
export const getFavorite = () => {
return async (dispatch) => {
try {
const favoriteJokes = await AsyncStorage.getItem('favorites');
let favoriteJokesList = favoriteJokes != null ? JSON.parse(favoriteJokes) : [];
favoriteJokesList = favoriteJokesList.filter(joke => joke.id !== undefined);
await AsyncStorage.setItem('favorites', JSON.stringify(favoriteJokesList));
const favorites = favoriteJokesList.map(joke => new SampleJoke(joke["type"], joke["setup"], joke["punchline"], joke["image"], joke["id"]))
dispatch(setFavoriteJoke(favorites));
} catch (e) {
console.log(e);
}
}
}
export const removeFavoriteJoke = (joke : CustomJoke) => {
return async dispatch => {
try {
const favoriteJokes = await AsyncStorage.getItem('favorites');
let favoriteJokesList: CustomJoke[] = favoriteJokes != null ? JokeFactory.createCustomJokes(favoriteJokes) : [];
const index = favoriteJokesList.findIndex((j) => j.id === joke.id);
if (index !== -1) {
favoriteJokesList.splice(index, 1);
await AsyncStorage.setItem('favorites', JSON.stringify(favoriteJokesList.map(j => ({type: j.type, setup: j.setup, punchline: j.punchline, image: j.image, id: j.id}))));
dispatch(setFavoriteJoke(favoriteJokesList));
}
} catch (e) {
console.log("An error occurred", e);
}
}
}
export type AppDispatch = typeof store.dispatch;
export type AppStore = ReturnType<typeof store.getState>;
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<AppStore> = useSelector;
export default store;

@ -0,0 +1,99 @@
import {FlatList, Image, SafeAreaView, ScrollView, SectionListComponent, StyleSheet, Text, View} from "react-native";
import {indigo, purpleColor} from "../Theme";
import {Joke} from "../model/Joke";
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";
export function AccueilScreen() {
const DataGen = useSelector((state: any) => state.sampleReducer.recentJokes);
const DataCate = useSelector((state: any) => state.categorieReducer.categories);
const dispatch = useDispatch();
useEffect(() => {
const getJokes = async () => {
// @ts-ignore
await dispatch(getLatestJokes());
};
getJokes();
const getTopCategories = async () => {
// @ts-ignore
dispatch(setCategories(await getCategorie()));
};
getTopCategories();
}, [dispatch]);
return (
<SafeAreaView style={styles.container}>
<View>
<Image source={require('../assets/logo.png')} style={styles.imgStyle}/>
<Text style={styles.title}>Chat C'est Drôle</Text>
<Text style={styles.titleAccueil}>Dernieres Blagues</Text>
</View>
<FlatList showsHorizontalScrollIndicator={false} horizontal={true}
data={DataGen}
renderItem={HorizontalListJokeComponent}
keyExtractor={(item: CustomJoke) => item.id.toString()}
/>
<View style={styles.categories}>
<Text style={styles.titleAccueil} >Top Categories</Text>
<Image source={require("../assets/fire_icon.png")}/>
</View>
<FlatList showsHorizontalScrollIndicator={false} horizontal={true}
data={DataCate}
renderItem={ListAllCategories}
keyExtractor={(item : Categorie) => item.name}/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
title: {
fontSize: 24,
color: 'darksalmon',
textAlign: 'center',
fontWeight: 'bold',
marginVertical: 20,
},
titleResume: {
fontSize: 15,
fontWeight: 'bold',
marginBottom: 20,
},
container: {
flex: 1,
backgroundColor: purpleColor,
},
top: {
backgroundColor : indigo,
},
imgStyle: {
alignSelf: 'center',
margin: 5,
},
titleAccueil: {
fontSize: 20,
color: 'white',
fontWeight: 'bold',
margin: 10,
},
categories: {
flexDirection: 'row',
alignItems: 'center',
}
});

@ -0,0 +1,169 @@
import React, { useEffect, useState } from "react";
import { Button, StyleSheet, Text, TextInput, TouchableOpacity, View } from "react-native";
import { darksalmonColor, indigo, purpleColor, whiteColor } from "../Theme";
import { useDispatch, useSelector } from "react-redux";
import { postJoke} from "../redux/actions/customAction";
export function AddJokeScreen() {
const [joke, setJoke] = useState("");
const [jokeFall, setJokeFall] = useState("");
const [category, setCategory] = useState("");
const [categoryExceeded, setCategoryExceeded] = useState(false);
const [buttonDisabled, setButtonDisabled] = useState(true);
const MAX_CATEGORY_LENGTH = 10;
const dispatch = useDispatch();
useEffect(() => {
if (joke !== "" && jokeFall !== "" && category !== "") {
setButtonDisabled(false);
} else {
setButtonDisabled(true);
}
}, [joke, jokeFall, category]);
const postjoke = async () => {
// @ts-ignore
await dispatch(postJoke(joke, jokeFall, category));
clearFields();
};
const clearFields = () => {
setJoke("");
setJokeFall("");
setCategory("");
setCategoryExceeded(false);
setButtonDisabled(true);
};
const handleCategoryChange = (text) => {
if (text.length > MAX_CATEGORY_LENGTH) {
setCategoryExceeded(true);
} else {
setCategoryExceeded(false);
setCategory(text);
}
};
return (
<View style={styles.font}>
<Text style={styles.titleTextInput}>Blague</Text>
<TextInput
editable
multiline
style={styles.textInput}
value={joke}
onChangeText={(text) => setJoke(text)}
/>
<Text style={styles.titleTextInput}>Chute de la blague</Text>
<TextInput
editable
multiline
style={styles.textInput}
value={jokeFall}
onChangeText={(text) => setJokeFall(text)}
/>
<Text style={styles.titleTextInput}>Catégorie</Text>
<TextInput
editable
multiline
style={styles.textInput1}
value={category}
onChangeText={handleCategoryChange}
></TextInput>
<View style={styles.characterCountContainer}>
<Text style={styles.characterCountText}>
{category.length}/{MAX_CATEGORY_LENGTH}
</Text>
</View>
<TouchableOpacity
style={[styles.viewButtonCreate, { opacity: buttonDisabled ? 0.5 : 1 }]}
disabled={buttonDisabled} // Désactiver le bouton si buttonDisabled est vrai
onPress={postjoke}
>
<Text style={styles.textButton1}>CRÉER</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.viewButtonClear} onPress={clearFields}>
<Text style={styles.textButton2}>EFFACER</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
font: {
backgroundColor: purpleColor,
width: "100%",
height: "100%",
},
titleTextInput: {
fontSize: 20,
color: whiteColor,
textAlign: "left",
fontWeight: "bold",
marginTop: 25,
marginVertical: 10,
marginLeft: 10,
},
textInput: {
backgroundColor: indigo,
height: "13%",
color: whiteColor,
textAlign: "left",
textAlignVertical: "top",
margin: 10,
padding: 30,
paddingTop: 15,
},
textInput1: {
backgroundColor: indigo,
height: "9%",
color: whiteColor,
textAlign: "left",
textAlignVertical: "top",
margin: 10,
padding: 20,
paddingTop: 10,
fontSize: 12,
},
viewButtonCreate: {
backgroundColor: darksalmonColor,
height: "7%",
marginLeft: 10,
marginRight: 10,
marginTop: 20,
borderRadius: 10,
},
viewButtonClear: {
backgroundColor: whiteColor,
height: "7%",
marginLeft: 10,
marginRight: 10,
marginTop: 20,
borderRadius: 10,
},
textButton1: {
fontSize: 16,
color: whiteColor,
alignSelf: "center",
padding: 12,
fontWeight: "bold",
},
textButton2: {
fontSize: 16,
color: darksalmonColor,
alignSelf: "center",
padding: 12,
fontWeight: "bold",
},
characterCountContainer: {
alignItems: 'flex-end',
bottom: 10,
right: 10,
},
characterCountText: {
fontSize: 12,
color: whiteColor,
},
});

@ -0,0 +1,69 @@
import {View, Text, StyleSheet, TouchableOpacity, Image} from "react-native";
import {indigo, purpleColor, whiteColor} from "../Theme";
import React, {useEffect} from "react";
import {CustomJoke} from "../model/CustomJoke";
import {DetailJoke} from "../components/DetailJoke";
import {Joke} from "../model/Joke";
import {useDispatch, useSelector} from "react-redux";
import {getCompletJokes, setCompletJokes, setSample} from "../redux/actions/sampleAction";
import {useIsFocused, useNavigation, validatePathConfig} from "@react-navigation/native";
import {deleteJoke, getJokesCustomsById} from "../redux/actions/customAction";
import {Navigation} from "../navigation/Navigation";
export default function JokeDetailScreen({route}) {
const navigation = useNavigation();
const isFocused = useIsFocused();
const dispatch = useDispatch();
const jokeId = route.params.joke;
const isFavoris = route.params.isFavoris
const DataGen = typeof jokeId === "string" ? useSelector((state: any) => state.customReducer.completCustomJoke) : useSelector((state: any) => state.sampleReducer.completJoke);
// const DataGen = useSelector((state: any) => state.sampleReducer.completJoke);
useEffect(() => {
const getDetails = async () => {
// @ts-ignore
{ typeof jokeId === "string" ? await dispatch(getJokesCustomsById(jokeId)) : await dispatch(getCompletJokes(jokeId));}
};
getDetails();
}, [dispatch]);
async function deleteJokes() {
// @ts-ignore
await dispatch(deleteJoke(jokeId));
navigation.goBack();
}
return (
<View style={styles.font}>
<DetailJoke item={DataGen}/>
{typeof jokeId === "string" ? <TouchableOpacity onPress={deleteJokes}>
<Image style={styles.img} source={require('../assets/delete-icon.png')} />
</TouchableOpacity> : null}
</View>
);
}
const styles = StyleSheet.create({
font: {
backgroundColor: purpleColor,
width: '100%',
height: '100%',
},
text: {
fontSize: 24,
color: 'darksalmon',
textAlign: 'center',
fontWeight: 'bold',
marginVertical: 20,
},
img: {
width: 50,
height: 50,
margin: 20,
alignSelf: 'center'
}
});

@ -0,0 +1,103 @@
import {useTheme} from "@react-navigation/native";
import {darksalmonColor, indigo, whiteColor} from "../Theme";
import React, {useEffect, useState} from "react";
import {getFavorite} from "../redux/store";
import {FlatList, SafeAreaView, StyleSheet, TouchableHighlight, View, Text} from "react-native";
import {JokeListItems} from "../components/ListeJokeComponent";
import {CustomJoke} from "../model/CustomJoke";
import {SampleJoke} from "../model/SampleJoke";
import {useAppSelector, useAppDispatch} from "../redux/store";
import {useDispatch} from "react-redux";
export function ListFavoriteJokeScreen({route, navigation}){
const favoriteJokes: CustomJoke[] = useAppSelector((state) => state.customReducer.favoriteJokes);
const dispatch = useDispatch();
useEffect(() => {
const getFavoriteJokes = async () => {
// @ts-ignore
await dispatch(getFavorite());
}
getFavoriteJokes();
}, []);
const styles = themeSettings();
return (
<SafeAreaView style={styles.container}>
{ favoriteJokes.length ? (
<FlatList
data = {favoriteJokes}
renderItem={({ item }) => (
<TouchableHighlight onPress={() => navigation.navigate("JokeDetail", {"joke" : item.id})}>
<JokeListItems item={item}/>
</TouchableHighlight>
)}
keyExtractor={(item) => item.id.toString()}
/>
) : (
<Text style={styles.title}>Aucun favoris</Text>
)}
</SafeAreaView>
);
}
export function themeSettings() {
const {colors} = useTheme();
const styles = StyleSheet.create({
title: {
fontSize: 24,
color: colors.text,
textAlign: 'center',
fontWeight: 'bold',
marginVertical: 20,
},
titleResume: {
fontSize: 15,
fontWeight: 'bold',
marginBottom: 20,
},
container: {
flex: 1,
backgroundColor: colors.background,
},
top: {
backgroundColor : indigo,
},
header: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
margin: 9,
},
headerText: {
fontSize: 18,
color: whiteColor,
textAlign: 'center',
fontWeight: 'bold',
},
img2: {
tintColor: whiteColor,
justifyContent: "center",
alignItems: "center",
},
button2:{
flexDirection: "row",
justifyContent: "center",
marginRight: 10,
borderRadius: 10,
alignItems: "center",
height: 30,
width: 70,
borderColor: darksalmonColor,
borderWidth: 1,
backgroundColor: darksalmonColor,
},
});
return styles;
}

@ -1,35 +1,73 @@
import {FlatList, SafeAreaView, StyleSheet, Text, View} from "react-native";
import React from "react";
import {FlatList, Image, SafeAreaView, StyleSheet, Text, TouchableHighlight, TouchableOpacity, View} from "react-native";
import React, {useContext, useEffect, useState} from "react";
import {JokeListItems} from "../components/ListeJokeComponent";
import {Joke} from "../model/Joke";
import {JokeFactory} from "../model/JokeFactory";
import {JokeStub} from "../model/JokeStub";
import {indigo} from "../Theme";
import {darksalmonColor, greyColor, indigo, purpleColor, whiteColor} from "../Theme";
import {CustomJoke} from "../model/CustomJoke";
import {useDispatch, useSelector} from 'react-redux';
import {getSampleJoke, setSample} from "../redux/actions/sampleAction";
import {getJokesCustoms} from "../redux/actions/customAction";
import {useIsFocused} from "@react-navigation/native";
export function ListJokeScreen({route, navigation}) {
const [isActivated2, setIsActivated2] = useState(false);
const isFocused = useIsFocused();
const DATACUSTOM = JokeFactory.createCustomJokes(JokeStub.customJokes)
const DATASAMPLE = JokeFactory.createSampleJokes(JokeStub.sampleJokes)
//@ts-ignore
let DataGen = DATACUSTOM.concat(DATASAMPLE);
const [styles, setStyles] = useState(stylesLight);
function toggleActivation2() {
setIsActivated2(!isActivated2);
}
const DataGen = useSelector((state: any) => state.sampleReducer.sampleJoke);
const DataCustomsJoke = useSelector((state : any) => state.customReducer.customJokes)
const dispatch = useDispatch();
const getCustoms = async () => {
// @ts-ignore
await dispatch(getJokesCustoms());
}
const getSamples = async () => {
// @ts-ignore
await dispatch(getSampleJoke());
}
useEffect(() => {
if (isFocused) {
if (isActivated2) {
getCustoms();
} else {
getSamples();
}
}
}, [isFocused, isActivated2]);
export function ListJokeScreen() {
return (
<SafeAreaView style={styles.container}>
<View style={styles.top}>
<Text style={styles.title}>Liste des Blagues</Text>
<View style={styles.header}>
<Text style={styles.headerText}>Afficher les exemples</Text>
<TouchableOpacity style={styles.button2} onPress={toggleActivation2}>
<Image source={isActivated2 ? require('../assets/eye_icon.png') : require('../assets/eye_off_icon.png')} style={styles.img2} />
</TouchableOpacity>
</View>
<FlatList
data={DataGen}
renderItem={JokeListItems}
keyExtractor={(item: Joke) => item.summary()}
/>
data={isActivated2 ? DataCustomsJoke : DataGen}
renderItem={({ item }) => (
<TouchableHighlight onPress={() => navigation.navigate("JokeDetail", {"joke" : item.id})}>
<JokeListItems item={item}/>
</TouchableHighlight>
)}
keyExtractor={(item: CustomJoke) => item.id.toString()}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
const stylesDark = StyleSheet.create({
title: {
fontSize: 24,
@ -45,12 +83,98 @@ const styles = StyleSheet.create({
},
container: {
flex: 1,
backgroundColor: indigo,
backgroundColor: purpleColor ,
},
top: {
backgroundColor : "rgba(14, 14, 44, 1)"
backgroundColor : indigo,
},
header: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
margin: 9,
},
headerText: {
fontSize: 18,
color: whiteColor,
textAlign: 'center',
fontWeight: 'bold',
},
img2: {
tintColor: whiteColor,
justifyContent: "center",
alignItems: "center",
},
button2:{
flexDirection: "row",
justifyContent: "center",
marginRight: 10,
borderRadius: 10,
alignItems: "center",
height: 30,
width: 70,
borderColor: darksalmonColor,
borderWidth: 1,
backgroundColor: darksalmonColor,
},
});
const stylesLight = StyleSheet.create({
title: {
fontSize: 24,
color: darksalmonColor,
textAlign: 'center',
fontWeight: 'bold',
marginVertical: 20,
},
titleResume: {
fontSize: 15,
fontWeight: 'bold',
marginBottom: 20,
},
container: {
flex: 1,
backgroundColor: greyColor,
},
top: {
backgroundColor : indigo,
},
header: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
margin: 9,
},
headerText: {
fontSize: 18,
color: whiteColor,
textAlign: 'center',
fontWeight: 'bold',
},
img2: {
tintColor: whiteColor,
justifyContent: "center",
alignItems: "center",
},
button2:{
flexDirection: "row",
justifyContent: "center",
marginRight: 10,
borderRadius: 10,
alignItems: "center",
height: 30,
width: 70,
borderColor: darksalmonColor,
borderWidth: 1,
backgroundColor: darksalmonColor,
},
});

@ -0,0 +1,125 @@
import React, {useContext, useEffect, useState} from "react";
import {
Appearance,
Button,
FlatList,
Image,
SafeAreaView,
ScrollView,
SectionListComponent,
StyleSheet, Switch, SwitchComponent,
Text, useColorScheme,
View
} from "react-native";
import usePersonalTheme, {
blackColor,
darksalmonColor,
greyColor,
indigo,
purpleColor,
whiteColor
} from "../Theme";
import {isEnabled} from "react-native/Libraries/Performance/Systrace";
import {DarkTheme, DefaultTheme, useTheme} from "@react-navigation/native";
import {storeTheme} from "../redux/store";
export function SettingsScreen() {
const [isEnabled, setIsEnabled] = useState(false);
const toggleSwitch = () => {
setIsEnabled(previousState => {
const newIsEnabled = !previousState;
const newTheme = newIsEnabled ? DarkTheme : DefaultTheme;
console.log("newTheme", newTheme);
storeTheme(newTheme);
return newIsEnabled;
});
};
const styles = themeSettings();
return (
<SafeAreaView style={styles.container}>
<Text style={styles.title}>Réglages</Text>
<View style={styles.settingsContainer}>
<View style={styles.settingRow}>
<View style={styles.settingRow}>
<Image style={styles.img} source={require('../assets/darkmode_icon.png')}/>
<Text style={styles.settingText}>Dark Mode</Text>
</View>
<Switch style={styles.switch}
trackColor={{ false: whiteColor, true: darksalmonColor }}
thumbColor={isEnabled ? whiteColor :darksalmonColor}
onValueChange={toggleSwitch}
value={isEnabled}
/>
</View>
</View>
</SafeAreaView>
);
};
export function themeSettings() {
const theme = useTheme();
const colors = theme.colors;
console.log("themeSettings", colors)
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background,
},
header: {
padding: 16,
alignItems: 'center',
},
headerText: {
color: whiteColor,
fontSize: 18,
},
settingsContainer: {
backgroundColor: indigo,
margin: 16,
},
title: {
color: whiteColor,
fontSize: 20,
fontWeight: 'bold',
marginTop: 7,
marginLeft: 15,
marginBottom: 8,
},
settingRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: 12,
},
settingText: {
color: whiteColor,
marginLeft: 10,
},
img: {
marginLeft: 10,
width: 30,
height: 30,
tintColor: darksalmonColor,
},
switch: {
marginRight: 10,
}
});
return styles;
}

@ -0,0 +1,63 @@
import {describe,test ,expect, jest, it} from "@jest/globals";
import {CustomJoke} from "../../model/CustomJoke";
import {
CustomActionType,
getJokesCustoms,
setCustomsJoke,
setCustomsJokeById, setDeleteCustomJoke, setFavoriteJoke,
setPostJoke
} from "../../redux/actions/customAction";
describe('Action tests', () => {
it('setPostJoke creates correct action', () => {
const postJoke = new CustomJoke('test', 'Why did the chicken...', 'To get to the other side!', 'http://www.jokes.com/joke1', "1");
const expectedAction = {
type: CustomActionType.POST_CUSTOM_JOKE,
payload: postJoke,
};
expect(setPostJoke(postJoke)).toEqual(expectedAction);
});
it ('setCustomsJoke creates correct action', () => {
const customJokes = [new CustomJoke('test', 'Why did the chicken...', 'To get to the other side!', 'http://www.jokes.com/joke1', "1")];
const expectedAction = {
type: CustomActionType.FETCH_CUSTOMS_JOKE,
payload: customJokes,
};
expect(setCustomsJoke(customJokes)).toEqual(expectedAction);
}
);
it('setCustomsJokeById creates correct action', () => {
const completCustomJoke = new CustomJoke('test', 'Why did the chicken...', 'To get to the other side!', 'http://www.jokes.com/joke1', "1");
const expectedAction = {
type: CustomActionType.FETCH_CUSTOMS_JOKE_BY_ID,
payload: completCustomJoke,
};
expect(setCustomsJokeById(completCustomJoke)).toEqual(expectedAction);
}
);
it('setDeleteCustomJoke creates correct action', () => {
const deleteCustomJoke = new CustomJoke('test', 'Why did the chicken...', 'To get to the other side!', 'http://www.jokes.com/joke1', "1");
const expectedAction = {
type: CustomActionType.DELETE_CUSTOM_JOKE,
payload: deleteCustomJoke,
};
expect(setDeleteCustomJoke(deleteCustomJoke)).toEqual(expectedAction);
}
);
it('setFavoriteJoke creates correct action', () => {
const favoriteJokes = [new CustomJoke('test', 'Why did the chicken...', 'To get to the other side!', 'http://www.jokes.com/joke1', "1")];
const expectedAction = {
type: CustomActionType.FETCH_FAVORITE_JOKE,
payload: favoriteJokes,
};
expect(setFavoriteJoke(favoriteJokes)).toEqual(expectedAction);
}
);
});

@ -1,152 +1,49 @@
# Énoncé semaine 1
- [Énoncé - Semaine 1](#énoncé-semaine-1)
- [Délai de livraison](#délai-de-livraison)
- [Travail demandé](#travail-demandé)
- [Package 1 : Modèle](#package-1--modèle--stub)
- [Package 2 : Extensions pour les tests en CLI](#package-2--types--extensions-)
- [Premier test : Affichage des stubs](#premier-tests---affichage-des-stubs)
- [Critères d'évaluation](#critères-dévaluation)
Durant cette première semaine, vous allez créer votre projet, le configurer pour la suite, écrire vos classes du modèle
et définir des données statiques dans un Stub que vous pourrez utiliser pour les prochains TP.
# Délai de livraison
Votre travail est à rendre sur une branche tp1 et doit être fusionné à la branche principale lorsqu'il est terminé.
La livraison est attendue à la fin du TP.
Un retard jusqu'à la fin du second ou du troisième TP est autorisé mais se verra attribué des pénalités (
cf. [Critères d'évaluation](#critères-dévaluation)).
# Travail demandé
Durant la première semaine, il vous est demandé de :
* Créer le projet
* Créer les classes Typescript du modèle
* Créer un Stub pour ce modèle (que vous utiliserez dans le TP suivant)
## Package 1 : Modèle & Stub
Voici un diagramme de classes de ce qui est attendu pour votre modèle :
```mermaid
classDiagram
direction TB
class Joke {
<<abstract>>
+type : string
+setup : string
+punchline : string
+image: string
+summary() string
+description(): string
}
class SampleJoke {
+id: number
}
class CustomJoke {
+id: string
}
class JokeFactory {
+createCustomJokes(jsonArray: string)$ CustomJoke[]
+createSampleJokes(jsonArray: string)$ SampleJoke[]
}
Joke <|-- CustomJoke
Joke <|-- SampleJoke
```
En français, vous avez donc à préparer les classes suivantes :
- ```Joke``` : Classe abstraite qui représente les propriétés communes au blagues du catalogue et aux blagues que vous
allez créer par la suite. Elle contient également une méthode _summary_ qui renvoit uniquement les 25 premiers
caractères de la blague suivis par "..." .
Elle contient également une méthode _description_ qui doit renvoyer le type de la blague suivi par son résumé et le
tout séparé par un tiret (par exemple: "general - What did the fish say wh...").
- ```SampleJoke``` : Classe qui représente les blagues du catalogue (et qui hérite de ```AbstractJoke```)
- ```CustomJoke``` : Classe qui représente les blagues que vous allez créer par la suite (et qui hérite
de ```AbstractJoke```)
- ```JokeFactory``` : Une classe utilitaire qui vous permettra de convertir les données JSON que vous recevrez de l'API
vers vos classes Typescript.
Maintenant que notre modèle est prêt, nous allons pouvoir utiliser la classe _JokeFactory_ pour générer un stub de
_SampleJoke_ et un stub
de _CustomJoke_ dans un nouveau fichier que vous pouvez par exemple appeler _JokeStub.ts_.
## Package 2 : Types (Extensions)
Pour cette partie, vous allez devoir ajouter un comportement sur les tableaux Typescript afin de définir une
fonction d'affichage que vous aller utiliser pour avoir un aperçu des données de vos stubs depuis l'application.
Pour celà, vous devez vous appuyer sur la méthode _description_ de la partie précédente afin de n'avoir qu'un condensé
des
informations pour faciliter l'affichage.
## Premier tests - Affichage des stubs
Avec la méthode d'extension de la partie 2 de ce tp, vous allez devoir afficher sur la page d'accueil de votre
application
les données de vos deux stubs (Custom et Sample).
Le contenu de chaque stub devra être affiché avec une simple balise `<Text></Text>`
# Critères d'évaluation
Pour être obtenir les points, vous devez faire valider vos aquis par l'enseignant à l'oral pendant les TP (ou si
l'enseignant le propose, lors d'une évaluation écrite).
Cette validation doit avoir lieu avant la fin du TP pour obtenir tous les points.
Si la validation a lieu pendant le TP2, une pénalité de 50% est appliquée.
Si elle a lieu pendant le TP3, une pénalité de 75% est appliquée.
Par la suite, plus aucun point n'est attribué.
**Légende**
symbole | signification
--- | ---
☢️ | si ce critère n'est pas respecté => 0/20
🎬 | évalué à la fin du TP
1⃣ | ctirère de niveau 1 : tant que vous n'avez pas obtenu tous les points sur les critères de niveau 1, les points sur les critères suivants ne sont pas attribués
2⃣ | critère de niveau 2 : tant que vous n'avez pas obtenu tous les points sur les critères de niveau 1 et 2, les points sur les critères de niveau 3 ne sont pas attribués
3⃣ | critère de niveau 3
**Critères**
niveau | description | coeff | pénalités TP2 | pénalités TP3
--- | --- | --- | --- | ---
☢️ | Le dépôt doit être accessible par l'enseignant | ☢️
☢️ | un .gitignore doit exister au premier push | ☢️
🎬 | le projet compile et s'exécute | 4 | 50% | 75%
1⃣ | j'ai créé le projet en typescript | 2 | 50% | 75%
1⃣ | j'ai créé un *Package* *Model* | 2 | 50% | 75%
1⃣ | j'ai créé la classe ```Joke``` | 1 | 50% | 75%
1⃣ | j'ai créé la classe ```SampleJoke``` | 1 | 50% | 75%
1⃣ | j'ai créé la classe ```CustomJoke``` | 1 | 50% | 75%
1⃣ | mes classes sont bien des fichiers Typescript | 1 | 50% | 75%
1⃣ | ```Joke``` contient des getters publics | 1 | 50% | 75%
1⃣ | ```Joke``` possède une méthode qui renvoit les 25 premiers caractères de la blague | 1 | 50% | 75%
1⃣ | ```Joke``` possède un constructeur | 1 | 50% | 75%
2⃣ | ```Joke``` est accessible par les fichiers typescript | 1 | 50% | 75%
1⃣ | ```SampleJoke``` contient des getters publics | 1 | 50% | 75%
1⃣ | ```SampleJoke``` possède un constructeur | 1 | 50% | 75%
2⃣ | ```SampleJoke``` est accessible par les fichiers typescript | 1 | 50% | 75%
1⃣ | ```CustomJoke``` contient des getters publics | 1 | 50% | 75%
1⃣ | ```CustomJoke``` possède un constructeur | 1 | 50% | 75%
2⃣ | ```CustomJoke``` est accessible par les fichiers typescript | 1 | 50% | 75%
2⃣ | ```JokeFactory``` est accessible par les fichiers typescript | 1 | 50% | 75%
1⃣ | ```JokeFactory``` contient une méthode statique permettant de transformer un tableau de JSON au format _string_ en un tableau de _CustomJoke_ | 1 | 50% | 75%
1⃣ | ```JokeFactory``` contient une méthode statique permettant de transformer un tableau de JSON au format _string_ en un tableau de _SampleJoke_ | 1 | 50% | 75%
2⃣ | j'ai un stub pour les CustomJoke | 1 | 50% | 75%
2⃣ | j'ai un stub pour les CustomJoke qui utilise la factory | 2 | 50% | 75%
2⃣ | j'ai un stub pour les SampleJoke | 1 | 50% | 75%
2⃣ | j'ai un stub pour les SampleJoke qui utilise la factory | 2 | 50% | 75%
3⃣ | j'ai déclaré ma méthode d'extension | 2 | 50% | 75%
3⃣ | j'ai fait l'implémentation de ma méthode d'extension | 2 | 50% | 75%
3⃣ | j'ai correctement chargé mes extensions | 2 | 50% | 75%
2⃣ | j'ai affiché le contenu de mon _CustomStub_ | 1 | 50% | 75%
2⃣ | j'ai affiché le contenu de mon _SampleStub_ | 1 | 50% | 75%
2⃣ | je sais utiliser `let` | 2 | 50% | 75%
3⃣ | mon dépôt possède un readme qui apporte quelque chose... | 2 | 50% | 75%
3⃣ | mon code est documenté | 1 | 50% | 75%
<h1 align="center">JokeAPP</h1>
<p align="center">
<img src="https://img.shields.io/badge/React_Native-20232A.svg?style=for-the-badge&logo=react&logoColor=61DAFB" alt="React Native"/>
<img src="https://img.shields.io/badge/JavaScript-F7DF1E.svg?style=for-the-badge&logo=javascript&logoColor=black" alt="JavaScript"/>
<img src="https://img.shields.io/badge/TypeScript-3178C6.svg?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript"/>
<img src="https://img.shields.io/badge/Node.js-339933.svg?style=for-the-badge&logo=node.js&logoColor=white" alt="Node.js"/>
<img src="https://img.shields.io/badge/Expo-000020.svg?style=for-the-badge&logo=expo&logoColor=white" alt="Expo"/>
<img src="https://img.shields.io/badge/Android-3DDC84.svg?style=for-the-badge&logo=android&logoColor=white" alt="Android"/>
<img src="https://img.shields.io/badge/iOS-000000.svg?style=for-the-badge&logo=ios&logoColor=white" alt="iOS"/>
</p>
## Introduction
`JokeAPP` est un projet qui à pour but la découverte et la maitrise de `React-Native` à travers différentes parties abordant chacune différents principes.
## Structure du Projet
Chaque partie du projet est associée à une branche distincte dans ce dépôt. Vous pouvez passer d'une partie à l'autre en changeant de branche. Une fois la partie finie elle se sera merge sur la branche master.
- [x] Partie 1 - `part1`
- Création projet, création classes Typescript, création Stub pour ce modèle
- [x] Partie 2 - `part2`
- Creation page "Catalogue" + implementation FlatList de "SamplesJokes"
- [x] Partie 3 - `part3`
- Implementation navigation et création page "Accueil"
- [x] Partie 4 - `part4`
- Connection à l'<a href="https://iut-weather-api.azurewebsites.net/swagger-ui/#/Jokes">API Jokes</a>
- [x] Partie 5 - `part5`
- Implementation détail d'une "Joke" + StackNavigation
- [x] Partie 6 - `tp6`
- Création de "CustomsJokes"
- [x] Partie 7 - `tp7`
- Changement de theme et suppresion de "CustomsJokes"
- [x] Partie 8 - `pt8`
- Ajout de la fonctionnalité favorie
- [x] Partie 9 - `tp9`
- Réalisations des tests et déploiement sur sonar
## Installation et Configuration
1. Clonez le dépôt : `git clone https://codefirst.iut.uca.fr/git/tony.fages/Tp_ReactNative.git`
2. Ouvrez le fichier cloné avec l'IDE WebStrom.
3. Aller grâce à votre terminal sur le projet et taper la commande `npm install` afin d'installer toute les dépendances
4. Taper la commande `npx expo start` afin de demarer le serveur permettant l'affiche du projet
5. Pour l'affichage du projet une fois le serveur lancer appuyer sur `i` ou `a` pour démarer le simulateur `ios` ou `android` ou bien installer l'application <a href="https://expo.dev">expo</a> et scanner le qr code pour le lancer directement sur votre téléphone
## Développeur
<a href="https://codefirst.iut.uca.fr/git/tony.fages/Tp_ReactNative.git" style="margin-right: 20px">Tony Fages</a>

Loading…
Cancel
Save