Skip to content
Snippets Groups Projects
Commit beb22b8f authored by HARTZ HENRI's avatar HARTZ HENRI
Browse files

:twisted_rightwards_arrows: merge Feature/142 add sound response

Merge branch 'feature/142-add-sound-response' into 'develop'
parents dbf688f4 196b00c3
Branches
1 merge request!137Feature/142 add sound response
Pipeline #325753 passed with stages
in 10 seconds
......@@ -13,13 +13,15 @@
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true
"supportsTablet": true,
"bundleIdentifier": "com.lykomonix.vili"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
}
},
"package": "com.lykomonix.vili"
},
"web": {
"favicon": "./assets/favicon.png"
......
......@@ -2,6 +2,8 @@ import {TouchableOpacity, Text, StyleSheet, View, Image} from "react-native";
import {ButtonsStyles} from "../../styles/ButtonsStyles";
import {TextsStyles} from "../../styles/TextsStyles";
import React from "react";
import Base64AudioPlayer from "./Base64AudioPlayer";
interface Props{
text: string;
......@@ -9,6 +11,7 @@ interface Props{
buttonStyle?: any;
buttonText?: any;
indicator?: number | undefined;
forceEnd: boolean;
}
/**
......@@ -18,7 +21,7 @@ interface Props{
* @param buttonStyle - Surcharge du style du bouton
* @param buttonText - Surcharge du style du texte du bouton
*/
export default function AnswerButton({text, handleButtonPressed, buttonStyle, buttonText, indicator}: Props){
export default function AnswerButton({text, handleButtonPressed, buttonStyle, buttonText, indicator, forceEnd}: Props){
return(
<View style={styles.containerButton}>
<TouchableOpacity onPress={handleButtonPressed} style={[styles.defaultButton,buttonStyle]}>
......@@ -35,9 +38,9 @@ export default function AnswerButton({text, handleButtonPressed, buttonStyle, bu
/>
:
text.includes('data:audio') ?
<Text style={[TextsStyles.defaultButtonText, buttonText]}>sound: not supported</Text>
<Base64AudioPlayer base64Audio={text} forceEnd={forceEnd}/>
:
<Text style={[TextsStyles.defaultButtonText, buttonText]}>{text}</Text>
<Text style={[TextsStyles.defaultButtonText, buttonText]}>{text}</Text>
}
</TouchableOpacity>
</View>
......
import {TouchableOpacity, Text, StyleSheet, View, Image} from "react-native";
import {ButtonsStyles} from "../../styles/ButtonsStyles";
import {TextsStyles} from "../../styles/TextsStyles";
interface Props{
url: string;
handleButtonPressed:() => void;
buttonStyle?: any;
indicator?: number | undefined;
}
/**
* AnswerButtonImage : Un bouton par défaut, il possède déjà un style mais on peut le surcharger grâce à la propriété buttonStyle
* @param url - Texte du bouton
* @param handleButtonPressed - Fonction qui sera exécutée lorsque le bouton sera pressé
* @param buttonStyle - Surcharge du style du bouton
* @param buttonText - Surcharge du style du texte du bouton
*/
export default function AnswerButtonImage({url, handleButtonPressed, buttonStyle, indicator}: Props){
return(
<View style={styles.containerButton}>
<TouchableOpacity onPress={handleButtonPressed} style={[styles.defaultButton,buttonStyle]}>
{indicator !== undefined && (
<View style={styles.indicator}>
<Text style={styles.indicatorText}>{indicator}</Text>
</View>
)}
<Image
style={[styles.answerImage]}
source={{uri: url}}
/>
</TouchableOpacity>
</View>
)
}
const styles = StyleSheet.create({
defaultButton: {
backgroundColor: "#45128C",
padding: 10,
alignItems: "center",
justifyContent: 'center',
borderRadius: 10,
},
indicator: {
position: "absolute",
top: "-16%",
left: "5%",
width: 25,
height: 25,
borderRadius: 15,
backgroundColor: "#D3D3D3",
justifyContent: "center",
alignItems: "center",
zIndex: 1,
},
indicatorText: {
color: "#000",
fontWeight: "bold",
fontSize: 14,
},
answerImage: {
width: '100%',
height: '100%',
},
containerButton: {
height: 150,
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}
})
\ No newline at end of file
import React, { useEffect, useState } from 'react';
import { View, Button, Text } from 'react-native';
import { Audio } from 'expo-av';
interface Props {
base64Audio: string;
forceEnd: boolean;
}
export default function Base64AudioPlayer({ base64Audio, forceEnd }: Props) {
const [sound, setSound] = useState<Audio.Sound | null>(null);
const [isPlaying, setIsPlaying] = useState(false);
const playAudio = async () => {
try {
const { sound } = await Audio.Sound.createAsync(
{ uri: `${base64Audio}` }
);
setSound(sound);
await sound.playAsync();
setIsPlaying(true);
sound.setOnPlaybackStatusUpdate((status) => {
if (status.isLoaded && !status.isBuffering && status.didJustFinish) {
setIsPlaying(false);
}
});
} catch (error) {
console.error('Erreur lors de la lecture de l\'audio :', error);
}
};
const stopAudio = async () => {
if (sound) {
await sound.stopAsync();
setIsPlaying(false);
}
};
useEffect(() => {
stopAudio();
}, [forceEnd]);
return (
<View style={{ padding: 20 }}>
<Text style={{ marginBottom: 10 }}>Appuyer pour jouer l'audio</Text>
<Button title={isPlaying ? 'Arrêter l\'audio' : 'Jouer l\'audio'} onPress={isPlaying ? stopAudio : playAudio} />
</View>
);
}
\ No newline at end of file
......@@ -19,6 +19,7 @@
"axios": "^1.7.9",
"dotenv": "^16.4.7",
"expo": "~52.0.14",
"expo-av": "~15.0.2",
"expo-splash-screen": "^0.29.13",
"expo-status-bar": "~2.0.0",
"he": "^1.2.0",
......@@ -28,9 +29,11 @@
"react-i18next": "^15.1.1",
"react-native": "0.76.3",
"react-native-dropdown-select-list": "^2.0.5",
"react-native-fs": "^2.20.0",
"react-native-progress": "^5.0.1",
"react-native-safe-area-context": "^4.12.0",
"react-native-screens": "~4.1.0",
"react-native-sound": "^0.11.2",
"react-native-sse": "^1.2.1",
"react-native-vector-icons": "^10.2.0",
"react-query": "^3.39.3",
......@@ -4662,6 +4665,11 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/base-64": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
"integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA=="
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
......@@ -6126,6 +6134,23 @@
"react-native": "*"
}
},
"node_modules/expo-av": {
"version": "15.0.2",
"resolved": "https://registry.npmjs.org/expo-av/-/expo-av-15.0.2.tgz",
"integrity": "sha512-AHIHXdqLgK1dfHZF0JzX3YSVySGMrWn9QtPzaVjw54FAzvXfMt4sIoq4qRL/9XWCP9+ICcCs/u3EcvmxQjrfcA==",
"license": "MIT",
"peerDependencies": {
"expo": "*",
"react": "*",
"react-native": "*",
"react-native-web": "*"
},
"peerDependenciesMeta": {
"react-native-web": {
"optional": true
}
}
},
"node_modules/expo-constants": {
"version": "17.0.3",
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-17.0.3.tgz",
......@@ -9740,6 +9765,25 @@
"integrity": "sha512-TepbcagQVUMB6nLuIlVU2ghRpQHAECOeZWe8K04ymW6NqbKbxuczZSDFfdCiABiiQ2dFD+8Dz65y4K7/uUEqGg==",
"license": "MIT"
},
"node_modules/react-native-fs": {
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/react-native-fs/-/react-native-fs-2.20.0.tgz",
"integrity": "sha512-VkTBzs7fIDUiy/XajOSNk0XazFE9l+QlMAce7lGuebZcag5CnjszB+u4BdqzwaQOdcYb5wsJIsqq4kxInIRpJQ==",
"license": "MIT",
"dependencies": {
"base-64": "^0.1.0",
"utf8": "^3.0.0"
},
"peerDependencies": {
"react-native": "*",
"react-native-windows": "*"
},
"peerDependenciesMeta": {
"react-native-windows": {
"optional": true
}
}
},
"node_modules/react-native-progress": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-native-progress/-/react-native-progress-5.0.1.tgz",
......@@ -9776,6 +9820,15 @@
"react-native": "*"
}
},
"node_modules/react-native-sound": {
"version": "0.11.2",
"resolved": "https://registry.npmjs.org/react-native-sound/-/react-native-sound-0.11.2.tgz",
"integrity": "sha512-LmGc8lgOK3qecYMVQpyHvww/C+wgT6sWeMpVbOe4NCRGC2yKd4fo4U0KBUo9PO7AqKESO3I/2GZg1/C0+bwiiA==",
"license": "MIT",
"peerDependencies": {
"react-native": ">=0.8.0"
}
},
"node_modules/react-native-sse": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/react-native-sse/-/react-native-sse-1.2.1.tgz",
......@@ -11563,6 +11616,12 @@
"react": ">=16.8"
}
},
"node_modules/utf8": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz",
"integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==",
"license": "MIT"
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
......
......@@ -36,7 +36,8 @@
"react-native-vector-icons": "^10.2.0",
"react-query": "^3.39.3",
"rxjs": "^7.8.1",
"socket.io-client": "^4.8.1"
"socket.io-client": "^4.8.1",
"expo-av": "~15.0.2"
},
"devDependencies": {
"@babel/core": "^7.20.0",
......
......@@ -11,7 +11,6 @@ import {Answer} from "../../models/Answer";
import {Quiz} from "../../models/Quiz";
import AnswerButton from "../../components/buttons/AnswerButton";
import {QuizInformations} from "../../models/QuizInformations";
import AnswerButtonImage from "../../components/buttons/AnswerButtonImage";
interface Props {
quizInformations?: QuizInformations;
......@@ -71,6 +70,7 @@ export default function PlayingQuizBody({ quizInformations, runId, actualQuestio
const [quizState, setQuizState] = useState(QuizState.ANSWERING);
const [correctAnswerId, setCorrectAnswerId] = useState<number | null>(null);
const [isLastQuestion, setIsLastQuestion] = useState(false);
const [forceEnd, setForceEnd] = useState(false);
const {answerQuestion} = useQuizService();
const getCorrectAnswer = (answers: Answer[]): Answer => {
......@@ -81,6 +81,7 @@ export default function PlayingQuizBody({ quizInformations, runId, actualQuestio
const onValidation = async () => {
if(selectedAnswerId === null) return;
setQuizState(QuizState.LOADING);
setForceEnd(!forceEnd);
const answerId = actualQuestion.answers.find((answer) => answer.id === selectedAnswerId)?.id;
if(!answerId || !runId) return;
const answereFetched = await answerQuestion(runId, actualQuestion.id, answerId);
......@@ -101,7 +102,8 @@ export default function PlayingQuizBody({ quizInformations, runId, actualQuestio
}
};
const onContinue = () => {
const onContinue = async () => {
await setForceEnd(!forceEnd);
if(isLastQuestion) {
navigation.reset({
index: 0,
......@@ -136,23 +138,15 @@ export default function PlayingQuizBody({ quizInformations, runId, actualQuestio
contentContainerStyle={styles.buttonListContentContainer}
>
{actualQuestion.answers.map((answer, index) => (
actualQuestion.type === "image" ?
<AnswerButtonImage
key={index}
url={answer.text}
handleButtonPressed={() => onAnsweredButtonClicked(answer.id)}
buttonStyle={getStyleOfAnswerButton(answer.id, selectedAnswerId, correctAnswerId, quizState)}
/>
:
<AnswerButton
key={index}
text={answer.text}
handleButtonPressed={() => onAnsweredButtonClicked(answer.id)}
buttonStyle={getStyleOfAnswerButton(answer.id, selectedAnswerId, correctAnswerId, quizState)}
buttonText={getStyleOfAnswerButtonText(answer.id, selectedAnswerId, correctAnswerId, quizState)}
/>
<AnswerButton
key={index}
text={answer.text}
handleButtonPressed={() => onAnsweredButtonClicked(answer.id)}
buttonStyle={getStyleOfAnswerButton(answer.id, selectedAnswerId, correctAnswerId, quizState)}
buttonText={getStyleOfAnswerButtonText(answer.id, selectedAnswerId, correctAnswerId, quizState)}
forceEnd={forceEnd}
/>
))}
{/* </View> */}
</ScrollView>
<View style={styles.playButtonContainer}>
<BlueButton onPress={() => quizState === QuizState.ANSWERING ? onValidation() : onContinue()} text={quizState === QuizState.ANSWERING ? "CONFIRM" : "CONTINUE"} buttonStyle={{borderRadius: 50}} isDisabled={false}/>
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment