import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../models/announcement_comment.dart'; import '../../providers/announcements_provider.dart'; import '../../providers/profile_provider.dart'; import '../../utils/app_time.dart'; import '../../utils/snackbar.dart'; import '../../widgets/profile_avatar.dart'; /// Inline, collapsible comments section for an announcement card. class AnnouncementCommentsSection extends ConsumerStatefulWidget { const AnnouncementCommentsSection({ super.key, required this.announcementId, }); final String announcementId; @override ConsumerState createState() => _AnnouncementCommentsSectionState(); } class _AnnouncementCommentsSectionState extends ConsumerState { final _controller = TextEditingController(); bool _sending = false; @override void dispose() { _controller.dispose(); super.dispose(); } Future _submit() async { final text = _controller.text.trim(); if (text.isEmpty || _sending) return; setState(() => _sending = true); try { await ref.read(announcementsControllerProvider).addComment( announcementId: widget.announcementId, body: text, ); _controller.clear(); if (mounted) showSuccessSnackBar(context, 'Comment posted.'); } on AnnouncementNotificationException { // Comment was posted; only push notification delivery failed. _controller.clear(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Comment posted, but notifications may not have been sent.'), ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Failed to post comment: $e')), ); } } finally { if (mounted) setState(() => _sending = false); } } @override Widget build(BuildContext context) { final commentsAsync = ref.watch(announcementCommentsProvider(widget.announcementId)); final profiles = ref.watch(profilesProvider).valueOrNull ?? []; final cs = Theme.of(context).colorScheme; final tt = Theme.of(context).textTheme; final currentUserId = ref.watch(currentUserIdProvider); return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Divider(height: 1, color: cs.outlineVariant.withValues(alpha: 0.5)), commentsAsync.when( loading: () => const Padding( padding: EdgeInsets.all(16), child: Center(child: CircularProgressIndicator(strokeWidth: 2)), ), error: (e, _) => Padding( padding: const EdgeInsets.all(16), child: Text('Error loading comments', style: tt.bodySmall?.copyWith(color: cs.error)), ), data: (comments) => comments.isEmpty ? Padding( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12), child: Text( 'No comments yet', style: tt.bodySmall ?.copyWith(color: cs.onSurfaceVariant), ), ) : ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), itemCount: comments.length, separatorBuilder: (_, _) => const SizedBox(height: 8), itemBuilder: (context, index) { final comment = comments[index]; return _CommentTile( comment: comment, profiles: profiles, currentUserId: currentUserId, onDelete: () async { await ref .read(announcementsControllerProvider) .deleteComment(comment.id); }, ); }, ), ), // Input row Padding( padding: const EdgeInsets.fromLTRB(16, 4, 16, 12), child: Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ Expanded( child: TextField( controller: _controller, maxLines: null, minLines: 1, keyboardType: TextInputType.multiline, textInputAction: TextInputAction.newline, decoration: InputDecoration( hintText: 'Write a comment...', isDense: true, contentPadding: const EdgeInsets.symmetric( horizontal: 12, vertical: 10), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: cs.outlineVariant), ), ), style: tt.bodyMedium, ), ), const SizedBox(width: 8), IconButton( onPressed: _sending ? null : _submit, icon: _sending ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : Icon(Icons.send, color: cs.primary), tooltip: 'Send', ), ], ), ), ], ); } } class _CommentTile extends StatelessWidget { const _CommentTile({ required this.comment, required this.profiles, required this.currentUserId, required this.onDelete, }); final AnnouncementComment comment; final List profiles; final String? currentUserId; final VoidCallback onDelete; @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final tt = Theme.of(context).textTheme; // Resolve author name String authorName = 'Unknown'; String? avatarUrl; for (final p in profiles) { if (p.id == comment.authorId) { authorName = p.fullName; avatarUrl = p.avatarUrl; break; } } final isOwn = comment.authorId == currentUserId; return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ ProfileAvatar(fullName: authorName, avatarUrl: avatarUrl, radius: 14), const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Flexible( child: Text( authorName, style: tt.labelMedium ?.copyWith(fontWeight: FontWeight.w600), overflow: TextOverflow.ellipsis, ), ), const SizedBox(width: 8), Flexible( child: Text( _relativeTime(comment.createdAt), style: tt.labelSmall ?.copyWith(color: cs.onSurfaceVariant), overflow: TextOverflow.ellipsis, ), ), ], ), const SizedBox(height: 2), Text(comment.body, style: tt.bodySmall), ], ), ), if (isOwn) IconButton( icon: Icon(Icons.close, size: 16, color: cs.onSurfaceVariant), onPressed: onDelete, tooltip: 'Delete', visualDensity: VisualDensity.compact, padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 28, minHeight: 28), ), ], ); } } String _relativeTime(DateTime dt) { final now = AppTime.now(); final diff = now.difference(dt); if (diff.inMinutes < 1) return 'just now'; if (diff.inMinutes < 60) return '${diff.inMinutes}m ago'; if (diff.inHours < 24) return '${diff.inHours}h ago'; if (diff.inDays < 7) return '${diff.inDays}d ago'; return AppTime.formatDate(dt); }