import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:test_sa/controllers/api_routes/urls.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/models/ppm/recurrent_wo.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; import 'package:test_sa/views/widgets/buttons/app_back_button.dart'; import 'package:test_sa/modules/cm_module/cm_request_utils.dart'; import 'package:test_sa/views/widgets/total_working_time_detail_bottomsheet.dart'; class CleanerPreviewDialog extends StatelessWidget { final RecurrentWoData model; final double totalWorkingHours; final VoidCallback onConfirm; const CleanerPreviewDialog({ Key? key, required this.model, required this.totalWorkingHours, required this.onConfirm, }) : super(key: key); static void show( BuildContext context, { required RecurrentWoData model, required double totalWorkingHours, required VoidCallback onConfirm, }) { showDialog( context: context, barrierDismissible: false, builder: (dialogContext) => CleanerPreviewDialog( model: model, totalWorkingHours: totalWorkingHours, onConfirm: onConfirm, ), ); } @override Widget build(BuildContext context) { return Dialog( backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.symmetric(horizontal: 16), child: Container( constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.85), decoration: BoxDecoration( color: Theme.of(context).scaffoldBackgroundColor, borderRadius: BorderRadius.circular(14), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Header matching DefaultAppBar style Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: Theme.of(context).scaffoldBackgroundColor, borderRadius: const BorderRadius.only( topLeft: Radius.circular(14), topRight: Radius.circular(14), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: 'Confirm Details'.heading3(context).custom( fontWeight: FontWeight.w500, ), ), Icon( Icons.close, color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, size: 24, ).onPress(() => Navigator.pop(context)), ], ), ), Divider(height: 1, color: context.isDark ? AppColor.neutral60 : AppColor.neutral100), // Content Expanded( child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildPreviewSection(context, 'Task Information', [ _buildPreviewRow(context, context.translation.title, model.title ?? '-'), _buildPreviewRow(context, context.translation.taskNo, model.taskNo ?? '-'), _buildPreviewRow(context, context.translation.site, model.site?.siteName ?? '-'), _buildPreviewRow(context, context.translation.department, model.department?.name ?? '-'), _buildPreviewRow(context, context.translation.floor, model.floor?.name ?? '-'), _buildPreviewRow(context, context.translation.room, model.room?.name ?? '-'), _buildPreviewRow(context, context.translation.assignedEmployee, model.engineer?.userName ?? '-'), ]), 16.height, // Total Working Time Section - Using WorkingTimeTile design _buildPreviewSection(context, 'Total Working Time', [ _buildWorkingTimeTile(context), ]), 16.height, if (model.planRecurrentTaskCheckLists != null && model.planRecurrentTaskCheckLists!.isNotEmpty) ...[ _buildPreviewSection(context, 'Checklist', [ ...model.planRecurrentTaskCheckLists! .map( (item) => Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon( item.isChecked ? Icons.check_circle : Icons.circle_outlined, color: item.isChecked ? AppColor.primary10 : AppColor.neutral120, size: 20, ), 8.width, RichText( text: TextSpan( children: [ TextSpan( text: item.title, style: AppTextStyles.bodyText2.copyWith( color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, ), ), if (item.isMandatory) TextSpan( text: ' *', style: AppTextStyles.bodyText2.copyWith( color: Colors.red, ), ), ], ), ).expanded, ], ), // Show comment if item is checked and has a comment if (item.isChecked && item.comment.isNotEmpty == true) ...[ 8.height, Container( width: double.infinity, padding: const EdgeInsets.all(8), margin: const EdgeInsets.only(left: 28), decoration: BoxDecoration( color: context.isDark ? AppColor.neutral60.withValues(alpha: 0.3) : AppColor.neutral100.withValues(alpha: 0.5), borderRadius: BorderRadius.circular(6), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ 'Comment:'.bodyText2(context).custom( fontWeight: FontWeight.w600, color: context.isDark ? AppColor.neutral30 : AppColor.neutral120, ), 4.height, item.comment.bodyText2(context).custom( color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, ), ], ), ), ], ], ), ), ) .toList(), ]), 16.height, ], if (model.comment != null && model.comment!.isNotEmpty) ...[ _buildPreviewSection(context, context.translation.comment, [ model.comment!.bodyText(context), ]), 16.height, ], if (model.imageRoom != null && model.imageRoom!.isNotEmpty) ...[ _buildPreviewSection(context, 'Captured Image', [ _buildImagePreview(context, model.imageRoom!), ]), 16.height, ], ], ), ), ), // Footer with Confirm button Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( color: Theme.of(context).scaffoldBackgroundColor, border: Border(top: BorderSide(color: context.isDark ? AppColor.neutral60 : AppColor.neutral100)), borderRadius: BorderRadius.only( bottomLeft: Radius.circular(14), bottomRight: Radius.circular(14), ), ), child: AppFilledButton( label: context.translation.complete, buttonColor: AppColor.primary10, onPressed: () { Navigator.pop(context); onConfirm(); }, ), ), ], ), ), ); } Widget _buildPreviewSection(BuildContext context, String title, List children) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ title.heading6(context).custom(fontWeight: FontWeight.w600), 8.height, Column( crossAxisAlignment: CrossAxisAlignment.start, children: children, ).toShadowContainer(context, padding: 12), ], ); } Widget _buildPreviewRow(BuildContext context, String label, String value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( flex: 2, child: '$label:'.bodyText2(context).custom(color: AppColor.neutral120), ), 8.width, Expanded( flex: 3, child: value.bodyText2(context).custom(color: AppColor.neutral120), ), ], ), ); } Widget _buildWorkingTimeTile(BuildContext context) { // Create timer list from model data, same as in RecurrentTaskInfoWidget final timerList = model.planRecurrentTaskTimers?.map((e) { return TimerHistoryModel( id: e.id, startTime: e.startTime, endTime: e.endTime, workingHours: e.workingHours, ); }).toList() ?? []; return Container( height: 56.toScreenHeight, padding: EdgeInsets.symmetric(horizontal: 16.toScreenWidth), alignment: Alignment.centerLeft, decoration: BoxDecoration( color: AppColor.fieldBgColor(context), borderRadius: BorderRadius.circular(10), ), child: Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ Text( "Total Working Time", style: Theme.of(context).textTheme.bodySmall?.copyWith( color: context.isDark ? null : AppColor.neutral20, fontWeight: FontWeight.w500, ), ), Text( " ${CMRequestUtils.formatTotalWorkingHours(totalWorkingHours)}", style: Theme.of(context).textTheme.bodyMedium, ), ], ).expanded, 8.width, const Icon(Icons.info_outline_rounded, color: AppColor.primary10), ], ), ).onPress(() async { await showModalBottomSheet( context: context, isDismissible: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), clipBehavior: Clip.antiAliasWithSaveLayer, builder: (BuildContext context) => TotalWorkingTimeDetailBottomSheet(timerList: timerList), ); }); } Widget _buildImagePreview(BuildContext context, String imageString) { Widget imageWidget; // First check if it's base64 encoded (contains | separator) if (imageString.contains('|')) { // Format: "filename|base64data" try { final base64Data = imageString.split('|').last; imageWidget = Image.memory(base64Decode(base64Data), fit: BoxFit.cover); return Container( height: 200, width: double.infinity, decoration: BoxDecoration( color: AppColor.fieldBgColor(context), borderRadius: BorderRadius.circular(8), ), child: imageWidget, ).onPress(() { Navigator.of(context).push( MaterialPageRoute( builder: (_) => Scaffold( body: SafeArea( child: Stack( children: [ InteractiveViewer(child: imageWidget).center, const ABackButton(), ], ), ), ), ), ); }); } catch (e) { debugPrint('Error decoding base64 image: $e'); return _buildCameraIcon(context); } } // Check if it's a local file path if (_isLocalUrl(imageString)) { try { imageWidget = Image.file(File(imageString), fit: BoxFit.cover); return Container( height: 200, width: double.infinity, decoration: BoxDecoration( color: AppColor.fieldBgColor(context), borderRadius: BorderRadius.circular(8), ), child: imageWidget, ).onPress(() { Navigator.of(context).push( MaterialPageRoute( builder: (_) => Scaffold( body: SafeArea( child: Stack( children: [ InteractiveViewer(child: imageWidget).center, const ABackButton(), ], ), ), ), ), ); }); } catch (e) { debugPrint('Error loading local file: $e'); return _buildCameraIcon(context); } } // If not local and not base64, it's a server filename - use NetworkImage with loading indicator final imageUrl = URLs.getFileUrl(imageString); if (imageUrl != null) { imageWidget = Image.network( imageUrl, fit: BoxFit.cover, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) { return child; } return Center( child: CircularProgressIndicator( color: AppColor.primary10, value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null, ), ); }, errorBuilder: (context, error, stackTrace) { debugPrint('Error loading network image: $error'); return _buildCameraIcon(context); }, ); return Container( height: 200, width: double.infinity, decoration: BoxDecoration( color: AppColor.fieldBgColor(context), borderRadius: BorderRadius.circular(8), ), child: imageWidget, ).onPress(() { Navigator.of(context).push( MaterialPageRoute( builder: (_) => Scaffold( body: SafeArea( child: Stack( children: [ InteractiveViewer(child: imageWidget).center, const ABackButton(), ], ), ), ), )); }); } return _buildCameraIcon(context); } /// Build camera icon widget for fallback Widget _buildCameraIcon(BuildContext context) { return Container( height: 200, width: double.infinity, decoration: BoxDecoration( color: AppColor.fieldBgColor(context), borderRadius: BorderRadius.circular(8), ), child: Icon( Icons.camera_alt, size: 48, color: context.isDark ? AppColor.white10 : AppColor.black10, ), ); } /// Check if the URL is a local file path bool _isLocalUrl(String url) { return url.startsWith('/') || url.startsWith('file://') || (url.length > 1 && url[1] == ':'); } }