diff --git a/node_modules/.yarn-integrity b/node_modules/.yarn-integrity
new file mode 100644
index 0000000..fe703d0
--- /dev/null
+++ b/node_modules/.yarn-integrity
@@ -0,0 +1,10 @@
+{
+ "systemParams": "linux-x64-137",
+ "modulesFolders": [],
+ "flags": [],
+ "linkedModules": [],
+ "topLevelPatterns": [],
+ "lockfileEntries": {},
+ "files": [],
+ "artifacts": {}
+}
\ No newline at end of file
diff --git a/vds-app/App/components/Banner.js b/vds-app/App/components/Banner.js
index 4afb78a..993b636 100644
--- a/vds-app/App/components/Banner.js
+++ b/vds-app/App/components/Banner.js
@@ -2,8 +2,8 @@ import React from "react"
import { View, StyleSheet, StatusBar, Text, Dimensions } from "react-native"
import { colors, texts, credentials } from "./Variables"
-import { BannerAd, BannerAdSize, TestIds } from 'react-native-google-mobile-ads'
-const adUnitId = __DEV__ ? TestIds.INTERSTITIAL : 'ca-app-pub-4145771316565790/1848957462'
+// import { BannerAd, BannerAdSize, TestIds } from 'react-native-google-mobile-ads'
+// const adUnitId = __DEV__ ? TestIds.INTERSTITIAL : 'ca-app-pub-4145771316565790/1848957462'
const screen = Dimensions.get("window")
@@ -26,7 +26,7 @@ export const Banner = () => {
if(__DEV__) {
banner = DEV BANNER
} else {
- banner =
+ banner = BANNER //
}
return (
diff --git a/vds-app/App/index.js b/vds-app/App/index.js
index 617da30..9774627 100644
--- a/vds-app/App/index.js
+++ b/vds-app/App/index.js
@@ -1,94 +1,43 @@
-import 'react-native-gesture-handler'
-import { createAppContainer } from "react-navigation"
-import { createStackNavigator } from "react-navigation-stack"
+import 'react-native-gesture-handler';
+import * as React from 'react';
+import { NavigationContainer } from '@react-navigation/native';
+import { createNativeStackNavigator } from '@react-navigation/native-stack';
-import Splash from "./screens/Splash"
-import QuizIndex from "./screens/QuizIndex"
-import Quiz from "./screens/Quiz"
-import TrueFalse from "./screens/TrueFalse"
-import Exam from "./screens/Exam"
-import Results from "./screens/Results"
-import ResultsTrueFalse from "./screens/ResultsTrueFalse"
-import Recap from "./screens/Recap"
-import RecapTrueFalse from "./screens/RecapTrueFalse"
-import Info from "./screens/Info"
-import Setup from "./screens/Setup"
-import Dictionary from "./screens/Dictionary"
-import { colors, texts} from "./components/Variables"
+import Splash from "./screens/Splash";
+import QuizIndex from "./screens/QuizIndex";
+import Quiz from "./screens/Quiz";
+import TrueFalse from "./screens/TrueFalse";
+import Exam from "./screens/Exam";
+import Results from "./screens/Results";
+import ResultsTrueFalse from "./screens/ResultsTrueFalse";
+import Recap from "./screens/Recap";
+import RecapTrueFalse from "./screens/RecapTrueFalse";
+import Info from "./screens/Info";
+import Setup from "./screens/Setup";
+import Dictionary from "./screens/Dictionary";
-const MainStack = createStackNavigator({
- Splash: {
- screen: Splash,
- navigationOptions: ({ navigation }) => ({
- headerShown: false
- })
- },
- RecapTrueFalse: {
- screen: RecapTrueFalse,
- navigationOptions: ({ navigation }) => ({
- headerShown: false
- })
- },
- Recap: {
- screen: Recap,
- navigationOptions: ({ navigation }) => ({
- headerShown: false
- })
- },
- Results: {
- screen: Results,
- navigationOptions: ({ navigation }) => ({
- headerShown: false
- })
- },
- ResultsTrueFalse: {
- screen: ResultsTrueFalse,
- navigationOptions: ({ navigation }) => ({
- headerShown: false
- })
- },
- Info: {
- screen: Info,
- navigationOptions: ({ navigation }) => ({
- headerShown: false
- })
- },
- Dictionary: {
- screen: Dictionary,
- navigationOptions: ({ navigation }) => ({
- headerShown: false
- })
- },
- QuizIndex: {
- screen: QuizIndex,
- navigationOptions: ({ navigation }) => ({
- headerShown: false
- })
- },
- Quiz: {
- screen: Quiz,
- navigationOptions: ({ navigation }) => ({
- headerShown: false
- })
- },
- TrueFalse: {
- screen: TrueFalse,
- navigationOptions: ({ navigation }) => ({
- headerShown: false
- })
- },
- Exam: {
- screen: Exam,
- navigationOptions: ({ navigation }) => ({
- headerShown: false
- })
- },
- Setup: {
- screen: Setup,
- navigationOptions: ({ navigation }) => ({
- headerShown: false
- })
- }
-})
+const Stack = createNativeStackNavigator();
-export default createAppContainer(MainStack)
+export default function App() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/vds-app/App/screens/Exam.js b/vds-app/App/screens/Exam.js
index 27e5a01..ee4ce36 100644
--- a/vds-app/App/screens/Exam.js
+++ b/vds-app/App/screens/Exam.js
@@ -1,20 +1,20 @@
-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"
+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,
@@ -26,107 +26,93 @@ const allQuestions = {
meteorology: meteorologyQuestions,
physiopathology: physiopathologyQuestions,
pilotingTechniques: pilotingTechniquesQuestions
-}
+};
-const bgImage = require("../assets/bg.jpg")
+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%"
- },
-})
-
-let interval = null
-const maxTime = 1800
+ 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%" }
+});
-class Exam extends React.Component {
+const MAX_TIME = 1800; // 30 minutes
- state = {
- correctCount: 0,
- pointsCount: 0,
- totalPoints: 0,
- wrongCount: 0,
- wrongAnswers: [],
- totalCount: this.props.navigation.getParam("questions", []).length,
- availableIds: this.props.navigation.getParam("questions", []).map(a => a.id),
- activeQuestionId: this.props.navigation.getParam("questions", [])[
- Math.floor(Math.random() * this.props.navigation.getParam("questions", []).length)
- ].id,
- answered: false,
- answerCorrect: false,
- results: false,
- timer: maxTime
+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.addEventListener('hardwareBackPress', this.handleBackButton)
+ // BackHandler subscription
+ this.backHandler = BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
+
+ // Start timer
+ this.startTimer();
}
componentWillUnmount() {
- BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton)
+ this.backHandler?.remove();
+ if (this.interval) clearInterval(this.interval);
}
- handleBackButton = () => {
+ 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);
+ }
- let tmpQuestions = []
+ handleBackButton = () => {
+ let tmpQuestions = [];
AsyncStorage.getItem('setupData').then((value) => {
- let 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 index != currentIndex)
- }
- })
+ const setupData = JSON.parse(value) || {};
- this.props.navigation.navigate("Splash", {
- examQuestions: tmpQuestions
- })
+ 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);
+ }
+ });
- return true
+ this.props.navigation.navigate("Splash", { examQuestions: tmpQuestions });
+ });
- })
+ return true;
}
showResults = () => {
@@ -140,125 +126,87 @@ class Exam extends React.Component {
totalPoints: this.state.totalPoints,
wrongAnswers: this.state.wrongAnswers
}
- })
+ });
}
answer = (correct, id, question) => {
- this.setState(
- state => {
- const nextState = { answered: true, clickedId: id, totalPoints: state.totalPoints + parseInt(question.points)}
-
- if (correct) {
- nextState.correctCount = state.correctCount + 1
- nextState.pointsCount = state.pointsCount + parseInt(question.points)
- nextState.answerCorrect = true
- } else {
- nextState.wrongCount = state.wrongCount + 1
- nextState.answerCorrect = false
- nextState.wrongAnswers = state.wrongAnswers
- nextState.wrongAnswers.push(
- { question: question.question,
- id: question.id,
- clicked: id,
- answers: question.answers
- }
- )
- }
- return nextState
- },
- () => {
- if(this.state.timer > 1 || (this.state.correctCount+this.state.wrongCount) < this.state.totalCount) {
- setTimeout(() => this.nextQuestion(), correct ? 750 : 3500)
- }
+ 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);
- const updatedIndexes = this.state.availableIds.filter( item => item != this.state.activeQuestionId)
- const nextId = updatedIndexes[Math.floor(Math.random() * updatedIndexes.length)]
- let resultsShow = (this.state.timer <= 1 || (this.state.correctCount+this.state.wrongCount) == this.state.totalCount) ? true : false
-
- if (!updatedIndexes.length) {
-
- clearInterval(interval)
- this.showResults()
-
- } else {
- this.setState( (state) => {
- return {
- availableIds: updatedIndexes,
- activeQuestionId: nextId,
- answered: false,
- results: resultsShow
- }
- })
+ if (!updatedIndexes.length || (correctCount + wrongCount) === totalCount) {
+ clearInterval(this.interval);
+ this.showResults();
+ return;
}
- }
- componentWillUnmount(){
- clearInterval(interval)
+ const nextId = updatedIndexes[Math.floor(Math.random() * updatedIndexes.length)];
+ this.setState({ availableIds: updatedIndexes, activeQuestionId: nextId, answered: false, results: false, clickedId: null });
}
render() {
- const questions = this.props.navigation.getParam("questions", [])
- const question = questions.filter(item => item.id == this.state.activeQuestionId)[0] || questions[0]
-
- if(this.state.timer==maxTime) {
- interval = setInterval( () => {
- this.setState( (state) => {
- return {
- timer: this.state.timer-1,
- results: this.state.timer <= 1 || false
- }
- })
- }, 1000)
- }
-
- if(this.state.timer < 1 || (this.state.correctCount+this.state.wrongCount) == this.state.totalCount) {
- clearInterval(interval)
- setTimeout ( () => {
- this.showResults()
- }, 1000)
- }
+ 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 (
-
-
-
- {!this.state.results ?
-
- {new Date(this.state.timer * 1000).toISOString().substr(11, 8)}
-
- {question.id}
- {question.question}
-
-
- {question.answers.map(answer => (
-
-
-
-
- {`${this.state.correctCount+this.state.wrongCount}/${this.state.totalCount}`}
-
-
- : }
-
-
+
+
+
+ {!results && question ? (
+
+ {new Date(this.state.timer * 1000).toISOString().substr(11, 8)}
+ {question.id}
+ {question.question}
+
+
+ {question.answers.map(answer => (
+
+
+ {`${correctCount + wrongCount}/${totalCount}`}
+
+ ) : (
+
+ )}
+
- )
+ );
}
}
-export default Exam
+export default Exam;
diff --git a/vds-app/App/screens/Quiz.js b/vds-app/App/screens/Quiz.js
index adeb928..c032cf6 100644
--- a/vds-app/App/screens/Quiz.js
+++ b/vds-app/App/screens/Quiz.js
@@ -1,105 +1,58 @@
-import React from "react"
-import { View, ScrollView, StyleSheet, StatusBar, Text, Dimensions, ImageBackground, BackHandler } from "react-native"
-import { Picker } from '@react-native-picker/picker'
-import SafeAreaView from 'react-native-safe-area-view'
-import AsyncStorage from '@react-native-async-storage/async-storage'
+import React from "react";
+import { View, ScrollView, StyleSheet, Text, Dimensions, ImageBackground, BackHandler } from "react-native";
+import { Picker } from '@react-native-picker/picker';
+import SafeAreaView from 'react-native-safe-area-view';
+import AsyncStorage from '@react-native-async-storage/async-storage';
-import { Button, ButtonContainer } from "../components/Button"
-import { Banner } from "../components/Banner"
-import { texts, colors, credentials } from "../components/Variables"
+import { Button, ButtonContainer } from "../components/Button";
+import { Banner } from "../components/Banner";
+import { texts, colors } from "../components/Variables";
-const bgImage = require("../assets/bg.jpg")
-const screen = Dimensions.get("window")
+const bgImage = require("../assets/bg.jpg");
+const screen = Dimensions.get("window");
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
- },
- safearea: {
- flex: 1,
- paddingHorizontal: 20,
- paddingTop: 20,
- justifyContent: "space-between"
- },
- box: {
- width: screen.width,
- paddingVertical: 10,
- overflow: "hidden"
- },
- scrollView: {
- //margin: 10,
- height: screen.height-20
- },
- bg: {
- width: "100%",
- height: "100%"
- },
- dropdownContainer: {
- marginTop: 20,
- borderRadius: 5,
- width: "100%",
- textAlign: "center",
- backgroundColor: colors.white_alpha2
- },
- dropdown: {
- color: colors.black,
- fontSize: 16,
- width: "100%",
- textAlign: "center",
- fontWeight: "600",
- backgroundColor: "transparent"
- },
- dropdownItem: {
- color: colors.white,
- backgroundColor: "red",
- fontSize: 16,
- borderRadius: 10,
- textAlign: "center",
- fontWeight: "600"
- },
- bannerContainer: {
- flex: 1,
- alignItems: "center",
- justifyContent: "center"
- }
-})
+ 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 },
+ safearea: { flex: 1, paddingHorizontal: 20, paddingTop: 20, justifyContent: "space-between" },
+ box: { width: screen.width, paddingVertical: 10, overflow: "hidden" },
+ scrollView: { height: screen.height - 20 },
+ bg: { width: "100%", height: "100%" },
+ dropdownContainer: { marginTop: 20, borderRadius: 5, width: "100%", textAlign: "center", backgroundColor: colors.white_alpha2 },
+ dropdown: { color: colors.black, fontSize: 16, width: "100%", textAlign: "center", fontWeight: "600", backgroundColor: "transparent" },
+ dropdownItem: { color: colors.white, backgroundColor: "red", fontSize: 16, borderRadius: 10, textAlign: "center", fontWeight: "600" },
+ bannerContainer: { flex: 1, alignItems: "center", justifyContent: "center" }
+});
class Quiz extends React.Component {
-
- state = {
- correctCount: 0,
- wrongCount: 0,
- wrongAnswers: [],
- pointsCount: 0,
- totalPoints: 0,
- totalCount: this.props.navigation.getParam("questions", []).length,
- availableIds: this.props.navigation.getParam("questions", []).map(a => a.id),
- availableQuestions: this.props.navigation.getParam("questions", []),
- activeQuestionId: this.props.navigation.getParam("questions", [])[
- this.props.navigation.getParam("randomQuestions") ?
- Math.floor(Math.random() * this.props.navigation.getParam("questions", []).length) : 0
- ].id,
- minIndex: 0,
- answered: false,
- answerCorrect: false,
- results: false,
- setupData: {}
+ constructor(props) {
+ super(props);
+
+ // Access route params safely
+ const { questions = [], randomQuestions = false, isWrong = false } = props.route.params || {};
+
+ this.state = {
+ correctCount: 0,
+ wrongCount: 0,
+ wrongAnswers: [],
+ pointsCount: 0,
+ totalPoints: 0,
+ totalCount: questions.length,
+ availableIds: questions.map(q => q.id),
+ availableQuestions: questions,
+ activeQuestionId: randomQuestions
+ ? questions[Math.floor(Math.random() * questions.length)]?.id
+ : questions[0]?.id,
+ minIndex: 0,
+ answered: false,
+ answerCorrect: false,
+ results: false,
+ setupData: {},
+ isWrongParam: isWrong,
+ randomQuestionsParam: randomQuestions,
+ clickedId: null
+ };
}
bannerError = (e) => {
@@ -107,66 +60,69 @@ class Quiz extends React.Component {
}
componentDidMount() {
- BackHandler.addEventListener('hardwareBackPress', this.handleBackButton)
+ // BackHandler subscription
+ this.backHandler = BackHandler.addEventListener(
+ 'hardwareBackPress',
+ this.handleBackButton
+ );
AsyncStorage.getItem('setupData').then((value) => {
- this.setState( (state) => {
- return {
- setupData: JSON.parse(value)
- }
- })
- })
+ this.setState({ setupData: JSON.parse(value) || {} });
+ });
+ }
+
+ componentWillUnmount() {
+ this.backHandler?.remove();
}
handleBackButton = () => {
- this.props.navigation.navigate("Splash")
- return true
+ this.props.navigation.navigate("Splash");
+ return true;
}
answer = (correct, id, question) => {
this.setState(
state => {
- const nextState = { answered: true, clickedId: id, totalPoints: state.totalPoints + parseInt(question.points)}
+ const nextState = { answered: true, clickedId: id, totalPoints: state.totalPoints + parseInt(question.points) };
if (correct) {
- nextState.correctCount = state.correctCount + 1
- nextState.pointsCount = state.pointsCount + parseInt(question.points)
- nextState.answerCorrect = true
+ nextState.correctCount = state.correctCount + 1;
+ nextState.pointsCount = state.pointsCount + parseInt(question.points);
+ nextState.answerCorrect = true;
} else {
- nextState.wrongCount = state.wrongCount + 1
- nextState.answerCorrect = false,
- nextState.wrongAnswers = state.wrongAnswers
- nextState.wrongAnswers.push(
- { question: question.question,
- id: question.id,
- clicked: id,
- answers: question.answers
- }
- )
+ nextState.wrongCount = state.wrongCount + 1;
+ nextState.answerCorrect = false;
+ nextState.wrongAnswers = [...state.wrongAnswers, {
+ question: question.question,
+ id: question.id,
+ clicked: id,
+ answers: question.answers
+ }];
}
- return nextState
+ return nextState;
},
- () => {
- setTimeout(() => this.nextQuestion(), correct ? 750 : 3000)
- }
- )
+ () => setTimeout(() => this.nextQuestion(), correct ? 750 : 3000)
+ );
}
nextQuestion = () => {
+ const { availableIds, availableQuestions, activeQuestionId, minIndex } = this.state;
- const updatedIndexes = this.state.availableIds.filter( item => item != this.state.activeQuestionId)
- const updatedQuestions = this.state.availableQuestions.filter( item => updatedIndexes.indexOf(item.id) > -1)
- const nextId = this.props.navigation.getParam("randomQuestions") ?
- updatedIndexes[Math.floor(Math.random() * updatedIndexes.length)] :
- updatedIndexes[this.state.minIndex]
- let resultsShow = (this.state.timer <= 1 || (this.state.correctCount+this.state.wrongCount) == this.state.totalCount) ? true : false
+ const updatedIndexes = availableIds.filter(item => item !== activeQuestionId);
+ const updatedQuestions = availableQuestions.filter(item => updatedIndexes.includes(item.id));
+
+ const nextId = this.state.randomQuestionsParam
+ ? updatedIndexes[Math.floor(Math.random() * updatedIndexes.length)]
+ : updatedIndexes[minIndex];
+
+ const resultsShow = !updatedIndexes.length || (this.state.correctCount + this.state.wrongCount) === this.state.totalCount;
if (!updatedIndexes.length) {
this.props.navigation.navigate("Results", {
results: {
isExam: false,
- isWrong: this.props.navigation.getParam("isWrong"),
+ isWrong: this.state.isWrongParam,
total: this.state.totalCount,
correct: this.state.correctCount,
wrong: this.state.wrongCount,
@@ -174,95 +130,84 @@ class Quiz extends React.Component {
totalPoints: this.state.totalPoints,
wrongAnswers: this.state.wrongAnswers
}
- })
-
+ });
} else {
-
- this.setState( (state) => {
- return {
- availableIds: updatedIndexes,
- availableQuestions: updatedQuestions,
- minIndex: this.state.minIndex >= updatedIndexes.length - 1 ? 0 : this.state.minIndex,
- activeQuestionId: nextId,
- answered: false,
- results: resultsShow
- }
- })
+ this.setState({
+ availableIds: updatedIndexes,
+ availableQuestions: updatedQuestions,
+ minIndex: minIndex >= updatedIndexes.length - 1 ? 0 : minIndex,
+ activeQuestionId: nextId,
+ answered: false,
+ results: resultsShow,
+ clickedId: null
+ });
}
}
jumpTo = (questionId, itemIndex) => {
- if(itemIndex) {
- this.setState( (state) => {
- return {
- activeQuestionId: questionId,
- minIndex: itemIndex-1
- }
- })
+ if (itemIndex) {
+ this.setState({
+ activeQuestionId: questionId,
+ minIndex: itemIndex - 1
+ });
}
}
render() {
- const questions = this.props.navigation.getParam("questions", [])
- const question = questions.filter(item => item.id == this.state.activeQuestionId)[0] || questions[0]
+ const { availableQuestions, activeQuestionId, results, correctCount, wrongCount, totalCount, clickedId } = this.state;
+
+ const question = availableQuestions.find(item => item.id === activeQuestionId) || availableQuestions[0];
return (
-
- {!this.state.results ?
-
-
- {question.id}
- {question.question}
+
+ {!results ? (
+
+ {question?.id}
+ {question?.question}
- {question.answers.map( (answer, index) => (
+ {question?.answers.map(answer => (
-
-
-
- {`${this.state.correctCount+this.state.wrongCount}/${this.state.totalCount}`}
-
-
-
- this.jumpTo(itemValue, itemIndex)}
- >
-
- {this.state.availableQuestions.map( (item, index) => (
-
- ))}
-
-
-
-
-
- : }
+ {`${correctCount + wrongCount}/${totalCount}`}
+
+
+ this.jumpTo(itemValue, itemIndex)}
+ >
+
+ {availableQuestions.map((item, index) => (
+
+ ))}
+
+
+
+ ) : (
+
+ )}
-
-
- )
+ );
}
}
-export default Quiz
+export default Quiz;
diff --git a/vds-app/App/screens/Recap.js b/vds-app/App/screens/Recap.js
index 3e12047..d89e194 100644
--- a/vds-app/App/screens/Recap.js
+++ b/vds-app/App/screens/Recap.js
@@ -1,25 +1,21 @@
-import React from "react"
-import { View, ScrollView, StyleSheet, StatusBar, Text, Dimensions, Image, 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 { Banner } from "../components/Banner"
-import { colors, texts, examScheme, credentials } 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 screen = Dimensions.get("window")
-const header = require("../assets/header.png")
-const bgImage = require("../assets/bg.jpg")
+import React from "react";
+import { View, ScrollView, StyleSheet, Text, Image, 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 { Banner } from "../components/Banner";
+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,
@@ -31,130 +27,77 @@ const allQuestions = {
meteorology: meteorologyQuestions,
physiopathology: physiopathologyQuestions,
pilotingTechniques: pilotingTechniquesQuestions
-}
+};
+
+const header = require("../assets/header.png");
const styles = StyleSheet.create({
- container: {
- backgroundColor: colors.dark_blue,
- flex: 1
- },
- safearea: {
- flex: 1,
- marginTop: 0,
- justifyContent: "space-between",
- paddingHorizontal: 20,
- paddingBottom: 40
- },
- headerContainer: {
- marginTop: -40,
- alignItems: "center",
- justifyContent: "center",
- width: "100%",
- height: screen.width/1.5
- },
- header: {
- width: "100%"
- },
- box: {
- marginTop: 30,
- borderColor: colors.black_alpha,
- borderWidth: 1,
- padding: 15,
- borderRadius: 5,
- backgroundColor: colors.white_alpha
- },
- text: {
- color: colors.white,
- fontSize: 20,
- textAlign: "center",
- fontWeight: "600",
- paddingTop: 0
- },
- textCode: {
- color: colors.white,
- fontSize: 12,
- textAlign: "center",
- fontWeight: "500",
- paddingTop: 10,
- paddingBottom: 0
- },
- textBig: {
- color: colors.white,
- fontSize: 22,
- textAlign: "center",
- fontWeight: "400",
- paddingBottom: 15,
- textTransform: "uppercase",
- textShadowColor: 'rgba(0, 0, 0, 0.75)',
- textShadowOffset: {width: -1, height: 1},
- textShadowRadius: 10
- },
- bg: {
- width: "100%",
- height: "100%"
- },
- bannerContainer: {
- flex: 1,
- alignItems: "center",
- justifyContent: "center"
- }
-})
+ container: { backgroundColor: colors.dark_blue, flex: 1 },
+ safearea: { flex: 1, marginTop: 0, justifyContent: "space-between", paddingHorizontal: 20, paddingBottom: 40 },
+ headerContainer: { marginTop: -40, alignItems: "center", justifyContent: "center", width: "100%", height: 200 },
+ header: { width: "100%" },
+ box: { marginTop: 30, borderColor: colors.black_alpha, borderWidth: 1, padding: 15, borderRadius: 5, backgroundColor: colors.white_alpha },
+ text: { color: colors.white, fontSize: 20, textAlign: "center", fontWeight: "600", paddingTop: 0 },
+ textCode: { color: colors.white, fontSize: 12, textAlign: "center", fontWeight: "500", paddingTop: 10, paddingBottom: 0 },
+ textBig: { color: colors.white, fontSize: 22, textAlign: "center", fontWeight: "400", paddingBottom: 15, textTransform: "uppercase", textShadowColor: 'rgba(0, 0, 0, 0.75)', textShadowOffset: { width: -1, height: 1 }, textShadowRadius: 10 },
+ bannerContainer: { flex: 1, alignItems: "center", justifyContent: "center" }
+});
class Recap extends React.Component {
-
- state = {
- storeWrongAnswers: []
+ constructor(props) {
+ super(props);
+ this.state = {
+ storeWrongAnswers: []
+ };
}
componentDidMount() {
- BackHandler.addEventListener('hardwareBackPress', this.handleBackButton)
- AsyncStorage.getItem('storeWrongAnswers').then((value) => {
- //console.log('storeWrongAnswers: ', JSON.parse(value))
- this.setState( (state) => {
- return {
- storeWrongAnswers: JSON.parse(value)
- }
- })
- })
+ this.backHandler = BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
+
+ AsyncStorage.getItem('storeWrongAnswers').then(value => {
+ if (value) {
+ this.setState({ storeWrongAnswers: JSON.parse(value) });
+ }
+ });
}
componentWillUnmount() {
- BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton)
+ this.backHandler?.remove();
}
handleBackButton = () => {
+ const tmpQuestions = [];
+ AsyncStorage.getItem('setupData').then(value => {
+ const setupData = JSON.parse(value) || {};
- let tmpQuestions = []
- AsyncStorage.getItem('setupData').then((value) => {
- let setupData = JSON.parse(value)
+ examScheme.forEach(elem => {
+ let currentSection = setupData.excludeDelta
+ ? allQuestions[elem.section].filter(item => !item.delta)
+ : allQuestions[elem.section];
- examScheme.forEach( (elem) => {
- let currentSection = setupData.excludeDelta ? allQuestions[elem.section].filter(item => !item.delta) : allQuestions[elem.section]
- for(let i=0; i index != currentIndex)
+ 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,
storeWrongAnswers: this.state.storeWrongAnswers
- })
-
- return true
+ });
+ });
- })
+ return true;
}
render() {
-
- const questions = this.props.navigation.getParam("wrongAnswers", [])
+ const questions = this.props.route.params?.wrongAnswers || [];
return (
-
-
+
+
@@ -162,31 +105,30 @@ class Recap extends React.Component {
- {questions.map( (question, index) => (
+ {questions.map(question => (
{question.id}
{question.question}
- {question.answers.map( (answer, index) => (
+ {question.answers.map(answer => (
))}
))}
-
+
@@ -195,11 +137,9 @@ class Recap extends React.Component {
-
-
- )
+ );
}
}
-export default Recap
+export default Recap;
diff --git a/vds-app/App/screens/Results.js b/vds-app/App/screens/Results.js
index a57d9d4..42d6646 100644
--- a/vds-app/App/screens/Results.js
+++ b/vds-app/App/screens/Results.js
@@ -1,10 +1,10 @@
-import React from "react"
-import { View, ScrollView, StyleSheet, StatusBar, Text, Dimensions, Image, BackHandler} from "react-native"
-import SafeAreaView from 'react-native-safe-area-view'
-import AsyncStorage from '@react-native-async-storage/async-storage'
+import React from "react";
+import { View, ScrollView, StyleSheet, Text, Image, BackHandler } from "react-native";
+import SafeAreaView from 'react-native-safe-area-view';
-import { Button, ButtonContainer } from "../components/Button"
-import { colors, texts, examScheme } from "../components/Variables"
+import { Button, ButtonContainer } from "../components/Button";
+import { Banner } from "../components/Banner";
+import { colors, texts, examScheme } from "../components/Variables";
import aerodynamicsQuestions from "../data/aerodynamics"
import firstAidQuestions from "../data/firstAid"
@@ -16,11 +16,6 @@ import meteorologyQuestions from "../data/meteorology"
import physiopathologyQuestions from "../data/physiopathology"
import pilotingTechniquesQuestions from "../data/pilotingTechniques"
-const screen = Dimensions.get("window")
-const header = require("../assets/header.png")
-const maxTime = 0 // 10
-let interval = null
-
const allQuestions = {
aerodynamics: aerodynamicsQuestions,
firstAid: firstAidQuestions,
@@ -31,234 +26,93 @@ const allQuestions = {
meteorology: meteorologyQuestions,
physiopathology: physiopathologyQuestions,
pilotingTechniques: pilotingTechniquesQuestions
-}
+};
-const styles = StyleSheet.create({
- container: {
- backgroundColor: colors.dark_blue,
- flex: 1
- },
- safearea: {
- flex: 1,
- marginTop: 0,
- justifyContent: "space-between",
- paddingHorizontal: 20,
- paddingBottom: 40
- },
- headerContainer: {
- marginTop: -40,
- alignItems: "center",
- justifyContent: "center",
- width: "100%",
- height: screen.width/1.5
- },
- header: {
- width: "100%"
- },
- box: {
- marginTop: 20,
- marginBottom: 20,
- marginHorizontal: 20,
- width: screen.width-80,
- borderRadius: 10,
- borderBottomEndRadius: 80,
- borderTopStartRadius: 100,
- backgroundColor: colors.white_alpha,
- borderColor: colors.white,
- borderWidth: 2,
- paddingVertical: 30
- },
- boxCorrect: {
- backgroundColor: colors.green_light,
- },
- boxWrong: {
- backgroundColor: colors.red,
- },
- boxUnsafe: {
- backgroundColor: colors.orange,
- },
- button: {
- width: screen.width-80,
- marginHorizontal: 20
- },
- text: {
- color: colors.white,
- fontSize: 22,
- textAlign: "center",
- fontWeight: "400",
- lineHeight: 40,
- textShadowColor: 'rgba(0, 0, 0, 0.75)',
- textShadowOffset: {width: -1, height: 1},
- textShadowRadius: 10
- },
- textSmall: {
- color: colors.white,
- marginTop: 20,
- fontSize: 26,
- textAlign: "center",
- fontWeight: "500",
- lineHeight: 30,
- textShadowColor: 'rgba(0, 0, 0, 0.75)',
- textShadowOffset: {width: -1, height: 1},
- textShadowRadius: 10
- },
- textLabel: {
- paddingHorizontal: 20,
- paddingVertical: 20
- },
- correct: {
- color: colors.green
- },
- wrong: {
- color: colors.red
- },
- unsafe: {
- color: colors.yellow
- }
-})
-
-class Results extends React.Component {
-
- state = {
- bannerExpanded: true,
- timer: maxTime,
- storeWrongAnswers: []
- }
+const header = require("../assets/header.png");
+const styles = StyleSheet.create({
+ container: { backgroundColor: colors.blue, flex: 1 },
+ safearea: { flex: 1, marginTop: 0, justifyContent: "space-between", paddingHorizontal: 20, paddingBottom: 40 },
+ headerContainer: { marginTop: -40, alignItems: "center", justifyContent: "center", width: "100%", height: 200 },
+ header: { width: "100%" },
+ box: { marginTop: 30, borderColor: colors.black_alpha, borderWidth: 1, padding: 15, borderRadius: 5, backgroundColor: colors.white_alpha },
+ text: { color: colors.white, fontSize: 20, textAlign: "center", fontWeight: "600" },
+ textCode: { color: colors.white, fontSize: 12, textAlign: "center", fontWeight: "500", paddingTop: 10 },
+ textBig: { color: colors.white, fontSize: 22, textAlign: "center", fontWeight: "400", paddingBottom: 15, textTransform: "uppercase", textShadowColor: 'rgba(0,0,0,0.75)', textShadowOffset: {width:-1, height:1}, textShadowRadius:10 },
+ bannerContainer: { flex: 1, alignItems: "center", justifyContent: "center" }
+});
+
+class RecapTrueFalse extends React.Component {
componentDidMount() {
- BackHandler.addEventListener('hardwareBackPress', this.handleBackButton)
-
- AsyncStorage.getItem('storeWrongAnswers').then( (value) => {
- const currentResults = this.props.navigation.getParam("results")
- const wrongAnswers = currentResults.wrongAnswers || []
- const stored = JSON.parse(value) || []
- const result = currentResults.isWrong ? wrongAnswers : Object.assign([], wrongAnswers, stored);
- AsyncStorage.setItem('storeWrongAnswers', JSON.stringify(result))
-
- this.setState( (state) => {
- return {
- storeWrongAnswers: result
- }
- })
-
- })
+ this.backHandler = BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
}
componentWillUnmount() {
- BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton)
+ this.backHandler?.remove();
}
handleBackButton = () => {
+ const tmpQuestions = [];
+ let fullQuestions = [];
- let tmpQuestions = []
- AsyncStorage.getItem('setupData').then((value) => {
- let setupData = JSON.parse(value)
+ examScheme.forEach(elem => {
+ fullQuestions.push(...allQuestions[elem.section]);
+ });
- examScheme.forEach( (elem) => {
- let currentSection = setupData.excludeDelta ? allQuestions[elem.section].filter(item => !item.delta) : allQuestions[elem.section]
- for(let i=0; i index != currentIndex)
- }
- })
-
- this.props.navigation.navigate("Splash", {
- examQuestions: tmpQuestions,
- storeWrongAnswers: this.state.storeWrongAnswers
- })
-
- return true
+ for (let i = 0; i < 10; i++) {
+ const index = Math.floor(Math.random() * fullQuestions.length);
+ tmpQuestions.push(fullQuestions[index]);
+ fullQuestions.splice(index, 1);
+ }
- })
+ this.props.navigation.navigate("Splash", { trueFalseQuestions: tmpQuestions });
+ return true;
}
-
render() {
-
- const currentResults = this.props.navigation.getParam("results")
- const wrongAnswers = currentResults.wrongAnswers || null
- const percentage = currentResults.total ? (100/currentResults.total) * currentResults.correct : 0
- let resultStyle = ''//currentResults.points >= 80 ? currentResults.points >= 85 ? styles.correct : styles.unsafe : styles.wrong
- let boxStyle = currentResults.points >= 80 ? currentResults.points >= 85 ? styles.boxCorrect : styles.boxUnsafe : styles.boxWrong
-
- if(!currentResults.isExam) {
- resultStyle = ''//percentage >= 80 ? percentage >= 85 ? styles.correct : styles.unsafe : styles.wrong
- boxStyle = percentage >= 80 ? percentage >= 85 ? styles.boxCorrect : styles.boxUnsafe : styles.boxWrong
- }
-
+ const questions = this.props.route.params?.wrongAnswers || [];
return (
-
-
+
+
+ {texts.recapTitle}
+
-
-
- {`${texts.corrects}: ${currentResults.correct}`}
-
-
- {`${texts.wrongs}: ${currentResults.wrong}`}
-
-
- {`${texts.percentage}: ${Math.round(percentage)}%`}
-
- {
- currentResults.points ? (
-
- {`${texts.points}: ${currentResults.points}/${currentResults.totalPoints}`}
-
- ) : null
- }
-
- {currentResults.isExam ?
-
- {currentResults.points >= 80 ? currentResults.points >= 85 ? texts.exam_passed : texts.exam_needs_oral : texts.exam_not_passed}
- :
- }
-
-
- {wrongAnswers.length ?
-
- :
-
- {this.handleBackButton()}
- }
- />
+ {questions.map(q => (
+
+ {q.id}
+ {q.question}
+
+
+ {q.answers.map(ans => (
+ q.clicked === ans.id && (
+
+ )
+ ))}
+
- }
+ ))}
+
+
+
-
+
+
+
+
- )
+ );
}
}
-export default Results
+export default RecapTrueFalse;
diff --git a/vds-app/App/screens/ResultsTrueFalse.js b/vds-app/App/screens/ResultsTrueFalse.js
index 287635a..42d6646 100644
--- a/vds-app/App/screens/ResultsTrueFalse.js
+++ b/vds-app/App/screens/ResultsTrueFalse.js
@@ -1,9 +1,10 @@
-import React from "react"
-import { View, ScrollView, StyleSheet, StatusBar, Text, Dimensions, Image, BackHandler} from "react-native"
-import SafeAreaView from 'react-native-safe-area-view'
+import React from "react";
+import { View, ScrollView, StyleSheet, Text, Image, BackHandler } from "react-native";
+import SafeAreaView from 'react-native-safe-area-view';
-import { Button, ButtonContainer } from "../components/Button"
-import { colors, texts, examScheme } from "../components/Variables"
+import { Button, ButtonContainer } from "../components/Button";
+import { Banner } from "../components/Banner";
+import { colors, texts, examScheme } from "../components/Variables";
import aerodynamicsQuestions from "../data/aerodynamics"
import firstAidQuestions from "../data/firstAid"
@@ -15,11 +16,6 @@ import meteorologyQuestions from "../data/meteorology"
import physiopathologyQuestions from "../data/physiopathology"
import pilotingTechniquesQuestions from "../data/pilotingTechniques"
-const screen = Dimensions.get("window")
-const header = require("../assets/header.png")
-const maxTime = 0 // 10
-let interval = null
-
const allQuestions = {
aerodynamics: aerodynamicsQuestions,
firstAid: firstAidQuestions,
@@ -30,205 +26,93 @@ const allQuestions = {
meteorology: meteorologyQuestions,
physiopathology: physiopathologyQuestions,
pilotingTechniques: pilotingTechniquesQuestions
-}
-
-const styles = StyleSheet.create({
- container: {
- backgroundColor: colors.dark_blue,
- flex: 1
- },
- safearea: {
- flex: 1,
- marginTop: 0,
- justifyContent: "space-between",
- paddingHorizontal: 20,
- paddingBottom: 40
- },
- headerContainer: {
- marginTop: -40,
- alignItems: "center",
- justifyContent: "center",
- width: "100%",
- height: screen.width/1.5
- },
- header: {
- width: "100%"
- },
- box: {
- marginTop: 20,
- marginBottom: 20,
- marginHorizontal: 20,
- width: screen.width-80,
- borderRadius: 10,
- borderBottomEndRadius: 80,
- borderTopStartRadius: 100,
- backgroundColor: colors.white_alpha,
- borderColor: colors.white,
- borderWidth: 2,
- paddingVertical: 30
- },
- boxCorrect: {
- backgroundColor: colors.green_light,
- },
- boxWrong: {
- backgroundColor: colors.red,
- },
- boxUnsafe: {
- backgroundColor: colors.orange,
- },
- button: {
- width: screen.width-80,
- marginHorizontal: 20
- },
- text: {
- color: colors.white,
- fontSize: 22,
- textAlign: "center",
- fontWeight: "400",
- lineHeight: 40,
- textShadowColor: 'rgba(0, 0, 0, 0.75)',
- textShadowOffset: {width: -1, height: 1},
- textShadowRadius: 10
- },
- textSmall: {
- color: colors.white,
- marginTop: 20,
- fontSize: 26,
- textAlign: "center",
- fontWeight: "500",
- lineHeight: 30,
- textShadowColor: 'rgba(0, 0, 0, 0.75)',
- textShadowOffset: {width: -1, height: 1},
- textShadowRadius: 10
- },
- textLabel: {
- paddingHorizontal: 20,
- paddingVertical: 20
- },
- correct: {
- color: colors.green
- },
- wrong: {
- color: colors.red
- },
- unsafe: {
- color: colors.yellow
- }
-})
+};
-class Results extends React.Component {
-
- state = {
- bannerExpanded: true,
- timer: maxTime
- }
+const header = require("../assets/header.png");
+const styles = StyleSheet.create({
+ container: { backgroundColor: colors.blue, flex: 1 },
+ safearea: { flex: 1, marginTop: 0, justifyContent: "space-between", paddingHorizontal: 20, paddingBottom: 40 },
+ headerContainer: { marginTop: -40, alignItems: "center", justifyContent: "center", width: "100%", height: 200 },
+ header: { width: "100%" },
+ box: { marginTop: 30, borderColor: colors.black_alpha, borderWidth: 1, padding: 15, borderRadius: 5, backgroundColor: colors.white_alpha },
+ text: { color: colors.white, fontSize: 20, textAlign: "center", fontWeight: "600" },
+ textCode: { color: colors.white, fontSize: 12, textAlign: "center", fontWeight: "500", paddingTop: 10 },
+ textBig: { color: colors.white, fontSize: 22, textAlign: "center", fontWeight: "400", paddingBottom: 15, textTransform: "uppercase", textShadowColor: 'rgba(0,0,0,0.75)', textShadowOffset: {width:-1, height:1}, textShadowRadius:10 },
+ bannerContainer: { flex: 1, alignItems: "center", justifyContent: "center" }
+});
+
+class RecapTrueFalse extends React.Component {
componentDidMount() {
- BackHandler.addEventListener('hardwareBackPress', this.handleBackButton)
+ this.backHandler = BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
}
componentWillUnmount() {
- BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton)
+ this.backHandler?.remove();
}
handleBackButton = () => {
+ const tmpQuestions = [];
+ let fullQuestions = [];
- let tmpQuestions = []
- let fullQuestions = []
+ examScheme.forEach(elem => {
+ fullQuestions.push(...allQuestions[elem.section]);
+ });
- examScheme.forEach( (elem) => {
- let currentSection = allQuestions[elem.section]
- for(let i=0; i index != currentIndex)
+ for (let i = 0; i < 10; i++) {
+ const index = Math.floor(Math.random() * fullQuestions.length);
+ tmpQuestions.push(fullQuestions[index]);
+ fullQuestions.splice(index, 1);
}
- this.props.navigation.navigate("Splash", {
- trueFalseQuestions: tmpQuestions
- })
- return true
+ this.props.navigation.navigate("Splash", { trueFalseQuestions: tmpQuestions });
+ return true;
}
render() {
-
- const currentResults = this.props.navigation.getParam("results")
- const wrongAnswers = currentResults.wrongAnswers || null
- const percentage = currentResults.total ? (100/currentResults.total) * currentResults.correct : 0
- let resultStyle = ''
- let boxStyle = currentResults.points >= 80 ? currentResults.points >= 85 ? styles.boxCorrect : styles.boxUnsafe : styles.boxWrong
-
- if(!currentResults.isExam) {
- resultStyle = ''
- boxStyle = percentage >= 80 ? percentage >= 85 ? styles.boxCorrect : styles.boxUnsafe : styles.boxWrong
- }
-
- //console.log(currentResults)
+ const questions = this.props.route.params?.wrongAnswers || [];
return (
-
-
+
+
+ {texts.recapTitle}
+
-
-
- {`${texts.corrects}: ${currentResults.correct}`}
-
-
- {`${texts.wrongs}: ${currentResults.wrong}`}
-
-
- {`${texts.percentage}: ${Math.round(percentage)}%`}
-
-
-
-
- {wrongAnswers.length ?
-
- {
- this.props.navigation.navigate("RecapTrueFalse", {
- wrongAnswers: wrongAnswers
- })
- }}/>
- {this.handleBackButton()}
- }
- />
-
- :
-
- {this.handleBackButton()}
- }
- />
+ {questions.map(q => (
+
+ {q.id}
+ {q.question}
+
+
+ {q.answers.map(ans => (
+ q.clicked === ans.id && (
+
+ )
+ ))}
+
- }
+ ))}
+
+
+
-
+
+
+
+
- )
+ );
}
}
-export default Results
+export default RecapTrueFalse;
diff --git a/vds-app/App/screens/Splash.js b/vds-app/App/screens/Splash.js
index 9333e9b..a70742d 100644
--- a/vds-app/App/screens/Splash.js
+++ b/vds-app/App/screens/Splash.js
@@ -1,78 +1,39 @@
-import React from "react"
-import { View, ScrollView, StyleSheet, StatusBar, Text, Dimensions, Image, Alert, BackHandler } from "react-native"
-import AsyncStorage from '@react-native-async-storage/async-storage'
-import SafeAreaView from 'react-native-safe-area-view'
-
-import { Button, ButtonContainer } from "../components/Button"
-import { Banner } from "../components/Banner"
-import { colors, texts, credentials } from "../components/Variables"
-import { examQuestions } from "../components/ExamQuestions"
-import { trueFalseQuestions } from "../components/TrueFalseQuestions"
-
-const screen = Dimensions.get("window")
-const header = require("../assets/header.png")
-const pkg = require('../../app.json')
-const maxTime = 0 // 10
-let interval = null
+import React from "react";
+import { View, ScrollView, StyleSheet, StatusBar, Text, Dimensions, Image, Alert, BackHandler } from "react-native";
+import AsyncStorage from '@react-native-async-storage/async-storage';
+import SafeAreaView from 'react-native-safe-area-view';
+
+import { Button, ButtonContainer } from "../components/Button";
+import { Banner } from "../components/Banner";
+import { colors, texts } from "../components/Variables";
+import { examQuestions } from "../components/ExamQuestions";
+import { trueFalseQuestions } from "../components/TrueFalseQuestions";
+
+const screen = Dimensions.get("window");
+const header = require("../assets/header.png");
+const maxTime = 0; // 10
+let interval = null;
const styles = StyleSheet.create({
- container: {
- backgroundColor: colors.dark_blue,
- flex: 1
- },
- title: {
- color: colors.white,
- fontSize: 25,
- textAlign: "center",
- fontWeight: "600",
- paddingVertical: 20
- },
- text: {
- color: colors.white,
- fontSize: 20,
- textAlign: "center",
- fontWeight: "400",
- paddingVertical: 20,
- marginTop: 20,
- },
- timer: {
- color: colors.white,
- fontSize: 30,
- textAlign: "center",
- fontWeight: "600",
- paddingVertical: 20,
- marginBottom: 20,
- },
- safearea: {
- flex: 1,
- marginTop: 0,
- justifyContent: "space-between",
- paddingHorizontal: 20
- },
- headerContainer: {
- marginTop: 20,
- alignItems: "center",
- justifyContent: "center",
- width: "100%",
- height: 150
- },
- header: {
- width: "100%"
- },
- bannerContainer: {
- flex: 1,
- alignItems: "center",
- justifyContent: "center"
- }
-})
+ container: { backgroundColor: colors.dark_blue, flex: 1 },
+ safearea: { flex: 1, justifyContent: "space-between", paddingHorizontal: 20 },
+ headerContainer: { marginTop: 20, alignItems: "center", justifyContent: "center", width: "100%", height: 150 },
+ header: { width: "100%" },
+ bannerContainer: { flex: 1, alignItems: "center", justifyContent: "center" }
+});
class Splash extends React.Component {
+ constructor(props) {
+ super(props);
+
+ const params = props.route.params || {};
- state = {
- bannerExpanded: true,
- timer: maxTime,
- storeWrongAnswers: this.props.navigation.getParam("storeWrongAnswers", []) || [],
- setupData: {}
+ this.state = {
+ bannerExpanded: true,
+ timer: maxTime,
+ storeWrongAnswers: params.storeWrongAnswers || [],
+ setupData: {}
+ };
}
bannerError = (e) => {
@@ -80,73 +41,68 @@ class Splash extends React.Component {
}
componentDidMount() {
- BackHandler.addEventListener('hardwareBackPress', this.handleBackButton)
- AsyncStorage.getItem('storeWrongAnswers').then( (value) => {
- if(!value) {
- AsyncStorage.setItem('storeWrongAnswers', JSON.stringify([]))
- }
-
- AsyncStorage.getItem('setupData').then((setup) => {
- if(!setup) {
- AsyncStorage.setItem('setupData', JSON.stringify({}))
- }
- this.setState( (state) => {
- return {
- storeWrongAnswers: value ? JSON.parse(value) : [],
- setupData: setup ? JSON.parse(setup) : {}
- }
- })
- })
- })
+ // Save subscription for removal later
+ this.backHandler = BackHandler.addEventListener(
+ 'hardwareBackPress',
+ this.handleBackButton
+ );
+ AsyncStorage.getItem('storeWrongAnswers').then((value) => {
+ if (!value) AsyncStorage.setItem('storeWrongAnswers', JSON.stringify([]));
+ AsyncStorage.getItem('setupData').then((setup) => {
+ if (!setup) AsyncStorage.setItem('setupData', JSON.stringify({}));
+
+ this.setState({
+ storeWrongAnswers: value ? JSON.parse(value) : [],
+ setupData: setup ? JSON.parse(setup) : {}
+ });
+ });
+ });
}
componentWillUnmount() {
- BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton)
+ // Remove subscription correctly
+ this.backHandler?.remove();
+
+ if (interval) clearInterval(interval);
}
+
handleBackButton = () => {
Alert.alert(
texts.exit,
texts.exitQuestion,
[
- {text: 'No', onPress: () => console.log('Cancel Pressed'), style: 'cancel'},
- {text: 'Si', onPress: () => BackHandler.exitApp()},
+ { text: 'No', onPress: () => console.log('Cancel Pressed'), style: 'cancel' },
+ { text: 'Si', onPress: () => BackHandler.exitApp() },
],
{ cancelable: false }
- )
- return true
+ );
+ return true;
}
- render() {
-
- const storeWrongAnswers = this.props.navigation.getParam("storeWrongAnswers") || this.state.storeWrongAnswers
-
- if(this.state.timer==maxTime) {
- interval = setInterval( () => {
- this.setState( (state) => {
- return {
- timer: this.state.timer-1,
- }
- })
- }, 1000)
+ startTimer = () => {
+ if (this.state.timer === maxTime) {
+ interval = setInterval(() => {
+ this.setState((state) => ({ timer: state.timer - 1 }));
+ }, 1000);
}
- if(this.state.timer < 1) {
- clearInterval(interval)
- setTimeout( () => {
- this.setState( (state) => {
- return {
- bannerExpanded: false
- }
- })
- }, 500)
+ if (this.state.timer < 1 && interval) {
+ clearInterval(interval);
+ interval = null;
+ setTimeout(() => this.setState({ bannerExpanded: false }), 500);
}
+ }
+
+ render() {
+ const { storeWrongAnswers } = this.state;
+ this.startTimer();
return (
-
-
+
+
@@ -157,127 +113,123 @@ class Splash extends React.Component {
text={texts.section_quizzes}
subtitle={`(${texts.section_quizzes_subtitle})`}
isBig={false}
- hasBg={true}
+ hasBg
noPadding={false}
- hasShadow={true}
- noBorder={true}
+ hasShadow
+ noBorder
color={colors.white_alpha}
- onPress={() =>
- this.props.navigation.navigate("QuizIndex", {
- title: texts.section_quizzes,
- color: colors.white_alpha
- })}
+ onPress={() => this.props.navigation.navigate("QuizIndex", {
+ title: texts.section_quizzes,
+ color: colors.white_alpha
+ })}
/>
- this.props.navigation.navigate("Exam", {
- title: texts.exam,
- questions: this.props.navigation.getParam("examQuestions") || examQuestions,
- color: colors.white_alpha
- })}
+ onPress={() => this.props.navigation.navigate("Exam", {
+ title: texts.exam,
+ questions: this.props.route.params?.examQuestions || examQuestions,
+ color: colors.white_alpha
+ })}
/>
- {
- storeWrongAnswers.length ? (
-
- this.props.navigation.navigate("Quiz", {
- title: texts.wrong_review,
- questions: storeWrongAnswers,
- isWrong: true,
- color: colors.blue
- })}
- />
- ) : null
- }
+ {storeWrongAnswers.length ? (
+ this.props.navigation.navigate("Quiz", {
+ title: texts.wrong_review,
+ questions: storeWrongAnswers,
+ isWrong: true,
+ color: colors.blue
+ })}
+ />
+ ) : null}
- this.props.navigation.navigate("TrueFalse", {
- title: texts.trueFalse,
- questions: this.props.navigation.getParam("trueFalseQuestions") || trueFalseQuestions,
- color: colors.white_alpha
- })}
+ onPress={() => this.props.navigation.navigate("TrueFalse", {
+ title: texts.trueFalse,
+ questions: this.props.route.params?.trueFalseQuestions || trueFalseQuestions,
+ color: colors.white_alpha
+ })}
/>
+
this.props.navigation.navigate("Dictionary", {})}
+ onPress={() => this.props.navigation.navigate("Dictionary")}
/>
+
this.props.navigation.navigate("Setup", {})}
+ onPress={() => this.props.navigation.navigate("Setup")}
/>
+
this.props.navigation.navigate("Info", {})}
+ onPress={() => this.props.navigation.navigate("Info")}
/>
+
this.handleBackButton()}
+ onPress={this.handleBackButton}
/>
-
-
+
-
- )
+ );
}
}
-export default Splash
+export default Splash;
diff --git a/vds-app/App/screens/TrueFalse.js b/vds-app/App/screens/TrueFalse.js
index 97ef47f..6aaebed 100644
--- a/vds-app/App/screens/TrueFalse.js
+++ b/vds-app/App/screens/TrueFalse.js
@@ -1,10 +1,10 @@
-import React from "react"
-import { View, ScrollView, StyleSheet, StatusBar, Text, Dimensions, ImageBackground, BackHandler } from "react-native"
-import SafeAreaView from 'react-native-safe-area-view'
+import React from "react";
+import { View, ScrollView, StyleSheet, Text, ImageBackground, Dimensions, BackHandler } from "react-native";
+import SafeAreaView from 'react-native-safe-area-view';
-import { Button, ButtonContainer } from "../components/Button"
-import { Banner } from "../components/Banner"
-import { texts, colors, examScheme, credentials } from "../components/Variables"
+import { Button, ButtonContainer } from "../components/Button";
+import { Banner } from "../components/Banner";
+import { texts, colors, examScheme } from "../components/Variables";
import aerodynamicsQuestions from "../data/aerodynamics"
import firstAidQuestions from "../data/firstAid"
@@ -26,10 +26,11 @@ const allQuestions = {
meteorology: meteorologyQuestions,
physiopathology: physiopathologyQuestions,
pilotingTechniques: pilotingTechniquesQuestions
-}
+};
+
-const bgImage = require("../assets/bg.jpg")
-const screen = Dimensions.get("window")
+const bgImage = require("../assets/bg.jpg");
+const screen = Dimensions.get("window");
const styles = StyleSheet.create({
container: {
@@ -54,7 +55,7 @@ const styles = StyleSheet.create({
textAnswer: {
color: colors.white,
backgroundColor: colors.black_alpha,
- borderColor: colors.white,
+ borderColor: colors.white,
borderWidth: 2,
fontSize: 18,
textAlign: "center",
@@ -91,175 +92,112 @@ const styles = StyleSheet.create({
}
})
-class Quiz extends React.Component {
+class Quiz extends React.Component {
state = {
correctCount: 0,
wrongCount: 0,
wrongAnswers: [],
- totalCount: this.props.navigation.getParam("questions", []).length,
- availableIds: this.props.navigation.getParam("questions", []).map(a => a.id),
- activeQuestionId: this.props.navigation.getParam("questions", [])[
- Math.floor(Math.random() * this.props.navigation.getParam("questions", []).length)
- ].id,
- activeAnswerId: Math.floor(Math.random() * 3),
+ availableIds: [],
+ activeQuestionId: null,
+ activeAnswerId: 0,
clickedAnswer: false,
- answered: false,
- results: false
- }
-
- bannerError = (e) => {
- //console.log("Banner error (footer): ", e)
- }
+ answered: false
+ };
componentDidMount() {
- BackHandler.addEventListener('hardwareBackPress', this.handleBackButton)
+ this.backHandler = BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
+
+ const questions = this.props.route.params?.questions || [];
+ this.setState({
+ availableIds: questions.map(q => q.id),
+ activeQuestionId: questions.length ? questions[Math.floor(Math.random() * questions.length)].id : null
+ });
}
componentWillUnmount() {
- BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton)
+ this.backHandler?.remove();
}
handleBackButton = () => {
+ const tmpQuestions = [];
+ let fullQuestions = [];
- let tmpQuestions = []
- let fullQuestions = []
-
- examScheme.forEach( (elem) => {
- let currentSection = allQuestions[elem.section]
- for(let i=0; i fullQuestions.push(...allQuestions[elem.section]));
- for(let i=0; i<10; i++) {
- const currentIndex = Math.floor(Math.random() * fullQuestions.length)
- tmpQuestions.push(fullQuestions[currentIndex])
- fullQuestions = fullQuestions.filter( (item, index) => index != currentIndex)
+ for (let i = 0; i < 10; i++) {
+ const idx = Math.floor(Math.random() * fullQuestions.length);
+ tmpQuestions.push(fullQuestions[idx]);
+ fullQuestions.splice(idx, 1);
}
- this.props.navigation.navigate("Splash", {
- trueFalseQuestions: tmpQuestions
- })
- return true
+ this.props.navigation.navigate("Splash", { trueFalseQuestions: tmpQuestions });
+ return true;
}
answer = (answer, correct, id, question) => {
- this.setState(
- state => {
- const nextState = { answered: true, clickedId: id, clickedAnswer: answer }
-
- if ((correct && answer) || (!correct && !answer)) {
- //console.log('ok')
- nextState.correctCount = state.correctCount + 1
- } else {
- //console.log('ko')
- nextState.wrongCount = state.wrongCount + 1
- nextState.wrongAnswers = state.wrongAnswers
- nextState.wrongAnswers.push(
- { question: question.question,
- id: question.id,
- clicked: question.answers[state.activeAnswerId].id,
- answers: question.answers
- }
- )
-
- }
-
- return nextState
- },
- () => {
- setTimeout(() => this.nextQuestion(), 2000)
+ this.setState(state => {
+ const nextState = { answered: true, clickedId: id, clickedAnswer: answer };
+
+ if ((correct && answer) || (!correct && !answer)) {
+ nextState.correctCount = state.correctCount + 1;
+ } else {
+ nextState.wrongCount = state.wrongCount + 1;
+ nextState.wrongAnswers = [...state.wrongAnswers, { question: question.question, id: question.id, clicked: question.answers[state.activeAnswerId].id, answers: question.answers }];
}
- )
+
+ return nextState;
+ }, () => setTimeout(this.nextQuestion, 2000));
}
nextQuestion = () => {
-
- const updatedIndexes = this.state.availableIds.filter( item => item != this.state.activeQuestionId)
- const nextId = updatedIndexes[Math.floor(Math.random() * updatedIndexes.length)]
-
+ const updatedIndexes = this.state.availableIds.filter(id => id !== this.state.activeQuestionId);
if (!updatedIndexes.length) {
- //console.log(this.state.wrongAnswers)
this.props.navigation.navigate("ResultsTrueFalse", {
results: {
isExam: false,
- total: this.state.totalCount,
+ total: this.state.availableIds.length,
correct: this.state.correctCount,
wrong: this.state.wrongCount,
wrongAnswers: this.state.wrongAnswers
}
- })
-
- } else {
- this.setState( (state) => {
- return {
- availableIds: updatedIndexes,
- activeQuestionId: nextId,
- activeAnswerId: Math.floor(Math.random() * 3),
- answered: false
- }
- })
+ });
+ return;
}
+
+ const nextId = updatedIndexes[Math.floor(Math.random() * updatedIndexes.length)];
+ this.setState({ availableIds: updatedIndexes, activeQuestionId: nextId, activeAnswerId: Math.floor(Math.random() * 3), answered: false });
}
render() {
- const questions = this.props.navigation.getParam("questions", [])
- const question = questions.filter(item => item.id == this.state.activeQuestionId)[0] || questions[0]
- const randomAnswer = question.answers[this.state.activeAnswerId]
-
- //console.log({id: randomAnswer.id, clicked: this.state.clickedId, answered: this.state.answered, isCorrect: randomAnswer.correct || false})
+ const questions = this.props.route.params?.questions || [];
+ const question = questions.find(q => q.id === this.state.activeQuestionId) || questions[0];
+ const randomAnswer = question?.answers[this.state.activeAnswerId];
return (
-
-
-
-
-
-
-
- {!this.state.results ?
-
-
- {question.id}
- {question.question}
- {randomAnswer.text}
-
-
- this.answer(true, randomAnswer.correct || false, randomAnswer.id, question)}
- />
-
- this.answer(false, randomAnswer.correct || false, randomAnswer.id, question)}
- />
-
-
-
-
-
- {`${this.state.correctCount+this.state.wrongCount}/${this.state.totalCount}`}
-
-
- : }
-
-
-
-
-
-
-
+
+
+
+ {question?.id}
+ {question?.question}
+ {randomAnswer?.text}
+
+
+ this.answer(true, randomAnswer?.correct, randomAnswer?.id, question)} />
+ this.answer(false, randomAnswer?.correct, randomAnswer?.id, question)} />
+
+
+ {`${this.state.correctCount + this.state.wrongCount}/${this.state.availableIds.length}`}
+
+
+
- )
+ );
}
}
-export default Quiz
+export default Quiz;
diff --git a/vds-app/android/.gitignore b/vds-app/android/.gitignore
new file mode 100644
index 0000000..8a6be07
--- /dev/null
+++ b/vds-app/android/.gitignore
@@ -0,0 +1,16 @@
+# OSX
+#
+.DS_Store
+
+# Android/IntelliJ
+#
+build/
+.idea
+.gradle
+local.properties
+*.iml
+*.hprof
+.cxx/
+
+# Bundle artifacts
+*.jsbundle
diff --git a/vds-app/android/app/build.gradle b/vds-app/android/app/build.gradle
new file mode 100644
index 0000000..391d3ba
--- /dev/null
+++ b/vds-app/android/app/build.gradle
@@ -0,0 +1,182 @@
+apply plugin: "com.android.application"
+apply plugin: "org.jetbrains.kotlin.android"
+apply plugin: "com.facebook.react"
+
+def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath()
+
+/**
+ * This is the configuration block to customize your React Native Android app.
+ * By default you don't need to apply any configuration, just uncomment the lines you need.
+ */
+react {
+ entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android", "absolute"].execute(null, rootDir).text.trim())
+ reactNativeDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
+ hermesCommand = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc"
+ codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
+
+ enableBundleCompression = (findProperty('android.enableBundleCompression') ?: false).toBoolean()
+ // Use Expo CLI to bundle the app, this ensures the Metro config
+ // works correctly with Expo projects.
+ cliFile = new File(["node", "--print", "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim())
+ bundleCommand = "export:embed"
+
+ /* Folders */
+ // The root of your project, i.e. where "package.json" lives. Default is '../..'
+ // root = file("../../")
+ // The folder where the react-native NPM package is. Default is ../../node_modules/react-native
+ // reactNativeDir = file("../../node_modules/react-native")
+ // The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen
+ // codegenDir = file("../../node_modules/@react-native/codegen")
+
+ /* Variants */
+ // The list of variants to that are debuggable. For those we're going to
+ // skip the bundling of the JS bundle and the assets. By default is just 'debug'.
+ // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
+ // debuggableVariants = ["liteDebug", "prodDebug"]
+
+ /* Bundling */
+ // A list containing the node command and its flags. Default is just 'node'.
+ // nodeExecutableAndArgs = ["node"]
+
+ //
+ // The path to the CLI configuration file. Default is empty.
+ // bundleConfig = file(../rn-cli.config.js)
+ //
+ // The name of the generated asset file containing your JS bundle
+ // bundleAssetName = "MyApplication.android.bundle"
+ //
+ // The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
+ // entryFile = file("../js/MyApplication.android.js")
+ //
+ // A list of extra flags to pass to the 'bundle' commands.
+ // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
+ // extraPackagerArgs = []
+
+ /* Hermes Commands */
+ // The hermes compiler command to run. By default it is 'hermesc'
+ // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
+ //
+ // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
+ // hermesFlags = ["-O", "-output-source-map"]
+
+ /* Autolinking */
+ autolinkLibrariesWithApp()
+}
+
+/**
+ * Set this to true in release builds to optimize the app using [R8](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization).
+ */
+def enableMinifyInReleaseBuilds = (findProperty('android.enableMinifyInReleaseBuilds') ?: false).toBoolean()
+
+/**
+ * The preferred build flavor of JavaScriptCore (JSC)
+ *
+ * For example, to use the international variant, you can use:
+ * `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
+ *
+ * The international variant includes ICU i18n library and necessary data
+ * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
+ * give correct results when using with locales other than en-US. Note that
+ * this variant is about 6MiB larger per architecture than default.
+ */
+def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
+
+android {
+ ndkVersion rootProject.ext.ndkVersion
+
+ buildToolsVersion rootProject.ext.buildToolsVersion
+ compileSdk rootProject.ext.compileSdkVersion
+
+ namespace 'com.dslak.vdsquiz'
+ defaultConfig {
+ applicationId 'com.dslak.vdsquiz'
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
+ versionCode 31
+ versionName "3.8.3"
+
+ buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
+ }
+ signingConfigs {
+ debug {
+ storeFile file('debug.keystore')
+ storePassword 'android'
+ keyAlias 'androiddebugkey'
+ keyPassword 'android'
+ }
+ }
+ buildTypes {
+ debug {
+ signingConfig signingConfigs.debug
+ }
+ release {
+ // Caution! In production, you need to generate your own keystore file.
+ // see https://reactnative.dev/docs/signed-apk-android.
+ signingConfig signingConfigs.debug
+ def enableShrinkResources = findProperty('android.enableShrinkResourcesInReleaseBuilds') ?: 'false'
+ shrinkResources enableShrinkResources.toBoolean()
+ minifyEnabled enableMinifyInReleaseBuilds
+ proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
+ def enablePngCrunchInRelease = findProperty('android.enablePngCrunchInReleaseBuilds') ?: 'true'
+ crunchPngs enablePngCrunchInRelease.toBoolean()
+ }
+ }
+ packagingOptions {
+ jniLibs {
+ def enableLegacyPackaging = findProperty('expo.useLegacyPackaging') ?: 'false'
+ useLegacyPackaging enableLegacyPackaging.toBoolean()
+ }
+ }
+ androidResources {
+ ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~'
+ }
+}
+
+// Apply static values from `gradle.properties` to the `android.packagingOptions`
+// Accepts values in comma delimited lists, example:
+// android.packagingOptions.pickFirsts=/LICENSE,**/picasa.ini
+["pickFirsts", "excludes", "merges", "doNotStrip"].each { prop ->
+ // Split option: 'foo,bar' -> ['foo', 'bar']
+ def options = (findProperty("android.packagingOptions.$prop") ?: "").split(",");
+ // Trim all elements in place.
+ for (i in 0.. 0) {
+ println "android.packagingOptions.$prop += $options ($options.length)"
+ // Ex: android.packagingOptions.pickFirsts += '**/SCCS/**'
+ options.each {
+ android.packagingOptions[prop] += it
+ }
+ }
+}
+
+dependencies {
+ // The version of react-native is set by the React Native Gradle Plugin
+ implementation("com.facebook.react:react-android")
+
+ def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true";
+ def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true";
+ def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true";
+
+ if (isGifEnabled) {
+ // For animated gif support
+ implementation("com.facebook.fresco:animated-gif:${expoLibs.versions.fresco.get()}")
+ }
+
+ if (isWebpEnabled) {
+ // For webp support
+ implementation("com.facebook.fresco:webpsupport:${expoLibs.versions.fresco.get()}")
+ if (isWebpAnimatedEnabled) {
+ // Animated webp support
+ implementation("com.facebook.fresco:animated-webp:${expoLibs.versions.fresco.get()}")
+ }
+ }
+
+ if (hermesEnabled.toBoolean()) {
+ implementation("com.facebook.react:hermes-android")
+ } else {
+ implementation jscFlavor
+ }
+}
diff --git a/vds-app/android/app/debug.keystore b/vds-app/android/app/debug.keystore
new file mode 100644
index 0000000..364e105
Binary files /dev/null and b/vds-app/android/app/debug.keystore differ
diff --git a/vds-app/android/app/proguard-rules.pro b/vds-app/android/app/proguard-rules.pro
new file mode 100644
index 0000000..551eb41
--- /dev/null
+++ b/vds-app/android/app/proguard-rules.pro
@@ -0,0 +1,14 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# react-native-reanimated
+-keep class com.swmansion.reanimated.** { *; }
+-keep class com.facebook.react.turbomodule.** { *; }
+
+# Add any project specific keep options here:
diff --git a/vds-app/android/app/src/debug/AndroidManifest.xml b/vds-app/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..3ec2507
--- /dev/null
+++ b/vds-app/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/vds-app/android/app/src/main/AndroidManifest.xml b/vds-app/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..6d8591d
--- /dev/null
+++ b/vds-app/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vds-app/android/app/src/main/java/com/dslak/vdsquiz/MainActivity.kt b/vds-app/android/app/src/main/java/com/dslak/vdsquiz/MainActivity.kt
new file mode 100644
index 0000000..975f0d2
--- /dev/null
+++ b/vds-app/android/app/src/main/java/com/dslak/vdsquiz/MainActivity.kt
@@ -0,0 +1,61 @@
+package com.dslak.vdsquiz
+
+import android.os.Build
+import android.os.Bundle
+
+import com.facebook.react.ReactActivity
+import com.facebook.react.ReactActivityDelegate
+import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
+import com.facebook.react.defaults.DefaultReactActivityDelegate
+
+import expo.modules.ReactActivityDelegateWrapper
+
+class MainActivity : ReactActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ // Set the theme to AppTheme BEFORE onCreate to support
+ // coloring the background, status bar, and navigation bar.
+ // This is required for expo-splash-screen.
+ setTheme(R.style.AppTheme);
+ super.onCreate(null)
+ }
+
+ /**
+ * Returns the name of the main component registered from JavaScript. This is used to schedule
+ * rendering of the component.
+ */
+ override fun getMainComponentName(): String = "main"
+
+ /**
+ * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
+ * which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
+ */
+ override fun createReactActivityDelegate(): ReactActivityDelegate {
+ return ReactActivityDelegateWrapper(
+ this,
+ BuildConfig.IS_NEW_ARCHITECTURE_ENABLED,
+ object : DefaultReactActivityDelegate(
+ this,
+ mainComponentName,
+ fabricEnabled
+ ){})
+ }
+
+ /**
+ * Align the back button behavior with Android S
+ * where moving root activities to background instead of finishing activities.
+ * @see onBackPressed
+ */
+ override fun invokeDefaultOnBackPressed() {
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
+ if (!moveTaskToBack(false)) {
+ // For non-root activities, use the default implementation to finish them.
+ super.invokeDefaultOnBackPressed()
+ }
+ return
+ }
+
+ // Use the default back button implementation on Android S
+ // because it's doing more than [Activity.moveTaskToBack] in fact.
+ super.invokeDefaultOnBackPressed()
+ }
+}
diff --git a/vds-app/android/app/src/main/java/com/dslak/vdsquiz/MainApplication.kt b/vds-app/android/app/src/main/java/com/dslak/vdsquiz/MainApplication.kt
new file mode 100644
index 0000000..348185d
--- /dev/null
+++ b/vds-app/android/app/src/main/java/com/dslak/vdsquiz/MainApplication.kt
@@ -0,0 +1,56 @@
+package com.dslak.vdsquiz
+
+import android.app.Application
+import android.content.res.Configuration
+
+import com.facebook.react.PackageList
+import com.facebook.react.ReactApplication
+import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
+import com.facebook.react.ReactNativeHost
+import com.facebook.react.ReactPackage
+import com.facebook.react.ReactHost
+import com.facebook.react.common.ReleaseLevel
+import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint
+import com.facebook.react.defaults.DefaultReactNativeHost
+
+import expo.modules.ApplicationLifecycleDispatcher
+import expo.modules.ReactNativeHostWrapper
+
+class MainApplication : Application(), ReactApplication {
+
+ override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper(
+ this,
+ object : DefaultReactNativeHost(this) {
+ override fun getPackages(): List =
+ PackageList(this).packages.apply {
+ // Packages that cannot be autolinked yet can be added manually here, for example:
+ // add(MyReactNativePackage())
+ }
+
+ override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry"
+
+ override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
+
+ override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
+ }
+ )
+
+ override val reactHost: ReactHost
+ get() = ReactNativeHostWrapper.createReactHost(applicationContext, reactNativeHost)
+
+ override fun onCreate() {
+ super.onCreate()
+ DefaultNewArchitectureEntryPoint.releaseLevel = try {
+ ReleaseLevel.valueOf(BuildConfig.REACT_NATIVE_RELEASE_LEVEL.uppercase())
+ } catch (e: IllegalArgumentException) {
+ ReleaseLevel.STABLE
+ }
+ loadReactNative(this)
+ ApplicationLifecycleDispatcher.onApplicationCreate(this)
+ }
+
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ super.onConfigurationChanged(newConfig)
+ ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig)
+ }
+}
diff --git a/vds-app/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png b/vds-app/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png
new file mode 100644
index 0000000..33c047f
Binary files /dev/null and b/vds-app/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png differ
diff --git a/vds-app/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png b/vds-app/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png
new file mode 100644
index 0000000..e523f97
Binary files /dev/null and b/vds-app/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png differ
diff --git a/vds-app/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png b/vds-app/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png
new file mode 100644
index 0000000..4758e33
Binary files /dev/null and b/vds-app/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png differ
diff --git a/vds-app/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png b/vds-app/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png
new file mode 100644
index 0000000..f60f726
Binary files /dev/null and b/vds-app/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png differ
diff --git a/vds-app/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png b/vds-app/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png
new file mode 100644
index 0000000..5a07903
Binary files /dev/null and b/vds-app/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png differ
diff --git a/vds-app/android/app/src/main/res/drawable/ic_launcher_background.xml b/vds-app/android/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..883b2a0
--- /dev/null
+++ b/vds-app/android/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,6 @@
+
+
+ -
+
+
+
\ No newline at end of file
diff --git a/vds-app/android/app/src/main/res/drawable/rn_edit_text_material.xml b/vds-app/android/app/src/main/res/drawable/rn_edit_text_material.xml
new file mode 100644
index 0000000..5c25e72
--- /dev/null
+++ b/vds-app/android/app/src/main/res/drawable/rn_edit_text_material.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vds-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/vds-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..b8931b4
Binary files /dev/null and b/vds-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/vds-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/vds-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..4fd31de
Binary files /dev/null and b/vds-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ
diff --git a/vds-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/vds-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..d5b454d
Binary files /dev/null and b/vds-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/vds-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/vds-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..dd2253b
Binary files /dev/null and b/vds-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ
diff --git a/vds-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/vds-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..8bb26f0
Binary files /dev/null and b/vds-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/vds-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/vds-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..90544ad
Binary files /dev/null and b/vds-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ
diff --git a/vds-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/vds-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..2085b3f
Binary files /dev/null and b/vds-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/vds-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/vds-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..21239f8
Binary files /dev/null and b/vds-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ
diff --git a/vds-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/vds-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..12256b1
Binary files /dev/null and b/vds-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/vds-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/vds-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..4688cd3
Binary files /dev/null and b/vds-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ
diff --git a/vds-app/android/app/src/main/res/values-night/colors.xml b/vds-app/android/app/src/main/res/values-night/colors.xml
new file mode 100644
index 0000000..3c05de5
--- /dev/null
+++ b/vds-app/android/app/src/main/res/values-night/colors.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/vds-app/android/app/src/main/res/values/colors.xml b/vds-app/android/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..6426cc8
--- /dev/null
+++ b/vds-app/android/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+ #8c0072
+ #ffffff
+ #023c69
+ #8c0072
+
\ No newline at end of file
diff --git a/vds-app/android/app/src/main/res/values/strings.xml b/vds-app/android/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..ae90b27
--- /dev/null
+++ b/vds-app/android/app/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+ VDS Quiz
+ cover
+ false
+
\ No newline at end of file
diff --git a/vds-app/android/app/src/main/res/values/styles.xml b/vds-app/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..7171c42
--- /dev/null
+++ b/vds-app/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/vds-app/android/build.gradle b/vds-app/android/build.gradle
new file mode 100644
index 0000000..0554dd1
--- /dev/null
+++ b/vds-app/android/build.gradle
@@ -0,0 +1,24 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath('com.android.tools.build:gradle')
+ classpath('com.facebook.react:react-native-gradle-plugin')
+ classpath('org.jetbrains.kotlin:kotlin-gradle-plugin')
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ maven { url 'https://www.jitpack.io' }
+ }
+}
+
+apply plugin: "expo-root-project"
+apply plugin: "com.facebook.react.rootproject"
diff --git a/vds-app/android/gradle.properties b/vds-app/android/gradle.properties
new file mode 100644
index 0000000..02f4c87
--- /dev/null
+++ b/vds-app/android/gradle.properties
@@ -0,0 +1,69 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
+org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+org.gradle.parallel=true
+
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+
+# Enable AAPT2 PNG crunching
+android.enablePngCrunchInReleaseBuilds=true
+
+# Use this property to specify which architecture you want to build.
+# You can also override it from the CLI using
+# ./gradlew -PreactNativeArchitectures=x86_64
+reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
+
+# Use this property to enable support to the new architecture.
+# This will allow you to use TurboModules and the Fabric render in
+# your application. You should enable this flag either if you want
+# to write custom TurboModules/Fabric components OR use libraries that
+# are providing them.
+newArchEnabled=true
+
+# Use this property to enable or disable the Hermes JS engine.
+# If set to false, you will be using JSC instead.
+hermesEnabled=true
+
+# Use this property to enable edge-to-edge display support.
+# This allows your app to draw behind system bars for an immersive UI.
+# Note: Only works with ReactActivity and should not be used with custom Activity.
+edgeToEdgeEnabled=true
+
+# Enable GIF support in React Native images (~200 B increase)
+expo.gif.enabled=true
+# Enable webp support in React Native images (~85 KB increase)
+expo.webp.enabled=true
+# Enable animated webp support (~3.4 MB increase)
+# Disabled by default because iOS doesn't support animated webp
+expo.webp.animated=false
+
+# Enable network inspector
+EX_DEV_CLIENT_NETWORK_INSPECTOR=true
+
+# Use legacy packaging to compress native libraries in the resulting APK.
+expo.useLegacyPackaging=false
+
+# Specifies whether the app is configured to use edge-to-edge via the app config or plugin
+# WARNING: This property has been deprecated and will be removed in Expo SDK 55. Use `edgeToEdgeEnabled` or `react.edgeToEdgeEnabled` to determine whether the project is using edge-to-edge.
+expo.edgeToEdgeEnabled=true
+
+android.compileSdkVersion=34
+android.targetSdkVersion=34
+android.buildToolsVersion=34.0.0
\ No newline at end of file
diff --git a/vds-app/android/gradle/wrapper/gradle-wrapper.jar b/vds-app/android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..1b33c55
Binary files /dev/null and b/vds-app/android/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/vds-app/android/gradle/wrapper/gradle-wrapper.properties b/vds-app/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..d4081da
--- /dev/null
+++ b/vds-app/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/vds-app/android/gradlew b/vds-app/android/gradlew
new file mode 100755
index 0000000..7f94d3d
--- /dev/null
+++ b/vds-app/android/gradlew
@@ -0,0 +1,251 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH="\\\"\\\""
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/vds-app/android/gradlew.bat b/vds-app/android/gradlew.bat
new file mode 100644
index 0000000..5eed7ee
--- /dev/null
+++ b/vds-app/android/gradlew.bat
@@ -0,0 +1,94 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/vds-app/android/settings.gradle b/vds-app/android/settings.gradle
new file mode 100644
index 0000000..27df3d2
--- /dev/null
+++ b/vds-app/android/settings.gradle
@@ -0,0 +1,39 @@
+pluginManagement {
+ def reactNativeGradlePlugin = new File(
+ providers.exec {
+ workingDir(rootDir)
+ commandLine("node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })")
+ }.standardOutput.asText.get().trim()
+ ).getParentFile().absolutePath
+ includeBuild(reactNativeGradlePlugin)
+
+ def expoPluginsPath = new File(
+ providers.exec {
+ workingDir(rootDir)
+ commandLine("node", "--print", "require.resolve('expo-modules-autolinking/package.json', { paths: [require.resolve('expo/package.json')] })")
+ }.standardOutput.asText.get().trim(),
+ "../android/expo-gradle-plugin"
+ ).absolutePath
+ includeBuild(expoPluginsPath)
+}
+
+plugins {
+ id("com.facebook.react.settings")
+ id("expo-autolinking-settings")
+}
+
+extensions.configure(com.facebook.react.ReactSettingsExtension) { ex ->
+ if (System.getenv('EXPO_USE_COMMUNITY_AUTOLINKING') == '1') {
+ ex.autolinkLibrariesFromCommand()
+ } else {
+ ex.autolinkLibrariesFromCommand(expoAutolinking.rnConfigCommand)
+ }
+}
+expoAutolinking.useExpoModules()
+
+rootProject.name = 'VDS Quiz'
+
+expoAutolinking.useExpoVersionCatalog()
+
+include ':app'
+includeBuild(expoAutolinking.reactNativeGradlePlugin)
diff --git a/vds-app/app.json b/vds-app/app.json
index 88bc279..09d0734 100644
--- a/vds-app/app.json
+++ b/vds-app/app.json
@@ -1,10 +1,7 @@
{
- "displayName": "VDS Quiz",
- "name": "VDS Quiz",
"expo": {
"name": "VDS Quiz",
"slug": "VDS-Quiz",
- "privacy": "public",
"platforms": [
"ios",
"android",
@@ -29,7 +26,7 @@
"icon": "./assets/icon.png",
"package": "com.dslak.vdsquiz",
"permissions": [],
- "versionCode": 30
+ "versionCode": 31
},
"ios": {
"icon": "./assets/iconIOS.png",
@@ -49,12 +46,12 @@
"expo-build-properties",
{
"android": {
- "compileSdkVersion": 33,
- "targetSdkVersion": 33,
- "buildToolsVersion": "33.0.0"
+ "compileSdkVersion": 35,
+ "targetSdkVersion": 35,
+ "buildToolsVersion": "35.0.0"
},
"ios": {
- "deploymentTarget": "13.0"
+ "deploymentTarget": "15.1"
}
}
]
diff --git a/vds-app/ios/.gitignore b/vds-app/ios/.gitignore
new file mode 100644
index 0000000..8beb344
--- /dev/null
+++ b/vds-app/ios/.gitignore
@@ -0,0 +1,30 @@
+# OSX
+#
+.DS_Store
+
+# Xcode
+#
+build/
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+xcuserdata
+*.xccheckout
+*.moved-aside
+DerivedData
+*.hmap
+*.ipa
+*.xcuserstate
+project.xcworkspace
+.xcode.env.local
+
+# Bundle artifacts
+*.jsbundle
+
+# CocoaPods
+/Pods/
diff --git a/vds-app/ios/.xcode.env b/vds-app/ios/.xcode.env
new file mode 100644
index 0000000..3d5782c
--- /dev/null
+++ b/vds-app/ios/.xcode.env
@@ -0,0 +1,11 @@
+# This `.xcode.env` file is versioned and is used to source the environment
+# used when running script phases inside Xcode.
+# To customize your local environment, you can create an `.xcode.env.local`
+# file that is not versioned.
+
+# NODE_BINARY variable contains the PATH to the node executable.
+#
+# Customize the NODE_BINARY variable here.
+# For example, to use nvm with brew, add the following line
+# . "$(brew --prefix nvm)/nvm.sh" --no-use
+export NODE_BINARY=$(command -v node)
diff --git a/vds-app/ios/Podfile b/vds-app/ios/Podfile
new file mode 100644
index 0000000..01a2333
--- /dev/null
+++ b/vds-app/ios/Podfile
@@ -0,0 +1,52 @@
+require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking")
+require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")
+
+require 'json'
+podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {}
+
+ENV['RCT_NEW_ARCH_ENABLED'] ||= '0' if podfile_properties['newArchEnabled'] == 'false'
+ENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] ||= podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR']
+ENV['RCT_USE_RN_DEP'] ||= '1' if podfile_properties['ios.buildReactNativeFromSource'] != 'true' && podfile_properties['newArchEnabled'] != 'false'
+ENV['RCT_USE_PREBUILT_RNCORE'] ||= '1' if podfile_properties['ios.buildReactNativeFromSource'] != 'true' && podfile_properties['newArchEnabled'] != 'false'
+platform :ios, podfile_properties['ios.deploymentTarget'] || '15.1'
+
+prepare_react_native_project!
+
+target 'VDSQuiz' do
+ use_expo_modules!
+
+ if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1'
+ config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"];
+ else
+ config_command = [
+ 'npx',
+ 'expo-modules-autolinking',
+ 'react-native-config',
+ '--json',
+ '--platform',
+ 'ios'
+ ]
+ end
+
+ config = use_native_modules!(config_command)
+
+ use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']
+ use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']
+
+ use_react_native!(
+ :path => config[:reactNativePath],
+ :hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes',
+ # An absolute path to your application root.
+ :app_path => "#{Pod::Config.instance.installation_root}/..",
+ :privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false',
+ )
+
+ post_install do |installer|
+ react_native_post_install(
+ installer,
+ config[:reactNativePath],
+ :mac_catalyst_enabled => false,
+ :ccache_enabled => podfile_properties['apple.ccacheEnabled'] == 'true',
+ )
+ end
+end
diff --git a/vds-app/ios/Podfile.properties.json b/vds-app/ios/Podfile.properties.json
new file mode 100644
index 0000000..b74192b
--- /dev/null
+++ b/vds-app/ios/Podfile.properties.json
@@ -0,0 +1,6 @@
+{
+ "expo.jsEngine": "hermes",
+ "EX_DEV_CLIENT_NETWORK_INSPECTOR": "true",
+ "ios.deploymentTarget": "15.1",
+ "apple.privacyManifestAggregationEnabled": "true"
+}
diff --git a/vds-app/ios/VDSQuiz.xcodeproj/project.pbxproj b/vds-app/ios/VDSQuiz.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..2e6bb67
--- /dev/null
+++ b/vds-app/ios/VDSQuiz.xcodeproj/project.pbxproj
@@ -0,0 +1,436 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 54;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
+ 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; };
+ BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; };
+ F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11748412D0307B40044C1D9 /* AppDelegate.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 13B07F961A680F5B00A75B9A /* VDSQuiz.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VDSQuiz.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = VDSQuiz/Images.xcassets; sourceTree = ""; };
+ 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = VDSQuiz/Info.plist; sourceTree = ""; };
+ AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = VDSQuiz/SplashScreen.storyboard; sourceTree = ""; };
+ BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = ""; };
+ ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
+ F11748412D0307B40044C1D9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = VDSQuiz/AppDelegate.swift; sourceTree = ""; };
+ F11748442D0722820044C1D9 /* VDSQuiz-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "VDSQuiz-Bridging-Header.h"; path = "VDSQuiz/VDSQuiz-Bridging-Header.h"; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 13B07FAE1A68108700A75B9A /* VDSQuiz */ = {
+ isa = PBXGroup;
+ children = (
+ F11748412D0307B40044C1D9 /* AppDelegate.swift */,
+ F11748442D0722820044C1D9 /* VDSQuiz-Bridging-Header.h */,
+ BB2F792B24A3F905000567C9 /* Supporting */,
+ 13B07FB51A68108700A75B9A /* Images.xcassets */,
+ 13B07FB61A68108700A75B9A /* Info.plist */,
+ AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */,
+ );
+ name = VDSQuiz;
+ sourceTree = "";
+ };
+ 2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+ 832341AE1AAA6A7D00B99B32 /* Libraries */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ name = Libraries;
+ sourceTree = "";
+ };
+ 83CBB9F61A601CBA00E9B192 = {
+ isa = PBXGroup;
+ children = (
+ 13B07FAE1A68108700A75B9A /* VDSQuiz */,
+ 832341AE1AAA6A7D00B99B32 /* Libraries */,
+ 83CBBA001A601CBA00E9B192 /* Products */,
+ 2D16E6871FA4F8E400B85C8A /* Frameworks */,
+ );
+ indentWidth = 2;
+ sourceTree = "";
+ tabWidth = 2;
+ usesTabs = 0;
+ };
+ 83CBBA001A601CBA00E9B192 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 13B07F961A680F5B00A75B9A /* VDSQuiz.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ BB2F792B24A3F905000567C9 /* Supporting */ = {
+ isa = PBXGroup;
+ children = (
+ BB2F792C24A3F905000567C9 /* Expo.plist */,
+ );
+ name = Supporting;
+ path = VDSQuiz/Supporting;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 13B07F861A680F5B00A75B9A /* VDSQuiz */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "VDSQuiz" */;
+ buildPhases = (
+ 08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */,
+ 13B07F871A680F5B00A75B9A /* Sources */,
+ 13B07F8C1A680F5B00A75B9A /* Frameworks */,
+ 13B07F8E1A680F5B00A75B9A /* Resources */,
+ 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
+ 800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = VDSQuiz;
+ productName = VDSQuiz;
+ productReference = 13B07F961A680F5B00A75B9A /* VDSQuiz.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 83CBB9F71A601CBA00E9B192 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 1130;
+ TargetAttributes = {
+ 13B07F861A680F5B00A75B9A = {
+ LastSwiftMigration = 1250;
+ };
+ };
+ };
+ buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "VDSQuiz" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 83CBB9F61A601CBA00E9B192;
+ productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 13B07F861A680F5B00A75B9A /* VDSQuiz */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 13B07F8E1A680F5B00A75B9A /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ BB2F792D24A3F905000567C9 /* Expo.plist in Resources */,
+ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
+ 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {
+ isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "$(SRCROOT)/.xcode.env",
+ "$(SRCROOT)/.xcode.env.local",
+ );
+ name = "Bundle React Native code and images";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n# Source .xcode.env.updates if it exists to allow\n# SKIP_BUNDLING to be unset if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.updates\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.updates\"\nfi\n# Source local changes to allow overrides\n# if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n";
+ };
+ 08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-VDSQuiz-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-VDSQuiz/Pods-VDSQuiz-resources.sh",
+ "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle",
+ "${PODS_CONFIGURATION_BUILD_DIR}/EXUpdates/EXUpdates.bundle",
+ "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle",
+ );
+ name = "[CP] Copy Pods Resources";
+ outputPaths = (
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXUpdates.bundle",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-VDSQuiz/Pods-VDSQuiz-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 13B07F871A680F5B00A75B9A /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ 13B07F941A680F5B00A75B9A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ ENABLE_BITCODE = NO;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "$(inherited)",
+ "FB_SONARKIT_ENABLED=1",
+ );
+ INFOPLIST_FILE = VDSQuiz/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.1;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-ObjC",
+ "-lc++",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = "com.dslak.vdsquiz";
+ PRODUCT_NAME = "VDSQuiz";
+ SWIFT_OBJC_BRIDGING_HEADER = "VDSQuiz/VDSQuiz-Bridging-Header.h";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ TARGETED_DEVICE_FAMILY = "1,2";
+ CODE_SIGN_ENTITLEMENTS = VDSQuiz/VDSQuiz.entitlements;
+ };
+ name = Debug;
+ };
+ 13B07F951A680F5B00A75B9A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ INFOPLIST_FILE = VDSQuiz/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.1;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-ObjC",
+ "-lc++",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = "com.dslak.vdsquiz";
+ PRODUCT_NAME = "VDSQuiz";
+ SWIFT_OBJC_BRIDGING_HEADER = "VDSQuiz/VDSQuiz-Bridging-Header.h";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ TARGETED_DEVICE_FAMILY = "1,2";
+ CODE_SIGN_ENTITLEMENTS = VDSQuiz/VDSQuiz.entitlements;
+ };
+ name = Release;
+ };
+ 83CBBA201A601CBA00E9B192 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "c++20";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.1;
+ LD_RUNPATH_SEARCH_PATHS = (
+ /usr/lib/swift,
+ "$(inherited)",
+ );
+ LIBRARY_SEARCH_PATHS = "\"$(inherited)\"";
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ };
+ name = Debug;
+ };
+ 83CBBA211A601CBA00E9B192 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "c++20";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = YES;
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.1;
+ LD_RUNPATH_SEARCH_PATHS = (
+ /usr/lib/swift,
+ "$(inherited)",
+ );
+ LIBRARY_SEARCH_PATHS = "\"$(inherited)\"";
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "VDSQuiz" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 13B07F941A680F5B00A75B9A /* Debug */,
+ 13B07F951A680F5B00A75B9A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "VDSQuiz" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 83CBBA201A601CBA00E9B192 /* Debug */,
+ 83CBBA211A601CBA00E9B192 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
+}
diff --git a/vds-app/ios/VDSQuiz.xcodeproj/xcshareddata/xcschemes/VDSQuiz.xcscheme b/vds-app/ios/VDSQuiz.xcodeproj/xcshareddata/xcschemes/VDSQuiz.xcscheme
new file mode 100644
index 0000000..5aec35d
--- /dev/null
+++ b/vds-app/ios/VDSQuiz.xcodeproj/xcshareddata/xcschemes/VDSQuiz.xcscheme
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vds-app/ios/VDSQuiz/AppDelegate.swift b/vds-app/ios/VDSQuiz/AppDelegate.swift
new file mode 100644
index 0000000..a7887e1
--- /dev/null
+++ b/vds-app/ios/VDSQuiz/AppDelegate.swift
@@ -0,0 +1,70 @@
+import Expo
+import React
+import ReactAppDependencyProvider
+
+@UIApplicationMain
+public class AppDelegate: ExpoAppDelegate {
+ var window: UIWindow?
+
+ var reactNativeDelegate: ExpoReactNativeFactoryDelegate?
+ var reactNativeFactory: RCTReactNativeFactory?
+
+ public override func application(
+ _ application: UIApplication,
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
+ ) -> Bool {
+ let delegate = ReactNativeDelegate()
+ let factory = ExpoReactNativeFactory(delegate: delegate)
+ delegate.dependencyProvider = RCTAppDependencyProvider()
+
+ reactNativeDelegate = delegate
+ reactNativeFactory = factory
+ bindReactNativeFactory(factory)
+
+#if os(iOS) || os(tvOS)
+ window = UIWindow(frame: UIScreen.main.bounds)
+ factory.startReactNative(
+ withModuleName: "main",
+ in: window,
+ launchOptions: launchOptions)
+#endif
+
+ return super.application(application, didFinishLaunchingWithOptions: launchOptions)
+ }
+
+ // Linking API
+ public override func application(
+ _ app: UIApplication,
+ open url: URL,
+ options: [UIApplication.OpenURLOptionsKey: Any] = [:]
+ ) -> Bool {
+ return super.application(app, open: url, options: options) || RCTLinkingManager.application(app, open: url, options: options)
+ }
+
+ // Universal Links
+ public override func application(
+ _ application: UIApplication,
+ continue userActivity: NSUserActivity,
+ restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
+ ) -> Bool {
+ let result = RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
+ return super.application(application, continue: userActivity, restorationHandler: restorationHandler) || result
+ }
+}
+
+class ReactNativeDelegate: ExpoReactNativeFactoryDelegate {
+ // Extension point for config-plugins
+
+ override func sourceURL(for bridge: RCTBridge) -> URL? {
+ // needed to return the correct URL for expo-dev-client.
+ bridge.bundleURL ?? bundleURL()
+ }
+
+ override func bundleURL() -> URL? {
+#if DEBUG
+ return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: ".expo/.virtual-metro-entry")
+#else
+ return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
+#endif
+ }
+}
diff --git a/vds-app/ios/VDSQuiz/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png b/vds-app/ios/VDSQuiz/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png
new file mode 100644
index 0000000..c144264
Binary files /dev/null and b/vds-app/ios/VDSQuiz/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png differ
diff --git a/vds-app/ios/VDSQuiz/Images.xcassets/AppIcon.appiconset/Contents.json b/vds-app/ios/VDSQuiz/Images.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..90d8d4c
--- /dev/null
+++ b/vds-app/ios/VDSQuiz/Images.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,14 @@
+{
+ "images": [
+ {
+ "filename": "App-Icon-1024x1024@1x.png",
+ "idiom": "universal",
+ "platform": "ios",
+ "size": "1024x1024"
+ }
+ ],
+ "info": {
+ "version": 1,
+ "author": "expo"
+ }
+}
\ No newline at end of file
diff --git a/vds-app/ios/VDSQuiz/Images.xcassets/Contents.json b/vds-app/ios/VDSQuiz/Images.xcassets/Contents.json
new file mode 100644
index 0000000..ed285c2
--- /dev/null
+++ b/vds-app/ios/VDSQuiz/Images.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "expo"
+ }
+}
diff --git a/vds-app/ios/VDSQuiz/Images.xcassets/SplashScreenBackground.colorset/Contents.json b/vds-app/ios/VDSQuiz/Images.xcassets/SplashScreenBackground.colorset/Contents.json
new file mode 100644
index 0000000..127680e
--- /dev/null
+++ b/vds-app/ios/VDSQuiz/Images.xcassets/SplashScreenBackground.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors": [
+ {
+ "color": {
+ "components": {
+ "alpha": "1.000",
+ "blue": "0.447058823529412",
+ "green": "0.00000000000000",
+ "red": "0.549019607843137"
+ },
+ "color-space": "srgb"
+ },
+ "idiom": "universal"
+ }
+ ],
+ "info": {
+ "version": 1,
+ "author": "expo"
+ }
+}
\ No newline at end of file
diff --git a/vds-app/ios/VDSQuiz/Images.xcassets/SplashScreenLegacy.imageset/Contents.json b/vds-app/ios/VDSQuiz/Images.xcassets/SplashScreenLegacy.imageset/Contents.json
new file mode 100644
index 0000000..f65c008
--- /dev/null
+++ b/vds-app/ios/VDSQuiz/Images.xcassets/SplashScreenLegacy.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images": [
+ {
+ "idiom": "universal",
+ "filename": "image.png",
+ "scale": "1x"
+ },
+ {
+ "idiom": "universal",
+ "filename": "image@2x.png",
+ "scale": "2x"
+ },
+ {
+ "idiom": "universal",
+ "filename": "image@3x.png",
+ "scale": "3x"
+ }
+ ],
+ "info": {
+ "version": 1,
+ "author": "expo"
+ }
+}
\ No newline at end of file
diff --git a/vds-app/ios/VDSQuiz/Images.xcassets/SplashScreenLegacy.imageset/image.png b/vds-app/ios/VDSQuiz/Images.xcassets/SplashScreenLegacy.imageset/image.png
new file mode 100644
index 0000000..11af7f4
Binary files /dev/null and b/vds-app/ios/VDSQuiz/Images.xcassets/SplashScreenLegacy.imageset/image.png differ
diff --git a/vds-app/ios/VDSQuiz/Images.xcassets/SplashScreenLegacy.imageset/image@2x.png b/vds-app/ios/VDSQuiz/Images.xcassets/SplashScreenLegacy.imageset/image@2x.png
new file mode 100644
index 0000000..11af7f4
Binary files /dev/null and b/vds-app/ios/VDSQuiz/Images.xcassets/SplashScreenLegacy.imageset/image@2x.png differ
diff --git a/vds-app/ios/VDSQuiz/Images.xcassets/SplashScreenLegacy.imageset/image@3x.png b/vds-app/ios/VDSQuiz/Images.xcassets/SplashScreenLegacy.imageset/image@3x.png
new file mode 100644
index 0000000..11af7f4
Binary files /dev/null and b/vds-app/ios/VDSQuiz/Images.xcassets/SplashScreenLegacy.imageset/image@3x.png differ
diff --git a/vds-app/ios/VDSQuiz/Info.plist b/vds-app/ios/VDSQuiz/Info.plist
new file mode 100644
index 0000000..439d002
--- /dev/null
+++ b/vds-app/ios/VDSQuiz/Info.plist
@@ -0,0 +1,80 @@
+
+
+
+
+ CADisableMinimumFrameDurationOnPhone
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ VDS Quiz
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 3.8.3
+ CFBundleSignature
+ ????
+ CFBundleURLTypes
+
+
+ CFBundleURLSchemes
+
+ com.dslak.vdsquiz
+
+
+
+ CFBundleURLSchemes
+
+ exp+vds-quiz
+
+
+
+ CFBundleVersion
+ 1
+ LSMinimumSystemVersion
+ 12.0
+ LSRequiresIPhoneOS
+
+ NSAppTransportSecurity
+
+ NSAllowsArbitraryLoads
+
+ NSAllowsLocalNetworking
+
+
+ UILaunchStoryboardName
+ SplashScreen
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+ UIRequiresFullScreen
+
+ UIStatusBarStyle
+ UIStatusBarStyleDefault
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UIUserInterfaceStyle
+ Light
+ UIViewControllerBasedStatusBarAppearance
+
+
+
\ No newline at end of file
diff --git a/vds-app/ios/VDSQuiz/SplashScreen.storyboard b/vds-app/ios/VDSQuiz/SplashScreen.storyboard
new file mode 100644
index 0000000..31ba639
--- /dev/null
+++ b/vds-app/ios/VDSQuiz/SplashScreen.storyboard
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vds-app/ios/VDSQuiz/Supporting/Expo.plist b/vds-app/ios/VDSQuiz/Supporting/Expo.plist
new file mode 100644
index 0000000..0a5ea4e
--- /dev/null
+++ b/vds-app/ios/VDSQuiz/Supporting/Expo.plist
@@ -0,0 +1,14 @@
+
+
+
+
+ EXUpdatesCheckOnLaunch
+ ALWAYS
+ EXUpdatesEnabled
+
+ EXUpdatesLaunchWaitMs
+ 10
+ EXUpdatesURL
+ https://u.expo.dev/7a0112f0-f4a6-11e9-b7eb-0ba61596acb6
+
+
\ No newline at end of file
diff --git a/vds-app/ios/VDSQuiz/VDSQuiz-Bridging-Header.h b/vds-app/ios/VDSQuiz/VDSQuiz-Bridging-Header.h
new file mode 100644
index 0000000..8361941
--- /dev/null
+++ b/vds-app/ios/VDSQuiz/VDSQuiz-Bridging-Header.h
@@ -0,0 +1,3 @@
+//
+// Use this file to import your target's public headers that you would like to expose to Swift.
+//
diff --git a/vds-app/ios/VDSQuiz/VDSQuiz.entitlements b/vds-app/ios/VDSQuiz/VDSQuiz.entitlements
new file mode 100644
index 0000000..f683276
--- /dev/null
+++ b/vds-app/ios/VDSQuiz/VDSQuiz.entitlements
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/vds-app/package.json b/vds-app/package.json
index 67ffcb7..d42b55a 100644
--- a/vds-app/package.json
+++ b/vds-app/package.json
@@ -5,47 +5,40 @@
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"start": "expo start",
- "android": "expo start --android",
+ "android": "expo run:android",
"web": "expo start --web",
"build-android-old": "expo build:android -t app-bundle",
"build-android": "eas build -p android",
"build-android-local": "eas build -p android --profile preview",
"build-ios": "expo build:ios",
- "ios": "expo start --ios",
+ "ios": "expo run:ios",
"eject": "expo eject",
"lint": "eslint ."
},
"dependencies": {
- "@expo/config-plugins": "^6.0.1",
"@react-native-admob/admob": "^2.0.1",
- "@react-native-async-storage/async-storage": "1.17.11",
- "@react-native-masked-view/masked-view": "^0.2.8",
- "@react-native-picker/picker": "2.4.8",
- "expo": "^48.0.8",
- "expo-build-properties": "~0.5.1",
- "expo-dev-client": "~2.1.6",
- "expo-permissions": "^14.1.1",
- "expo-updates": "~0.16.3",
- "react": "^18.2.0",
- "react-native": "0.71.4",
- "react-native-gesture-handler": "^2.9.0",
- "react-native-google-mobile-ads": "^10.0.0",
- "react-native-reanimated": "~2.14.4",
- "react-native-safe-area-context": "^4.5.0",
+ "@react-native-async-storage/async-storage": "^2.2.0",
+ "@react-native-community/cli": "^20.0.2",
+ "@react-native-masked-view/masked-view": "^0.3.2",
+ "@react-native-picker/picker": "2.11.1",
+ "@react-navigation/bottom-tabs": "^7.4.7",
+ "@react-navigation/native": "^7.1.17",
+ "@react-navigation/native-stack": "^7.3.26",
+ "expo": "^54.0.4",
+ "expo-build-properties": "^1.0.8",
+ "expo-dev-client": "^6.0.12",
+ "expo-updates": "^29.0.10",
+ "react": "19.1.0",
+ "react-native": "^0.81.4",
+ "react-native-gesture-handler": "^2.28.0",
+ "react-native-google-mobile-ads": "^15.7.0",
+ "react-native-reanimated": "^4.1.0",
+ "react-native-safe-area-context": "^5.6.1",
"react-native-safe-area-view": "^1.1.1",
- "react-native-screens": "^3.20.0",
- "react-navigation": "^4.4.4",
- "react-navigation-stack": "^2.10.4",
+ "react-native-screens": "^4.16.0",
+ "react-native-worklets": "^0.5.1",
"remove-console-logs": "^0.1.0"
},
- "devDependencies": {
- "@babel/core": "^7.20.0",
- "@babel/preset-env": "^7.20.2",
- "babel-preset-expo": "^9.3.0",
- "eslint": "^8.36.0",
- "eslint-config-handlebarlabs": "^0.0.6",
- "prettier": "^2.8.6"
- },
"peerDependencies": {
"expo-modules-autolinking": "^1.1.2"
},
@@ -54,5 +47,9 @@
"./assets/fonts/"
]
},
- "private": true
+ "private": true,
+ "devDependencies": {
+ "@babel/core": "^7.28.4",
+ "@babel/preset-env": "^7.28.3"
+ }
}
diff --git a/yarn.lock b/yarn.lock
index 2a2b06a..fb57ccd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,21 +2,3 @@
# yarn lockfile v1
-"@react-native-async-storage/async-storage@^1.17.12":
- version "1.17.12"
- resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.17.12.tgz#a39e4df5b06795ce49b2ca5b7ca9b8faadf8e621"
- integrity sha512-BXg4OxFdjPTRt+8MvN6jz4muq0/2zII3s7HeT/11e4Zeh3WCgk/BleLzUcDfVqF3OzFHUqEkSrb76d6Ndjd/Nw==
- dependencies:
- merge-options "^3.0.4"
-
-is-plain-obj@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
- integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==
-
-merge-options@^3.0.4:
- version "3.0.4"
- resolved "https://registry.yarnpkg.com/merge-options/-/merge-options-3.0.4.tgz#84709c2aa2a4b24c1981f66c179fe5565cc6dbb7"
- integrity sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==
- dependencies:
- is-plain-obj "^2.1.0"