react-native-elearn-ui 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/module/components/Icon.js +27 -0
- package/lib/module/components/Icon.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/screens/PearlsScreen.js +741 -0
- package/lib/module/screens/PearlsScreen.js.map +1 -0
- package/lib/module/screens/QbankScreen.js +18 -3
- package/lib/module/screens/QbankScreen.js.map +1 -1
- package/lib/module/screens/QuestionScreen.js +22 -10
- package/lib/module/screens/QuestionScreen.js.map +1 -1
- package/lib/module/screens/QuizStartScreen.js +55 -7
- package/lib/module/screens/QuizStartScreen.js.map +1 -1
- package/lib/module/screens/ResultScreen.js +10 -6
- package/lib/module/screens/ResultScreen.js.map +1 -1
- package/lib/module/screens/TopicListScreen.js +53 -6
- package/lib/module/screens/TopicListScreen.js.map +1 -1
- package/lib/module/screens/index.js +1 -0
- package/lib/module/screens/index.js.map +1 -1
- package/lib/typescript/src/components/Icon.d.ts +1 -1
- package/lib/typescript/src/components/Icon.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/screens/PearlsScreen.d.ts +8 -0
- package/lib/typescript/src/screens/PearlsScreen.d.ts.map +1 -0
- package/lib/typescript/src/screens/QbankScreen.d.ts +3 -0
- package/lib/typescript/src/screens/QbankScreen.d.ts.map +1 -1
- package/lib/typescript/src/screens/QuestionScreen.d.ts +6 -4
- package/lib/typescript/src/screens/QuestionScreen.d.ts.map +1 -1
- package/lib/typescript/src/screens/QuizStartScreen.d.ts +6 -3
- package/lib/typescript/src/screens/QuizStartScreen.d.ts.map +1 -1
- package/lib/typescript/src/screens/ResultScreen.d.ts +2 -1
- package/lib/typescript/src/screens/ResultScreen.d.ts.map +1 -1
- package/lib/typescript/src/screens/TopicListScreen.d.ts +4 -2
- package/lib/typescript/src/screens/TopicListScreen.d.ts.map +1 -1
- package/lib/typescript/src/screens/index.d.ts +1 -0
- package/lib/typescript/src/screens/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Icon.tsx +35 -1
- package/src/index.tsx +1 -0
- package/src/screens/PearlsScreen.tsx +720 -0
- package/src/screens/QbankScreen.tsx +24 -3
- package/src/screens/QuestionScreen.tsx +28 -14
- package/src/screens/QuizStartScreen.tsx +45 -10
- package/src/screens/ResultScreen.tsx +13 -8
- package/src/screens/TopicListScreen.tsx +40 -7
- package/src/screens/index.ts +1 -0
|
@@ -12,17 +12,32 @@ import { useElearnConfig } from '../context/ElearnConfigContext';
|
|
|
12
12
|
import { useTheme } from '../context/ThemeContext';
|
|
13
13
|
import { Typography, Card, ProgressBar, Icon } from '../components';
|
|
14
14
|
import type { IconName } from '../components';
|
|
15
|
+
import type { Course } from '../types';
|
|
15
16
|
|
|
16
17
|
interface QbankScreenProps {
|
|
17
18
|
hideHeader?: boolean;
|
|
19
|
+
onBack?: () => void;
|
|
20
|
+
onSelectCourse?: (course: Course) => void;
|
|
18
21
|
}
|
|
19
22
|
|
|
20
|
-
export const QbankScreen: React.FC<QbankScreenProps> = ({
|
|
23
|
+
export const QbankScreen: React.FC<QbankScreenProps> = ({
|
|
24
|
+
hideHeader = false,
|
|
25
|
+
onBack,
|
|
26
|
+
onSelectCourse,
|
|
27
|
+
}) => {
|
|
21
28
|
const { config, callbacks } = useElearnConfig();
|
|
22
29
|
const theme = useTheme();
|
|
23
30
|
|
|
24
31
|
const courses = config.courses || [];
|
|
25
32
|
|
|
33
|
+
const handleBack = () => {
|
|
34
|
+
if (onBack) {
|
|
35
|
+
onBack();
|
|
36
|
+
} else {
|
|
37
|
+
callbacks.onBackToDashboard?.();
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
26
41
|
return (
|
|
27
42
|
<SafeAreaView style={[styles.safeArea, { backgroundColor: theme.background }]}>
|
|
28
43
|
<StatusBar barStyle="dark-content" />
|
|
@@ -31,7 +46,7 @@ export const QbankScreen: React.FC<QbankScreenProps> = ({ hideHeader = false })
|
|
|
31
46
|
{!hideHeader && (
|
|
32
47
|
<View style={styles.header}>
|
|
33
48
|
<TouchableOpacity
|
|
34
|
-
onPress={
|
|
49
|
+
onPress={handleBack}
|
|
35
50
|
style={[styles.backButton, { borderColor: theme.border }]}
|
|
36
51
|
>
|
|
37
52
|
<Icon name="arrow-left" size={20} color={theme.textPrimary} />
|
|
@@ -145,7 +160,13 @@ export const QbankScreen: React.FC<QbankScreenProps> = ({ hideHeader = false })
|
|
|
145
160
|
<Card
|
|
146
161
|
key={course.id}
|
|
147
162
|
bordered
|
|
148
|
-
onPress={() =>
|
|
163
|
+
onPress={() => {
|
|
164
|
+
if (onSelectCourse) {
|
|
165
|
+
onSelectCourse(course);
|
|
166
|
+
} else {
|
|
167
|
+
callbacks.onSelectCourse?.(course);
|
|
168
|
+
}
|
|
169
|
+
}}
|
|
149
170
|
style={styles.subjectCard}
|
|
150
171
|
>
|
|
151
172
|
<View style={styles.subjectRow}>
|
|
@@ -14,22 +14,32 @@ import { useTheme } from '../context/ThemeContext';
|
|
|
14
14
|
import { Typography, Button, Icon } from '../components';
|
|
15
15
|
|
|
16
16
|
interface QuestionScreenProps {
|
|
17
|
-
quizId
|
|
18
|
-
questions
|
|
19
|
-
title
|
|
17
|
+
quizId?: string;
|
|
18
|
+
questions?: Question[];
|
|
19
|
+
title?: string;
|
|
20
|
+
topicId?: string;
|
|
20
21
|
onClose: () => void;
|
|
22
|
+
onFinishQuiz?: (quizId: string, results: QuizResults) => void;
|
|
21
23
|
hideHeader?: boolean;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
export const QuestionScreen: React.FC<QuestionScreenProps> = ({
|
|
25
27
|
quizId,
|
|
26
28
|
questions,
|
|
29
|
+
title,
|
|
30
|
+
topicId,
|
|
27
31
|
onClose,
|
|
32
|
+
onFinishQuiz,
|
|
28
33
|
hideHeader = false,
|
|
29
34
|
}) => {
|
|
30
|
-
const { callbacks } = useElearnConfig();
|
|
35
|
+
const { config, callbacks } = useElearnConfig();
|
|
31
36
|
const theme = useTheme();
|
|
32
37
|
|
|
38
|
+
const activeTopic = topicId ? config.courses?.flatMap((c) => c.topics).find((t) => t.id === topicId) : undefined;
|
|
39
|
+
const activeQuestions = questions || activeTopic?.questions || [];
|
|
40
|
+
const activeTitle = title || activeTopic?.title || 'Quiz';
|
|
41
|
+
const activeQuizId = quizId || topicId || '';
|
|
42
|
+
|
|
33
43
|
const [currentIndex, setCurrentIndex] = useState(0);
|
|
34
44
|
const [selectedOption, setSelectedOption] = useState<number | null>(null);
|
|
35
45
|
const [isAnswered, setIsAnswered] = useState(false);
|
|
@@ -38,7 +48,7 @@ export const QuestionScreen: React.FC<QuestionScreenProps> = ({
|
|
|
38
48
|
const [startTime] = useState(Date.now());
|
|
39
49
|
const [isPaused, setIsPaused] = useState(false);
|
|
40
50
|
|
|
41
|
-
if (!
|
|
51
|
+
if (!activeQuestions || activeQuestions.length === 0) {
|
|
42
52
|
return (
|
|
43
53
|
<SafeAreaView style={[styles.safeArea, { backgroundColor: theme.background }]}>
|
|
44
54
|
<View style={styles.errorContainer}>
|
|
@@ -49,7 +59,7 @@ export const QuestionScreen: React.FC<QuestionScreenProps> = ({
|
|
|
49
59
|
);
|
|
50
60
|
}
|
|
51
61
|
|
|
52
|
-
const currentQuestion =
|
|
62
|
+
const currentQuestion = activeQuestions[currentIndex]!;
|
|
53
63
|
const optionPrefixes = ['A', 'B', 'C', 'D', 'E', 'F'];
|
|
54
64
|
|
|
55
65
|
const handleOptionPress = (index: number) => {
|
|
@@ -68,19 +78,19 @@ export const QuestionScreen: React.FC<QuestionScreenProps> = ({
|
|
|
68
78
|
};
|
|
69
79
|
|
|
70
80
|
const handleNext = () => {
|
|
71
|
-
if (currentIndex <
|
|
81
|
+
if (currentIndex < activeQuestions.length - 1) {
|
|
72
82
|
setCurrentIndex((prev) => prev + 1);
|
|
73
83
|
setSelectedOption(null);
|
|
74
84
|
setIsAnswered(false);
|
|
75
85
|
} else {
|
|
76
86
|
// Last question - finish quiz
|
|
77
87
|
const totalTimeSeconds = Math.round((Date.now() - startTime) / 1000);
|
|
78
|
-
const wrongCount =
|
|
79
|
-
const accuracy = Math.round((correctCount /
|
|
88
|
+
const wrongCount = activeQuestions.length - correctCount;
|
|
89
|
+
const accuracy = Math.round((correctCount / activeQuestions.length) * 100);
|
|
80
90
|
|
|
81
91
|
const results: QuizResults = {
|
|
82
|
-
quizId,
|
|
83
|
-
totalQuestions:
|
|
92
|
+
quizId: activeQuizId,
|
|
93
|
+
totalQuestions: activeQuestions.length,
|
|
84
94
|
correctCount,
|
|
85
95
|
wrongCount,
|
|
86
96
|
skippedCount: 0,
|
|
@@ -89,7 +99,11 @@ export const QuestionScreen: React.FC<QuestionScreenProps> = ({
|
|
|
89
99
|
userAnswers,
|
|
90
100
|
};
|
|
91
101
|
|
|
92
|
-
|
|
102
|
+
if (onFinishQuiz) {
|
|
103
|
+
onFinishQuiz(activeQuizId, results);
|
|
104
|
+
} else {
|
|
105
|
+
callbacks.onFinishQuiz?.(activeQuizId, results);
|
|
106
|
+
}
|
|
93
107
|
}
|
|
94
108
|
};
|
|
95
109
|
|
|
@@ -105,7 +119,7 @@ export const QuestionScreen: React.FC<QuestionScreenProps> = ({
|
|
|
105
119
|
</TouchableOpacity>
|
|
106
120
|
|
|
107
121
|
<Typography variant="h2" bold style={styles.headerTitle}>
|
|
108
|
-
QBank
|
|
122
|
+
{activeTitle || 'QBank'}
|
|
109
123
|
</Typography>
|
|
110
124
|
|
|
111
125
|
<TouchableOpacity style={styles.shareButton}>
|
|
@@ -119,7 +133,7 @@ export const QuestionScreen: React.FC<QuestionScreenProps> = ({
|
|
|
119
133
|
{/* Question Header & Pause/Bookmark Actions Row */}
|
|
120
134
|
<View style={styles.metaRow}>
|
|
121
135
|
<Typography variant="caption" bold color={theme.textSecondary} style={styles.questionNumLabel}>
|
|
122
|
-
QUESTION {currentIndex + 1}/{
|
|
136
|
+
QUESTION {currentIndex + 1}/{activeQuestions.length}
|
|
123
137
|
</Typography>
|
|
124
138
|
<View style={styles.metaActions}>
|
|
125
139
|
<TouchableOpacity style={styles.metaActionBtn}>
|
|
@@ -6,32 +6,67 @@ import {
|
|
|
6
6
|
SafeAreaView,
|
|
7
7
|
StatusBar,
|
|
8
8
|
} from 'react-native';
|
|
9
|
-
import type { Topic, Course } from '../types';
|
|
9
|
+
import type { Topic, Course, Question } from '../types';
|
|
10
10
|
import { useElearnConfig } from '../context/ElearnConfigContext';
|
|
11
11
|
import { useTheme } from '../context/ThemeContext';
|
|
12
12
|
import { Typography, Card, Button, Icon } from '../components';
|
|
13
13
|
|
|
14
14
|
interface QuizStartScreenProps {
|
|
15
|
-
course
|
|
16
|
-
|
|
15
|
+
course?: Course;
|
|
16
|
+
courseId?: string;
|
|
17
|
+
topic?: Topic;
|
|
18
|
+
topicId?: string;
|
|
17
19
|
onBack: () => void;
|
|
18
20
|
hideHeader?: boolean;
|
|
21
|
+
onStartQuiz?: (quizId: string, questions: Question[], title: string) => void;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
export const QuizStartScreen: React.FC<QuizStartScreenProps> = ({
|
|
25
|
+
course,
|
|
26
|
+
courseId,
|
|
22
27
|
topic,
|
|
28
|
+
topicId,
|
|
23
29
|
onBack,
|
|
24
30
|
hideHeader = false,
|
|
31
|
+
onStartQuiz,
|
|
25
32
|
}) => {
|
|
26
|
-
const { callbacks } = useElearnConfig();
|
|
33
|
+
const { config, callbacks } = useElearnConfig();
|
|
27
34
|
const theme = useTheme();
|
|
28
35
|
|
|
36
|
+
const activeCourse = course || config.courses?.find((c) => c.id === courseId);
|
|
37
|
+
const activeTopic = topic || activeCourse?.topics.find((t) => t.id === topicId) || config.courses?.flatMap((c) => c.topics).find((t) => t.id === topicId);
|
|
38
|
+
|
|
29
39
|
const handleStart = () => {
|
|
30
|
-
if (
|
|
31
|
-
|
|
40
|
+
if (activeTopic && activeTopic.questions) {
|
|
41
|
+
if (onStartQuiz) {
|
|
42
|
+
onStartQuiz(activeTopic.id, activeTopic.questions, activeTopic.title);
|
|
43
|
+
} else {
|
|
44
|
+
callbacks.onStartQuiz?.(activeTopic.id, activeTopic.questions, activeTopic.title);
|
|
45
|
+
}
|
|
32
46
|
}
|
|
33
47
|
};
|
|
34
48
|
|
|
49
|
+
if (!activeTopic) {
|
|
50
|
+
return (
|
|
51
|
+
<SafeAreaView style={[styles.safeArea, { backgroundColor: theme.background }]}>
|
|
52
|
+
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 }}>
|
|
53
|
+
<Typography variant="h2" bold style={{ marginBottom: 12 }}>Topic not found</Typography>
|
|
54
|
+
<TouchableOpacity
|
|
55
|
+
onPress={onBack}
|
|
56
|
+
style={{
|
|
57
|
+
paddingHorizontal: 20,
|
|
58
|
+
paddingVertical: 10,
|
|
59
|
+
borderRadius: 8,
|
|
60
|
+
backgroundColor: theme.primary,
|
|
61
|
+
}}
|
|
62
|
+
>
|
|
63
|
+
<Typography variant="body" bold color="#FFFFFF">Go Back</Typography>
|
|
64
|
+
</TouchableOpacity>
|
|
65
|
+
</View>
|
|
66
|
+
</SafeAreaView>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
35
70
|
// Mock list of glance topics matching mockup 3
|
|
36
71
|
const glanceTopics = [
|
|
37
72
|
'Cerebral Cortex',
|
|
@@ -67,7 +102,7 @@ export const QuizStartScreen: React.FC<QuizStartScreenProps> = ({
|
|
|
67
102
|
|
|
68
103
|
{/* Section Category Title */}
|
|
69
104
|
<Typography variant="h1" bold color={theme.textPrimary} style={styles.categoryTitle}>
|
|
70
|
-
{
|
|
105
|
+
{activeTopic.category || 'Neuro Anatomy'}
|
|
71
106
|
</Typography>
|
|
72
107
|
|
|
73
108
|
{/* Main Info Box */}
|
|
@@ -80,10 +115,10 @@ export const QuizStartScreen: React.FC<QuizStartScreenProps> = ({
|
|
|
80
115
|
{/* Right details */}
|
|
81
116
|
<View style={styles.topicHeaderDetails}>
|
|
82
117
|
<Typography variant="h2" bold color={theme.textPrimary} style={styles.topicMainTitle}>
|
|
83
|
-
{
|
|
118
|
+
{activeTopic.title}
|
|
84
119
|
</Typography>
|
|
85
120
|
<Typography variant="caption" color={theme.textSecondary}>
|
|
86
|
-
{
|
|
121
|
+
{activeTopic.totalQuestions} Questions
|
|
87
122
|
</Typography>
|
|
88
123
|
</View>
|
|
89
124
|
</View>
|
|
@@ -94,7 +129,7 @@ export const QuizStartScreen: React.FC<QuizStartScreenProps> = ({
|
|
|
94
129
|
onPress={handleStart}
|
|
95
130
|
variant="primary"
|
|
96
131
|
style={[styles.startButton, { backgroundColor: theme.primary }]}
|
|
97
|
-
disabled={!
|
|
132
|
+
disabled={!activeTopic.questions || activeTopic.questions.length === 0}
|
|
98
133
|
/>
|
|
99
134
|
</Card>
|
|
100
135
|
|
|
@@ -15,7 +15,8 @@ import { Typography, Card, CircularProgress, Button, Icon } from '../components'
|
|
|
15
15
|
|
|
16
16
|
interface ResultScreenProps {
|
|
17
17
|
results: QuizResults;
|
|
18
|
-
questions
|
|
18
|
+
questions?: Question[];
|
|
19
|
+
topicId?: string;
|
|
19
20
|
onClose: () => void;
|
|
20
21
|
onReviewAnswers?: () => void;
|
|
21
22
|
hideHeader?: boolean;
|
|
@@ -24,12 +25,16 @@ interface ResultScreenProps {
|
|
|
24
25
|
export const ResultScreen: React.FC<ResultScreenProps> = ({
|
|
25
26
|
results,
|
|
26
27
|
questions,
|
|
28
|
+
topicId,
|
|
27
29
|
onClose,
|
|
28
30
|
hideHeader = false,
|
|
29
31
|
}) => {
|
|
30
|
-
const { getString } = useElearnConfig();
|
|
32
|
+
const { config, getString } = useElearnConfig();
|
|
31
33
|
const theme = useTheme();
|
|
32
34
|
|
|
35
|
+
const activeTopic = topicId ? config.courses?.flatMap((c) => c.topics).find((t) => t.id === topicId) : undefined;
|
|
36
|
+
const activeQuestions = questions || activeTopic?.questions || [];
|
|
37
|
+
|
|
33
38
|
// Selected question index for individual review
|
|
34
39
|
const [selectedReviewIndex, setSelectedReviewIndex] = useState<number | null>(null);
|
|
35
40
|
|
|
@@ -51,14 +56,14 @@ export const ResultScreen: React.FC<ResultScreenProps> = ({
|
|
|
51
56
|
};
|
|
52
57
|
|
|
53
58
|
const handleNextReview = () => {
|
|
54
|
-
if (selectedReviewIndex !== null && selectedReviewIndex <
|
|
59
|
+
if (selectedReviewIndex !== null && selectedReviewIndex < activeQuestions.length - 1) {
|
|
55
60
|
setSelectedReviewIndex(selectedReviewIndex + 1);
|
|
56
61
|
}
|
|
57
62
|
};
|
|
58
63
|
|
|
59
64
|
// --- Sub-View: Individual Question Detail Review ---
|
|
60
65
|
if (selectedReviewIndex !== null) {
|
|
61
|
-
const currentReviewQuestion =
|
|
66
|
+
const currentReviewQuestion = activeQuestions[selectedReviewIndex]!;
|
|
62
67
|
const userAnswerIdx = results.userAnswers[currentReviewQuestion.id];
|
|
63
68
|
// Removed unused isCorrect variable
|
|
64
69
|
|
|
@@ -88,7 +93,7 @@ export const ResultScreen: React.FC<ResultScreenProps> = ({
|
|
|
88
93
|
{/* Question Index Meta */}
|
|
89
94
|
<View style={styles.metaRow}>
|
|
90
95
|
<Typography variant="caption" bold color={theme.textSecondary} style={styles.questionNumLabel}>
|
|
91
|
-
QUESTION {selectedReviewIndex + 1}/{
|
|
96
|
+
QUESTION {selectedReviewIndex + 1}/{activeQuestions.length}
|
|
92
97
|
</Typography>
|
|
93
98
|
<View style={styles.metaActions}>
|
|
94
99
|
<TouchableOpacity style={styles.metaActionBtn}>
|
|
@@ -211,11 +216,11 @@ export const ResultScreen: React.FC<ResultScreenProps> = ({
|
|
|
211
216
|
{/* Next Button */}
|
|
212
217
|
<TouchableOpacity
|
|
213
218
|
onPress={handleNextReview}
|
|
214
|
-
disabled={selectedReviewIndex ===
|
|
219
|
+
disabled={selectedReviewIndex === activeQuestions.length - 1}
|
|
215
220
|
style={[
|
|
216
221
|
styles.nextActionBtn,
|
|
217
222
|
{ backgroundColor: theme.primary },
|
|
218
|
-
selectedReviewIndex ===
|
|
223
|
+
selectedReviewIndex === activeQuestions.length - 1 && { opacity: 0.4 }
|
|
219
224
|
]}
|
|
220
225
|
>
|
|
221
226
|
<Typography variant="body" bold color="#FFFFFF">
|
|
@@ -303,7 +308,7 @@ export const ResultScreen: React.FC<ResultScreenProps> = ({
|
|
|
303
308
|
</View>
|
|
304
309
|
|
|
305
310
|
<View style={styles.reviewList}>
|
|
306
|
-
{
|
|
311
|
+
{activeQuestions.map((question, idx) => {
|
|
307
312
|
const userAnswerIdx = results.userAnswers[question.id];
|
|
308
313
|
const isCorrect = userAnswerIdx === question.correctOptionIndex;
|
|
309
314
|
|
|
@@ -13,26 +13,53 @@ import { useTheme } from '../context/ThemeContext';
|
|
|
13
13
|
import { Typography, Card, Icon } from '../components';
|
|
14
14
|
|
|
15
15
|
interface TopicListScreenProps {
|
|
16
|
-
course
|
|
16
|
+
course?: Course;
|
|
17
|
+
courseId?: string;
|
|
17
18
|
onBack: () => void;
|
|
18
19
|
hideHeader?: boolean;
|
|
20
|
+
onSelectTopic?: (course: Course, topic: Topic) => void;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export const TopicListScreen: React.FC<TopicListScreenProps> = ({
|
|
22
24
|
course,
|
|
25
|
+
courseId,
|
|
23
26
|
onBack,
|
|
24
27
|
hideHeader = false,
|
|
28
|
+
onSelectTopic,
|
|
25
29
|
}) => {
|
|
26
|
-
const { callbacks } = useElearnConfig();
|
|
30
|
+
const { config, callbacks } = useElearnConfig();
|
|
27
31
|
const theme = useTheme();
|
|
28
32
|
|
|
33
|
+
const activeCourse = course || config.courses?.find((c) => c.id === courseId);
|
|
34
|
+
|
|
29
35
|
// Active filter tab
|
|
30
36
|
const [activeTab, setActiveTab] = useState('All');
|
|
31
37
|
const tabs = ['All', 'New', 'Completed', 'Paused', 'Not Started'];
|
|
32
38
|
|
|
39
|
+
if (!activeCourse) {
|
|
40
|
+
return (
|
|
41
|
+
<SafeAreaView style={[styles.safeArea, { backgroundColor: theme.background }]}>
|
|
42
|
+
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 }}>
|
|
43
|
+
<Typography variant="h2" bold style={{ marginBottom: 12 }}>Course not found</Typography>
|
|
44
|
+
<TouchableOpacity
|
|
45
|
+
onPress={onBack}
|
|
46
|
+
style={{
|
|
47
|
+
paddingHorizontal: 20,
|
|
48
|
+
paddingVertical: 10,
|
|
49
|
+
borderRadius: 8,
|
|
50
|
+
backgroundColor: theme.primary,
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
<Typography variant="body" bold color="#FFFFFF">Go Back</Typography>
|
|
54
|
+
</TouchableOpacity>
|
|
55
|
+
</View>
|
|
56
|
+
</SafeAreaView>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
33
60
|
// Group topics by category
|
|
34
61
|
const categoriesMap: { [key: string]: Topic[] } = {};
|
|
35
|
-
|
|
62
|
+
activeCourse.topics.forEach((topic) => {
|
|
36
63
|
const cat = topic.category || 'GENERAL';
|
|
37
64
|
if (!categoriesMap[cat]) {
|
|
38
65
|
categoriesMap[cat] = [];
|
|
@@ -69,10 +96,10 @@ export const TopicListScreen: React.FC<TopicListScreenProps> = ({
|
|
|
69
96
|
{/* Subject Header Suffix */}
|
|
70
97
|
<View style={styles.subjectHeader}>
|
|
71
98
|
<Typography variant="h1" bold color={theme.textPrimary} style={styles.subjectTitle}>
|
|
72
|
-
{
|
|
99
|
+
{activeCourse.title}
|
|
73
100
|
</Typography>
|
|
74
101
|
<Typography variant="body" color={theme.textSecondary}>
|
|
75
|
-
{
|
|
102
|
+
{activeCourse.topics.length} Modules
|
|
76
103
|
</Typography>
|
|
77
104
|
</View>
|
|
78
105
|
|
|
@@ -122,7 +149,7 @@ export const TopicListScreen: React.FC<TopicListScreenProps> = ({
|
|
|
122
149
|
{list.map((topic) => {
|
|
123
150
|
globalIndex++;
|
|
124
151
|
const currentIndex = globalIndex;
|
|
125
|
-
const isLastInCourse = currentIndex ===
|
|
152
|
+
const isLastInCourse = currentIndex === activeCourse.topics.length;
|
|
126
153
|
|
|
127
154
|
return (
|
|
128
155
|
<View key={topic.id} style={styles.timelineItemRow}>
|
|
@@ -146,7 +173,13 @@ export const TopicListScreen: React.FC<TopicListScreenProps> = ({
|
|
|
146
173
|
{/* Right Side Info Card */}
|
|
147
174
|
<TouchableOpacity
|
|
148
175
|
activeOpacity={0.8}
|
|
149
|
-
onPress={() =>
|
|
176
|
+
onPress={() => {
|
|
177
|
+
if (onSelectTopic) {
|
|
178
|
+
onSelectTopic(activeCourse, topic);
|
|
179
|
+
} else {
|
|
180
|
+
callbacks.onSelectTopic?.(activeCourse, topic);
|
|
181
|
+
}
|
|
182
|
+
}}
|
|
150
183
|
style={styles.cardTouchable}
|
|
151
184
|
>
|
|
152
185
|
<Card bordered style={styles.topicCard}>
|
package/src/screens/index.ts
CHANGED