Compare commits

..

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

@ -1,22 +0,0 @@
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,8 +419,7 @@ FodyWeavers.xsd
.LSOverride
# Icon must end with two \r
Icon
Icon
# Thumbnails
._*
@ -441,4 +440,3 @@ Network Trash Folder
Temporary Items
.apdisk
coverage

5
.idea/.gitignore vendored

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

@ -1,12 +0,0 @@
<?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>

@ -1,8 +0,0 @@
<?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>

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

@ -5,11 +5,9 @@ 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}>

@ -4,7 +4,6 @@ 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;
@ -13,27 +12,14 @@ type DetailJokeProps = {
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 [isActivated, setIsActivated] = useState(false);
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);
}

@ -8,7 +8,7 @@ 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";
import StackNavigation from "./StackNavigation";
const homeIcon = require("../assets/home_icon.png");
const listIcon = require("../assets/list_icon.png");
const addIcon = require("../assets/add_icon.png");
@ -18,9 +18,11 @@ import store, {getTheme, storeTheme} from "../redux/store";
export function Navigation(){
const BottomTabNavigator = createBottomTabNavigator();
const [themes, setThemes] = useState<Theme>(DefaultTheme);
const [themes, setThemes] = useState<Theme | null>(null);
useEffect(() => {
const fetchTheme = async () => {
@ -34,6 +36,10 @@ export function Navigation(){
if (themes == null) {
return null;
}
console.log("ici le theme", themes);
return (
<NavigationContainer theme={ themes.dark === false ? DefaultTheme : DarkTheme} >
<BottomTabNavigator.Navigator initialRouteName="Home" screenOptions={{
@ -57,7 +63,7 @@ export function Navigation(){
/>
)
}}/>
<BottomTabNavigator.Screen name="Catalogue" component={CatalogueScreen}
<BottomTabNavigator.Screen name="Catalogue" component={StackNavigation}
options={{
tabBarIcon: ({focused}) => (
<Image source={listIcon}
@ -78,14 +84,13 @@ export function Navigation(){
}}/>
<BottomTabNavigator.Screen name="Favoris" component={FavoriteScreen}
<BottomTabNavigator.Screen name="Favoris" component={ListJokeScreen}
options={{
tabBarIcon: ({focused}) => (
<Image source={favIcon}
style={{ tintColor: focused ? darksalmonColor : purpleColor }}
/>
),
headerShown: false,
)
}}/>
<BottomTabNavigator.Screen name="Paramètres" component={SettingsScreen}
options={{

@ -6,40 +6,12 @@ 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 default function StackNavigation() {
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={
<Stack.Navigator initialRouteName="CatalogueStack" screenOptions={
{
headerTitleStyle: {
fontSize: 24,
@ -51,15 +23,15 @@ export function FavoriteScreen(){
backgroundColor: indigo,
},
headerTitle : "Favoris",
headerTitle : "Catalogue",
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.Screen name="CatalogueStack" component={ListJokeScreen} options={{ headerTitle: 'Catalogue' }}/>
<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,28 +6,7 @@
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"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"
]
}
"web": "expo start --web"
},
"dependencies": {
"@expo/ngrok": "^2.5.0",
@ -37,21 +16,18 @@
"@react-navigation/stack": "^6.3.21",
"@reduxjs/toolkit": "^2.2.1",
"@types/react": "~18.2.45",
"expo": "^50.0.14",
"expo": "~50.0.3",
"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",
"jest-expo": "^50.0.4"
"@expo/ngrok": "^4.1.0"
},
"private": true
}

@ -6,7 +6,6 @@ export enum CustomActionType {
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 {
@ -24,8 +23,6 @@ export interface CustomsAction {
payload: CustomJoke[];
}
export type Action = CustomAction;
@ -58,12 +55,6 @@ export const setDeleteCustomJoke = (deleteCustomJoke: CustomJoke): postCustomAct
}
}
export const setFavoriteJoke = (favoriteJokes: CustomJoke[]): CustomsAction => {
return {
type: CustomActionType.FETCH_FAVORITE_JOKE,
payload: favoriteJokes
}
}
export const postJoke = (type : string, setup : string, punchline : string) => {

@ -8,7 +8,6 @@ interface state {
customJokes: CustomJoke[];
completCustomJoke: CustomJoke;
deleteCustomJoke: CustomJoke;
favoriteJokes: CustomJoke[];
}
// initial state for sampleJokes
@ -17,7 +16,6 @@ const initialState: state = {
customJokes: [],
completCustomJoke: {} as CustomJoke,
deleteCustomJoke: {} as CustomJoke,
favoriteJokes: []
}
// app reducer for sampleJokes
@ -44,11 +42,6 @@ export default appReducer = (state = initialState, action: Action) => {
...state,
deleteCustomJoke: action.payload,
}
case CustomActionType.FETCH_FAVORITE_JOKE:
return {
...state,
favoriteJokes: action.payload,
}
default:
return state;

@ -3,13 +3,7 @@ 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";
import {Theme} from "@react-navigation/native";
const reducer = {
categorieReducer: categorieReducer,
@ -18,6 +12,7 @@ const reducer = {
};
// @ts-ignore
const store = configureStore({
// @ts-ignore
reducer,
@ -41,62 +36,9 @@ export const storeTheme = async (theme) => {
export const getTheme = async () => {
try {
const jsonValue = await AsyncStorage.getItem('@theme')
return jsonValue != null ? JSON.parse(jsonValue) as Theme : DefaultTheme;
return jsonValue != null ? JSON.parse(jsonValue) as Theme : null;
} 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;

@ -14,17 +14,16 @@ export default function JokeDetailScreen({route}) {
const isFocused = useIsFocused();
const dispatch = useDispatch();
const jokeId = route.params.joke;
const isFavoris = route.params.isFavoris
const state = route.params.state;
console.log(state);
const DataGen = typeof jokeId === "string" ? useSelector((state: any) => state.customReducer.completCustomJoke) : useSelector((state: any) => state.sampleReducer.completJoke);
const DataGen = state ? 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));}
{ state ? await dispatch(getJokesCustomsById(jokeId)) : await dispatch(getCompletJokes(jokeId));}
};
getDetails();
@ -39,7 +38,7 @@ export default function JokeDetailScreen({route}) {
return (
<View style={styles.font}>
<DetailJoke item={DataGen}/>
{typeof jokeId === "string" ? <TouchableOpacity onPress={deleteJokes}>
{state ? <TouchableOpacity onPress={deleteJokes}>
<Image style={styles.img} source={require('../assets/delete-icon.png')} />
</TouchableOpacity> : null}
</View>

@ -1,103 +0,0 @@
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;
}

@ -58,7 +58,7 @@ export function ListJokeScreen({route, navigation}) {
<FlatList
data={isActivated2 ? DataCustomsJoke : DataGen}
renderItem={({ item }) => (
<TouchableHighlight onPress={() => navigation.navigate("JokeDetail", {"joke" : item.id})}>
<TouchableHighlight onPress={() => navigation.navigate("JokeDetail", {"joke" : item.id, "state" : isActivated2})}>
<JokeListItems item={item}/>
</TouchableHighlight>
)}

@ -1,63 +0,0 @@
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);
}
);
});

@ -20,22 +20,12 @@ Chaque partie du projet est associée à une branche distincte dans ce dépôt.
- [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
- [ ] Partie 2 - `part2`
- *
- [ ] Partie 3 - `part3`
- *
- [ ] Partie 4 - `part4`
- *
## Installation et Configuration
1. Clonez le dépôt : `git clone https://codefirst.iut.uca.fr/git/tony.fages/Tp_ReactNative.git`

Loading…
Cancel
Save