react-native-elearn-ui 0.1.5 → 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.
@@ -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, ProgressBar, Icon } from '../components';
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, getString } = useElearnConfig();
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="h3" bold style={styles.headerTitle} numberOfLines={1}>
43
- {course.title}
58
+ <Typography variant="h2" bold style={styles.headerTitle}>
59
+ QBank
44
60
  </Typography>
45
- <View style={styles.headerRightPlaceholder} />
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 Summary Card */}
52
- <Card style={[styles.summaryCard, { backgroundColor: theme.primary }]}>
53
- <Typography variant="h2" bold color="#FFFFFF">
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
- {course.description && (
57
- <Typography variant="caption" color="rgba(255, 255, 255, 0.8)" style={styles.summaryDesc}>
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
- <View style={styles.topicsList}>
85
- {course.topics.map((topic) => {
86
- const pct = Math.round(topic.progress * 100);
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
- <TouchableOpacity
90
- key={topic.id}
91
- activeOpacity={0.8}
92
- onPress={() => callbacks.onSelectTopic?.(course, topic)}
93
- >
94
- <Card bordered style={styles.topicCard}>
95
- <View style={styles.topicCardHeader}>
96
- <View style={styles.topicTitleSection}>
97
- <Typography variant="h3" bold color={theme.textPrimary}>
98
- {topic.title}
99
- </Typography>
100
- <Typography variant="caption" color={theme.textSecondary}>
101
- {topic.completedQuestions}/{topic.totalQuestions} {getString('questionsCountLabel')}
102
- </Typography>
103
- </View>
104
- <View style={styles.pctBadge}>
105
- <Typography variant="caption" bold color={theme.primary}>
106
- {pct}%
107
- </Typography>
108
- </View>
109
- </View>
110
- <ProgressBar progress={topic.progress} style={styles.topicProgressBar} />
111
- </Card>
112
- </TouchableOpacity>
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: 20,
214
+ paddingHorizontal: 16,
131
215
  paddingVertical: 12,
132
216
  },
133
217
  backButton: {
134
- width: 40,
135
- height: 40,
136
- borderRadius: 20,
137
- borderWidth: 1,
138
- justifyContent: 'center',
139
- alignItems: 'center',
218
+ padding: 6,
140
219
  },
141
220
  headerTitle: {
142
- flex: 1,
143
- textAlign: 'center',
144
- marginHorizontal: 12,
221
+ fontSize: 18,
222
+ fontWeight: '700',
145
223
  },
146
- headerRightPlaceholder: {
147
- width: 40,
224
+ searchButton: {
225
+ padding: 6,
148
226
  },
149
227
  scrollContent: {
150
- paddingHorizontal: 20,
151
- paddingBottom: 24,
228
+ paddingHorizontal: 16,
229
+ paddingBottom: 32,
152
230
  },
153
- summaryCard: {
154
- padding: 20,
155
- borderRadius: 20,
231
+ subjectHeader: {
156
232
  marginTop: 12,
233
+ marginBottom: 16,
157
234
  },
158
- summaryDesc: {
159
- marginTop: 6,
235
+ subjectTitle: {
236
+ fontSize: 22,
237
+ marginBottom: 2,
160
238
  },
161
- progressSummaryContainer: {
162
- flexDirection: 'row',
163
- justifyContent: 'space-between',
164
- alignItems: 'flex-end',
165
- marginTop: 20,
166
- marginBottom: 8,
239
+ tabsContainer: {
240
+ marginBottom: 16,
167
241
  },
168
- progressSummaryText: {
169
- flex: 1,
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
- summaryProgressBar: {
172
- backgroundColor: 'rgba(255, 255, 255, 0.25)',
262
+ categoryItems: {
263
+ marginTop: 2,
173
264
  },
174
- sectionHeader: {
175
- marginTop: 24,
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
- topicsList: {
179
- marginTop: 4,
293
+ nodeText: {
294
+ fontSize: 11,
295
+ },
296
+ cardTouchable: {
297
+ flex: 1,
180
298
  },
181
299
  topicCard: {
182
- padding: 16,
183
- marginBottom: 12,
300
+ padding: 12,
301
+ borderRadius: 12,
302
+ flex: 1,
184
303
  },
185
- topicCardHeader: {
304
+ cardMainRow: {
186
305
  flexDirection: 'row',
187
306
  justifyContent: 'space-between',
188
307
  alignItems: 'center',
189
- marginBottom: 12,
308
+ flex: 1,
190
309
  },
191
- topicTitleSection: {
310
+ cardInfoCol: {
192
311
  flex: 1,
193
- paddingRight: 12,
312
+ paddingRight: 8,
194
313
  },
195
- pctBadge: {
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: 4,
198
- borderRadius: 8,
199
- backgroundColor: 'rgba(0, 122, 135, 0.1)', // Dynamic overlay of primary color
342
+ paddingVertical: 3,
200
343
  },
201
- topicProgressBar: {
202
- height: 6,
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
  });
@@ -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 {