react-native-elearn-ui 0.1.28 → 0.1.32

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.
Files changed (34) hide show
  1. package/lib/module/components/Icon.js +9 -0
  2. package/lib/module/components/Icon.js.map +1 -1
  3. package/lib/module/index.js +1 -0
  4. package/lib/module/index.js.map +1 -1
  5. package/lib/module/screens/index.js +2 -0
  6. package/lib/module/screens/index.js.map +1 -1
  7. package/lib/module/screens/qbank/QBankOverviewScreen.js +435 -0
  8. package/lib/module/screens/qbank/QBankOverviewScreen.js.map +1 -0
  9. package/lib/module/screens/qbank/QBankQuestionScreen.js +181 -123
  10. package/lib/module/screens/qbank/QBankQuestionScreen.js.map +1 -1
  11. package/lib/module/screens/qbank/QBankSuccessScreen.js +336 -0
  12. package/lib/module/screens/qbank/QBankSuccessScreen.js.map +1 -0
  13. package/lib/typescript/src/components/Icon.d.ts +1 -1
  14. package/lib/typescript/src/components/Icon.d.ts.map +1 -1
  15. package/lib/typescript/src/index.d.ts +1 -0
  16. package/lib/typescript/src/index.d.ts.map +1 -1
  17. package/lib/typescript/src/screens/index.d.ts +2 -0
  18. package/lib/typescript/src/screens/index.d.ts.map +1 -1
  19. package/lib/typescript/src/screens/qbank/QBankOverviewScreen.d.ts +4 -0
  20. package/lib/typescript/src/screens/qbank/QBankOverviewScreen.d.ts.map +1 -0
  21. package/lib/typescript/src/screens/qbank/QBankQuestionScreen.d.ts +7 -9
  22. package/lib/typescript/src/screens/qbank/QBankQuestionScreen.d.ts.map +1 -1
  23. package/lib/typescript/src/screens/qbank/QBankSuccessScreen.d.ts +4 -0
  24. package/lib/typescript/src/screens/qbank/QBankSuccessScreen.d.ts.map +1 -0
  25. package/lib/typescript/src/types/index.d.ts +39 -7
  26. package/lib/typescript/src/types/index.d.ts.map +1 -1
  27. package/package.json +1 -1
  28. package/src/components/Icon.tsx +6 -1
  29. package/src/index.tsx +1 -0
  30. package/src/screens/index.ts +2 -0
  31. package/src/screens/qbank/QBankOverviewScreen.tsx +392 -0
  32. package/src/screens/qbank/QBankQuestionScreen.tsx +164 -109
  33. package/src/screens/qbank/QBankSuccessScreen.tsx +292 -0
  34. package/src/types/index.ts +43 -7
@@ -0,0 +1,392 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import {
3
+ View,
4
+ ScrollView,
5
+ StyleSheet,
6
+ TouchableOpacity,
7
+ Animated,
8
+ Dimensions,
9
+ Platform,
10
+ } from 'react-native';
11
+ import { SafeAreaView } from 'react-native-safe-area-context';
12
+ import { useTheme } from '../../context/ThemeContext';
13
+ import { Typography, Icon } from '../../components';
14
+ import type { QBankOverviewScreenProps, QBankOverviewQuestion } from '../../types';
15
+
16
+ const { height: SCREEN_HEIGHT } = Dimensions.get('window');
17
+
18
+ type FilterType = 'all' | 'correct' | 'incorrect' | 'missed';
19
+
20
+ export const QBankOverviewScreen: React.FC<QBankOverviewScreenProps> = ({
21
+ topicTitle,
22
+ moduleTitle,
23
+ totalQuestions,
24
+ correctCount,
25
+ incorrectCount,
26
+ missedCount,
27
+ questions,
28
+ onBack,
29
+ onPressQuestion,
30
+ }) => {
31
+ const theme = useTheme();
32
+ const [filter, setFilter] = useState<FilterType>('all');
33
+ const [isSheetOpen, setIsSheetOpen] = useState(false);
34
+
35
+ // Animation value for Bottom Sheet
36
+ const translateY = useRef(new Animated.Value(SCREEN_HEIGHT)).current;
37
+ const fadeAnim = useRef(new Animated.Value(0)).current;
38
+
39
+ const percentage = Math.round((correctCount / totalQuestions) * 100) || 0;
40
+
41
+ const correctWidth = (correctCount / totalQuestions) * 100;
42
+ const incorrectWidth = (incorrectCount / totalQuestions) * 100;
43
+ const missedWidth = (missedCount / totalQuestions) * 100;
44
+
45
+ const filteredQuestions = questions.filter(q => filter === 'all' || q.status === filter);
46
+
47
+ useEffect(() => {
48
+ if (isSheetOpen) {
49
+ Animated.parallel([
50
+ Animated.timing(translateY, {
51
+ toValue: 0,
52
+ duration: 300,
53
+ useNativeDriver: true,
54
+ }),
55
+ Animated.timing(fadeAnim, {
56
+ toValue: 0.5,
57
+ duration: 300,
58
+ useNativeDriver: true,
59
+ }),
60
+ ]).start();
61
+ } else {
62
+ Animated.parallel([
63
+ Animated.timing(translateY, {
64
+ toValue: SCREEN_HEIGHT,
65
+ duration: 250,
66
+ useNativeDriver: true,
67
+ }),
68
+ Animated.timing(fadeAnim, {
69
+ toValue: 0,
70
+ duration: 250,
71
+ useNativeDriver: true,
72
+ }),
73
+ ]).start();
74
+ }
75
+ }, [isSheetOpen, translateY, fadeAnim]);
76
+
77
+ const renderStatusBadge = (status: QBankOverviewQuestion['status']) => {
78
+ let color = '';
79
+ let text = '';
80
+
81
+ switch (status) {
82
+ case 'correct':
83
+ color = theme.success;
84
+ text = 'Correct';
85
+ break;
86
+ case 'incorrect':
87
+ color = '#EF4444';
88
+ text = 'InCorrect';
89
+ break;
90
+ case 'missed':
91
+ color = '#F59E0B';
92
+ text = 'Missed';
93
+ break;
94
+ }
95
+
96
+ return (
97
+ <View style={[styles.statusBadge, { borderColor: color }]}>
98
+ <Typography variant="caption" color={color} style={{ fontSize: 10, fontWeight: '600' }}>
99
+ {text}
100
+ </Typography>
101
+ </View>
102
+ );
103
+ };
104
+
105
+ return (
106
+ <SafeAreaView style={styles.container} edges={['top', 'bottom']}>
107
+ {/* Header */}
108
+ <View style={styles.header}>
109
+ <TouchableOpacity onPress={onBack} style={styles.headerBtn}>
110
+ <Icon name="chevron-left" size={24} color={theme.textPrimary} />
111
+ </TouchableOpacity>
112
+ <Typography variant="h3" bold color={theme.textPrimary} style={styles.headerTitle}>
113
+ Overview
114
+ </Typography>
115
+ <View style={styles.headerBtn} />
116
+ </View>
117
+
118
+ <ScrollView contentContainerStyle={styles.scrollContent} showsVerticalScrollIndicator={false}>
119
+ {/* Sub Header */}
120
+ <View style={styles.subHeader}>
121
+ <Typography variant="h3" bold color={theme.textPrimary}>
122
+ {topicTitle} <Typography variant="body" color={theme.textSecondary}>• {moduleTitle}</Typography>
123
+ </Typography>
124
+ <TouchableOpacity onPress={() => setIsSheetOpen(true)} style={styles.filterBtn}>
125
+ <Icon name="filter" size={20} color={theme.textSecondary} />
126
+ </TouchableOpacity>
127
+ </View>
128
+
129
+ {/* Summary Card */}
130
+ <View style={styles.card}>
131
+ <Typography variant="caption" color={theme.textSecondary} style={styles.totalText}>
132
+ Total Question {totalQuestions}
133
+ </Typography>
134
+
135
+ <View style={styles.statsRow}>
136
+ <View style={styles.statsLabels}>
137
+ <Typography variant="caption" color={theme.success} style={styles.statLabel}>
138
+ {String(correctCount).padStart(2, '0')} Correct
139
+ </Typography>
140
+ <Typography variant="caption" color="#EF4444" style={styles.statLabel}>
141
+ {String(incorrectCount).padStart(2, '0')} Incorrect
142
+ </Typography>
143
+ <Typography variant="caption" color="#F59E0B" style={styles.statLabel}>
144
+ {String(missedCount).padStart(2, '0')} Missed
145
+ </Typography>
146
+ </View>
147
+
148
+ <View style={styles.percentageBlock}>
149
+ <Typography variant="h3" bold color={theme.textPrimary}>
150
+ {percentage}%
151
+ </Typography>
152
+ <Typography variant="caption" color={theme.textSecondary} style={{fontSize: 10}}>
153
+ Correct
154
+ </Typography>
155
+ </View>
156
+ </View>
157
+
158
+ <View style={styles.progressBarContainer}>
159
+ {correctWidth > 0 && <View style={[styles.progressSegment, { width: `${correctWidth}%`, backgroundColor: theme.success }]} />}
160
+ {incorrectWidth > 0 && <View style={[styles.progressSegment, { width: `${incorrectWidth}%`, backgroundColor: '#EF4444' }]} />}
161
+ {missedWidth > 0 && <View style={[styles.progressSegment, { width: `${missedWidth}%`, backgroundColor: '#F59E0B' }]} />}
162
+ {totalQuestions === 0 && <View style={[styles.progressSegment, { width: '100%', backgroundColor: '#E5E7EB' }]} />}
163
+ </View>
164
+ </View>
165
+
166
+ {/* Question List */}
167
+ <View style={styles.listContainer}>
168
+ {filteredQuestions.map((q) => (
169
+ <TouchableOpacity
170
+ key={q.id}
171
+ style={styles.listItem}
172
+ onPress={() => onPressQuestion?.(q.id)}
173
+ >
174
+ <View style={styles.listTextContainer}>
175
+ <Typography variant="body" bold color={theme.textPrimary} style={styles.listText}>
176
+ {q.number}. {q.title}
177
+ </Typography>
178
+ </View>
179
+ {renderStatusBadge(q.status)}
180
+ </TouchableOpacity>
181
+ ))}
182
+ </View>
183
+ </ScrollView>
184
+
185
+ {/* Bottom Sheet Backdrop */}
186
+ {isSheetOpen && (
187
+ <Animated.View
188
+ style={[styles.backdrop, { opacity: fadeAnim }]}
189
+ pointerEvents="auto"
190
+ >
191
+ <TouchableOpacity
192
+ style={StyleSheet.absoluteFill}
193
+ activeOpacity={1}
194
+ onPress={() => setIsSheetOpen(false)}
195
+ />
196
+ </Animated.View>
197
+ )}
198
+
199
+ {/* Bottom Sheet Modal */}
200
+ <Animated.View
201
+ style={[
202
+ styles.bottomSheet,
203
+ { transform: [{ translateY }] }
204
+ ]}
205
+ >
206
+ <View style={styles.sheetHandle} />
207
+ <Typography variant="h3" bold color={theme.textPrimary} style={styles.sheetTitle}>
208
+ Filter Questions
209
+ </Typography>
210
+
211
+ {(['all', 'correct', 'incorrect', 'missed'] as FilterType[]).map((type) => (
212
+ <TouchableOpacity
213
+ key={type}
214
+ style={styles.sheetOption}
215
+ onPress={() => {
216
+ setFilter(type);
217
+ setIsSheetOpen(false);
218
+ }}
219
+ >
220
+ <Typography variant="body" color={filter === type ? theme.primary : theme.textPrimary} style={{textTransform: 'capitalize'}}>
221
+ {type}
222
+ </Typography>
223
+ {filter === type && <Icon name="check" size={20} color={theme.primary} />}
224
+ </TouchableOpacity>
225
+ ))}
226
+ </Animated.View>
227
+ </SafeAreaView>
228
+ );
229
+ };
230
+
231
+ const styles = StyleSheet.create({
232
+ container: {
233
+ flex: 1,
234
+ backgroundColor: '#F9FAFB',
235
+ },
236
+ header: {
237
+ flexDirection: 'row',
238
+ alignItems: 'center',
239
+ paddingHorizontal: 16,
240
+ paddingVertical: 12,
241
+ backgroundColor: '#FFFFFF',
242
+ },
243
+ headerBtn: {
244
+ width: 40,
245
+ height: 40,
246
+ justifyContent: 'center',
247
+ },
248
+ headerTitle: {
249
+ flex: 1,
250
+ textAlign: 'center',
251
+ fontSize: 18,
252
+ },
253
+ scrollContent: {
254
+ padding: 16,
255
+ paddingBottom: 40,
256
+ },
257
+ subHeader: {
258
+ flexDirection: 'row',
259
+ alignItems: 'center',
260
+ justifyContent: 'space-between',
261
+ marginBottom: 20,
262
+ marginTop: 8,
263
+ },
264
+ filterBtn: {
265
+ width: 32,
266
+ height: 32,
267
+ borderRadius: 16,
268
+ borderWidth: 1,
269
+ borderColor: '#D1D5DB',
270
+ justifyContent: 'center',
271
+ alignItems: 'center',
272
+ backgroundColor: '#FFFFFF',
273
+ },
274
+ card: {
275
+ backgroundColor: '#FFFFFF',
276
+ borderRadius: 12,
277
+ padding: 16,
278
+ marginBottom: 24,
279
+ shadowColor: '#000',
280
+ shadowOffset: { width: 0, height: 2 },
281
+ shadowOpacity: 0.05,
282
+ shadowRadius: 4,
283
+ elevation: 2,
284
+ },
285
+ totalText: {
286
+ marginBottom: 12,
287
+ },
288
+ statsRow: {
289
+ flexDirection: 'row',
290
+ justifyContent: 'space-between',
291
+ alignItems: 'flex-end',
292
+ marginBottom: 12,
293
+ },
294
+ statsLabels: {
295
+ flexDirection: 'row',
296
+ alignItems: 'center',
297
+ flex: 1,
298
+ flexWrap: 'wrap',
299
+ },
300
+ statLabel: {
301
+ marginRight: 16,
302
+ fontSize: 12,
303
+ marginBottom: 4,
304
+ },
305
+ percentageBlock: {
306
+ alignItems: 'center',
307
+ justifyContent: 'center',
308
+ },
309
+ progressBarContainer: {
310
+ height: 8,
311
+ backgroundColor: '#E5E7EB',
312
+ borderRadius: 4,
313
+ flexDirection: 'row',
314
+ overflow: 'hidden',
315
+ },
316
+ progressSegment: {
317
+ height: '100%',
318
+ },
319
+ listContainer: {
320
+ gap: 12,
321
+ },
322
+ listItem: {
323
+ backgroundColor: '#FFFFFF',
324
+ borderRadius: 8,
325
+ padding: 16,
326
+ flexDirection: 'row',
327
+ alignItems: 'center',
328
+ justifyContent: 'space-between',
329
+ shadowColor: '#000',
330
+ shadowOffset: { width: 0, height: 1 },
331
+ shadowOpacity: 0.02,
332
+ shadowRadius: 2,
333
+ elevation: 1,
334
+ },
335
+ listTextContainer: {
336
+ flex: 1,
337
+ paddingRight: 12,
338
+ },
339
+ listText: {
340
+ fontSize: 14,
341
+ lineHeight: 20,
342
+ },
343
+ statusBadge: {
344
+ paddingHorizontal: 12,
345
+ paddingVertical: 4,
346
+ borderRadius: 12,
347
+ borderWidth: 1,
348
+ },
349
+
350
+ // Bottom Sheet Styles
351
+ backdrop: {
352
+ ...StyleSheet.absoluteFillObject,
353
+ backgroundColor: '#000',
354
+ zIndex: 10,
355
+ },
356
+ bottomSheet: {
357
+ position: 'absolute',
358
+ bottom: 0,
359
+ left: 0,
360
+ right: 0,
361
+ backgroundColor: '#FFFFFF',
362
+ borderTopLeftRadius: 24,
363
+ borderTopRightRadius: 24,
364
+ padding: 24,
365
+ paddingBottom: Platform.OS === 'ios' ? 40 : 24,
366
+ zIndex: 20,
367
+ shadowColor: '#000',
368
+ shadowOffset: { width: 0, height: -4 },
369
+ shadowOpacity: 0.1,
370
+ shadowRadius: 8,
371
+ elevation: 10,
372
+ },
373
+ sheetHandle: {
374
+ width: 40,
375
+ height: 4,
376
+ backgroundColor: '#D1D5DB',
377
+ borderRadius: 2,
378
+ alignSelf: 'center',
379
+ marginBottom: 20,
380
+ },
381
+ sheetTitle: {
382
+ marginBottom: 16,
383
+ },
384
+ sheetOption: {
385
+ flexDirection: 'row',
386
+ alignItems: 'center',
387
+ justifyContent: 'space-between',
388
+ paddingVertical: 16,
389
+ borderBottomWidth: 1,
390
+ borderBottomColor: '#F3F4F6',
391
+ },
392
+ });