Aidra Connect 10.0.2+16
Aidra Connect Mobile Application
Loading...
Searching...
No Matches
course_detail_screen.dart
Go to the documentation of this file.
1import 'package:connect/core/localization/app_localizations.dart';
2import 'package:connect/core/router/routes.dart';
3import 'package:connect/core/ui/theme/color_palette.dart';
4import 'package:flutter/material.dart';
5import 'package:flutter_bloc/flutter_bloc.dart';
6import 'package:flutter_screenutil/flutter_screenutil.dart';
7import 'package:go_router/go_router.dart';
8import 'package:hugeicons/hugeicons.dart';
9
10import '../../../../core/ui/screens/views/faild_to_fetch_data_view.dart';
11import '../../../../core/ui/widgets/custom_scaffold.dart';
12import '../../domain/entities/course_entity.dart';
13import '../logic/cubit/elearning_v2_cubit.dart';
14import 'qcm_screen.dart';
15
17 final int courseId;
18
20 super.key,
21 required this.courseId,
22 });
23
24 @override
25 State<CourseDetailScreen> createState() => _CourseDetailScreenState();
26}
27
28class _CourseDetailScreenState extends State<CourseDetailScreen> {
29 @override
30 void initState() {
31 super.initState();
33 }
34
36 context.read<ElearningV2Cubit>().loadCourseDetails(widget.courseId);
37 }
38
39 @override
40 Widget build(BuildContext context) {
41 return BlocBuilder<ElearningV2Cubit, ElearningV2State>(
42 builder: (context, state) {
43 return CustomScaffold(
45 ? state.course.title ??
46 AppLocalizations.of(context).translate('Course Details')
47 : AppLocalizations.of(context).translate('Course Details'),
48 isLoading: state is LoadingCourseDetailsState,
49 isLeadingVisible: true,
50 body: SafeArea(
51 child: _buildBody(state),
52 ),
53 );
54 },
55 );
56 }
57
59 if (state is CourseDetailsLoadedState) {
61 state.course,
62 state.pdfPath,
63 state.qcmList.isNotEmpty,
64 );
65 } else if (state is CourseDetailsLoadingFailureState) {
66 return FailedToFetchDataView(
67 onRetry: _loadCourseDetails,
68 );
69 } else {
70 return const SizedBox();
71 }
72 }
73
74 Widget _buildCourseDetails(CourseEntity course, String pdfPath, bool hasQcm) {
75 Color mainColor = _parseColor(course.iconColor ?? '#4CAF50', context);
76 return SingleChildScrollView(
77 padding: EdgeInsets.all(16.r),
78 child: Column(
79 crossAxisAlignment: CrossAxisAlignment.start,
80 children: [
81 // Course title and description
82 Text(
83 course.title ??
84 AppLocalizations.of(context).translate('Untitled Course'),
85 style: Theme.of(context).textTheme.displayMedium,
86 ),
87 SizedBox(height: 8.r),
88 Text(
89 course.description ?? '',
90 style: Theme.of(context).textTheme.bodyMedium?.copyWith(
91 color: Theme.of(context).hintColor,
92 ),
93 ),
94
95 // Progress indicator
96 Container(
97 decoration: BoxDecoration(
98 color: Theme.of(context).colorScheme.surface,
99 borderRadius: BorderRadius.circular(16),
100 ),
101 margin: EdgeInsets.symmetric(vertical: 16.sp),
102 padding: EdgeInsets.all(15.sp),
103 child: Column(
104 crossAxisAlignment: CrossAxisAlignment.start,
105 children: [
106 Row(
107 children: [
108 Icon(HugeIcons.strokeRoundedProgress02, size: 20),
109 SizedBox(width: 7.sp),
110 Text(
111 AppLocalizations.of(context).translate('Your Progress'),
112 style: Theme.of(context).textTheme.bodyLarge?.copyWith(
113 fontWeight: FontWeight.bold,
114 ),
115 ),
116 ],
117 ),
118 SizedBox(height: 12.r),
119 LinearProgressIndicator(
120 value: course.progress,
121 color: mainColor,
122 backgroundColor: mainColor.withOpacity(0.11),
123 valueColor: AlwaysStoppedAnimation<Color>(
124 mainColor,
125 ),
126 borderRadius: BorderRadius.circular(4.r),
127 minHeight: 3.r,
128 ),
129 SizedBox(height: 8.r),
130 Align(
131 alignment: Alignment.centerRight,
132 child: Text('${(course.progress! * 100).toInt()}%',
133 style: Theme.of(context).textTheme.displaySmall?.copyWith(
134 color: mainColor,
135 )),
136 ),
137 ],
138 ),
139 ),
140
141 // Learning steps
142
143 Text(
144 AppLocalizations.of(context).translate('Learning Steps'),
145 style: Theme.of(context).textTheme.bodyLarge,
146 ),
147 SizedBox(height: 16.r),
148
149 // PDF Reading step
151 color: mainColor,
152 title: AppLocalizations.of(context)
153 .translate('Step 1: Read the Material'),
154 description: AppLocalizations.of(context).translate(
155 'Read through the PDF material to understand the concepts'),
156 progress: course.progress! >= 0.5 ? 1.0 : course.progress! * 2,
157 onTap: () {
158 context.push(
159 Routes.pdfViewerScreen.route,
160 extra: {
161 'pdfPath': course.pdfPath,
162 'courseId': course.id,
163 'onComplete': (double progress) {
164 if (course.progress! < 0.5) {
165 context.read<ElearningV2Cubit>().updateProgress(
166 course.id!,
167 0.5,
168 );
169 }
170 },
171 },
172 );
173 },
174 ),
175 SizedBox(height: 16.r),
176 // QCM Quiz step
178 color: mainColor,
179 title: AppLocalizations.of(context)
180 .translate('Step 2: Test Your Knowledge'),
181 description: AppLocalizations.of(context)
182 .translate('Complete the quiz to test your understanding'),
183 progress:
184 course.progress! <= 0.5 ? 0.0 : (course.progress! - 0.5) * 2,
185 isDisabled: course.progress! < 0.5,
186 onTap: () {
187 if (course.progress! >= 0.5) {
188 Navigator.push(
189 context,
190 MaterialPageRoute(
191 builder: (context) => QcmScreen(
192 courseId: course.id!,
193 currentProgress: course.progress!,
194 onComplete: (double quizScore) {
195 // Calculate new progress (50% from PDF + 50% from quiz)
196 final newProgress = 0.5 + (quizScore * 0.5);
197 context.read<ElearningV2Cubit>().updateProgress(
198 course.id!,
199 newProgress,
200 );
201 },
202 ),
203 ),
204 );
205 }
206 },
207 ),
208 ],
209 ),
210 );
211 }
212
214 required String title,
215 required String description,
216 required double progress,
217 required VoidCallback onTap,
218 required Color color,
219 bool isDisabled = false,
220 }) {
221 return GestureDetector(
222 onTap: isDisabled ? null : onTap,
223 child: Container(
224 decoration: BoxDecoration(
225 color: Theme.of(context).colorScheme.surface,
226 borderRadius: BorderRadius.circular(16.r),
227 border: Border.all(
228 color: isDisabled
229 ? Theme.of(context).dividerColor
230 : color.withOpacity(0.3),
231 width: 1.5,
232 ),
233 ),
234 padding: EdgeInsets.all(16.r),
235 child: Row(
236 children: [
237 Container(
238 width: 40.r,
239 height: 40.r,
240 decoration: BoxDecoration(
241 color: isDisabled
242 ? Theme.of(context).disabledColor.withOpacity(0.1)
243 : color.withOpacity(0.1),
244 shape: BoxShape.circle,
245 ),
246 child: Icon(
247 HugeIcons.strokeRoundedLegalDocument01,
248 color: isDisabled ? Theme.of(context).disabledColor : color,
249 size: 20.r,
250 ),
251 ),
252 SizedBox(width: 12.r),
253 Expanded(
254 child: Column(
255 crossAxisAlignment: CrossAxisAlignment.start,
256 children: [
257 Text(
258 title,
259 style: Theme.of(context).textTheme.titleMedium?.copyWith(
260 fontWeight: FontWeight.bold,
261 color: isDisabled
262 ? Theme.of(context).disabledColor
263 : Theme.of(context).textTheme.titleMedium?.color,
264 ),
265 ),
266 SizedBox(height: 4.r),
267 Text(
268 description,
269 style: Theme.of(context).textTheme.bodyMedium?.copyWith(
270 color: isDisabled
271 ? Theme.of(context).hintColor.withOpacity(0.3)
272 : ColorPaletteV2.textSecondaryLight,
273 ),
274 maxLines: 2,
275 overflow: TextOverflow.ellipsis,
276 ),
277 ],
278 ),
279 ),
280 ],
281 ),
282 ),
283 );
284 }
285
286 // Helper method to parse hex color string to Color
287 Color _parseColor(String hexColor, BuildContext context) {
288 try {
289 hexColor = hexColor.replaceAll('#', '');
290 if (hexColor.length == 6) {
291 hexColor = 'FF$hexColor';
292 }
293 return Color(int.parse(hexColor, radix: 16));
294 } catch (e) {
295 return Theme.of(context).colorScheme.primary;
296 }
297 }
298}
AppLocalizations(this.locale)
String translate(String key)
static AppLocalizations of(BuildContext context)
const CourseDetailScreen({ super.key, required this.courseId, })
override State< CourseDetailScreen > createState()
Widget _buildLearningStep({ required String title, required String description, required double progress, required VoidCallback onTap, required Color color, bool isDisabled=false, })
void _loadCourseDetails()
Widget _buildCourseDetails(CourseEntity course, String pdfPath, bool hasQcm)
Color _parseColor(String hexColor, BuildContext context)
Widget _buildBody(ElearningV2State state)
const CourseDetailScreen({ super.key, required this.courseId, })
final Widget child
final EdgeInsets padding
override void initState()
final String pdfPath
class CoursesLoadingFailureState extends ElearningV2State course
const CourseDetailsLoadedState(this.course, this.pdfPath, this.qcmList)
final Color backgroundColor
final VoidCallback onTap
final Color color
Definition failures.dart:1
override Widget build(BuildContext context)
final double value
final String title
final double progress