react-native-elearn-ui 0.1.23 → 0.1.28
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/BottomSheet.js +134 -0
- package/lib/module/components/BottomSheet.js.map +1 -0
- package/lib/module/components/Icon.js +30 -0
- package/lib/module/components/Icon.js.map +1 -1
- package/lib/module/components/index.js +1 -0
- package/lib/module/components/index.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/screens/index.js +1 -0
- package/lib/module/screens/index.js.map +1 -1
- package/lib/module/screens/qbank/QBankQuestionScreen.js +415 -0
- package/lib/module/screens/qbank/QBankQuestionScreen.js.map +1 -0
- package/lib/module/screens/qbank/QBankSubTopicScreen.js +55 -75
- package/lib/module/screens/qbank/QBankSubTopicScreen.js.map +1 -1
- package/lib/typescript/src/components/BottomSheet.d.ts +9 -0
- package/lib/typescript/src/components/BottomSheet.d.ts.map +1 -0
- 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/components/index.d.ts +1 -0
- package/lib/typescript/src/components/index.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/index.d.ts +1 -0
- package/lib/typescript/src/screens/index.d.ts.map +1 -1
- package/lib/typescript/src/screens/qbank/QBankQuestionScreen.d.ts +16 -0
- package/lib/typescript/src/screens/qbank/QBankQuestionScreen.d.ts.map +1 -0
- package/lib/typescript/src/screens/qbank/QBankSubTopicScreen.d.ts +1 -0
- package/lib/typescript/src/screens/qbank/QBankSubTopicScreen.d.ts.map +1 -1
- package/lib/typescript/src/types/index.d.ts +15 -0
- package/lib/typescript/src/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/BottomSheet.tsx +144 -0
- package/src/components/Icon.tsx +12 -2
- package/src/components/index.ts +1 -0
- package/src/index.tsx +1 -0
- package/src/screens/index.ts +1 -0
- package/src/screens/qbank/QBankQuestionScreen.tsx +393 -0
- package/src/screens/qbank/QBankSubTopicScreen.tsx +42 -61
- package/src/types/index.ts +17 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
ScrollView,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
TouchableOpacity,
|
|
7
|
+
StatusBar,
|
|
8
|
+
} from 'react-native';
|
|
9
|
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
10
|
+
import { useTheme } from '../../context/ThemeContext';
|
|
11
|
+
import { Typography, Icon, Button } from '../../components';
|
|
12
|
+
import type { QBankQuestionParams, QBankOption } from '../../types';
|
|
13
|
+
|
|
14
|
+
interface QBankQuestionScreenProps {
|
|
15
|
+
params: QBankQuestionParams;
|
|
16
|
+
initialTimeSeconds?: number;
|
|
17
|
+
onTimeUp?: () => void;
|
|
18
|
+
onBack?: () => void;
|
|
19
|
+
onNext?: () => void;
|
|
20
|
+
onPrev?: () => void;
|
|
21
|
+
onPressOption?: (optionId: string) => void;
|
|
22
|
+
onPressReport?: () => void;
|
|
23
|
+
onPressUsers?: () => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const formatTime = (totalSeconds: number) => {
|
|
27
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
28
|
+
const seconds = totalSeconds % 60;
|
|
29
|
+
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const QBankQuestionScreen: React.FC<QBankQuestionScreenProps> = ({
|
|
33
|
+
params,
|
|
34
|
+
initialTimeSeconds = 60,
|
|
35
|
+
onTimeUp,
|
|
36
|
+
onBack,
|
|
37
|
+
onNext,
|
|
38
|
+
onPrev,
|
|
39
|
+
onPressOption,
|
|
40
|
+
onPressReport,
|
|
41
|
+
onPressUsers,
|
|
42
|
+
}) => {
|
|
43
|
+
const theme = useTheme();
|
|
44
|
+
const [timeLeft, setTimeLeft] = useState(initialTimeSeconds);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (timeLeft <= 0) {
|
|
48
|
+
onTimeUp?.();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const timer = setInterval(() => {
|
|
53
|
+
setTimeLeft((prev) => prev - 1);
|
|
54
|
+
}, 1000);
|
|
55
|
+
|
|
56
|
+
return () => clearInterval(timer);
|
|
57
|
+
}, [timeLeft, onTimeUp]);
|
|
58
|
+
|
|
59
|
+
const progressPercentage = (params.questionNumber / params.totalQuestions) * 100;
|
|
60
|
+
|
|
61
|
+
const renderOption = (option: QBankOption, index: number) => {
|
|
62
|
+
const letters = ['A', 'B', 'C', 'D', 'E', 'F'];
|
|
63
|
+
const letter = letters[index] || '';
|
|
64
|
+
|
|
65
|
+
let borderColor = '#E5E7EB'; // Default grey
|
|
66
|
+
let backgroundColor = '#FFFFFF';
|
|
67
|
+
let iconName: 'check-circle' | 'x-circle' | undefined;
|
|
68
|
+
let iconColor = theme.textSecondary;
|
|
69
|
+
let statusText = '';
|
|
70
|
+
let statusColor = '';
|
|
71
|
+
|
|
72
|
+
if (option.status === 'correct') {
|
|
73
|
+
borderColor = theme.success;
|
|
74
|
+
backgroundColor = '#F0FDF4'; // light green bg
|
|
75
|
+
iconName = 'check-circle';
|
|
76
|
+
iconColor = theme.success;
|
|
77
|
+
statusText = 'Correct';
|
|
78
|
+
statusColor = theme.success;
|
|
79
|
+
} else if (option.status === 'incorrect') {
|
|
80
|
+
borderColor = '#EF4444';
|
|
81
|
+
backgroundColor = '#FEF2F2'; // light red bg
|
|
82
|
+
iconName = 'x-circle';
|
|
83
|
+
iconColor = '#EF4444';
|
|
84
|
+
statusText = 'InCorrect'; // matching user image
|
|
85
|
+
statusColor = '#EF4444';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<TouchableOpacity
|
|
90
|
+
key={option.id}
|
|
91
|
+
style={[styles.optionContainer, { borderColor, backgroundColor }]}
|
|
92
|
+
onPress={() => onPressOption?.(option.id)}
|
|
93
|
+
activeOpacity={0.7}
|
|
94
|
+
>
|
|
95
|
+
<View style={styles.optionContent}>
|
|
96
|
+
{iconName ? (
|
|
97
|
+
<Icon name={iconName} size={20} color={iconColor} />
|
|
98
|
+
) : (
|
|
99
|
+
<View style={styles.emptyCircle} />
|
|
100
|
+
)}
|
|
101
|
+
<View style={styles.optionTextWrapper}>
|
|
102
|
+
<Typography variant="body" color={theme.textPrimary} style={styles.optionText}>
|
|
103
|
+
{letter}. {option.text}
|
|
104
|
+
</Typography>
|
|
105
|
+
{(option.status === 'correct' || option.status === 'incorrect') && (
|
|
106
|
+
<Typography variant="caption" color={statusColor} style={styles.optionStatusText}>
|
|
107
|
+
{statusText} <Typography variant="caption" color={theme.textSecondary}>[{option.percentage}%]</Typography>
|
|
108
|
+
</Typography>
|
|
109
|
+
)}
|
|
110
|
+
{option.status === 'default' && option.percentage !== undefined && (
|
|
111
|
+
<Typography variant="caption" color={theme.textSecondary} style={styles.optionStatusText}>
|
|
112
|
+
[{option.percentage}%]
|
|
113
|
+
</Typography>
|
|
114
|
+
)}
|
|
115
|
+
</View>
|
|
116
|
+
</View>
|
|
117
|
+
</TouchableOpacity>
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<SafeAreaView style={[styles.container, { backgroundColor: '#E5E7EB' }]} edges={['top', 'bottom']}>
|
|
123
|
+
<StatusBar barStyle="dark-content" backgroundColor="#FFFFFF" />
|
|
124
|
+
|
|
125
|
+
<View style={styles.card}>
|
|
126
|
+
{/* Header */}
|
|
127
|
+
<View style={styles.header}>
|
|
128
|
+
<TouchableOpacity onPress={onBack} style={styles.headerIconBtnLeft}>
|
|
129
|
+
<Icon name="chevron-left" size={24} color={theme.textPrimary} />
|
|
130
|
+
</TouchableOpacity>
|
|
131
|
+
<Typography variant="h3" color={theme.textPrimary} style={styles.headerTitle}>
|
|
132
|
+
{params.headerTitle}
|
|
133
|
+
</Typography>
|
|
134
|
+
<View style={styles.headerIconBtnRight} />
|
|
135
|
+
</View>
|
|
136
|
+
|
|
137
|
+
{/* Progress Bar */}
|
|
138
|
+
<View style={styles.progressTrack}>
|
|
139
|
+
<View style={[styles.progressFill, { width: `${progressPercentage}%`, backgroundColor: theme.success }]} />
|
|
140
|
+
</View>
|
|
141
|
+
|
|
142
|
+
<ScrollView
|
|
143
|
+
contentContainerStyle={styles.scrollContent}
|
|
144
|
+
showsVerticalScrollIndicator={false}
|
|
145
|
+
>
|
|
146
|
+
{/* Top Bar: Question Number & Timer */}
|
|
147
|
+
<View style={styles.topBar}>
|
|
148
|
+
<Typography variant="caption" bold color={theme.textSecondary} style={styles.questionNumber}>
|
|
149
|
+
QUESTION {params.questionNumber}/{params.totalQuestions}
|
|
150
|
+
</Typography>
|
|
151
|
+
<View style={styles.timerWrapper}>
|
|
152
|
+
<Icon name="clock" size={14} color={theme.textSecondary} />
|
|
153
|
+
<Typography variant="caption" color={theme.textSecondary} style={styles.timerText}>
|
|
154
|
+
{formatTime(timeLeft)}
|
|
155
|
+
</Typography>
|
|
156
|
+
</View>
|
|
157
|
+
</View>
|
|
158
|
+
|
|
159
|
+
{/* Question Text */}
|
|
160
|
+
<Typography variant="h3" bold color={theme.textPrimary} style={styles.questionText}>
|
|
161
|
+
{params.questionText}
|
|
162
|
+
</Typography>
|
|
163
|
+
|
|
164
|
+
{/* Options */}
|
|
165
|
+
<View style={styles.optionsList}>
|
|
166
|
+
{params.options.map((option, index) => renderOption(option, index))}
|
|
167
|
+
</View>
|
|
168
|
+
|
|
169
|
+
{/* Explanation Section */}
|
|
170
|
+
{params.explanation && (
|
|
171
|
+
<View style={styles.explanationContainer}>
|
|
172
|
+
<Typography variant="caption" bold color={theme.textPrimary} style={styles.explanationTitle}>
|
|
173
|
+
EXPLANATION
|
|
174
|
+
</Typography>
|
|
175
|
+
<Typography variant="body" color={theme.textSecondary} style={styles.explanationText}>
|
|
176
|
+
{params.explanation}
|
|
177
|
+
</Typography>
|
|
178
|
+
|
|
179
|
+
{params.referenceId && (
|
|
180
|
+
<View style={styles.referenceContainer}>
|
|
181
|
+
<Typography variant="caption" color={theme.textSecondary} style={styles.referenceText}>
|
|
182
|
+
{params.referenceId}
|
|
183
|
+
</Typography>
|
|
184
|
+
</View>
|
|
185
|
+
)}
|
|
186
|
+
</View>
|
|
187
|
+
)}
|
|
188
|
+
</ScrollView>
|
|
189
|
+
|
|
190
|
+
{/* Bottom Actions */}
|
|
191
|
+
<View style={styles.bottomActions}>
|
|
192
|
+
<View style={styles.leftActions}>
|
|
193
|
+
<TouchableOpacity style={styles.iconAction} onPress={onPressReport}>
|
|
194
|
+
<Icon name="alert-circle" size={20} color={theme.textSecondary} />
|
|
195
|
+
</TouchableOpacity>
|
|
196
|
+
<TouchableOpacity style={styles.iconAction} onPress={onPressUsers}>
|
|
197
|
+
<Icon name="users" size={20} color={theme.textSecondary} />
|
|
198
|
+
</TouchableOpacity>
|
|
199
|
+
</View>
|
|
200
|
+
<View style={styles.rightActions}>
|
|
201
|
+
<TouchableOpacity style={styles.prevBtn} onPress={onPrev}>
|
|
202
|
+
<Typography variant="body" bold color={theme.textPrimary}>
|
|
203
|
+
Prev
|
|
204
|
+
</Typography>
|
|
205
|
+
</TouchableOpacity>
|
|
206
|
+
<Button
|
|
207
|
+
title="Next"
|
|
208
|
+
onPress={onNext || (() => {})}
|
|
209
|
+
style={styles.nextBtn}
|
|
210
|
+
textStyle={styles.nextBtnText}
|
|
211
|
+
/>
|
|
212
|
+
</View>
|
|
213
|
+
</View>
|
|
214
|
+
|
|
215
|
+
</View>
|
|
216
|
+
</SafeAreaView>
|
|
217
|
+
);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const styles = StyleSheet.create({
|
|
221
|
+
container: {
|
|
222
|
+
flex: 1,
|
|
223
|
+
padding: 16, // Gray margin around the card
|
|
224
|
+
},
|
|
225
|
+
card: {
|
|
226
|
+
flex: 1,
|
|
227
|
+
backgroundColor: '#FFFFFF',
|
|
228
|
+
borderRadius: 8,
|
|
229
|
+
overflow: 'hidden',
|
|
230
|
+
shadowColor: '#000',
|
|
231
|
+
shadowOffset: { width: 0, height: 2 },
|
|
232
|
+
shadowOpacity: 0.1,
|
|
233
|
+
shadowRadius: 4,
|
|
234
|
+
elevation: 4,
|
|
235
|
+
},
|
|
236
|
+
header: {
|
|
237
|
+
flexDirection: 'row',
|
|
238
|
+
alignItems: 'center',
|
|
239
|
+
paddingHorizontal: 16,
|
|
240
|
+
paddingVertical: 12,
|
|
241
|
+
},
|
|
242
|
+
headerIconBtnLeft: {
|
|
243
|
+
width: 40,
|
|
244
|
+
height: 40,
|
|
245
|
+
justifyContent: 'center',
|
|
246
|
+
alignItems: 'flex-start',
|
|
247
|
+
},
|
|
248
|
+
headerRightActions: {
|
|
249
|
+
flexDirection: 'row',
|
|
250
|
+
alignItems: 'center',
|
|
251
|
+
},
|
|
252
|
+
headerIconBtnRight: {
|
|
253
|
+
width: 40,
|
|
254
|
+
height: 40,
|
|
255
|
+
justifyContent: 'center',
|
|
256
|
+
alignItems: 'flex-end',
|
|
257
|
+
},
|
|
258
|
+
headerTitle: {
|
|
259
|
+
fontSize: 16,
|
|
260
|
+
fontWeight: '600',
|
|
261
|
+
flex: 1,
|
|
262
|
+
textAlign: 'center',
|
|
263
|
+
},
|
|
264
|
+
progressTrack: {
|
|
265
|
+
height: 4,
|
|
266
|
+
backgroundColor: '#E5E7EB',
|
|
267
|
+
width: '100%',
|
|
268
|
+
},
|
|
269
|
+
progressFill: {
|
|
270
|
+
height: '100%',
|
|
271
|
+
},
|
|
272
|
+
scrollContent: {
|
|
273
|
+
padding: 20,
|
|
274
|
+
paddingBottom: 40,
|
|
275
|
+
},
|
|
276
|
+
topBar: {
|
|
277
|
+
flexDirection: 'row',
|
|
278
|
+
justifyContent: 'space-between',
|
|
279
|
+
alignItems: 'center',
|
|
280
|
+
marginBottom: 20,
|
|
281
|
+
},
|
|
282
|
+
questionNumber: {
|
|
283
|
+
fontSize: 12,
|
|
284
|
+
letterSpacing: 0.5,
|
|
285
|
+
},
|
|
286
|
+
timerWrapper: {
|
|
287
|
+
flexDirection: 'row',
|
|
288
|
+
alignItems: 'center',
|
|
289
|
+
},
|
|
290
|
+
timerText: {
|
|
291
|
+
fontSize: 12,
|
|
292
|
+
marginLeft: 6,
|
|
293
|
+
},
|
|
294
|
+
questionText: {
|
|
295
|
+
fontSize: 16,
|
|
296
|
+
lineHeight: 24,
|
|
297
|
+
marginBottom: 24,
|
|
298
|
+
},
|
|
299
|
+
optionsList: {
|
|
300
|
+
marginBottom: 32,
|
|
301
|
+
},
|
|
302
|
+
optionContainer: {
|
|
303
|
+
borderWidth: 1,
|
|
304
|
+
borderRadius: 8,
|
|
305
|
+
padding: 16,
|
|
306
|
+
marginBottom: 12,
|
|
307
|
+
},
|
|
308
|
+
optionContent: {
|
|
309
|
+
flexDirection: 'row',
|
|
310
|
+
alignItems: 'flex-start',
|
|
311
|
+
},
|
|
312
|
+
emptyCircle: {
|
|
313
|
+
width: 20,
|
|
314
|
+
height: 20,
|
|
315
|
+
borderRadius: 10,
|
|
316
|
+
borderWidth: 1,
|
|
317
|
+
borderColor: '#D1D5DB',
|
|
318
|
+
},
|
|
319
|
+
optionTextWrapper: {
|
|
320
|
+
flex: 1,
|
|
321
|
+
marginLeft: 12,
|
|
322
|
+
},
|
|
323
|
+
optionText: {
|
|
324
|
+
fontSize: 14,
|
|
325
|
+
lineHeight: 20,
|
|
326
|
+
},
|
|
327
|
+
optionStatusText: {
|
|
328
|
+
marginTop: 6,
|
|
329
|
+
fontSize: 12,
|
|
330
|
+
},
|
|
331
|
+
explanationContainer: {
|
|
332
|
+
marginTop: 16,
|
|
333
|
+
},
|
|
334
|
+
explanationTitle: {
|
|
335
|
+
fontSize: 12,
|
|
336
|
+
marginBottom: 12,
|
|
337
|
+
},
|
|
338
|
+
explanationText: {
|
|
339
|
+
fontSize: 14,
|
|
340
|
+
lineHeight: 22,
|
|
341
|
+
},
|
|
342
|
+
referenceContainer: {
|
|
343
|
+
marginTop: 24,
|
|
344
|
+
paddingTop: 16,
|
|
345
|
+
borderTopWidth: 1,
|
|
346
|
+
borderTopColor: '#F3F4F6',
|
|
347
|
+
alignItems: 'center',
|
|
348
|
+
},
|
|
349
|
+
referenceText: {
|
|
350
|
+
fontSize: 12,
|
|
351
|
+
},
|
|
352
|
+
bottomActions: {
|
|
353
|
+
flexDirection: 'row',
|
|
354
|
+
justifyContent: 'space-between',
|
|
355
|
+
alignItems: 'center',
|
|
356
|
+
padding: 16,
|
|
357
|
+
borderTopWidth: 1,
|
|
358
|
+
borderTopColor: '#F3F4F6',
|
|
359
|
+
backgroundColor: '#FFFFFF',
|
|
360
|
+
},
|
|
361
|
+
leftActions: {
|
|
362
|
+
flexDirection: 'row',
|
|
363
|
+
alignItems: 'center',
|
|
364
|
+
},
|
|
365
|
+
iconAction: {
|
|
366
|
+
width: 36,
|
|
367
|
+
height: 36,
|
|
368
|
+
justifyContent: 'center',
|
|
369
|
+
alignItems: 'center',
|
|
370
|
+
marginRight: 8,
|
|
371
|
+
borderRadius: 18,
|
|
372
|
+
borderWidth: 1,
|
|
373
|
+
borderColor: '#E5E7EB',
|
|
374
|
+
},
|
|
375
|
+
rightActions: {
|
|
376
|
+
flexDirection: 'row',
|
|
377
|
+
alignItems: 'center',
|
|
378
|
+
},
|
|
379
|
+
prevBtn: {
|
|
380
|
+
marginRight: 20,
|
|
381
|
+
},
|
|
382
|
+
nextBtn: {
|
|
383
|
+
backgroundColor: '#10B981',
|
|
384
|
+
paddingHorizontal: 24,
|
|
385
|
+
paddingVertical: 10,
|
|
386
|
+
height: 'auto',
|
|
387
|
+
minWidth: 80,
|
|
388
|
+
},
|
|
389
|
+
nextBtnText: {
|
|
390
|
+
fontSize: 14,
|
|
391
|
+
fontWeight: '600',
|
|
392
|
+
},
|
|
393
|
+
});
|
|
@@ -6,12 +6,10 @@ import {
|
|
|
6
6
|
TouchableOpacity,
|
|
7
7
|
StatusBar,
|
|
8
8
|
FlatList,
|
|
9
|
-
Modal,
|
|
10
|
-
TouchableWithoutFeedback,
|
|
11
9
|
} from 'react-native';
|
|
12
10
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
13
11
|
import { useTheme } from '../../context/ThemeContext';
|
|
14
|
-
import { Typography, Icon } from '../../components';
|
|
12
|
+
import { Typography, Icon, BottomSheet } from '../../components';
|
|
15
13
|
import type { QBankSubTopicParams } from '../../types';
|
|
16
14
|
|
|
17
15
|
interface QBankSubTopicScreenProps {
|
|
@@ -21,6 +19,7 @@ interface QBankSubTopicScreenProps {
|
|
|
21
19
|
onPressModule?: (moduleId: string) => void;
|
|
22
20
|
onPressFilter?: (filter: string) => void;
|
|
23
21
|
onPressSearch?: () => void;
|
|
22
|
+
isProUser?: boolean;
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
export const QBankSubTopicScreen: React.FC<QBankSubTopicScreenProps> = ({
|
|
@@ -30,6 +29,7 @@ export const QBankSubTopicScreen: React.FC<QBankSubTopicScreenProps> = ({
|
|
|
30
29
|
onPressModule,
|
|
31
30
|
onPressFilter,
|
|
32
31
|
onPressSearch,
|
|
32
|
+
isProUser = false,
|
|
33
33
|
}) => {
|
|
34
34
|
const theme = useTheme();
|
|
35
35
|
const [activeFilter, setActiveFilter] = useState(params.activeFilter || params.filters[0]);
|
|
@@ -193,25 +193,27 @@ export const QBankSubTopicScreen: React.FC<QBankSubTopicScreenProps> = ({
|
|
|
193
193
|
</Typography>
|
|
194
194
|
</View>
|
|
195
195
|
)}
|
|
196
|
-
{moduleItem.status === 'locked' && (
|
|
197
|
-
<View style={styles.
|
|
198
|
-
<
|
|
196
|
+
{moduleItem.status === 'locked' && !isProUser && (
|
|
197
|
+
<View style={styles.proBadge}>
|
|
198
|
+
<Typography variant="caption" bold color="#FFFFFF" style={{fontSize: 7}}>
|
|
199
|
+
PRO
|
|
200
|
+
</Typography>
|
|
199
201
|
</View>
|
|
200
202
|
)}
|
|
201
203
|
{moduleItem.attemptStatus === 'resume' && (
|
|
202
|
-
<Typography variant="caption" bold color="#F59E0B" style={{fontSize:
|
|
204
|
+
<Typography variant="caption" bold color="#F59E0B" style={{fontSize: 9}}>
|
|
203
205
|
RESUME
|
|
204
206
|
</Typography>
|
|
205
207
|
)}
|
|
206
208
|
{moduleItem.attemptStatus === 'completed' && (
|
|
207
209
|
<View style={{ alignItems: 'flex-end' }}>
|
|
208
210
|
<View style={styles.completedBadge}>
|
|
209
|
-
<Typography variant="caption" bold color="#FFFFFF" style={{fontSize:
|
|
211
|
+
<Typography variant="caption" bold color="#FFFFFF" style={{fontSize: 7}}>
|
|
210
212
|
COMPLETED
|
|
211
213
|
</Typography>
|
|
212
214
|
</View>
|
|
213
215
|
{moduleItem.correctPercentage !== undefined && (
|
|
214
|
-
<Typography variant="caption" bold color={theme.success} style={{fontSize:
|
|
216
|
+
<Typography variant="caption" bold color={theme.success} style={{fontSize: 10, marginTop: 4}}>
|
|
215
217
|
{moduleItem.correctPercentage}% Correct
|
|
216
218
|
</Typography>
|
|
217
219
|
)}
|
|
@@ -228,41 +230,34 @@ export const QBankSubTopicScreen: React.FC<QBankSubTopicScreenProps> = ({
|
|
|
228
230
|
</ScrollView>
|
|
229
231
|
|
|
230
232
|
{/* Table of Contents Bottom Sheet Modal */}
|
|
231
|
-
<
|
|
233
|
+
<BottomSheet
|
|
232
234
|
visible={showTOC}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
onRequestClose={() => setShowTOC(false)}
|
|
235
|
+
onClose={() => setShowTOC(false)}
|
|
236
|
+
height={400}
|
|
236
237
|
>
|
|
237
|
-
<
|
|
238
|
-
<
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
))}
|
|
261
|
-
</ScrollView>
|
|
262
|
-
</View>
|
|
263
|
-
</TouchableWithoutFeedback>
|
|
264
|
-
</TouchableOpacity>
|
|
265
|
-
</Modal>
|
|
238
|
+
<View style={styles.sheetHeader}>
|
|
239
|
+
<Typography variant="h3" bold color={theme.textPrimary}>
|
|
240
|
+
Table of Contents
|
|
241
|
+
</Typography>
|
|
242
|
+
<TouchableOpacity onPress={() => setShowTOC(false)}>
|
|
243
|
+
<Icon name="x" size={24} color={theme.textSecondary} />
|
|
244
|
+
</TouchableOpacity>
|
|
245
|
+
</View>
|
|
246
|
+
<ScrollView style={styles.sheetScroll}>
|
|
247
|
+
{params.sections.map((section) => (
|
|
248
|
+
<TouchableOpacity
|
|
249
|
+
key={section.id}
|
|
250
|
+
style={styles.sheetItem}
|
|
251
|
+
onPress={() => scrollToSection(section.id)}
|
|
252
|
+
>
|
|
253
|
+
<Typography variant="body" color={theme.textPrimary}>
|
|
254
|
+
{section.title}
|
|
255
|
+
</Typography>
|
|
256
|
+
<Icon name="chevron-right" size={20} color={theme.textSecondary} />
|
|
257
|
+
</TouchableOpacity>
|
|
258
|
+
))}
|
|
259
|
+
</ScrollView>
|
|
260
|
+
</BottomSheet>
|
|
266
261
|
|
|
267
262
|
</SafeAreaView>
|
|
268
263
|
);
|
|
@@ -436,13 +431,11 @@ const styles = StyleSheet.create({
|
|
|
436
431
|
borderColor: '#10B981',
|
|
437
432
|
backgroundColor: '#FFFFFF',
|
|
438
433
|
},
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
borderRadius:
|
|
443
|
-
backgroundColor: '#
|
|
444
|
-
justifyContent: 'center',
|
|
445
|
-
alignItems: 'center',
|
|
434
|
+
proBadge: {
|
|
435
|
+
paddingHorizontal: 6,
|
|
436
|
+
paddingVertical: 2,
|
|
437
|
+
borderRadius: 4,
|
|
438
|
+
backgroundColor: '#F59E0B',
|
|
446
439
|
},
|
|
447
440
|
completedBadge: {
|
|
448
441
|
paddingHorizontal: 6,
|
|
@@ -450,18 +443,6 @@ const styles = StyleSheet.create({
|
|
|
450
443
|
borderRadius: 4,
|
|
451
444
|
backgroundColor: '#10B981',
|
|
452
445
|
},
|
|
453
|
-
modalOverlay: {
|
|
454
|
-
flex: 1,
|
|
455
|
-
backgroundColor: 'rgba(0,0,0,0.4)',
|
|
456
|
-
justifyContent: 'flex-end',
|
|
457
|
-
},
|
|
458
|
-
bottomSheet: {
|
|
459
|
-
backgroundColor: '#FFFFFF',
|
|
460
|
-
borderTopLeftRadius: 20,
|
|
461
|
-
borderTopRightRadius: 20,
|
|
462
|
-
paddingBottom: 40,
|
|
463
|
-
maxHeight: '70%',
|
|
464
|
-
},
|
|
465
446
|
sheetHeader: {
|
|
466
447
|
flexDirection: 'row',
|
|
467
448
|
justifyContent: 'space-between',
|
package/src/types/index.ts
CHANGED
|
@@ -239,6 +239,23 @@ export interface QBankSubTopicSection {
|
|
|
239
239
|
modules: QBankSubTopicModule[];
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
export interface QBankOption {
|
|
243
|
+
id: string;
|
|
244
|
+
text: string;
|
|
245
|
+
status: 'default' | 'correct' | 'incorrect';
|
|
246
|
+
percentage?: number; // e.g. 20 for [20%]
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export interface QBankQuestionParams {
|
|
250
|
+
headerTitle: string;
|
|
251
|
+
questionNumber: number;
|
|
252
|
+
totalQuestions: number;
|
|
253
|
+
questionText: string;
|
|
254
|
+
options: QBankOption[];
|
|
255
|
+
explanation?: string;
|
|
256
|
+
referenceId?: string;
|
|
257
|
+
}
|
|
258
|
+
|
|
242
259
|
export interface QBankSubTopicParams {
|
|
243
260
|
header: {
|
|
244
261
|
title: string;
|