import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:marco/controller/task_planing/report_task_controller.dart'; import 'package:marco/helpers/utils/mixins/ui_mixin.dart'; import 'package:marco/helpers/widgets/my_button.dart'; import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/my_text_style.dart'; import 'package:marco/helpers/widgets/avatar.dart'; import 'package:marco/helpers/widgets/my_team_model_sheet.dart'; import 'package:intl/intl.dart'; import 'package:marco/helpers/widgets/image_viewer_dialog.dart'; import 'dart:io'; import 'package:marco/model/dailyTaskPlaning/create_task_botom_sheet.dart'; class CommentTaskBottomSheet extends StatefulWidget { final Map taskData; final VoidCallback? onCommentSuccess; final String taskDataId; final String workAreaId; final String activityId; const CommentTaskBottomSheet({ super.key, required this.taskData, this.onCommentSuccess, required this.taskDataId, required this.workAreaId, required this.activityId, }); @override State createState() => _CommentTaskBottomSheetState(); } class _Member { final String firstName; _Member(this.firstName); } class _CommentTaskBottomSheetState extends State with UIMixin { late ReportTaskController controller; final ScrollController _scrollController = ScrollController(); @override void initState() { super.initState(); controller = Get.put(ReportTaskController(), tag: widget.taskData['taskId'] ?? UniqueKey().toString()); final data = widget.taskData; controller.basicValidator.getController('assigned_date')?.text = data['assignedOn'] ?? ''; controller.basicValidator.getController('assigned_by')?.text = data['assignedBy'] ?? ''; controller.basicValidator.getController('work_area')?.text = data['location'] ?? ''; controller.basicValidator.getController('activity')?.text = data['activity'] ?? ''; controller.basicValidator.getController('planned_work')?.text = data['plannedWork'] ?? ''; controller.basicValidator.getController('completed_work')?.text = data['completedWork'] ?? ''; controller.basicValidator.getController('team_members')?.text = (data['teamMembers'] as List).join(', '); controller.basicValidator.getController('assigned')?.text = data['assigned'] ?? ''; controller.basicValidator.getController('task_id')?.text = data['taskId'] ?? ''; controller.basicValidator.getController('comment')?.clear(); controller.selectedImages.clear(); WidgetsBinding.instance.addPostFrameCallback((_) { if (_scrollController.hasClients) { _scrollController.jumpTo(_scrollController.position.maxScrollExtent); } }); } String timeAgo(String dateString) { try { DateTime date = DateTime.parse(dateString + "Z").toLocal(); final now = DateTime.now(); final difference = now.difference(date); if (difference.inDays > 8) { return DateFormat('dd-MM-yyyy').format(date); } else if (difference.inDays >= 1) { return '${difference.inDays} day${difference.inDays > 1 ? 's' : ''} ago'; } else if (difference.inHours >= 1) { return '${difference.inHours} hr${difference.inHours > 1 ? 's' : ''} ago'; } else if (difference.inMinutes >= 1) { return '${difference.inMinutes} min${difference.inMinutes > 1 ? 's' : ''} ago'; } else { return 'just now'; } } catch (e) { print('Error parsing date: $e'); return ''; } } @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), ), child: SingleChildScrollView( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom + 24, left: 24, right: 24, top: 12, ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Drag handle Container( width: 40, height: 4, margin: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( color: Colors.grey.shade400, borderRadius: BorderRadius.circular(2), ), ), GetBuilder( tag: widget.taskData['taskId'] ?? '', builder: (controller) { return Form( key: controller.basicValidator.formKey, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 4.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ MySpacing.height(12), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ MyText.titleMedium( "Comment Task", fontWeight: 600, fontSize: 18, ), ], ), const SizedBox(height: 12), // Second row: Right-aligned "+ Create Task" button Row( mainAxisAlignment: MainAxisAlignment.end, children: [ InkWell( onTap: () { showCreateTaskBottomSheet( workArea: widget.taskData['location'] ?? '', activity: widget.taskData['activity'] ?? '', completedWork: widget.taskData['completedWork'] ?? '', unit: widget.taskData['unit'] ?? '', onCategoryChanged: (category) { debugPrint( "Category changed to: $category"); }, parentTaskId: widget.taskDataId, plannedTask: int.tryParse( widget.taskData['plannedWork'] ?? '0') ?? 0, activityId: widget.activityId, workAreaId: widget.workAreaId, onSubmit: () { Navigator.of(context).pop(); }, ); }, borderRadius: BorderRadius.circular(16), child: Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6), decoration: BoxDecoration( color: Colors.blueAccent.withOpacity(0.1), borderRadius: BorderRadius.circular(16), ), child: MyText.bodySmall( "+ Create Task", fontWeight: 600, color: Colors.blueAccent, ), ), ), ], ), ], ), buildRow( "Assigned By", controller.basicValidator .getController('assigned_by') ?.text .trim(), icon: Icons.person_outline, ), buildRow( "Work Area", controller.basicValidator .getController('work_area') ?.text .trim(), icon: Icons.place_outlined, ), buildRow( "Activity", controller.basicValidator .getController('activity') ?.text .trim(), icon: Icons.assignment_outlined, ), buildRow( "Planned Work", controller.basicValidator .getController('planned_work') ?.text .trim(), icon: Icons.schedule_outlined, ), buildRow( "Completed Work", controller.basicValidator .getController('completed_work') ?.text .trim(), icon: Icons.done_all_outlined, ), buildTeamMembers(), if ((widget.taskData['reportedPreSignedUrls'] as List?) ?.isNotEmpty == true) buildReportedImagesSection( imageUrls: List.from( widget.taskData['reportedPreSignedUrls'] ?? []), context: context, ), Row( children: [ Icon(Icons.comment_outlined, size: 18, color: Colors.grey[700]), MySpacing.width(8), MyText.titleSmall( "Comment:", fontWeight: 600, ), ], ), MySpacing.height(8), TextFormField( validator: controller.basicValidator .getValidation('comment'), controller: controller.basicValidator .getController('comment'), keyboardType: TextInputType.text, decoration: InputDecoration( hintText: "eg: Work done successfully", hintStyle: MyTextStyle.bodySmall(xMuted: true), border: outlineInputBorder, enabledBorder: outlineInputBorder, focusedBorder: focusedInputBorder, contentPadding: MySpacing.all(16), isCollapsed: true, floatingLabelBehavior: FloatingLabelBehavior.never, ), ), MySpacing.height(16), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(Icons.camera_alt_outlined, size: 18, color: Colors.grey[700]), MySpacing.width(8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MyText.titleSmall("Attach Photos:", fontWeight: 600), MySpacing.height(12), ], ), ), ], ), Obx(() { final images = controller.selectedImages; return buildImagePickerSection( images: images, onCameraTap: () => controller.pickImages(fromCamera: true), onUploadTap: () => controller.pickImages(fromCamera: false), onRemoveImage: (index) => controller.removeImageAt(index), onPreviewImage: (index) { showDialog( context: context, builder: (_) => ImageViewerDialog( imageSources: images, initialIndex: index, ), ); }, ); }), MySpacing.height(24), buildCommentActionButtons( onCancel: () => Navigator.of(context).pop(), onSubmit: () async { if (controller.basicValidator.validateForm()) { await controller.commentTask( projectId: controller.basicValidator .getController('task_id') ?.text ?? '', comment: controller.basicValidator .getController('comment') ?.text ?? '', images: controller.selectedImages, ); if (widget.onCommentSuccess != null) { widget.onCommentSuccess!(); } } }, isLoading: controller.isLoading, ), MySpacing.height(10), if ((widget.taskData['taskComments'] as List?) ?.isNotEmpty == true) ...[ Row( children: [ MySpacing.width(10), Icon(Icons.chat_bubble_outline, size: 18, color: Colors.grey[700]), MySpacing.width(8), MyText.titleSmall( "Comments", fontWeight: 600, ), ], ), MySpacing.height(12), Builder( builder: (context) { final comments = List>.from( widget.taskData['taskComments'] as List, ); return buildCommentList(comments, context); }, ) ], ], ), ), ); }, ), ], ), ), ); } Widget buildReportedImagesSection({ required List imageUrls, required BuildContext context, String title = "Reported Images", }) { if (imageUrls.isEmpty) return const SizedBox(); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MySpacing.height(8), Padding( padding: const EdgeInsets.symmetric(horizontal: 0.0), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Icon(Icons.image_outlined, size: 18, color: Colors.grey[700]), MySpacing.width(8), MyText.titleSmall( title, fontWeight: 600, ), ], ), ), MySpacing.height(8), Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: SizedBox( height: 70, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: imageUrls.length, separatorBuilder: (_, __) => const SizedBox(width: 12), itemBuilder: (context, index) { final url = imageUrls[index]; return GestureDetector( onTap: () { showDialog( context: context, barrierColor: Colors.black54, builder: (_) => ImageViewerDialog( imageSources: imageUrls, initialIndex: index, ), ); }, child: ClipRRect( borderRadius: BorderRadius.circular(12), child: Image.network( url, width: 70, height: 70, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => Container( width: 70, height: 70, color: Colors.grey.shade200, child: Icon(Icons.broken_image, color: Colors.grey[600]), ), ), ), ); }, ), ), ), MySpacing.height(16), ], ); } Widget buildTeamMembers() { final teamMembersText = controller.basicValidator.getController('team_members')?.text ?? ''; final members = teamMembersText .split(',') .map((e) => e.trim()) .where((e) => e.isNotEmpty) .toList(); return Padding( padding: const EdgeInsets.only(bottom: 16), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ MyText.titleSmall( "Team Members:", fontWeight: 600, ), MySpacing.width(12), GestureDetector( onTap: () { TeamBottomSheet.show( context: context, teamMembers: members.map((name) => _Member(name)).toList(), ); }, child: SizedBox( height: 32, width: 100, child: Stack( children: [ for (int i = 0; i < members.length.clamp(0, 3); i++) Positioned( left: i * 24.0, child: Tooltip( message: members[i], child: Avatar( firstName: members[i], lastName: '', size: 32, ), ), ), if (members.length > 3) Positioned( left: 2 * 24.0, child: CircleAvatar( radius: 16, backgroundColor: Colors.grey.shade300, child: MyText.bodyMedium( '+${members.length - 3}', style: const TextStyle( fontSize: 12, color: Colors.black87), ), ), ), ], ), ), ), ], ), ); } Widget buildCommentActionButtons({ required VoidCallback onCancel, required Future Function() onSubmit, required RxBool isLoading, double? buttonHeight, }) { return Row( children: [ Expanded( child: OutlinedButton.icon( onPressed: onCancel, icon: const Icon(Icons.close, color: Colors.red, size: 18), label: MyText.bodyMedium("Cancel", color: Colors.red, fontWeight: 600), style: OutlinedButton.styleFrom( side: const BorderSide(color: Colors.red), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14), ), ), ), const SizedBox(width: 16), Expanded( child: Obx(() { return ElevatedButton.icon( onPressed: isLoading.value ? null : () => onSubmit(), icon: isLoading.value ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Colors.white), ), ) : const Icon(Icons.check_circle_outline, color: Colors.white, size: 18), label: isLoading.value ? const SizedBox() : MyText.bodyMedium("Comment", color: Colors.white, fontWeight: 600), style: ElevatedButton.styleFrom( backgroundColor: Colors.indigo, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14), ), ); }), ), ], ); } Widget buildRow(String label, String? value, {IconData? icon}) { return Padding( padding: const EdgeInsets.only(bottom: 16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (icon != null) Padding( padding: const EdgeInsets.only(right: 8.0, top: 2), child: Icon(icon, size: 18, color: Colors.grey[700]), ), MyText.titleSmall( "$label:", fontWeight: 600, ), MySpacing.width(12), Expanded( child: MyText.bodyMedium(value?.isNotEmpty == true ? value! : "-"), ), ], ), ); } Widget buildCommentList( List> comments, BuildContext context) { comments.sort((a, b) { final aDate = DateTime.tryParse(a['date'] ?? '') ?? DateTime.fromMillisecondsSinceEpoch(0); final bDate = DateTime.tryParse(b['date'] ?? '') ?? DateTime.fromMillisecondsSinceEpoch(0); return bDate.compareTo(aDate); // newest first }); return SizedBox( height: 300, child: ListView.builder( padding: const EdgeInsets.symmetric(vertical: 8), itemCount: comments.length, itemBuilder: (context, index) { final comment = comments[index]; final commentText = comment['text'] ?? '-'; final commentedBy = comment['commentedBy'] ?? 'Unknown'; final relativeTime = timeAgo(comment['date'] ?? ''); final imageUrls = List.from(comment['preSignedUrls'] ?? []); return Container( margin: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey.shade200, borderRadius: BorderRadius.circular(12), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Avatar( firstName: commentedBy.split(' ').first, lastName: commentedBy.split(' ').length > 1 ? commentedBy.split(' ').last : '', size: 32, ), const SizedBox(width: 12), Expanded( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ MyText.bodyMedium( commentedBy, fontWeight: 700, color: Colors.black87, ), MyText.bodySmall( relativeTime, fontSize: 12, color: Colors.black54, ), ], ), ), ], ), const SizedBox(height: 12), Row( children: [ Expanded( child: MyText.bodyMedium( commentText, fontWeight: 500, color: Colors.black87, ), ), ], ), const SizedBox(height: 12), if (imageUrls.isNotEmpty) ...[ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(Icons.attach_file_outlined, size: 18, color: Colors.grey[700]), MyText.bodyMedium( 'Attachments', fontWeight: 600, color: Colors.black87, ), ], ), const SizedBox(height: 8), SizedBox( height: 60, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: imageUrls.length, itemBuilder: (context, imageIndex) { final imageUrl = imageUrls[imageIndex]; return GestureDetector( onTap: () { showDialog( context: context, barrierColor: Colors.black54, builder: (_) => ImageViewerDialog( imageSources: imageUrls, initialIndex: imageIndex, ), ); }, child: Stack( children: [ Container( width: 60, height: 60, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), color: Colors.grey[100], boxShadow: [ BoxShadow( color: Colors.black26, blurRadius: 6, offset: Offset(2, 2), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(12), child: Image.network( imageUrl, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => Container( color: Colors.grey[300], child: Icon(Icons.broken_image, color: Colors.grey[700]), ), ), ), ), const Positioned( right: 4, bottom: 4, child: Icon(Icons.zoom_in, color: Colors.white70, size: 16), ), ], ), ); }, separatorBuilder: (_, __) => const SizedBox(width: 12), ), ), const SizedBox(height: 12), ], ], ), ), ], ), ); }, ), ); } Widget buildImagePickerSection({ required List images, required VoidCallback onCameraTap, required VoidCallback onUploadTap, required void Function(int index) onRemoveImage, required void Function(int initialIndex) onPreviewImage, }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (images.isEmpty) Container( height: 70, width: double.infinity, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey.shade300, width: 2), color: Colors.grey.shade100, ), child: Center( child: Icon(Icons.photo_camera_outlined, size: 48, color: Colors.grey.shade400), ), ) else SizedBox( height: 70, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: images.length, separatorBuilder: (_, __) => const SizedBox(width: 12), itemBuilder: (context, index) { final file = images[index]; return Stack( children: [ GestureDetector( onTap: () => onPreviewImage(index), child: ClipRRect( borderRadius: BorderRadius.circular(12), child: Image.file( file, height: 70, width: 70, fit: BoxFit.cover, ), ), ), Positioned( top: 4, right: 4, child: GestureDetector( onTap: () => onRemoveImage(index), child: Container( decoration: const BoxDecoration( color: Colors.black54, shape: BoxShape.circle, ), child: const Icon(Icons.close, size: 20, color: Colors.white), ), ), ), ], ); }, ), ), MySpacing.height(16), Row( children: [ Expanded( child: MyButton.outlined( onPressed: onCameraTap, padding: MySpacing.xy(12, 10), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.camera_alt, size: 16, color: Colors.blueAccent), MySpacing.width(6), MyText.bodySmall('Capture', color: Colors.blueAccent), ], ), ), ), MySpacing.width(12), Expanded( child: MyButton.outlined( onPressed: onUploadTap, padding: MySpacing.xy(12, 10), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.upload_file, size: 16, color: Colors.blueAccent), MySpacing.width(6), MyText.bodySmall('Upload', color: Colors.blueAccent), ], ), ), ), ], ), ], ); } }