import 'dart:io'; import 'package:flutter/material.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/image_viewer_dialog.dart'; import 'package:marco/helpers/widgets/avatar.dart'; import 'package:get/get.dart'; /// Show labeled row with optional icon 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! : "-"), ), ], ), ); } /// Show uploaded network images 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), Row( children: [ Icon(Icons.image_outlined, size: 18, color: Colors.grey[700]), MySpacing.width(8), MyText.titleSmall(title, fontWeight: 600), ], ), MySpacing.height(8), 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, 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), ], ); } /// Local image picker preview (with file images) 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), ], ), ), ), ], ), ], ); } /// Comment list widget Widget buildCommentList( List> comments, BuildContext context, String Function(String) timeAgo) { 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: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( 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), MyText.bodyMedium(commentText, fontWeight: 500, color: Colors.black87), const SizedBox(height: 12), if (imageUrls.isNotEmpty) ...[ Row( children: [ Icon(Icons.attach_file_outlined, size: 18, color: Colors.grey[700]), MySpacing.width(8), 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, builder: (_) => ImageViewerDialog( imageSources: imageUrls, initialIndex: imageIndex, ), ); }, child: ClipRRect( borderRadius: BorderRadius.circular(12), child: Image.network( imageUrl, width: 60, height: 60, fit: BoxFit.cover, ), ), ); }, separatorBuilder: (_, __) => const SizedBox(width: 12), ), ), ] ], ), ); }, ), ); } /// Cancel + Submit buttons Widget buildCommentActionButtons({ required VoidCallback onCancel, required Future Function() onSubmit, required RxBool isLoading, }) { 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.send, color: Colors.white, size: 18), label: isLoading.value ? const SizedBox() : MyText.bodyMedium("Submit", 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), ), ); }), ), ], ); } /// Converts a UTC timestamp to a relative time string 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 "${date.day.toString().padLeft(2, '0')}-${date.month.toString().padLeft(2, '0')}-${date.year}"; } 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) { return ''; } }