import 'dart:io'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:file_picker/file_picker.dart'; import 'package:image_picker/image_picker.dart'; import 'package:on_field_work/controller/service_project/service_project_details_screen_controller.dart'; import 'package:on_field_work/helpers/widgets/my_spacing.dart'; import 'package:on_field_work/helpers/widgets/my_text.dart'; import 'package:on_field_work/model/service_project/job_comments.dart'; import 'package:on_field_work/helpers/widgets/image_viewer_dialog.dart'; import 'package:on_field_work/helpers/utils/date_time_utils.dart'; import 'package:on_field_work/helpers/widgets/avatar.dart'; import 'package:on_field_work/helpers/widgets/time_stamp_image_helper.dart'; class AddCommentWidget extends StatefulWidget { final String jobId; final String jobTicketId; const AddCommentWidget({ super.key, required this.jobId, required this.jobTicketId, }); @override State createState() => _AddCommentWidgetState(); } class _AddCommentWidgetState extends State { final TextEditingController _controller = TextEditingController(); final List _selectedFiles = []; final ServiceProjectDetailsController controller = Get.find(); bool isSubmitting = false; bool isProcessingAttachment = false; @override void initState() { super.initState(); controller.fetchJobComments(refresh: true); } @override void dispose() { _controller.dispose(); super.dispose(); } // --- PICK MULTIPLE FILES --- Future _pickFiles() async { try { final result = await FilePicker.platform.pickFiles( type: FileType.custom, allowedExtensions: ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx'], allowMultiple: true, ); if (result != null) { setState(() { _selectedFiles.addAll( result.paths.whereType().map((path) => File(path))); }); } } catch (e) { Get.snackbar("Error", "Failed to pick files: $e"); } } // --- PICK IMAGE FROM CAMERA --- Future _pickFromCamera() async { try { final pickedFile = await controller.picker.pickImage(source: ImageSource.camera); if (pickedFile != null) { setState(() { controller.isProcessingAttachment.value = true; // optional: show loading }); File imageFile = File(pickedFile.path); // Add timestamp to the captured image File timestampedFile = await TimestampImageHelper.addTimestamp( imageFile: imageFile, ); setState(() { _selectedFiles.add(timestampedFile); }); } } catch (e) { Get.snackbar("Camera error", "$e", backgroundColor: Colors.red.shade200, colorText: Colors.white); } finally { setState(() { controller.isProcessingAttachment.value = false; }); } } // --- SUBMIT COMMENT --- Future _submitComment() async { if (_controller.text.trim().isEmpty && _selectedFiles.isEmpty) return; setState(() => isSubmitting = true); final success = await controller.addJobComment( jobId: widget.jobId, comment: _controller.text.trim(), files: _selectedFiles, ); setState(() => isSubmitting = false); if (success) { _controller.clear(); _selectedFiles.clear(); FocusScope.of(context).unfocus(); await controller.fetchJobComments(refresh: true); } } // --- HELPER: CHECK IF FILE IS IMAGE --- bool _isImage(String? fileName) { if (fileName == null) return false; final ext = fileName.split('.').last.toLowerCase(); return ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'].contains(ext); } // --- SELECTED FILES PREVIEW --- // --- SELECTED FILES PREVIEW (styled like expense attachments) --- Widget _buildSelectedFiles() { if (_selectedFiles.isEmpty) return const SizedBox.shrink(); return SizedBox( height: 44, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: _selectedFiles.length, separatorBuilder: (_, __) => const SizedBox(width: 8), itemBuilder: (context, index) { final file = _selectedFiles[index]; final fileName = file.path.split('/').last; final isImage = _isImage(fileName); return GestureDetector( onTap: isImage ? () { // Show image preview Get.to(() => ImageViewerDialog( imageSources: _selectedFiles.toList(), initialIndex: _selectedFiles .where((f) => _isImage(f.path.split('/').last)) .toList() .indexOf(file), captions: _selectedFiles .where((f) => _isImage(f.path.split('/').last)) .map((f) => f.path.split('/').last) .toList(), )); } : null, child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), decoration: BoxDecoration( color: isImage ? Colors.teal.shade50 : Colors.grey.shade100, borderRadius: BorderRadius.circular(10), border: Border.all( color: isImage ? Colors.teal.shade100 : Colors.grey.shade300, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( isImage ? Icons.insert_photo_outlined : Icons.insert_drive_file_outlined, size: 16, color: isImage ? Colors.teal.shade700 : Colors.grey.shade700, ), const SizedBox(width: 6), ConstrainedBox( constraints: const BoxConstraints(maxWidth: 100), child: Text( fileName, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 13, fontWeight: FontWeight.w500, color: isImage ? Colors.teal.shade700 : Colors.grey.shade700, ), ), ), const SizedBox(width: 6), GestureDetector( onTap: () => setState(() => _selectedFiles.removeAt(index)), child: Icon( Icons.close, size: 14, color: isImage ? Colors.teal.shade700 : Colors.grey.shade700, ), ), ], ), ), ); }, ), ); } // --- BUILD SINGLE COMMENT ITEM --- Widget _buildCommentItem(CommentItem comment) { final firstName = comment.createdBy?.firstName ?? ''; final lastName = comment.createdBy?.lastName ?? ''; final formattedDate = comment.createdAt != null ? DateTimeUtils.convertUtcToLocal(comment.createdAt!, format: 'dd MMM yyyy hh:mm a') : "Just now"; return Padding( padding: const EdgeInsets.only(bottom: 16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Avatar(firstName: firstName, lastName: lastName, size: 32), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Flexible( child: Text( "$firstName $lastName".trim().isNotEmpty ? "$firstName $lastName" : "Unknown User", style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 14), overflow: TextOverflow.ellipsis, maxLines: 2, ), ), const Padding( padding: EdgeInsets.symmetric(horizontal: 6.0), child: Text("•", style: TextStyle(fontSize: 14, color: Colors.grey)), ), Icon(Icons.access_time, size: 14, color: Colors.grey[600]), const SizedBox(width: 4), Text(formattedDate, style: TextStyle(fontSize: 13, color: Colors.grey[600])), ], ), const SizedBox(height: 4), if (comment.comment != null && comment.comment!.isNotEmpty) Padding( padding: const EdgeInsets.only(right: 8.0), child: Text( comment.comment!, style: const TextStyle(fontSize: 14, color: Colors.black87), ), ), if (comment.attachments != null && comment.attachments!.isNotEmpty) Padding( padding: const EdgeInsets.only(top: 8), child: SizedBox( height: 40, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: comment.attachments!.length, separatorBuilder: (_, __) => const SizedBox(width: 8), itemBuilder: (context, index) { final attachment = comment.attachments![index]; final isImage = _isImage(attachment.fileName); final imageAttachments = comment.attachments! .where((a) => _isImage(a.fileName)) .toList(); final imageIndex = imageAttachments.indexOf(attachment); return GestureDetector( onTap: isImage ? () { Get.to(() => ImageViewerDialog( imageSources: imageAttachments .map((a) => a.preSignedUrl ?? "") .toList(), initialIndex: imageIndex, captions: imageAttachments .map((a) => a.fileName ?? "") .toList(), )); } : null, child: Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 8), decoration: BoxDecoration( color: isImage ? Colors.teal.shade50 : Colors.purple.shade50, borderRadius: BorderRadius.circular(10), border: Border.all( color: isImage ? Colors.teal.shade100 : Colors.purple.shade100), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( isImage ? Icons.insert_photo_outlined : Icons.insert_drive_file_outlined, size: 16, color: isImage ? Colors.teal.shade700 : Colors.purple.shade700, ), const SizedBox(width: 6), ConstrainedBox( constraints: const BoxConstraints(maxWidth: 100), child: Text( attachment.fileName ?? "Attachment", maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 13, fontWeight: FontWeight.w500, color: isImage ? Colors.teal.shade700 : Colors.purple.shade700, ), ), ), ], ), ), ); }, ), ), ), ], ), ), ], ), ); } // --- COMMENT LIST --- Widget _buildCommentList() { return Obx(() { if (controller.isCommentsLoading.value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 32.0), child: Center( child: Column( children: [ const CircularProgressIndicator(strokeWidth: 3), MySpacing.height(12), MyText.bodyMedium("Loading comments...", color: Colors.grey.shade600), ], ), ), ); } if (controller.jobComments.isEmpty) { return Padding( padding: const EdgeInsets.symmetric(vertical: 32.0), child: Center( child: Column( children: [ Icon(Icons.chat_bubble_outline, size: 40, color: Colors.grey.shade400), MySpacing.height(8), MyText.bodyMedium("No comments yet.", color: Colors.grey.shade600), MyText.bodySmall("Be the first to post a comment.", color: Colors.grey.shade500), ], ), ), ); } return Column( children: controller.jobComments.map(_buildCommentItem).toList(), ); }); } @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MySpacing.height(12), TextField( controller: _controller, maxLines: 3, decoration: InputDecoration( hintText: "Type your comment here...", contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey.shade300), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: Colors.blue, width: 2), ), ), ), MySpacing.height(10), _buildSelectedFiles(), MySpacing.height(10), Row( children: [ // Attach file IconButton( onPressed: isSubmitting ? null : _pickFiles, icon: const Icon(Icons.attach_file, size: 24, color: Colors.blue), tooltip: "Attach File", ), // Camera (icon-only) Stack( alignment: Alignment.center, children: [ IconButton( onPressed: isSubmitting || controller.isProcessingAttachment.value ? null : _pickFromCamera, icon: const Icon(Icons.camera_alt, size: 24, color: Colors.blue), tooltip: "Camera", ), if (controller.isProcessingAttachment.value) const SizedBox( height: 24, width: 24, child: CircularProgressIndicator(strokeWidth: 2), ), ], ), const Spacer(), // Submit button ElevatedButton( onPressed: isSubmitting ? null : _submitComment, style: ElevatedButton.styleFrom( backgroundColor: Colors.blue.shade700, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), elevation: 2, ), child: isSubmitting ? const SizedBox( height: 18, width: 18, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white), ) : const Text("Post Comment"), ), ], ), MySpacing.height(30), const Divider(height: 1, thickness: 0.5), MySpacing.height(20), Obx(() => Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MyText.titleMedium( "Comments (${controller.jobComments.length})", fontWeight: 700), MySpacing.height(16), _buildCommentList(), ], )), ], ); } }