Aidra Driver 1.3.5+68
Aidra Driver - Your path to green energy
Loading...
Searching...
No Matches
qcm_screen.dart
Go to the documentation of this file.
1import 'package:flutter/material.dart';
2import 'package:flutter_bloc/flutter_bloc.dart';
3import 'package:flutter_screenutil/flutter_screenutil.dart';
4
5import '../../../../core/ui/widgets/custom_scaffold.dart';
6import '../../../../core/ui/widgets/faild_to_fetch_data_view.dart';
7import '../../domain/entities/qcm_entity.dart';
8import '../logic/cubit/elearning_v2_cubit.dart';
9
10class QcmScreen extends StatefulWidget {
11 final int courseId;
12 final double currentProgress;
13 final Function(double) onComplete;
14
15 const QcmScreen({
16 super.key,
17 required this.courseId,
18 required this.currentProgress,
19 required this.onComplete,
20 });
21
22 @override
23 State<QcmScreen> createState() => _QcmScreenState();
24}
25
26class _QcmScreenState extends State<QcmScreen> {
28 List<int?> _userAnswers = [];
29 bool _quizCompleted = false;
30 double _score = 0.0;
31
32 @override
33 void initState() {
34 super.initState();
36 }
37
39 context.read<ElearningV2Cubit>().loadCourseDetails(widget.courseId);
40 }
41
42 @override
43 Widget build(BuildContext context) {
44 return BlocBuilder<ElearningV2Cubit, ElearningV2State>(
45 builder: (context, state) {
46 return CustomScaffold(
47 title: 'Knowledge Check',
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) {
60 final qcmList = state.qcmList;
61
62 // Initialize user answers if not already done
63 if (_userAnswers.isEmpty) {
64 _userAnswers = List.filled(qcmList.length, null);
65 }
66
67 if (_quizCompleted) {
69 } else {
71 }
72 } else if (state is CourseDetailsLoadingFailureState) {
73 return FailedToFetchDataView(
74 onRetry: _loadQuestions,
75 );
76 } else {
77 return const SizedBox();
78 }
79 }
80
81 Widget _buildQuizQuestion(List<QcmEntity> questions) {
82 if (questions.isEmpty) {
83 return Center(
84 child: Text(
85 'No questions available for this course',
86 style: Theme.of(context).textTheme.bodyMedium,
87 ),
88 );
89 }
90
91 final currentQuestion = questions[_currentQuestionIndex];
92
93 return Padding(
94 padding: EdgeInsets.all(16.r),
96 crossAxisAlignment: CrossAxisAlignment.start,
97 children: [
98 // Progress indicator
99 LinearProgressIndicator(
100 value: (_currentQuestionIndex + 1) / questions.length,
101 backgroundColor: Colors.grey.withValues(alpha: 0.2),
102 valueColor: AlwaysStoppedAnimation<Color>(
103 Theme.of(context).primaryColor,
104 ),
105 borderRadius: BorderRadius.circular(4.r),
106 minHeight: 3.r,
107 ),
108 SizedBox(height: 8.r),
109 Row(
110 mainAxisAlignment: MainAxisAlignment.spaceBetween,
111 children: [
112 Text(
113 'Question ${_currentQuestionIndex + 1} of ${questions.length}',
114 style: Theme.of(context).textTheme.bodySmall?.copyWith(
115 color: Theme.of(context).hintColor,
116 ),
117 ),
118 Text(
119 '${((_currentQuestionIndex + 1) / questions.length * 100).toInt()}%',
120 style: Theme.of(context).textTheme.displaySmall?.copyWith(
121 color: Theme.of(context).colorScheme.primary,
122 ),
123 ),
124 ],
125 ),
126 SizedBox(height: 24.r),
127
128 // Question
129 Text(
130 currentQuestion.question ?? 'No question text',
131 style: Theme.of(context).textTheme.displaySmall,
132 ),
133 SizedBox(height: 24.r),
134
135 // Options
136 Expanded(
137 child: ListView.separated(
138 itemCount: currentQuestion.options?.length ?? 0,
139 separatorBuilder: (context, index) => SizedBox(height: 12.r),
140 itemBuilder: (context, index) {
142
143 return GestureDetector(
144 onTap: () {
145 setState(() {
147 });
148 },
149 child: Container(
150 padding: EdgeInsets.all(16.r),
151 decoration: BoxDecoration(
153 ? Theme.of(context).primaryColor.withValues(alpha: 0.1)
154 : Colors.transparent,
155 borderRadius: BorderRadius.circular(10.r),
156 ),
157 child: Row(
158 children: [
159 Container(
160 width: 24.r,
161 height: 24.r,
162 decoration: BoxDecoration(
163 shape: BoxShape.circle,
165 ? Theme.of(context).primaryColor
166 : Colors.transparent,
167 border: Border.all(
169 ? Theme.of(context).primaryColor
170 : Theme.of(context).hintColor,
171 width: 1,
172 ),
173 ),
175 ? Icon(
176 Icons.check,
177 size: 16.r,
178 color:
179 Theme.of(context).colorScheme.onPrimary,
180 )
181 : null,
182 ),
183 SizedBox(width: 12.r),
184 Expanded(
185 child: Text(
186 currentQuestion.options?[index] ?? '',
187 style: Theme.of(context).textTheme.bodyMedium,
188 ),
189 ),
190 ],
191 ),
192 ),
193 );
194 },
195 ),
196 ),
197
198 // Navigation buttons
199 SizedBox(height: 16.r),
200 Row(
201 mainAxisAlignment: MainAxisAlignment.spaceBetween,
202 children: [
203 if (_currentQuestionIndex > 0)
204 ElevatedButton(
205 onPressed: () {
206 setState(() {
208 });
209 },
210 style: ElevatedButton.styleFrom(
211 padding: EdgeInsets.symmetric(horizontal: 20, vertical: 15),
212 shape: RoundedRectangleBorder(
213 borderRadius: BorderRadius.circular(200),
214 ),
215 ),
216 child: Text('Previous'),
217 )
218 else
219 SizedBox(width: 100.r),
220 ElevatedButton(
221 style: ElevatedButton.styleFrom(
222 padding: EdgeInsets.symmetric(horizontal: 20, vertical: 15),
223 shape: RoundedRectangleBorder(
224 borderRadius: BorderRadius.circular(200),
225 ),
226 ),
228 ? () {
229 if (_currentQuestionIndex < questions.length - 1) {
230 setState(() {
231 _currentQuestionIndex++;
232 });
233 } else {
234 // Calculate score
235 int correctAnswers = 0;
236 for (int i = 0; i < questions.length; i++) {
237 if (_userAnswers[i] ==
238 questions[i].correctAnswerIndex) {
239 correctAnswers++;
240 }
241 }
242
243 _score = correctAnswers / questions.length;
244
245 setState(() {
246 _quizCompleted = true;
247 });
248 }
249 }
250 : null,
251 child: Text(
252 _currentQuestionIndex < questions.length - 1
253 ? 'Next'
254 : 'Finish',
255 ),
256 ),
257 ],
258 ),
259 ],
260 ),
261 );
262 }
263
264 Widget _buildQuizResults(List<QcmEntity> questions) {
265 final correctAnswers = _userAnswers
266 .asMap()
267 .entries
268 .where(
269 (entry) => entry.value == questions[entry.key].correctAnswerIndex,
270 )
271 .length;
272
273 return Padding(
274 padding: EdgeInsets.all(16.r),
275 child: Column(
276 crossAxisAlignment: CrossAxisAlignment.center,
277 children: [
278 SizedBox(height: 24.r),
279
280 // Score circle
281 Container(
282 width: 150.r,
283 height: 150.r,
284 decoration: BoxDecoration(
285 shape: BoxShape.circle,
286 color: Theme.of(context).primaryColor.withValues(alpha: 0.1),
287 ),
288 child: Center(
289 child: Column(
290 mainAxisAlignment: MainAxisAlignment.center,
291 children: [
292 Text(
293 '${(_score * 100).toInt()}%',
294 style: Theme.of(context).textTheme.displayLarge?.copyWith(
295 color: Theme.of(context).primaryColor,
296 ),
297 ),
298 Text(
299 'Score',
300 style: Theme.of(context).textTheme.bodyLarge,
301 ),
302 ],
303 ),
304 ),
305 ),
306 SizedBox(height: 30.r),
307
308 // Results summary
309 Center(
310 child: Text(
311 'Quiz Completed!',
312 style: Theme.of(context).textTheme.displayMedium,
313 ),
314 ),
315 SizedBox(height: 15.r),
316 Text(
317 'You answered $correctAnswers out of ${questions.length} questions correctly.',
318 style: Theme.of(context).textTheme.bodyMedium?.copyWith(
319 color: Theme.of(context).hintColor,
320 ),
321 textAlign: TextAlign.center,
322 ),
323 SizedBox(height: 32.r),
324
325 // Review answers button
326 ElevatedButton.icon(
327 onPressed: () {
328 setState(() {
329 _quizCompleted = false;
330 _currentQuestionIndex = 0;
331 });
332 },
333 style: ElevatedButton.styleFrom(
334 padding: EdgeInsets.symmetric(
335 horizontal: 30,
336 vertical: 20,
337 ),
338 shape: RoundedRectangleBorder(
339 borderRadius: BorderRadius.circular(200),
340 ),
341 backgroundColor: Theme.of(context).primaryColor.withValues(alpha: 0.11),
342 foregroundColor: Theme.of(context).primaryColor.withValues(alpha: 0.8),
343 ),
344 icon: Icon(Icons.refresh),
345 label: Text('Review Answers'),
346 ),
347 SizedBox(height: 16.r),
348
349 // Complete button
350 ElevatedButton.icon(
351 onPressed: () {
352 widget.onComplete(_score);
353 Navigator.pop(context);
354 },
355 style: ElevatedButton.styleFrom(
356 padding: EdgeInsets.symmetric(
357 horizontal: 30,
358 vertical: 20,
359 ),
360 // foregroundColor: ColorPalette.lightGreen,
361 backgroundColor: Theme.of(context).primaryColor,
362 shape: RoundedRectangleBorder(
363 borderRadius: BorderRadius.circular(200),
364 ),
365 ),
366 icon: Icon(Icons.check_circle),
367 label: Text('Complete'),
368 ),
369 ],
370 ),
371 );
372 }
373}
override void initState()
class App extends StatefulWidget build(BuildContext context)
Definition app.dart:31
bool isLoading
override State< QcmScreen > createState()
final Function(double) onComplete
final double currentProgress
const QcmScreen({ super.key, required this.courseId, required this.currentProgress, required this.onComplete, })
final Widget child
final EdgeInsets padding
Widget _buildBody()
final List< QcmEntity > qcmList
const CourseDetailsLoadedState(this.course, this.pdfPath, this.qcmList)
final Color color
Definition failures.dart:1
class GetPdfPathUseCase implements UseCase< String, PdfParams > courseId
final VoidCallback onPressed
bool _quizCompleted
void _loadQuestions()
List< int?> _userAnswers
double _score
class QcmScreen extends StatefulWidget _currentQuestionIndex
Widget _buildQuizResults(List< QcmEntity > questions)
Widget _buildQuizQuestion(List< QcmEntity > questions)
final VoidCallback onTap
final String title
class UnloadingCollectionItem extends StatefulWidget isSelected
final String label
style Text( '${ 'scheduling.reference'.tr()}:${collection.internalCode}', style:Theme.of(context).textTheme.bodySmall,)
style SizedBox(height:2.h)
style Column(crossAxisAlignment:CrossAxisAlignment.end, children:[Container(padding:EdgeInsets.symmetric(horizontal:8.w, vertical:4.h), decoration:BoxDecoration(color:ColorPalette.tiffanyBlue.withValues(alpha:0.1), borderRadius:BorderRadius.circular(12),), child:Text(collection.type ?? '', style:Theme.of(context).textTheme.bodySmall?.copyWith(color:ColorPalette.tiffanyBlue, fontWeight:FontWeight.bold,),),),],)