diff --git a/api/types.ts b/api/types.ts
index 2c4be95..ef7239b 100644
--- a/api/types.ts
+++ b/api/types.ts
@@ -29,8 +29,8 @@ export interface QuestionResponse {
type: QuestionTypes;
difficulty: number;
- variants: QuestionVariant[];
- correct_answers: number[];
+ variants?: QuestionVariant[];
+ correct_answers: number[]|string[];
category_id: number;
category: CategoryResponse;
diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx
index 9b77a9d..fb18439 100644
--- a/app/(tabs)/index.tsx
+++ b/app/(tabs)/index.tsx
@@ -4,9 +4,9 @@ import { StyleSheet } from "react-native";
import { useQuestions } from "@/api";
import { HelloWave } from "@/components/hello-wave";
import ParallaxScrollView from "@/components/parallax-scroll-view";
+import useAuthContext from "@/components/providers/auth-provider/hook";
import Question from "@/components/question";
import { ThemedView } from "@/components/themed-view";
-import useAuthContext from "@/components/ui/auth-provider/hook";
import { ThemedText } from "@/components/ui/themed-text";
import { router } from "expo-router";
diff --git a/app/(tabs)/me.tsx b/app/(tabs)/me.tsx
index 4182983..3720926 100644
--- a/app/(tabs)/me.tsx
+++ b/app/(tabs)/me.tsx
@@ -1,5 +1,5 @@
-import useAuthContext from '@/components/ui/auth-provider/hook';
+import useAuthContext from '@/components/providers/auth-provider/hook';
import { Button, ButtonText } from '@/components/ui/button';
import Content from '@/components/ui/content';
import { Divider } from '@/components/ui/divider';
diff --git a/app/(tabs)/questions.tsx b/app/(tabs)/questions.tsx
index ecc05f1..894f341 100644
--- a/app/(tabs)/questions.tsx
+++ b/app/(tabs)/questions.tsx
@@ -9,8 +9,7 @@ import { router } from 'expo-router';
export default function QuestionsScreen() {
const { data: questions, isLoading: isLoadingQuestions } = useQuestions();
- const questionsLoaded =
- !isLoadingQuestions && questions && questions.meta.total > 0;
+ const questionsLoaded = !isLoadingQuestions && questions && questions.meta.total > 0;
return (
@@ -25,6 +24,9 @@ export default function QuestionsScreen() {
onPress={() => router.push(`/questions/${question.id}`)}
/>
))}
+ {isLoadingQuestions && Array.from({ length: 5 }).map((_, i) => (
+
+ ))}
diff --git a/app/_layout.tsx b/app/_layout.tsx
index b91e533..d6c46de 100644
--- a/app/_layout.tsx
+++ b/app/_layout.tsx
@@ -10,7 +10,7 @@ import "react-native-reanimated";
import { useColorScheme } from "@/hooks/use-color-scheme";
-import AuthProvider from "@/components/ui/auth-provider";
+import AuthProvider from "@/components/providers/auth-provider";
import { GluestackUIProvider } from "@/components/ui/gluestack-ui-provider";
import "@/global.css";
import { ToastProvider } from 'react-native-toast-notifications';
diff --git a/app/login.tsx b/app/login.tsx
index 4561db0..51572d8 100644
--- a/app/login.tsx
+++ b/app/login.tsx
@@ -1,4 +1,4 @@
-import useAuthContext from "@/components/ui/auth-provider/hook";
+import useAuthContext from "@/components/providers/auth-provider/hook";
import Content from "@/components/ui/content";
import LoginForm, { LoginFormData } from "@/components/ui/login-form";
import Panel from "@/components/ui/panel";
diff --git a/app/questions/[id].tsx b/app/questions/[id].tsx
index 3e17747..211b043 100644
--- a/app/questions/[id].tsx
+++ b/app/questions/[id].tsx
@@ -1,23 +1,73 @@
import { useQuestion } from "@/api";
+import { QuestionTypes, QuestionVariant } from "@/api/types";
import Question from "@/components/question";
+import Answer from "@/components/ui/answer";
+import { Button, ButtonText } from "@/components/ui/button";
import Content from "@/components/ui/content";
-import { Skeleton } from "@/components/ui/skeleton";
+import { Divider } from "@/components/ui/divider";
import { Stack, useLocalSearchParams } from "expo-router";
+import { useState } from "react";
+import { View } from "react-native";
const QuestionScreen = () => {
const { id: idParam } = useLocalSearchParams<{ id: string }>();
const id = +idParam;
const { data, isLoading } = useQuestion(id);
+ const [withCorrect, setWithCorrect] = useState(false);
+
+ if (!data)
+ return (
+ <>
+
+ {isLoading && }
+ >
+ );
+
+ const answers: QuestionVariant[] = data.variants?.length
+ ? data.variants
+ : [
+ {
+ id: 1,
+ text: data.correct_answers[0] as string,
+ },
+ ];
+
+ const displayAnswers =
+ (data.type === QuestionTypes.Text && withCorrect) ||
+ data.type !== QuestionTypes.Text;
+
+ const withCorrectBtnText = withCorrect ? "ON" : "OFF";
return (
<>
-
-
- {isLoading && }
- { data && }
-
- >
- )
-}
+
+
+
-export default QuestionScreen;
\ No newline at end of file
+
+
+
+ {displayAnswers &&
+ answers.map((answer) => (
+
+ ))}
+
+
+
+
+ >
+ );
+};
+
+export default QuestionScreen;
diff --git a/components/ui/auth-provider/context.tsx b/components/providers/auth-provider/context.tsx
similarity index 100%
rename from components/ui/auth-provider/context.tsx
rename to components/providers/auth-provider/context.tsx
diff --git a/components/ui/auth-provider/hook.ts b/components/providers/auth-provider/hook.ts
similarity index 100%
rename from components/ui/auth-provider/hook.ts
rename to components/providers/auth-provider/hook.ts
diff --git a/components/ui/auth-provider/index.tsx b/components/providers/auth-provider/index.tsx
similarity index 100%
rename from components/ui/auth-provider/index.tsx
rename to components/providers/auth-provider/index.tsx
diff --git a/components/question.tsx b/components/question.tsx
index e5c6c43..816ae3d 100644
--- a/components/question.tsx
+++ b/components/question.tsx
@@ -1,32 +1,52 @@
import { QuestionResponse } from "@/api/types";
-import { Pressable, StyleSheet } from "react-native";
+import capitalizeFirst from "@/utils/capitalize-first";
+import { FontAwesome5 } from "@expo/vector-icons";
+import { Pressable, StyleSheet, View } from "react-native";
+import { Divider } from "./ui/divider";
import Panel from "./ui/panel";
+import { Skeleton } from "./ui/skeleton";
import { ThemedText } from "./ui/themed-text";
interface QuestionProps {
- question: QuestionResponse;
+ question?: QuestionResponse;
onPress?: () => void;
+ withMeta?: boolean;
}
-const Question = ({ question, onPress }: QuestionProps) => {
+const Question = ({ question, onPress, withMeta = false }: QuestionProps) => {
+ if (!question) return ;
+
return (
+
+ {question.category.name}
+
{question.title}
{question.description}
+
+
+
+
+
+ {capitalizeFirst(question.type)}
+
+
+
+ {question.difficulty}
+
+
- )
-}
+ );
+};
const styles = StyleSheet.create({
questionTitle: {
fontSize: 18,
fontWeight: "600",
- marginBottom: 10
+ marginBottom: 10,
},
});
-
-
-export default Question;
\ No newline at end of file
+export default Question;
diff --git a/components/ui/answer.tsx b/components/ui/answer.tsx
new file mode 100644
index 0000000..8cbaa29
--- /dev/null
+++ b/components/ui/answer.tsx
@@ -0,0 +1,148 @@
+import { QuestionVariant } from "@/api/types";
+import { FontAwesome5 } from "@expo/vector-icons";
+import { useMemo } from "react";
+import { Pressable, StyleSheet, View } from "react-native";
+import Panel from "./panel";
+import { Skeleton } from "./skeleton";
+import { ThemedText } from "./themed-text";
+
+interface AnswerProps {
+ answer?: QuestionVariant;
+ isTextType?: boolean;
+
+ userAnswers?: string[] | number[];
+ correctAnswers?: string[] | number[];
+
+ isCorrectUserAnswer?: boolean;
+
+ displayCorrect?: boolean;
+ displayUserAnswer?: boolean;
+
+ onPress?: () => void;
+}
+type UserAnsweredType = "correct"|"incorrect"|"non_answered";
+
+const Answer = ({
+ answer,
+ onPress,
+ userAnswers,
+ correctAnswers,
+
+ isTextType = false,
+ isCorrectUserAnswer = false,
+ displayCorrect = false,
+ displayUserAnswer = false
+}: AnswerProps) => {
+ // Check if user answered on this answer variation
+ const isAnsweredByUser = useMemo(() => {
+ if(!userAnswers || !answer) return false;
+
+ if(isTextType) {
+ return (userAnswers[0] as string).length > 0;
+ }
+ return (userAnswers as number[]).includes(answer.id);
+ }, [userAnswers, answer, isTextType]);
+
+ // Check if this answer variation is correct
+ const isCorrectAnswer = useMemo(() => {
+ if(!correctAnswers || !answer) return false;
+
+ if(isTextType) {
+ return true;
+ }
+
+ return (correctAnswers as number[]).includes(answer.id);
+ }, [correctAnswers, isTextType, answer]);
+
+ // Check if user answered correct on this answer variation
+ const userAnsweredCorrectly = useMemo(() => {
+ if(isTextType) return isCorrectUserAnswer ? "correct" : "incorrect";
+
+ if(!isAnsweredByUser && isCorrectAnswer)
+ return "non_answered";
+
+ return isCorrectUserAnswer ? "correct" : "incorrect";
+ }, [isTextType, isAnsweredByUser, isCorrectAnswer, isCorrectUserAnswer]);
+
+ const stylePanel = useMemo(() => {
+ let style = undefined;
+
+ if(displayCorrect && isCorrectAnswer && !isTextType)
+ style = styles.correctAnswer;
+
+ if(displayUserAnswer && isAnsweredByUser && !isTextType)
+ style = styles.userAnsweredAnswer;
+
+ if(displayUserAnswer && displayCorrect) {
+ switch (userAnsweredCorrectly) {
+ case 'correct':
+ if(isAnsweredByUser)
+ style = styles.correctAnswer;
+ break;
+ case 'incorrect':
+ style = styles.incorrectAnswer;
+ break;
+ case 'non_answered':
+ style = styles.nonAnsweredAnswer;
+ break;
+ }
+ }
+
+ return style;
+ }, [displayCorrect, displayUserAnswer, isAnsweredByUser, isCorrectAnswer, isTextType, userAnsweredCorrectly]);
+ const textColor = useMemo(() => {
+ if(stylePanel)
+ return "#ffffff";
+ else
+ return undefined;
+ }, [stylePanel]);
+
+ if (!answer) return ;
+
+ return (
+
+
+
+ {isTextType ? (
+
+ ) : (
+
+ {answer.id}
+
+ )}
+
+ {answer.text}
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ answerNumberation: {
+ width: 25,
+ height: 25,
+ alignItems: "center",
+ justifyContent: "center",
+ borderRadius: 5,
+ marginRight: 10,
+ backgroundColor: "#bdbdbd",
+ },
+ correctAnswer: {
+ backgroundColor: "#2ab300"
+ },
+ incorrectAnswer: {
+ backgroundColor: "#c40404"
+ },
+ nonAnsweredAnswer: {
+ borderWidth: 1,
+ borderStyle: "solid",
+ borderColor: "#2ab300"
+ },
+ userAnsweredAnswer: {
+ borderWidth: 1,
+ borderStyle: "solid",
+ borderColor: "#2cb3f2"
+ }
+});
+
+export default Answer;
diff --git a/components/ui/panel.tsx b/components/ui/panel.tsx
index 0c09dd1..fac6de0 100644
--- a/components/ui/panel.tsx
+++ b/components/ui/panel.tsx
@@ -1,16 +1,18 @@
import { useThemeColor } from "@/hooks/use-theme-color";
import { ReactNode } from "react";
-import { StyleSheet, View } from "react-native";
+import { StyleProp, StyleSheet, View, ViewStyle } from "react-native";
interface PanelProps {
children?: ReactNode;
+ className?: string;
+ style?: StyleProp;
}
-const Panel = ({children}: PanelProps) => {
+const Panel = ({children, className, style}: PanelProps) => {
const backgroundColor = useThemeColor({ }, 'background');
return (
-
+
{children}
)
diff --git a/components/ui/themed-text.tsx b/components/ui/themed-text.tsx
index d79d0a1..91bed6e 100644
--- a/components/ui/themed-text.tsx
+++ b/components/ui/themed-text.tsx
@@ -5,7 +5,7 @@ import { useThemeColor } from '@/hooks/use-theme-color';
export type ThemedTextProps = TextProps & {
lightColor?: string;
darkColor?: string;
- type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link';
+ type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link' | 'meta';
};
export function ThemedText({
@@ -15,7 +15,8 @@ export function ThemedText({
type = 'default',
...rest
}: ThemedTextProps) {
- const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text');
+ const colorProperty = type === 'meta' ? 'meta' : 'text';
+ const color = useThemeColor({ light: lightColor, dark: darkColor }, colorProperty);
return (