Aidra Driver 1.3.5+68
Aidra Driver - Your path to green energy
Loading...
Searching...
No Matches
settings_screen.dart
Go to the documentation of this file.
1import 'package:easy_localization/easy_localization.dart';
2import 'package:flutter/material.dart';
3import 'package:flutter_bloc/flutter_bloc.dart';
4
5import '../../core/api/api_client.dart';
6import '../../core/services/service_locator.dart';
7import '../../core/services/user_info_service.dart';
8import '../../core/ui/theme/color_palette.dart';
9import '../../core/ui/widgets/custom_scaffold.dart';
10import '../../core/ui/widgets/logo_header.dart';
11import '../auth/presentation/bloc/authentication_bloc/authentication_bloc.dart';
12import 'models/document_model.dart';
13import 'screens/pdf_viewer_screen.dart';
14import 'services/document_service.dart';
15
16class ProfileSettingsScreen extends StatefulWidget {
17 const ProfileSettingsScreen({super.key});
18
19 @override
20 State<ProfileSettingsScreen> createState() => _ProfileSettingsScreenState();
21}
22
23class _ProfileSettingsScreenState extends State<ProfileSettingsScreen> {
26 List<DocumentModel> _documents = [];
27 bool _isLoading = true;
28 double _engagement = 0;
29
30 @override
31 void initState() {
32 super.initState();
35 }
36
37 Future<void> _fetchEngagement() async {
38 try {
39 final authState = context.read<AuthenticationBloc>().state;
40 if (authState is AuthenticatedState) {
41 final response = await _apiClient.get(
42 path: 'dashboardDriver?userId=${authState.session.uid}&period=month',
43 );
44
45 if (response != null && response['doneVolume'] != null && mounted) {
46 setState(() {
47 _engagement = (response['doneVolume'] as num).toDouble();
48 });
49 }
50 }
51 } catch (e) {
52 print('Error loading engagement: $e');
53 }
54 }
55
56 Future<void> _fetchDocuments() async {
57 if (!mounted) return;
58
59 setState(() {
60 _isLoading = true;
61 });
62
63 try {
64 final authState = context.read<AuthenticationBloc>().state;
65 if (authState is AuthenticatedState) {
66 final documents = await _documentService.getPartnerDocuments(partnerId: int.tryParse(authState.session.partnerId!) ?? 0);
67 if (mounted) {
68 setState(() {
70 _isLoading = false;
71 });
72 }
73 }
74 } catch (e) {
75 if (mounted) {
76 setState(() {
77 _isLoading = false;
78 });
79 }
80 print('Error loading documents: $e');
81 }
82 }
83
84 @override
85 Widget build(BuildContext context) {
86 return CustomScaffold(
87 backgroundColor: ColorPalette.white,
88 isLeadingVisible: false,
89 body: SingleChildScrollView(
90 physics: const BouncingScrollPhysics(),
91 padding: const EdgeInsets.only(right: 20, left: 20, bottom: 32),
93 crossAxisAlignment: CrossAxisAlignment.start,
94 children: [
95 LogoHeader(title: 'settings.title'.tr()),
96 const SizedBox(height: 24),
98 const SizedBox(height: 40),
100 const SizedBox(height: 40),
102 ],
103 ),
104 ),
105 );
106 }
107
109 return Container(
110 padding: const EdgeInsets.all(20),
111 decoration: BoxDecoration(
112 gradient: LinearGradient(
113 colors: [
114 ColorPalette.lightGreen.withValues(alpha: 0.1),
115 ColorPalette.tiffanyBlue.withValues(alpha: 0.05),
116 ],
117 begin: Alignment.topLeft,
118 end: Alignment.bottomRight,
119 ),
120 borderRadius: BorderRadius.circular(16),
121 boxShadow: [
122 BoxShadow(
123 color: Colors.black.withValues(alpha: 0.03),
124 blurRadius: 10,
125 offset: const Offset(0, 4),
126 ),
127 ],
128 ),
129 child: Column(
130 children: [
131 Row(
132 crossAxisAlignment: CrossAxisAlignment.start,
133 children: [
134 // Profile Avatar with animation
135 Stack(
136 children: [
137 Container(
138 width: 80,
139 height: 80,
140 decoration: BoxDecoration(
141 shape: BoxShape.circle,
142 gradient: const LinearGradient(
144 begin: Alignment.topLeft,
145 end: Alignment.bottomRight,
146 ),
147 boxShadow: [
148 BoxShadow(
149 color: ColorPalette.lightGreen.withValues(alpha: 0.3),
150 blurRadius: 10,
151 offset: const Offset(0, 4),
152 ),
153 ],
154 ),
155 padding: const EdgeInsets.all(2),
156 child: const CircleAvatar(
157 backgroundColor: ColorPalette.white,
158 child: Icon(
159 Icons.person_outline,
160 size: 40,
162 ),
163 ),
164 ),
165 Positioned(
166 right: 0,
167 top: 0,
168 child: Container(
169 width: 16,
170 height: 16,
171 decoration: BoxDecoration(
173 shape: BoxShape.circle,
174 border: Border.all(
176 width: 2,
177 ),
178 boxShadow: [
179 BoxShadow(
180 color: Colors.black.withValues(alpha: 0.1),
181 blurRadius: 4,
182 offset: const Offset(0, 2),
183 ),
184 ],
185 ),
186 ),
187 ),
188 ],
189 ),
190 const SizedBox(width: 20),
191 // Name and Rating
192 Expanded(
193 child: Column(
194 crossAxisAlignment: CrossAxisAlignment.start,
195 children: [
196 Text(
198 style: const TextStyle(
199 fontSize: 18,
200 fontWeight: FontWeight.w700,
202 height: 1.2,
203 letterSpacing: -0.5,
204 ),
205 ),
206 const SizedBox(height: 4),
207 Text(
209 style: const TextStyle(
210 fontSize: 16,
212 height: 1.2,
213 ),
214 ),
215 const SizedBox(height: 12),
216 // Rating stars in a row
217 Row(
218 children: [
219 ...List.generate(
220 4,
221 (index) => const Padding(
222 padding: EdgeInsets.only(right: 2),
223 child: Icon(
224 Icons.star,
226 size: 18,
227 ),
228 ),
229 ),
230 const Padding(
231 padding: EdgeInsets.only(right: 2),
232 child: Icon(
233 Icons.star_half,
235 size: 18,
236 ),
237 ),
238 const SizedBox(width: 4),
239 const Text(
240 '4.5',
241 style: TextStyle(
242 fontWeight: FontWeight.bold,
244 ),
245 ),
246 ],
247 ),
248 ],
249 ),
250 ),
251 ],
252 ),
253 const SizedBox(height: 20),
254 // Logout button
255 ElevatedButton.icon(
256 onPressed: () => _handleLogout(context),
257 icon: const Icon(Icons.logout, color: Colors.white),
258 label: Text(
259 'settings.sign_out'.tr(),
260 style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
261 ),
262 style: ElevatedButton.styleFrom(
263 backgroundColor: ColorPalette.lightGreen,
264 minimumSize: const Size(double.infinity, 45),
265 shape: RoundedRectangleBorder(
266 borderRadius: BorderRadius.circular(12),
267 ),
268 elevation: 2,
269 ),
270 ),
271 ],
272 ),
273 );
274 }
275}
276
277Widget _buildMetricsRow(double engagement) {
278 return Container(
279 padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 16),
280 decoration: BoxDecoration(
281 color: Colors.white,
282 borderRadius: BorderRadius.circular(16),
283 boxShadow: [
284 BoxShadow(
285 color: Colors.black.withValues(alpha: 0.05),
286 blurRadius: 10,
287 offset: const Offset(0, 4),
288 ),
289 ],
290 ),
291 child: Row(
292 mainAxisAlignment: MainAxisAlignment.spaceBetween,
293 children: [
295 icon: Icons.track_changes,
296 title: 'settings.target'.tr(),
297 value: '0 KG',
299 ),
302 icon: Icons.attach_money,
303 title: 'settings.balance'.tr(),
304 value: 'SAR 0',
306 ),
309 icon: Icons.people_outline,
310 title: 'settings.engagement'.tr(),
311 value: '${engagement.toStringAsFixed(0)} KG',
313 ),
314 ],
315 ),
316 );
317}
318
320 return Container(
321 height: 40,
322 width: 1,
323 color: Colors.grey.withValues(alpha: 0.2),
324 );
325}
326
328 required IconData icon,
329 required String title,
330 required String value,
331 required Color color,
332}) {
333 return SizedBox(
334 width: 100,
335 child: Column(
336 children: [
337 Container(
338 padding: const EdgeInsets.all(12),
339 decoration: BoxDecoration(
340 color: color.withValues(alpha: 0.1),
341 shape: BoxShape.circle,
342 boxShadow: [
343 BoxShadow(
344 color: color.withValues(alpha: 0.1),
345 blurRadius: 8,
346 offset: const Offset(0, 3),
347 ),
348 ],
349 ),
350 child: Icon(icon, color: color, size: 24),
351 ),
352 const SizedBox(height: 10),
353 Text(
354 title,
355 style: const TextStyle(
357 fontSize: 14,
358 ),
359 ),
360 const SizedBox(height: 4),
361 Text(
362 value,
363 style: const TextStyle(
365 fontSize: 16,
366 fontWeight: FontWeight.bold,
367 ),
368 ),
369 ],
370 ),
371 );
372}
373
374Widget _buildDocumentsSection(BuildContext context, List<DocumentModel> documents, bool isLoading) {
375 return Column(
376 crossAxisAlignment: CrossAxisAlignment.start,
377 children: [
378 // Modern header with subtle decoration
379 Container(
380 margin: const EdgeInsets.only(bottom: 20),
381 padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
382 decoration: BoxDecoration(
384 borderRadius: BorderRadius.circular(12),
385 border: Border.all(
386 color: ColorPalette.lightGreen.withValues(alpha: 0.3),
387 width: 1,
388 ),
389 ),
390 child: Row(
391 children: [
392 Container(
393 padding: const EdgeInsets.all(8),
394 decoration: BoxDecoration(
395 color: ColorPalette.lightGreen.withValues(alpha: 0.1),
396 shape: BoxShape.circle,
397 ),
398 child: const Icon(
399 Icons.description_outlined,
401 size: 20,
402 ),
403 ),
404 const SizedBox(width: 12),
405 Text(
406 'settings.documents'.tr(),
407 style: TextStyle(
408 fontSize: 18,
409 fontWeight: FontWeight.bold,
411 ),
412 ),
413 ],
414 ),
415 ),
416
417 // Show loading indicator or documents
418 isLoading
419 ? const Center(
420 child: CircularProgressIndicator(
422 ),
423 )
424 : documents.isEmpty
425 ? const Center(
426 child: Text(
427 'No documents available',
428 style: TextStyle(
430 fontSize: 16,
431 ),
432 )
433 )
434 : GridView.builder(
435 shrinkWrap: true,
436 physics: const NeverScrollableScrollPhysics(),
437 gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
438 crossAxisCount: 2,
439 childAspectRatio: 0.85,
440 crossAxisSpacing: 16,
441 mainAxisSpacing: 20,
442 ),
443 itemCount: documents.length,
444 itemBuilder: (context, index) {
445 final document = documents[index];
446
447 // Determine icon and color based on document name
448 IconData icon;
449 Color color;
450
451 if (document.name.toLowerCase().contains('id') ||
452 document.name.toLowerCase().contains('personal')) {
453 icon = Icons.badge_outlined;
454 color = ColorPalette.blue;
455 } else if (document.name.toLowerCase().contains('licence') ||
456 document.name.toLowerCase().contains('driver')) {
457 icon = Icons.drive_eta_outlined;
458 color = ColorPalette.tiffanyBlue;
459 } else if (document.name.toLowerCase().contains('waybill') ||
460 document.name.toLowerCase().contains('naql')) {
461 icon = Icons.receipt_long_outlined;
462 color = ColorPalette.lightGreen;
463 } else if (document.name.toLowerCase().contains('company') ||
464 document.name.toLowerCase().contains('authorization')) {
465 icon = Icons.business_outlined;
466 color = ColorPalette.orange;
467 } else {
468 icon = Icons.insert_drive_file_outlined;
469 color = ColorPalette.lightGreen;
470 }
471
472 return _buildDocumentItem(
473 title: document.name,
474 color: color,
475 icon: icon,
476 onTap: () {
477 // Handle document tap - open PDF viewer
478 _openDocument(document, context);
479 },
480 );
481 },
482 ),
483 ],
484 );
485}
486
487void _openDocument(DocumentModel document, BuildContext context) {
488 Navigator.push(
489 context,
490 MaterialPageRoute(
491 builder: (context) => PDFViewerScreen(document: document),
492 ),
493 );
494}
495
497 required String title,
498 required Color color,
499 required VoidCallback onTap,
500 IconData icon = Icons.insert_drive_file_outlined,
501}) {
502 return GestureDetector(
503 onTap: onTap,
504 child: Container(
505 decoration: BoxDecoration(
506 color: Colors.white,
507 borderRadius: BorderRadius.circular(16),
508 boxShadow: [
509 BoxShadow(
510 color: Colors.black.withValues(alpha: 0.05),
511 blurRadius: 10,
512 offset: const Offset(0, 4),
513 ),
514 ],
515 border: Border.all(
516 color: color.withValues(alpha: 0.3),
517 width: 1.5,
518 ),
519 ),
520 child: Column(
521 mainAxisAlignment: MainAxisAlignment.center,
522 children: [
523 Container(
524 padding: const EdgeInsets.all(16),
525 decoration: BoxDecoration(
526 color: color.withValues(alpha: 0.1),
527 shape: BoxShape.circle,
528 ),
529 child: Icon(
530 icon,
531 color: color,
532 size: 32,
533 ),
534 ),
535 const SizedBox(height: 12),
536 Padding(
537 padding: const EdgeInsets.symmetric(horizontal: 8),
538 child: Text(
539 title,
540 style: const TextStyle(
542 fontSize: 14,
543 fontWeight: FontWeight.w600,
544 ),
545 textAlign: TextAlign.center,
546 maxLines: 2,
547 overflow: TextOverflow.ellipsis,
548 ),
549 ),
550 const SizedBox(height: 4),
551 Container(
552 padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
553 decoration: BoxDecoration(
554 color: color.withValues(alpha: 0.1),
555 borderRadius: BorderRadius.circular(12),
556 ),
557 child: Text(
558 'View',
559 style: TextStyle(
560 color: color,
561 fontSize: 12,
562 fontWeight: FontWeight.w500,
563 ),
564 ),
565 ),
566 ],
567 ),
568 ),
569 );
570}
571
572Future<void> _handleLogout(BuildContext context) async {
573 final bool? confirm = await showDialog<bool>(
574 context: context,
575 builder: (BuildContext context) {
576 return AlertDialog(
577 title: Text('settings.sign_out'.tr()),
578 content: Text('settings.confirm_logout'.tr()),
579 actions: [
580 TextButton(
581 onPressed: () => Navigator.of(context).pop(false),
582 child: Text('common.cancel'.tr()),
583 ),
584 TextButton(
585 onPressed: () => Navigator.of(context).pop(true),
586 child: Text('settings.sign_out'.tr(), style: TextStyle(color: ColorPalette.lightGreen)),
587 ),
588 ],
589 );
590 },
591 );
592
593 // If user confirmed logout
594 if (confirm == true) {
595 if (context.mounted) context.read<AuthenticationBloc>().add(SignOutEvent());
596 }
597 }
override void initState()
class App extends StatefulWidget build(BuildContext context)
Definition app.dart:31
bool isLoading
static const darkGrey
static const lightGreen
static const black
static const white
static const orange
static const tiffanyBlue
static const grey
override State< ProfileSettingsScreen > createState()
const ProfileSettingsScreen({super.key})
static final UserInfoService instance
final String name
Definition user_info.dart:4
final String email
Definition user_info.dart:2
final Partner partnerId
final Widget child
final EdgeInsets padding
final String path
class Partner String
class DocumentsScreen extends StatefulWidget documents
bool _isLoading
final Color color
Definition failures.dart:1
final VoidCallback onPressed
final sl
Widget _buildMetricsRow(double engagement)
Future< void > _fetchDocuments() async
Widget _buildMetricItem({ required IconData icon, required String title, required String value, required Color color, })
Widget _buildProfileHeader()
Future< void > _handleLogout(BuildContext context) async
Future< void > _fetchEngagement() async
Widget _buildDocumentItem({ required String title, required Color color, required VoidCallback onTap, IconData icon=Icons.insert_drive_file_outlined, })
void _openDocument(DocumentModel document, BuildContext context)
double _engagement
class ProfileSettingsScreen extends StatefulWidget _documentService
final ApiClient _apiClient
Widget _buildDocumentsSection(BuildContext context, List< DocumentModel > documents, bool isLoading)
List< DocumentModel > _documents
Widget _buildDivider()
final VoidCallback onTap
final String title
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,),),),],)