react-native-elearn-ui 0.1.5 → 0.1.7

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 (29) hide show
  1. package/lib/module/components/Icon.js +97 -0
  2. package/lib/module/components/Icon.js.map +1 -1
  3. package/lib/module/screens/QbankScreen.js +251 -99
  4. package/lib/module/screens/QbankScreen.js.map +1 -1
  5. package/lib/module/screens/QuestionScreen.js +219 -179
  6. package/lib/module/screens/QuestionScreen.js.map +1 -1
  7. package/lib/module/screens/QuizStartScreen.js +134 -131
  8. package/lib/module/screens/QuizStartScreen.js.map +1 -1
  9. package/lib/module/screens/ResultScreen.js +407 -85
  10. package/lib/module/screens/ResultScreen.js.map +1 -1
  11. package/lib/module/screens/TopicListScreen.js +284 -135
  12. package/lib/module/screens/TopicListScreen.js.map +1 -1
  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/screens/QbankScreen.d.ts.map +1 -1
  16. package/lib/typescript/src/screens/QuestionScreen.d.ts.map +1 -1
  17. package/lib/typescript/src/screens/QuizStartScreen.d.ts.map +1 -1
  18. package/lib/typescript/src/screens/ResultScreen.d.ts.map +1 -1
  19. package/lib/typescript/src/screens/TopicListScreen.d.ts.map +1 -1
  20. package/lib/typescript/src/types/index.d.ts +4 -0
  21. package/lib/typescript/src/types/index.d.ts.map +1 -1
  22. package/package.json +1 -1
  23. package/src/components/Icon.tsx +125 -1
  24. package/src/screens/QbankScreen.tsx +191 -67
  25. package/src/screens/QuestionScreen.tsx +179 -176
  26. package/src/screens/QuizStartScreen.tsx +118 -112
  27. package/src/screens/ResultScreen.tsx +382 -76
  28. package/src/screens/TopicListScreen.tsx +262 -111
  29. package/src/types/index.ts +4 -0
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect } from 'react';
1
+ import React, { useState } from 'react';
2
2
  import {
3
3
  View,
4
4
  ScrollView,
@@ -11,7 +11,7 @@ import {
11
11
  import type { Question, QuizResults } from '../types';
12
12
  import { useElearnConfig } from '../context/ElearnConfigContext';
13
13
  import { useTheme } from '../context/ThemeContext';
14
- import { Typography, Card, Button, Icon, ProgressBar } from '../components';
14
+ import { Typography, Button, Icon } from '../components';
15
15
 
16
16
  interface QuestionScreenProps {
17
17
  quizId: string;
@@ -24,11 +24,10 @@ interface QuestionScreenProps {
24
24
  export const QuestionScreen: React.FC<QuestionScreenProps> = ({
25
25
  quizId,
26
26
  questions,
27
- title,
28
27
  onClose,
29
28
  hideHeader = false,
30
29
  }) => {
31
- const { callbacks, getString } = useElearnConfig();
30
+ const { callbacks } = useElearnConfig();
32
31
  const theme = useTheme();
33
32
 
34
33
  const [currentIndex, setCurrentIndex] = useState(0);
@@ -37,15 +36,7 @@ export const QuestionScreen: React.FC<QuestionScreenProps> = ({
37
36
  const [userAnswers, setUserAnswers] = useState<{ [questionId: string]: number }>({});
38
37
  const [correctCount, setCorrectCount] = useState(0);
39
38
  const [startTime] = useState(Date.now());
40
- const [seconds, setSeconds] = useState(0);
41
-
42
- // Timer effect
43
- useEffect(() => {
44
- const timer = setInterval(() => {
45
- setSeconds((prev) => prev + 1);
46
- }, 1000);
47
- return () => clearInterval(timer);
48
- }, []);
39
+ const [isPaused, setIsPaused] = useState(false);
49
40
 
50
41
  if (!questions || questions.length === 0) {
51
42
  return (
@@ -61,12 +52,6 @@ export const QuestionScreen: React.FC<QuestionScreenProps> = ({
61
52
  const currentQuestion = questions[currentIndex]!;
62
53
  const optionPrefixes = ['A', 'B', 'C', 'D', 'E', 'F'];
63
54
 
64
- const formatTime = (totalSeconds: number) => {
65
- const mins = Math.floor(totalSeconds / 60);
66
- const secs = totalSeconds % 60;
67
- return `${mins}:${secs.toString().padStart(2, '0')}`;
68
- };
69
-
70
55
  const handleOptionPress = (index: number) => {
71
56
  if (isAnswered) return; // Prevent changing answer after selection
72
57
 
@@ -108,8 +93,6 @@ export const QuestionScreen: React.FC<QuestionScreenProps> = ({
108
93
  }
109
94
  };
110
95
 
111
- const progress = (currentIndex + 1) / questions.length;
112
-
113
96
  return (
114
97
  <SafeAreaView style={[styles.safeArea, { backgroundColor: theme.background }]}>
115
98
  <StatusBar barStyle="dark-content" />
@@ -117,77 +100,82 @@ export const QuestionScreen: React.FC<QuestionScreenProps> = ({
117
100
  {/* Header */}
118
101
  {!hideHeader && (
119
102
  <View style={styles.header}>
120
- <TouchableOpacity onPress={onClose} style={styles.closeButton}>
121
- <Icon name="x-circle" size={24} color={theme.textSecondary} />
103
+ <TouchableOpacity onPress={onClose} style={styles.backButton}>
104
+ <Icon name="arrow-left" size={20} color={theme.textPrimary} />
122
105
  </TouchableOpacity>
123
106
 
124
- <View style={styles.headerCenter}>
125
- <Typography variant="h3" bold numberOfLines={1}>
126
- {title}
127
- </Typography>
128
- </View>
107
+ <Typography variant="h2" bold style={styles.headerTitle}>
108
+ QBank
109
+ </Typography>
129
110
 
130
- <View style={styles.timerRow}>
131
- <Icon name="clock" size={16} color={theme.textSecondary} />
132
- <Typography variant="caption" bold color={theme.textSecondary} style={styles.timerText}>
133
- {formatTime(seconds)}
134
- </Typography>
135
- </View>
111
+ <TouchableOpacity style={styles.shareButton}>
112
+ <Icon name="share" size={20} color={theme.textPrimary} />
113
+ </TouchableOpacity>
136
114
  </View>
137
115
  )}
138
116
 
139
- {/* Progress Bar */}
140
- <ProgressBar progress={progress} height={4} style={styles.progressTracker} />
141
-
142
117
  <ScrollView contentContainerStyle={styles.scrollContent} showsVerticalScrollIndicator={false}>
143
118
 
144
- {/* Question Info */}
145
- <View style={styles.questionMeta}>
146
- <Typography variant="caption" color={theme.primary} bold>
147
- {getString('questionLabel').toUpperCase()} {currentIndex + 1} OF {questions.length}
119
+ {/* Question Header & Pause/Bookmark Actions Row */}
120
+ <View style={styles.metaRow}>
121
+ <Typography variant="caption" bold color={theme.textSecondary} style={styles.questionNumLabel}>
122
+ QUESTION {currentIndex + 1}/{questions.length}
148
123
  </Typography>
124
+ <View style={styles.metaActions}>
125
+ <TouchableOpacity style={styles.metaActionBtn}>
126
+ <Icon name="bookmark" size={18} color={theme.textSecondary} />
127
+ </TouchableOpacity>
128
+ <TouchableOpacity onPress={() => setIsPaused(!isPaused)} style={styles.metaActionBtn}>
129
+ <Icon name={isPaused ? 'play' : 'pause-circle'} size={18} color={theme.textSecondary} />
130
+ </TouchableOpacity>
131
+ </View>
149
132
  </View>
150
133
 
151
- {/* Question Text */}
152
- <Typography variant="h2" bold style={styles.questionText}>
134
+ {/* Question text */}
135
+ <Typography variant="h2" bold color={theme.textPrimary} style={styles.questionText}>
153
136
  {currentQuestion.text}
154
137
  </Typography>
155
138
 
156
- {/* Optional Diagram Image */}
139
+ {/* Neuron microscope image */}
157
140
  {currentQuestion.imageUrl && (
158
141
  <Image
159
142
  source={{ uri: currentQuestion.imageUrl }}
160
143
  style={styles.questionImage}
161
- resizeMode="contain"
144
+ resizeMode="cover"
162
145
  />
163
146
  )}
164
147
 
165
- {/* Options List */}
148
+ {/* Options list */}
166
149
  <View style={styles.optionsList}>
167
150
  {currentQuestion.options.map((option, idx) => {
168
151
  const isSelected = selectedOption === idx;
169
152
  const isCorrectAnswer = idx === currentQuestion.correctOptionIndex;
170
153
 
171
- let optionBg = theme.cardBackground;
154
+ let optionBg = '#FFFFFF';
172
155
  let optionBorder = theme.border;
173
- let showFeedbackIcon: 'check' | 'cross' | null = null;
174
-
156
+ let labelColor = theme.textPrimary;
157
+ let prefixColor = theme.textSecondary;
158
+ let statText = '';
159
+
175
160
  if (isAnswered) {
176
161
  if (isCorrectAnswer) {
177
- // Highlight correct answer in green
178
- optionBg = 'rgba(16, 185, 129, 0.08)';
179
- optionBorder = theme.success;
180
- showFeedbackIcon = 'check';
162
+ optionBg = '#E6F4F1'; // Tinted green bg
163
+ optionBorder = '#10B981'; // Green border
164
+ labelColor = '#065F46';
165
+ prefixColor = '#065F46';
166
+ statText = '73% got this Correct'; // Mock stats matching mockup 5
181
167
  } else if (isSelected) {
182
- // Highlight selected incorrect answer in red
183
- optionBg = 'rgba(239, 68, 68, 0.08)';
184
- optionBorder = theme.danger;
185
- showFeedbackIcon = 'cross';
168
+ optionBg = '#FEF2F2'; // Tinted red bg
169
+ optionBorder = '#EF4444'; // Red border
170
+ labelColor = '#991B1B';
171
+ prefixColor = '#991B1B';
172
+ statText = '27% Marked Wrong'; // Mock stats matching mockup 5
186
173
  }
187
174
  } else if (isSelected) {
188
- // Active highlight before submission
189
175
  optionBg = theme.secondary;
190
176
  optionBorder = theme.primary;
177
+ labelColor = theme.primary;
178
+ prefixColor = theme.primary;
191
179
  }
192
180
 
193
181
  return (
@@ -205,47 +193,25 @@ export const QuestionScreen: React.FC<QuestionScreenProps> = ({
205
193
  ]}
206
194
  >
207
195
  <View style={styles.optionRow}>
208
- <View
209
- style={[
210
- styles.prefixCircle,
211
- {
212
- backgroundColor: isSelected ? theme.primary : theme.secondary,
213
- },
214
- isAnswered && isCorrectAnswer && { backgroundColor: theme.success },
215
- isAnswered && isSelected && !isCorrectAnswer && { backgroundColor: theme.danger },
216
- ]}
217
- >
218
- <Typography
219
- variant="caption"
220
- bold
221
- color={
222
- isSelected || (isAnswered && isCorrectAnswer)
223
- ? theme.textInverse
224
- : theme.primary
225
- }
226
- >
227
- {optionPrefixes[idx] || idx + 1}
228
- </Typography>
229
- </View>
196
+ {/* Option letter label */}
197
+ <Typography variant="body" bold color={prefixColor} style={styles.optionPrefix}>
198
+ {optionPrefixes[idx] || idx + 1}
199
+ </Typography>
230
200
 
231
- <Typography
232
- variant="body"
233
- color={theme.textPrimary}
234
- style={styles.optionText}
235
- >
201
+ {/* Option text */}
202
+ <Typography variant="body" color={labelColor} style={styles.optionText}>
236
203
  {option}
237
204
  </Typography>
238
-
239
- {isAnswered && showFeedbackIcon && (
240
- <View style={styles.feedbackIcon}>
241
- <Icon
242
- name={showFeedbackIcon === 'check' ? 'check-circle' : 'x-circle'}
243
- size={20}
244
- color={showFeedbackIcon === 'check' ? theme.success : theme.danger}
245
- />
246
- </View>
247
- )}
248
205
  </View>
206
+
207
+ {/* Option stats badge below option text */}
208
+ {isAnswered && statText !== '' && (
209
+ <View style={styles.statBadgeRow}>
210
+ <Typography variant="caption" bold color={isCorrectAnswer ? '#10B981' : '#EF4444'}>
211
+ {statText}
212
+ </Typography>
213
+ </View>
214
+ )}
249
215
  </TouchableOpacity>
250
216
  );
251
217
  })}
@@ -253,34 +219,47 @@ export const QuestionScreen: React.FC<QuestionScreenProps> = ({
253
219
 
254
220
  {/* Explanation Card (Appears after answer selection) */}
255
221
  {isAnswered && (
256
- <Card bordered style={styles.explanationCard}>
257
- <View style={styles.explanationHeader}>
258
- <Icon name="info" size={20} color={theme.primary} />
259
- <Typography variant="h3" bold color={theme.primary} style={styles.explanationTitle}>
260
- {getString('explanationLabel')}
261
- </Typography>
262
- </View>
263
- <Typography variant="body" color={theme.textPrimary} style={styles.explanationBody}>
222
+ <View style={styles.explanationSection}>
223
+ <Typography variant="h2" bold color={theme.textPrimary} style={styles.explanationTitle}>
224
+ Explanation
225
+ </Typography>
226
+ <Typography variant="body" color={theme.textSecondary} style={styles.explanationBody}>
264
227
  {currentQuestion.explanation}
265
228
  </Typography>
266
- </Card>
229
+
230
+ <Typography variant="h3" bold color={theme.textSecondary} style={styles.rxdxId}>
231
+ RXDX ID: R23082
232
+ </Typography>
233
+ </View>
267
234
  )}
268
235
 
269
236
  </ScrollView>
270
237
 
271
- {/* Next/Submit Button Panel */}
238
+ {/* Answered State Bottom Actions Bar */}
272
239
  {isAnswered && (
273
- <View style={[styles.footer, { backgroundColor: theme.cardBackground, borderTopColor: theme.border }]}>
274
- <Button
275
- title={
276
- currentIndex === questions.length - 1
277
- ? getString('submitButtonLabel')
278
- : getString('nextButtonLabel')
279
- }
280
- onPress={handleNext}
281
- variant="primary"
282
- style={styles.nextButton}
283
- />
240
+ <View style={styles.footerRow}>
241
+ {/* Report action */}
242
+ <TouchableOpacity style={styles.footerActionBtn}>
243
+ <Icon name="alert-circle" size={18} color="#EF4444" />
244
+ <Typography variant="caption" bold color="#EF4444" style={styles.footerActionLabel}>
245
+ Report
246
+ </Typography>
247
+ </TouchableOpacity>
248
+
249
+ {/* Whatsapp action */}
250
+ <TouchableOpacity style={styles.footerActionBtn}>
251
+ <Icon name="whatsapp" size={18} color="#22C55E" />
252
+ <Typography variant="caption" bold color="#22C55E" style={styles.footerActionLabel}>
253
+ Whatsapp
254
+ </Typography>
255
+ </TouchableOpacity>
256
+
257
+ {/* Next action button */}
258
+ <TouchableOpacity onPress={handleNext} style={[styles.nextActionBtn, { backgroundColor: theme.primary }]}>
259
+ <Typography variant="body" bold color="#FFFFFF">
260
+ Next
261
+ </Typography>
262
+ </TouchableOpacity>
284
263
  </View>
285
264
  )}
286
265
  </SafeAreaView>
@@ -298,101 +277,125 @@ const styles = StyleSheet.create({
298
277
  paddingHorizontal: 16,
299
278
  paddingVertical: 12,
300
279
  },
301
- closeButton: {
302
- padding: 4,
280
+ backButton: {
281
+ padding: 6,
303
282
  },
304
- headerCenter: {
305
- flex: 1,
306
- alignItems: 'center',
307
- marginHorizontal: 12,
283
+ headerTitle: {
284
+ fontSize: 18,
285
+ fontWeight: '700',
286
+ },
287
+ shareButton: {
288
+ padding: 6,
289
+ },
290
+ scrollContent: {
291
+ paddingHorizontal: 16,
292
+ paddingTop: 8,
293
+ paddingBottom: 32,
308
294
  },
309
- timerRow: {
295
+ metaRow: {
310
296
  flexDirection: 'row',
297
+ justifyContent: 'space-between',
311
298
  alignItems: 'center',
312
- paddingHorizontal: 10,
313
- paddingVertical: 6,
314
- borderRadius: 12,
315
- backgroundColor: '#F1F5F9',
316
- },
317
- timerText: {
318
- marginLeft: 6,
299
+ marginBottom: 12,
319
300
  },
320
- progressTracker: {
321
- borderRadius: 0,
301
+ questionNumLabel: {
302
+ fontSize: 12,
303
+ letterSpacing: 0.8,
322
304
  },
323
- scrollContent: {
324
- paddingHorizontal: 20,
325
- paddingTop: 16,
326
- paddingBottom: 40,
305
+ metaActions: {
306
+ flexDirection: 'row',
307
+ alignItems: 'center',
327
308
  },
328
- questionMeta: {
329
- marginBottom: 8,
309
+ metaActionBtn: {
310
+ padding: 6,
311
+ marginLeft: 10,
330
312
  },
331
313
  questionText: {
332
- fontSize: 18,
333
- lineHeight: 24,
334
- marginBottom: 16,
314
+ fontSize: 16,
315
+ lineHeight: 22,
316
+ marginBottom: 14,
335
317
  },
336
318
  questionImage: {
337
319
  width: '100%',
338
320
  height: 180,
339
321
  borderRadius: 12,
340
- marginBottom: 20,
322
+ marginBottom: 16,
341
323
  backgroundColor: '#F8FAFC',
342
324
  },
343
325
  optionsList: {
344
- marginBottom: 20,
326
+ marginBottom: 12,
345
327
  },
346
328
  optionCard: {
347
- borderWidth: 1.5,
348
- borderRadius: 14,
349
- padding: 14,
350
- marginBottom: 12,
329
+ borderWidth: 1,
330
+ borderRadius: 10,
331
+ padding: 12,
332
+ marginBottom: 10,
351
333
  },
352
334
  optionRow: {
353
335
  flexDirection: 'row',
354
- alignItems: 'center',
336
+ alignItems: 'flex-start',
355
337
  },
356
- prefixCircle: {
357
- width: 28,
358
- height: 28,
359
- borderRadius: 14,
360
- justifyContent: 'center',
361
- alignItems: 'center',
362
- marginRight: 12,
338
+ optionPrefix: {
339
+ width: 24,
340
+ fontSize: 14,
341
+ fontWeight: '600',
363
342
  },
364
343
  optionText: {
365
344
  flex: 1,
366
- lineHeight: 20,
367
- },
368
- feedbackIcon: {
369
- marginLeft: 8,
345
+ fontSize: 14,
346
+ lineHeight: 18,
370
347
  },
371
- explanationCard: {
372
- padding: 20,
373
- borderRadius: 16,
374
- marginTop: 12,
375
- borderWidth: 1,
376
- borderStyle: 'dashed',
348
+ statBadgeRow: {
349
+ marginTop: 6,
350
+ paddingLeft: 24,
377
351
  },
378
- explanationHeader: {
379
- flexDirection: 'row',
380
- alignItems: 'center',
381
- marginBottom: 10,
352
+ explanationSection: {
353
+ marginTop: 14,
354
+ paddingTop: 14,
355
+ borderTopWidth: 1,
356
+ borderTopColor: '#E2E8F0',
382
357
  },
383
358
  explanationTitle: {
384
- marginLeft: 8,
359
+ fontSize: 15,
360
+ marginBottom: 6,
385
361
  },
386
362
  explanationBody: {
387
- lineHeight: 20,
363
+ fontSize: 13,
364
+ lineHeight: 18,
365
+ marginBottom: 16,
388
366
  },
389
- footer: {
390
- paddingHorizontal: 20,
391
- paddingVertical: 14,
367
+ rxdxId: {
368
+ fontSize: 12,
369
+ textAlign: 'center',
370
+ marginTop: 12,
371
+ },
372
+ footerRow: {
373
+ flexDirection: 'row',
374
+ alignItems: 'center',
375
+ paddingHorizontal: 16,
376
+ paddingVertical: 12,
377
+ backgroundColor: '#FFFFFF',
392
378
  borderTopWidth: 1,
379
+ borderTopColor: '#E2E8F0',
380
+ justifyContent: 'space-between',
393
381
  },
394
- nextButton: {
395
- width: '100%',
382
+ footerActionBtn: {
383
+ flexDirection: 'row',
384
+ alignItems: 'center',
385
+ paddingVertical: 8,
386
+ paddingHorizontal: 12,
387
+ },
388
+ footerActionLabel: {
389
+ marginLeft: 6,
390
+ fontSize: 12,
391
+ },
392
+ nextActionBtn: {
393
+ paddingHorizontal: 24,
394
+ paddingVertical: 10,
395
+ borderRadius: 8,
396
+ minWidth: 80,
397
+ justifyContent: 'center',
398
+ alignItems: 'center',
396
399
  },
397
400
  errorContainer: {
398
401
  flex: 1,