import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lucide_icons/lucide_icons.dart'; import 'package:image_picker/image_picker.dart'; import '../../domain/project_task.dart'; import '../../providers/project_provider.dart'; import '../../../transactions/presentation/widgets/attachment_gallery_screen.dart'; class TaskDetailsSheet extends ConsumerStatefulWidget { final ProjectTask task; const TaskDetailsSheet({super.key, required this.task}); @override ConsumerState createState() => _TaskDetailsSheetState(); } class _TaskDetailsSheetState extends ConsumerState { final TextEditingController _commentController = TextEditingController(); final List _imagesBase64 = []; bool _isSubmitting = false; Future _pickImage() async { if (_imagesBase64.length >= 3) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Maximum 3 images allowed'))); return; } final ImagePicker picker = ImagePicker(); final XFile? image = await picker.pickImage(source: ImageSource.gallery, imageQuality: 50); if (image != null) { final bytes = await image.readAsBytes(); setState(() { _imagesBase64.add(base64Encode(bytes)); }); } } Future _submitComment() async { if (_commentController.text.trim().isEmpty && _imagesBase64.isEmpty) return; setState(() => _isSubmitting = true); try { final repo = ref.read(projectRepositoryProvider); await repo.addComment( taskId: widget.task.id, content: _commentController.text.trim(), imagesBase64: _imagesBase64, ); _commentController.clear(); setState(() { _imagesBase64.clear(); }); ref.invalidate(projectTaskCommentsProvider(widget.task.id)); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error adding comment: $e'))); } } finally { if (mounted) setState(() => _isSubmitting = false); } } @override Widget build(BuildContext context) { final commentsAsync = ref.watch(projectTaskCommentsProvider(widget.task.id)); return Container( height: MediaQuery.of(context).size.height * 0.9, decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), child: Column( children: [ _buildHeader(context), Expanded( child: commentsAsync.when( data: (comments) { if (comments.isEmpty) return const Center(child: Text('No comments yet.')); return ListView.builder( padding: const EdgeInsets.all(16), itemCount: comments.length, itemBuilder: (context, index) { final c = comments[index]; return Card( margin: const EdgeInsets.only(bottom: 12), color: Colors.grey[100], elevation: 0, child: Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(c.content, style: const TextStyle(fontSize: 14)), if (c.images.isNotEmpty) ...[ const SizedBox(height: 8), Wrap( spacing: 8, children: c.images.asMap().entries.map((entry) { final imgIndex = entry.key; final img = entry.value; return GestureDetector( onTap: () { final allImages = c.images.map((i) => MemoryImage(base64Decode(i)) as ImageProvider).toList(); Navigator.push( context, MaterialPageRoute( builder: (context) => AttachmentGalleryScreen( images: allImages, initialIndex: imgIndex, ), ), ); }, child: ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.memory( base64Decode(img), width: 80, height: 80, fit: BoxFit.cover, ), ), ); }).toList(), ) ] ], ), ), ); }, ); }, loading: () => const Center(child: CircularProgressIndicator()), error: (err, stack) => Center(child: Text('Error: $err')), ), ), _buildCommentInput(), ], ), ); } Widget _buildHeader(BuildContext context) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration(border: Border(bottom: BorderSide(color: Colors.grey.shade300))), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(widget.task.title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), if (widget.task.description != null && widget.task.description!.isNotEmpty) Text(widget.task.description!, style: const TextStyle(color: Colors.grey)), ], ), ), IconButton(icon: const Icon(LucideIcons.x), onPressed: () => Navigator.pop(context)), ], ), ); } Widget _buildCommentInput() { return Container( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom + 16, left: 16, right: 16, top: 16, ), decoration: BoxDecoration( color: Colors.white, border: Border(top: BorderSide(color: Colors.grey.shade300)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (_imagesBase64.isNotEmpty) Padding( padding: const EdgeInsets.only(bottom: 8.0), child: Wrap( spacing: 8, children: _imagesBase64.asMap().entries.map((entry) { return Stack( children: [ ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.memory( base64Decode(entry.value), width: 60, height: 60, fit: BoxFit.cover, ), ), Positioned( right: 0, top: 0, child: GestureDetector( onTap: () { setState(() { _imagesBase64.removeAt(entry.key); }); }, child: Container( color: Colors.black54, child: const Icon(LucideIcons.x, size: 16, color: Colors.white), ), ), ), ], ); }).toList(), ), ), Row( children: [ IconButton( icon: const Icon(LucideIcons.image), onPressed: _pickImage, ), Expanded( child: TextField( controller: _commentController, decoration: const InputDecoration( hintText: 'Add a comment...', border: InputBorder.none, ), ), ), _isSubmitting ? const Padding(padding: EdgeInsets.all(12.0), child: SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2))) : IconButton( icon: const Icon(LucideIcons.send, color: Colors.blue), onPressed: _submitComment, ), ], ), ], ), ); } }