Main coves client
1import 'package:flutter/foundation.dart';
2import 'package:flutter/material.dart';
3
4import '../constants/app_colors.dart';
5import '../models/comment.dart';
6import 'comment_card.dart';
7
8/// Comment thread widget for displaying comments and their nested replies
9///
10/// Recursively displays a ThreadViewComment and its replies:
11/// - Renders the comment using CommentCard with optimistic voting
12/// via VoteProvider
13/// - Indents nested replies visually
14/// - Limits nesting depth to prevent excessive indentation
15/// - Shows "Load more replies" button when hasMore is true
16/// - Supports tap-to-reply via [onCommentTap] callback
17///
18/// The [maxDepth] parameter controls how deeply nested comments can be
19/// before they're rendered at the same level to prevent UI overflow.
20class CommentThread extends StatelessWidget {
21 const CommentThread({
22 required this.thread,
23 this.depth = 0,
24 this.maxDepth = 5,
25 this.currentTime,
26 this.onLoadMoreReplies,
27 this.onCommentTap,
28 super.key,
29 });
30
31 final ThreadViewComment thread;
32 final int depth;
33 final int maxDepth;
34 final DateTime? currentTime;
35 final VoidCallback? onLoadMoreReplies;
36
37 /// Callback when a comment is tapped (for reply functionality)
38 final void Function(ThreadViewComment)? onCommentTap;
39
40 @override
41 Widget build(BuildContext context) {
42 // Calculate effective depth (flatten after maxDepth)
43 final effectiveDepth = depth > maxDepth ? maxDepth : depth;
44
45 return Column(
46 crossAxisAlignment: CrossAxisAlignment.start,
47 children: [
48 // Render the comment with tap handler
49 CommentCard(
50 comment: thread.comment,
51 depth: effectiveDepth,
52 currentTime: currentTime,
53 onTap: onCommentTap != null ? () => onCommentTap!(thread) : null,
54 ),
55
56 // Render replies recursively
57 if (thread.replies != null && thread.replies!.isNotEmpty)
58 ...thread.replies!.map(
59 (reply) => CommentThread(
60 thread: reply,
61 depth: depth + 1,
62 maxDepth: maxDepth,
63 currentTime: currentTime,
64 onLoadMoreReplies: onLoadMoreReplies,
65 onCommentTap: onCommentTap,
66 ),
67 ),
68
69 // Show "Load more replies" button if there are more
70 if (thread.hasMore) _buildLoadMoreButton(context),
71 ],
72 );
73 }
74
75 /// Builds the "Load more replies" button
76 Widget _buildLoadMoreButton(BuildContext context) {
77 // Calculate left padding based on depth (align with replies)
78 final effectiveDepth = depth > maxDepth ? maxDepth : depth;
79 final leftPadding = 16.0 + ((effectiveDepth + 1) * 12.0);
80
81 return Container(
82 padding: EdgeInsets.fromLTRB(leftPadding, 8, 16, 8),
83 decoration: const BoxDecoration(
84 border: Border(bottom: BorderSide(color: AppColors.border)),
85 ),
86 child: InkWell(
87 onTap: () {
88 if (onLoadMoreReplies != null) {
89 onLoadMoreReplies!();
90 } else {
91 if (kDebugMode) {
92 debugPrint('Load more replies tapped (no handler provided)');
93 }
94 }
95 },
96 child: Padding(
97 padding: const EdgeInsets.symmetric(vertical: 4),
98 child: Row(
99 children: [
100 Icon(
101 Icons.add_circle_outline,
102 size: 16,
103 color: AppColors.primary.withValues(alpha: 0.8),
104 ),
105 const SizedBox(width: 6),
106 Text(
107 'Load more replies',
108 style: TextStyle(
109 color: AppColors.primary.withValues(alpha: 0.8),
110 fontSize: 13,
111 fontWeight: FontWeight.w500,
112 ),
113 ),
114 ],
115 ),
116 ),
117 ),
118 );
119 }
120}