You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							212 lines
						
					
					
						
							7.3 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							212 lines
						
					
					
						
							7.3 KiB
						
					
					
				| import React from "react"; | |
| import { View, ScrollView, StyleSheet, StatusBar, Text, ImageBackground, BackHandler } from "react-native"; | |
| import SafeAreaView from 'react-native-safe-area-view'; | |
| import AsyncStorage from '@react-native-async-storage/async-storage'; | |
| 
 | |
| import { Button, ButtonContainer } from "../components/Button"; | |
| import { colors, texts, examScheme } from "../components/Variables"; | |
| 
 | |
| import aerodynamicsQuestions from "../data/aerodynamics"; | |
| import firstAidQuestions from "../data/firstAid"; | |
| import flightSafetyQuestions from "../data/flightSafety"; | |
| import instrumentsQuestions from "../data/instruments"; | |
| import legislationQuestions from "../data/legislation"; | |
| import materialsQuestions from "../data/materials"; | |
| import meteorologyQuestions from "../data/meteorology"; | |
| import physiopathologyQuestions from "../data/physiopathology"; | |
| import pilotingTechniquesQuestions from "../data/pilotingTechniques"; | |
| 
 | |
| const allQuestions = { | |
|   aerodynamics: aerodynamicsQuestions, | |
|   firstAid: firstAidQuestions, | |
|   flightSafety: flightSafetyQuestions, | |
|   instruments: instrumentsQuestions, | |
|   legislation: legislationQuestions, | |
|   materials: materialsQuestions, | |
|   meteorology: meteorologyQuestions, | |
|   physiopathology: physiopathologyQuestions, | |
|   pilotingTechniques: pilotingTechniquesQuestions | |
| }; | |
| 
 | |
| const bgImage = require("../assets/bg.jpg"); | |
| 
 | |
| const styles = StyleSheet.create({ | |
|   container: { flex: 1 }, | |
|   text: { color: colors.white, fontSize: 20, textAlign: "center", fontWeight: "600", paddingTop: 5, paddingBottom: 20 }, | |
|   textCode: { color: colors.white, fontSize: 12, textAlign: "center", fontWeight: "500", paddingTop: 20, paddingBottom: 0 }, | |
|   timer: { color: colors.white, fontSize: 30, textAlign: "center", fontWeight: "600", paddingVertical: 10, marginTop: 30, backgroundColor: colors.white_alpha, borderRadius: 10, textShadowColor: 'rgba(0, 0, 0, 0.45)', textShadowOffset: { width: -1, height: 1 }, textShadowRadius: 2 }, | |
|   safearea: { flex: 1, marginTop: 20, paddingHorizontal: 20, justifyContent: "space-between" }, | |
|   bg: { width: "100%", height: "100%" } | |
| }); | |
| 
 | |
| const MAX_TIME = 1800; // 30 minutes | |
|  | |
| class Exam extends React.Component { | |
|   constructor(props) { | |
|     super(props); | |
|     const { questions = [] } = props.route.params || {}; | |
| 
 | |
|     this.state = { | |
|       correctCount: 0, | |
|       wrongCount: 0, | |
|       pointsCount: 0, | |
|       totalPoints: 0, | |
|       wrongAnswers: [], | |
|       totalCount: questions.length, | |
|       availableIds: questions.map(q => q.id), | |
|       activeQuestionId: questions.length ? questions[Math.floor(Math.random() * questions.length)].id : null, | |
|       answered: false, | |
|       answerCorrect: false, | |
|       results: false, | |
|       timer: MAX_TIME, | |
|       clickedId: null | |
|     }; | |
| 
 | |
|     this.interval = null; | |
|   } | |
| 
 | |
|   componentDidMount() { | |
|     // BackHandler subscription | |
|     this.backHandler =  BackHandler.addEventListener( 'hardwareBackPress', this.handleBackButton) | |
| 
 | |
|     // Start timer | |
|     this.startTimer(); | |
|   } | |
| 
 | |
|   componentWillUnmount() { | |
|     this.backHandler?.remove() | |
|     if (this.interval) clearInterval(this.interval); | |
|   } | |
| 
 | |
|   startTimer = () => { | |
|     this.interval = setInterval(() => { | |
|       this.setState(prev => { | |
|         if (prev.timer <= 1) { | |
|           clearInterval(this.interval); | |
|           this.showResults(); | |
|           return { timer: 0, results: true }; | |
|         } | |
|         return { timer: prev.timer - 1 }; | |
|       }); | |
|     }, 1000); | |
|   } | |
| 
 | |
|   handleBackButton = () => { | |
|     let tmpQuestions = []; | |
| 
 | |
|     AsyncStorage.getItem('setupData').then((value) => { | |
|       const setupData = JSON.parse(value) || {}; | |
| 
 | |
|       examScheme.forEach(elem => { | |
|         let currentSection = setupData.excludeDelta | |
|           ? allQuestions[elem.section].filter(item => !item.delta) | |
|           : allQuestions[elem.section]; | |
| 
 | |
|         for (let i = 0; i < elem.questions; i++) { | |
|           const currentIndex = Math.floor(Math.random() * currentSection.length); | |
|           tmpQuestions.push(currentSection[currentIndex]); | |
|           currentSection = currentSection.filter((_, index) => index !== currentIndex); | |
|         } | |
|       }); | |
| 
 | |
|       this.props.navigation.navigate("Splash", { examQuestions: tmpQuestions }); | |
|     }); | |
| 
 | |
|     return true; | |
|   } | |
| 
 | |
|   showResults = () => { | |
|     this.props.navigation.navigate("Results", { | |
|       results: { | |
|         isExam: true, | |
|         total: this.state.totalCount, | |
|         correct: this.state.correctCount, | |
|         wrong: this.state.wrongCount, | |
|         points: this.state.pointsCount, | |
|         totalPoints: this.state.totalPoints, | |
|         wrongAnswers: this.state.wrongAnswers | |
|       } | |
|     }); | |
|   } | |
| 
 | |
|   answer = (correct, id, question) => { | |
|     this.setState(prev => { | |
|       const nextState = { | |
|         answered: true, | |
|         clickedId: id, | |
|         totalPoints: prev.totalPoints + parseInt(question.points) | |
|       }; | |
| 
 | |
|       if (correct) { | |
|         nextState.correctCount = prev.correctCount + 1; | |
|         nextState.pointsCount = prev.pointsCount + parseInt(question.points); | |
|         nextState.answerCorrect = true; | |
|       } else { | |
|         nextState.wrongCount = prev.wrongCount + 1; | |
|         nextState.answerCorrect = false; | |
|         nextState.wrongAnswers = [...prev.wrongAnswers, { | |
|           question: question.question, | |
|           id: question.id, | |
|           clicked: id, | |
|           answers: question.answers | |
|         }]; | |
|       } | |
| 
 | |
|       return nextState; | |
|     }, () => setTimeout(() => this.nextQuestion(), correct ? 750 : 3500)); | |
|   } | |
| 
 | |
|   nextQuestion = () => { | |
|     const { availableIds, activeQuestionId, correctCount, wrongCount, totalCount } = this.state; | |
|     const updatedIndexes = availableIds.filter(id => id !== activeQuestionId); | |
| 
 | |
|     if (!updatedIndexes.length || (correctCount + wrongCount) === totalCount) { | |
|       clearInterval(this.interval); | |
|       this.showResults(); | |
|       return; | |
|     } | |
| 
 | |
|     const nextId = updatedIndexes[Math.floor(Math.random() * updatedIndexes.length)]; | |
|     this.setState({ availableIds: updatedIndexes, activeQuestionId: nextId, answered: false, results: false, clickedId: null }); | |
|   } | |
| 
 | |
|   render() { | |
|     const { availableIds, activeQuestionId, results, correctCount, wrongCount, totalCount, clickedId } = this.state; | |
|     const questions = this.props.route?.params?.questions || []; | |
|     const question = questions.find(q => q.id === activeQuestionId) || questions[0]; | |
| 
 | |
|     return ( | |
|       <ImageBackground source={bgImage} style={styles.bg} resizeMode="cover"> | |
|         <ScrollView style={styles.container}> | |
|           <StatusBar barStyle="light-content" /> | |
| 
 | |
|           {!results && question ? ( | |
|             <SafeAreaView style={styles.safearea}> | |
|               <Text style={styles.timer}>{new Date(this.state.timer * 1000).toISOString().substr(11, 8)}</Text> | |
|               <Text style={styles.textCode}>{question.id}</Text> | |
|               <Text style={styles.text}>{question.question}</Text> | |
| 
 | |
|               <ButtonContainer> | |
|                 {question.answers.map(answer => ( | |
|                   <Button | |
|                     key={answer.id} | |
|                     text={answer.text} | |
|                     noBorder | |
|                     colorize={{ id: answer.id, clicked: clickedId, answered: this.state.answered, isCorrect: answer.correct }} | |
|                     onPress={() => this.answer(answer.correct, answer.id, question)} | |
|                   /> | |
|                 ))} | |
|               </ButtonContainer> | |
| 
 | |
|               <Text style={styles.text}>{`${correctCount + wrongCount}/${totalCount}`}</Text> | |
|             </SafeAreaView> | |
|           ) : ( | |
|             <SafeAreaView style={styles.safearea}></SafeAreaView> | |
|           )} | |
|         </ScrollView> | |
|       </ImageBackground> | |
|     ); | |
|   } | |
| } | |
| 
 | |
| export default Exam;
 |