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///
17/// The [maxDepth] parameter controls how deeply nested comments can be
18/// before they're rendered at the same level to prevent UI overflow.
19class CommentThread extends StatelessWidget {
20 const CommentThread({
21 required this.thread,
22 this.depth = 0,
23 this.maxDepth = 5,
24 this.currentTime,
25 this.onLoadMoreReplies,
26 super.key,
27 });
28
29 final ThreadViewComment thread;
30 final int depth;
31 final int maxDepth;
32 final DateTime? currentTime;
33 final VoidCallback? onLoadMoreReplies;
34
35 @override
36 Widget build(BuildContext context) {
37 // Calculate effective depth (flatten after maxDepth)
38 final effectiveDepth = depth > maxDepth ? maxDepth : depth;
39
40 return Column(
41 crossAxisAlignment: CrossAxisAlignment.start,
42 children: [
43 // Render the comment
44 CommentCard(
45 comment: thread.comment,
46 depth: effectiveDepth,
47 currentTime: currentTime,
48 ),
49
50 // Render replies recursively
51 if (thread.replies != null && thread.replies!.isNotEmpty)
52 ...thread.replies!.map(
53 (reply) => CommentThread(
54 thread: reply,
55 depth: depth + 1,
56 maxDepth: maxDepth,
57 currentTime: currentTime,
58 onLoadMoreReplies: onLoadMoreReplies,
59 ),
60 ),
61
62 // Show "Load more replies" button if there are more
63 if (thread.hasMore) _buildLoadMoreButton(context),
64 ],
65 );
66 }
67
68 /// Builds the "Load more replies" button
69 Widget _buildLoadMoreButton(BuildContext context) {
70 // Calculate left padding based on depth (align with replies)
71 final effectiveDepth = depth > maxDepth ? maxDepth : depth;
72 final leftPadding = 16.0 + ((effectiveDepth + 1) * 12.0);
73
74 return Container(
75 padding: EdgeInsets.fromLTRB(leftPadding, 8, 16, 8),
76 decoration: const BoxDecoration(
77 border: Border(bottom: BorderSide(color: AppColors.border)),
78 ),
79 child: InkWell(
80 onTap: () {
81 if (onLoadMoreReplies != null) {
82 onLoadMoreReplies!();
83 } else {
84 if (kDebugMode) {
85 debugPrint('Load more replies tapped (no handler provided)');
86 }
87 }
88 },
89 child: Padding(
90 padding: const EdgeInsets.symmetric(vertical: 4),
91 child: Row(
92 children: [
93 Icon(
94 Icons.add_circle_outline,
95 size: 16,
96 color: AppColors.primary.withValues(alpha: 0.8),
97 ),
98 const SizedBox(width: 6),
99 Text(
100 'Load more replies',
101 style: TextStyle(
102 color: AppColors.primary.withValues(alpha: 0.8),
103 fontSize: 13,
104 fontWeight: FontWeight.w500,
105 ),
106 ),
107 ],
108 ),
109 ),
110 ),
111 );
112 }
113}