react-native-elearn-ui 0.1.4 → 0.1.6

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