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.
- package/lib/module/components/Icon.js +97 -0
- package/lib/module/components/Icon.js.map +1 -1
- package/lib/module/screens/QbankScreen.js +251 -99
- package/lib/module/screens/QbankScreen.js.map +1 -1
- package/lib/module/screens/QuestionScreen.js +219 -179
- package/lib/module/screens/QuestionScreen.js.map +1 -1
- package/lib/module/screens/QuizStartScreen.js +134 -131
- package/lib/module/screens/QuizStartScreen.js.map +1 -1
- package/lib/module/screens/ResultScreen.js +407 -85
- package/lib/module/screens/ResultScreen.js.map +1 -1
- package/lib/module/screens/TopicListScreen.js +284 -135
- package/lib/module/screens/TopicListScreen.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/screens/QbankScreen.d.ts.map +1 -1
- package/lib/typescript/src/screens/QuestionScreen.d.ts.map +1 -1
- package/lib/typescript/src/screens/QuizStartScreen.d.ts.map +1 -1
- package/lib/typescript/src/screens/ResultScreen.d.ts.map +1 -1
- package/lib/typescript/src/screens/TopicListScreen.d.ts.map +1 -1
- package/lib/typescript/src/types/index.d.ts +4 -0
- package/lib/typescript/src/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Icon.tsx +125 -1
- package/src/screens/QbankScreen.tsx +191 -67
- package/src/screens/QuestionScreen.tsx +179 -176
- package/src/screens/QuizStartScreen.tsx +118 -112
- package/src/screens/ResultScreen.tsx +382 -76
- package/src/screens/TopicListScreen.tsx +262 -111
- package/src/types/index.ts +4 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
View,
|
|
4
4
|
ScrollView,
|
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
SafeAreaView,
|
|
8
8
|
StatusBar,
|
|
9
9
|
} from 'react-native';
|
|
10
|
-
import type { Course } from '../types';
|
|
10
|
+
import type { Course, Topic } from '../types';
|
|
11
11
|
import { useElearnConfig } from '../context/ElearnConfigContext';
|
|
12
12
|
import { useTheme } from '../context/ThemeContext';
|
|
13
|
-
import { Typography, Card,
|
|
13
|
+
import { Typography, Card, Icon } from '../components';
|
|
14
14
|
|
|
15
15
|
interface TopicListScreenProps {
|
|
16
16
|
course: Course;
|
|
@@ -23,9 +23,28 @@ export const TopicListScreen: React.FC<TopicListScreenProps> = ({
|
|
|
23
23
|
onBack,
|
|
24
24
|
hideHeader = false,
|
|
25
25
|
}) => {
|
|
26
|
-
const { callbacks
|
|
26
|
+
const { callbacks } = useElearnConfig();
|
|
27
27
|
const theme = useTheme();
|
|
28
28
|
|
|
29
|
+
// Active filter tab
|
|
30
|
+
const [activeTab, setActiveTab] = useState('All');
|
|
31
|
+
const tabs = ['All', 'New', 'Completed', 'Paused', 'Not Started'];
|
|
32
|
+
|
|
33
|
+
// Group topics by category
|
|
34
|
+
const categoriesMap: { [key: string]: Topic[] } = {};
|
|
35
|
+
course.topics.forEach((topic) => {
|
|
36
|
+
const cat = topic.category || 'GENERAL';
|
|
37
|
+
if (!categoriesMap[cat]) {
|
|
38
|
+
categoriesMap[cat] = [];
|
|
39
|
+
}
|
|
40
|
+
categoriesMap[cat].push(topic);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const categories = Object.keys(categoriesMap);
|
|
44
|
+
|
|
45
|
+
// Keep a global sequential counter for the timeline nodes
|
|
46
|
+
let globalIndex = 0;
|
|
47
|
+
|
|
29
48
|
return (
|
|
30
49
|
<SafeAreaView style={[styles.safeArea, { backgroundColor: theme.background }]}>
|
|
31
50
|
<StatusBar barStyle="dark-content" />
|
|
@@ -33,83 +52,148 @@ export const TopicListScreen: React.FC<TopicListScreenProps> = ({
|
|
|
33
52
|
{/* Header */}
|
|
34
53
|
{!hideHeader && (
|
|
35
54
|
<View style={styles.header}>
|
|
36
|
-
<TouchableOpacity
|
|
37
|
-
onPress={onBack}
|
|
38
|
-
style={[styles.backButton, { borderColor: theme.border }]}
|
|
39
|
-
>
|
|
55
|
+
<TouchableOpacity onPress={onBack} style={styles.backButton}>
|
|
40
56
|
<Icon name="arrow-left" size={20} color={theme.textPrimary} />
|
|
41
57
|
</TouchableOpacity>
|
|
42
|
-
<Typography variant="
|
|
43
|
-
|
|
58
|
+
<Typography variant="h2" bold style={styles.headerTitle}>
|
|
59
|
+
QBank
|
|
44
60
|
</Typography>
|
|
45
|
-
<
|
|
61
|
+
<TouchableOpacity style={styles.searchButton}>
|
|
62
|
+
<Icon name="search" size={20} color={theme.textPrimary} />
|
|
63
|
+
</TouchableOpacity>
|
|
46
64
|
</View>
|
|
47
65
|
)}
|
|
48
66
|
|
|
49
67
|
<ScrollView contentContainerStyle={styles.scrollContent} showsVerticalScrollIndicator={false}>
|
|
50
68
|
|
|
51
|
-
{/* Subject
|
|
52
|
-
<
|
|
53
|
-
<Typography variant="
|
|
69
|
+
{/* Subject Header Suffix */}
|
|
70
|
+
<View style={styles.subjectHeader}>
|
|
71
|
+
<Typography variant="h1" bold color={theme.textPrimary} style={styles.subjectTitle}>
|
|
54
72
|
{course.title}
|
|
55
73
|
</Typography>
|
|
56
|
-
{
|
|
57
|
-
|
|
58
|
-
{course.description}
|
|
59
|
-
</Typography>
|
|
60
|
-
)}
|
|
61
|
-
<View style={styles.progressSummaryContainer}>
|
|
62
|
-
<View style={styles.progressSummaryText}>
|
|
63
|
-
<Typography variant="caption" color="#FFFFFF">
|
|
64
|
-
Overall Subject Progress
|
|
65
|
-
</Typography>
|
|
66
|
-
<Typography variant="label" bold color="#FFFFFF">
|
|
67
|
-
{course.completedQuestions}/{course.totalQuestions} Questions answered
|
|
68
|
-
</Typography>
|
|
69
|
-
</View>
|
|
70
|
-
<Typography variant="h2" bold color="#FFFFFF">
|
|
71
|
-
{Math.round(course.progress * 100)}%
|
|
72
|
-
</Typography>
|
|
73
|
-
</View>
|
|
74
|
-
<ProgressBar progress={course.progress} color="#FFFFFF" style={styles.summaryProgressBar} />
|
|
75
|
-
</Card>
|
|
76
|
-
|
|
77
|
-
{/* List of Topics */}
|
|
78
|
-
<View style={styles.sectionHeader}>
|
|
79
|
-
<Typography variant="h2" bold color={theme.textPrimary}>
|
|
80
|
-
{getString('topicsLabel')} ({course.topics.length})
|
|
74
|
+
<Typography variant="body" color={theme.textSecondary}>
|
|
75
|
+
{course.topics.length} Modules
|
|
81
76
|
</Typography>
|
|
82
77
|
</View>
|
|
83
78
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
79
|
+
{/* Horizontal Filters Tabs Bar */}
|
|
80
|
+
<View style={styles.tabsContainer}>
|
|
81
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.tabsScroll}>
|
|
82
|
+
{tabs.map((tab) => {
|
|
83
|
+
const isActive = activeTab === tab;
|
|
84
|
+
return (
|
|
85
|
+
<TouchableOpacity
|
|
86
|
+
key={tab}
|
|
87
|
+
onPress={() => setActiveTab(tab)}
|
|
88
|
+
style={[
|
|
89
|
+
styles.tabButton,
|
|
90
|
+
isActive
|
|
91
|
+
? { backgroundColor: theme.textPrimary }
|
|
92
|
+
: { borderColor: theme.border, borderWidth: 1 }
|
|
93
|
+
]}
|
|
94
|
+
>
|
|
95
|
+
<Typography
|
|
96
|
+
variant="caption"
|
|
97
|
+
bold
|
|
98
|
+
color={isActive ? '#FFFFFF' : theme.textSecondary}
|
|
99
|
+
>
|
|
100
|
+
{tab}
|
|
101
|
+
</Typography>
|
|
102
|
+
</TouchableOpacity>
|
|
103
|
+
);
|
|
104
|
+
})}
|
|
105
|
+
</ScrollView>
|
|
106
|
+
</View>
|
|
87
107
|
|
|
108
|
+
{/* Timeline of Chapters Categories */}
|
|
109
|
+
<View style={styles.listContainer}>
|
|
110
|
+
{categories.map((category) => {
|
|
111
|
+
const list = categoriesMap[category] || [];
|
|
112
|
+
|
|
88
113
|
return (
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
{
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
114
|
+
<View key={category} style={styles.categoryBlock}>
|
|
115
|
+
{/* Category Section Title */}
|
|
116
|
+
<Typography variant="caption" bold color={theme.textSecondary} style={styles.categoryTitle}>
|
|
117
|
+
{category.toUpperCase()}
|
|
118
|
+
</Typography>
|
|
119
|
+
|
|
120
|
+
{/* Timeline Items */}
|
|
121
|
+
<View style={styles.categoryItems}>
|
|
122
|
+
{list.map((topic) => {
|
|
123
|
+
globalIndex++;
|
|
124
|
+
const currentIndex = globalIndex;
|
|
125
|
+
const isLastInCourse = currentIndex === course.topics.length;
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<View key={topic.id} style={styles.timelineItemRow}>
|
|
129
|
+
|
|
130
|
+
{/* Left Side Timeline Connectors */}
|
|
131
|
+
<View style={styles.timelineLeftColumn}>
|
|
132
|
+
{/* Top Line connector */}
|
|
133
|
+
{currentIndex > 1 && <View style={[styles.timelineLine, { height: '50%', top: 0 }]} />}
|
|
134
|
+
|
|
135
|
+
{/* Bottom Line connector */}
|
|
136
|
+
{!isLastInCourse && <View style={[styles.timelineLine, { height: '100%', top: 16 }]} />}
|
|
137
|
+
|
|
138
|
+
{/* Sequential Number Circle */}
|
|
139
|
+
<View style={[styles.numberNode, { backgroundColor: '#8C9BAB' }]}>
|
|
140
|
+
<Typography variant="caption" bold color="#FFFFFF" style={styles.nodeText}>
|
|
141
|
+
{currentIndex}
|
|
142
|
+
</Typography>
|
|
143
|
+
</View>
|
|
144
|
+
</View>
|
|
145
|
+
|
|
146
|
+
{/* Right Side Info Card */}
|
|
147
|
+
<TouchableOpacity
|
|
148
|
+
activeOpacity={0.8}
|
|
149
|
+
onPress={() => callbacks.onSelectTopic?.(course, topic)}
|
|
150
|
+
style={styles.cardTouchable}
|
|
151
|
+
>
|
|
152
|
+
<Card bordered style={styles.topicCard}>
|
|
153
|
+
<View style={styles.cardMainRow}>
|
|
154
|
+
|
|
155
|
+
{/* Left Text details */}
|
|
156
|
+
<View style={styles.cardInfoCol}>
|
|
157
|
+
<Typography variant="h3" bold color={theme.textPrimary} style={styles.cardTitle}>
|
|
158
|
+
{topic.title}
|
|
159
|
+
</Typography>
|
|
160
|
+
|
|
161
|
+
<View style={styles.ratingRow}>
|
|
162
|
+
<Icon name="star" size={14} color="#EAB308" />
|
|
163
|
+
<Typography variant="caption" bold color={theme.textPrimary} style={styles.ratingVal}>
|
|
164
|
+
{topic.rating || '4.5'}
|
|
165
|
+
</Typography>
|
|
166
|
+
<View style={styles.bulletSeparator} />
|
|
167
|
+
<Typography variant="caption" color={theme.textSecondary}>
|
|
168
|
+
{topic.totalQuestions} Questions
|
|
169
|
+
</Typography>
|
|
170
|
+
</View>
|
|
171
|
+
</View>
|
|
172
|
+
|
|
173
|
+
{/* Right Badge / Lock option */}
|
|
174
|
+
<View style={styles.cardBadgeCol}>
|
|
175
|
+
{topic.isFree ? (
|
|
176
|
+
<View style={[styles.freeBadge, { borderColor: theme.primary }]}>
|
|
177
|
+
<Typography variant="caption" bold color={theme.primary} style={styles.freeBadgeText}>
|
|
178
|
+
FREE
|
|
179
|
+
</Typography>
|
|
180
|
+
</View>
|
|
181
|
+
) : topic.isLocked ? (
|
|
182
|
+
<View style={styles.lockContainer}>
|
|
183
|
+
<Icon name="lock" size={14} color={theme.textSecondary} />
|
|
184
|
+
</View>
|
|
185
|
+
) : null}
|
|
186
|
+
</View>
|
|
187
|
+
|
|
188
|
+
</View>
|
|
189
|
+
</Card>
|
|
190
|
+
</TouchableOpacity>
|
|
191
|
+
|
|
192
|
+
</View>
|
|
193
|
+
);
|
|
194
|
+
})}
|
|
195
|
+
</View>
|
|
196
|
+
</View>
|
|
113
197
|
);
|
|
114
198
|
})}
|
|
115
199
|
</View>
|
|
@@ -127,78 +211,145 @@ const styles = StyleSheet.create({
|
|
|
127
211
|
flexDirection: 'row',
|
|
128
212
|
justifyContent: 'space-between',
|
|
129
213
|
alignItems: 'center',
|
|
130
|
-
paddingHorizontal:
|
|
214
|
+
paddingHorizontal: 16,
|
|
131
215
|
paddingVertical: 12,
|
|
132
216
|
},
|
|
133
217
|
backButton: {
|
|
134
|
-
|
|
135
|
-
height: 40,
|
|
136
|
-
borderRadius: 20,
|
|
137
|
-
borderWidth: 1,
|
|
138
|
-
justifyContent: 'center',
|
|
139
|
-
alignItems: 'center',
|
|
218
|
+
padding: 6,
|
|
140
219
|
},
|
|
141
220
|
headerTitle: {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
marginHorizontal: 12,
|
|
221
|
+
fontSize: 18,
|
|
222
|
+
fontWeight: '700',
|
|
145
223
|
},
|
|
146
|
-
|
|
147
|
-
|
|
224
|
+
searchButton: {
|
|
225
|
+
padding: 6,
|
|
148
226
|
},
|
|
149
227
|
scrollContent: {
|
|
150
|
-
paddingHorizontal:
|
|
151
|
-
paddingBottom:
|
|
228
|
+
paddingHorizontal: 16,
|
|
229
|
+
paddingBottom: 32,
|
|
152
230
|
},
|
|
153
|
-
|
|
154
|
-
padding: 20,
|
|
155
|
-
borderRadius: 20,
|
|
231
|
+
subjectHeader: {
|
|
156
232
|
marginTop: 12,
|
|
233
|
+
marginBottom: 16,
|
|
157
234
|
},
|
|
158
|
-
|
|
159
|
-
|
|
235
|
+
subjectTitle: {
|
|
236
|
+
fontSize: 22,
|
|
237
|
+
marginBottom: 2,
|
|
160
238
|
},
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
justifyContent: 'space-between',
|
|
164
|
-
alignItems: 'flex-end',
|
|
165
|
-
marginTop: 20,
|
|
166
|
-
marginBottom: 8,
|
|
239
|
+
tabsContainer: {
|
|
240
|
+
marginBottom: 16,
|
|
167
241
|
},
|
|
168
|
-
|
|
169
|
-
|
|
242
|
+
tabsScroll: {
|
|
243
|
+
paddingVertical: 4,
|
|
244
|
+
},
|
|
245
|
+
tabButton: {
|
|
246
|
+
paddingHorizontal: 16,
|
|
247
|
+
paddingVertical: 6,
|
|
248
|
+
borderRadius: 20,
|
|
249
|
+
marginRight: 10,
|
|
250
|
+
},
|
|
251
|
+
listContainer: {
|
|
252
|
+
marginTop: 8,
|
|
253
|
+
},
|
|
254
|
+
categoryBlock: {
|
|
255
|
+
marginBottom: 24,
|
|
256
|
+
},
|
|
257
|
+
categoryTitle: {
|
|
258
|
+
fontSize: 11,
|
|
259
|
+
letterSpacing: 0.8,
|
|
260
|
+
marginBottom: 16,
|
|
170
261
|
},
|
|
171
|
-
|
|
172
|
-
|
|
262
|
+
categoryItems: {
|
|
263
|
+
marginTop: 2,
|
|
173
264
|
},
|
|
174
|
-
|
|
175
|
-
|
|
265
|
+
timelineItemRow: {
|
|
266
|
+
flexDirection: 'row',
|
|
267
|
+
alignItems: 'stretch',
|
|
176
268
|
marginBottom: 12,
|
|
269
|
+
minHeight: 80,
|
|
270
|
+
},
|
|
271
|
+
timelineLeftColumn: {
|
|
272
|
+
width: 36,
|
|
273
|
+
alignItems: 'center',
|
|
274
|
+
justifyContent: 'flex-start',
|
|
275
|
+
paddingTop: 12,
|
|
276
|
+
position: 'relative',
|
|
277
|
+
},
|
|
278
|
+
timelineLine: {
|
|
279
|
+
position: 'absolute',
|
|
280
|
+
left: 17, // center of the 36 width
|
|
281
|
+
width: 2,
|
|
282
|
+
backgroundColor: '#E2E8F0',
|
|
283
|
+
zIndex: -1,
|
|
284
|
+
},
|
|
285
|
+
numberNode: {
|
|
286
|
+
width: 24,
|
|
287
|
+
height: 24,
|
|
288
|
+
borderRadius: 12,
|
|
289
|
+
justifyContent: 'center',
|
|
290
|
+
alignItems: 'center',
|
|
291
|
+
zIndex: 1,
|
|
177
292
|
},
|
|
178
|
-
|
|
179
|
-
|
|
293
|
+
nodeText: {
|
|
294
|
+
fontSize: 11,
|
|
295
|
+
},
|
|
296
|
+
cardTouchable: {
|
|
297
|
+
flex: 1,
|
|
180
298
|
},
|
|
181
299
|
topicCard: {
|
|
182
|
-
padding:
|
|
183
|
-
|
|
300
|
+
padding: 12,
|
|
301
|
+
borderRadius: 12,
|
|
302
|
+
flex: 1,
|
|
184
303
|
},
|
|
185
|
-
|
|
304
|
+
cardMainRow: {
|
|
186
305
|
flexDirection: 'row',
|
|
187
306
|
justifyContent: 'space-between',
|
|
188
307
|
alignItems: 'center',
|
|
189
|
-
|
|
308
|
+
flex: 1,
|
|
190
309
|
},
|
|
191
|
-
|
|
310
|
+
cardInfoCol: {
|
|
192
311
|
flex: 1,
|
|
193
|
-
paddingRight:
|
|
312
|
+
paddingRight: 8,
|
|
194
313
|
},
|
|
195
|
-
|
|
314
|
+
cardTitle: {
|
|
315
|
+
fontSize: 14,
|
|
316
|
+
lineHeight: 18,
|
|
317
|
+
marginBottom: 6,
|
|
318
|
+
},
|
|
319
|
+
ratingRow: {
|
|
320
|
+
flexDirection: 'row',
|
|
321
|
+
alignItems: 'center',
|
|
322
|
+
},
|
|
323
|
+
ratingVal: {
|
|
324
|
+
fontSize: 11,
|
|
325
|
+
marginLeft: 4,
|
|
326
|
+
},
|
|
327
|
+
bulletSeparator: {
|
|
328
|
+
width: 3,
|
|
329
|
+
height: 3,
|
|
330
|
+
borderRadius: 1.5,
|
|
331
|
+
backgroundColor: '#94A3B8',
|
|
332
|
+
marginHorizontal: 6,
|
|
333
|
+
},
|
|
334
|
+
cardBadgeCol: {
|
|
335
|
+
justifyContent: 'center',
|
|
336
|
+
alignItems: 'flex-end',
|
|
337
|
+
},
|
|
338
|
+
freeBadge: {
|
|
339
|
+
borderWidth: 1.5,
|
|
340
|
+
borderRadius: 6,
|
|
196
341
|
paddingHorizontal: 8,
|
|
197
|
-
paddingVertical:
|
|
198
|
-
borderRadius: 8,
|
|
199
|
-
backgroundColor: 'rgba(0, 122, 135, 0.1)', // Dynamic overlay of primary color
|
|
342
|
+
paddingVertical: 3,
|
|
200
343
|
},
|
|
201
|
-
|
|
202
|
-
|
|
344
|
+
freeBadgeText: {
|
|
345
|
+
fontSize: 9,
|
|
346
|
+
},
|
|
347
|
+
lockContainer: {
|
|
348
|
+
width: 24,
|
|
349
|
+
height: 24,
|
|
350
|
+
borderRadius: 12,
|
|
351
|
+
backgroundColor: '#F1F5F9',
|
|
352
|
+
justifyContent: 'center',
|
|
353
|
+
alignItems: 'center',
|
|
203
354
|
},
|
|
204
355
|
});
|
package/src/types/index.ts
CHANGED
|
@@ -72,6 +72,10 @@ export interface Topic {
|
|
|
72
72
|
completedQuestions: number;
|
|
73
73
|
progress: number; // 0 to 1
|
|
74
74
|
questions?: Question[];
|
|
75
|
+
category?: string; // e.g. "NEURO ANATOMY"
|
|
76
|
+
rating?: number; // e.g. 4.5
|
|
77
|
+
isFree?: boolean;
|
|
78
|
+
isLocked?: boolean;
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
export interface Course {
|