Main coves client
1import 'package:flutter/foundation.dart';
2import 'package:flutter/material.dart';
3import 'package:flutter/services.dart';
4import 'package:provider/provider.dart';
5
6import '../constants/app_colors.dart';
7import '../models/post.dart';
8import '../providers/auth_provider.dart';
9import '../providers/vote_provider.dart';
10import '../utils/date_time_utils.dart';
11import 'icons/animated_heart_icon.dart';
12import 'icons/reply_icon.dart';
13import 'icons/share_icon.dart';
14import 'sign_in_dialog.dart';
15
16/// Action buttons row for post cards
17///
18/// Displays menu, share, comment, and like buttons with proper
19/// authentication handling and optimistic updates.
20class PostCardActions extends StatelessWidget {
21 const PostCardActions({required this.post, super.key});
22
23 final FeedViewPost post;
24
25 @override
26 Widget build(BuildContext context) {
27 return Row(
28 mainAxisAlignment: MainAxisAlignment.spaceBetween,
29 children: [
30 // Left side: Three dots menu and share
31 Row(
32 mainAxisSize: MainAxisSize.min,
33 children: [
34 // Three dots menu button
35 Semantics(
36 button: true,
37 label: 'Post options menu',
38 child: InkWell(
39 onTap: () {
40 // TODO: Show post options menu
41 if (kDebugMode) {
42 debugPrint('Menu button tapped for post');
43 }
44 },
45 child: Padding(
46 padding: const EdgeInsets.symmetric(
47 horizontal: 8,
48 vertical: 10,
49 ),
50 child: Icon(
51 Icons.more_horiz,
52 size: 20,
53 color: AppColors.textPrimary.withValues(alpha: 0.6),
54 ),
55 ),
56 ),
57 ),
58
59 // Share button
60 Semantics(
61 button: true,
62 label: 'Share post',
63 child: InkWell(
64 onTap: () {
65 // TODO: Handle share interaction with backend
66 if (kDebugMode) {
67 debugPrint('Share button tapped for post');
68 }
69 },
70 child: Padding(
71 padding: const EdgeInsets.symmetric(
72 horizontal: 8,
73 vertical: 10,
74 ),
75 child: ShareIcon(
76 color: AppColors.textPrimary.withValues(alpha: 0.6),
77 ),
78 ),
79 ),
80 ),
81 ],
82 ),
83
84 // Right side: Comment and heart
85 Row(
86 mainAxisSize: MainAxisSize.min,
87 children: [
88 // Comment button
89 Semantics(
90 button: true,
91 label:
92 'View ${post.post.stats.commentCount} ${post.post.stats.commentCount == 1 ? "comment" : "comments"}',
93 child: InkWell(
94 onTap: () {
95 // TODO: Navigate to post detail/comments screen
96 if (kDebugMode) {
97 debugPrint('Comment button tapped for post');
98 }
99 },
100 child: Padding(
101 padding: const EdgeInsets.symmetric(
102 horizontal: 12,
103 vertical: 10,
104 ),
105 child: Row(
106 mainAxisSize: MainAxisSize.min,
107 children: [
108 ReplyIcon(
109 color: AppColors.textPrimary.withValues(alpha: 0.6),
110 ),
111 const SizedBox(width: 5),
112 Text(
113 DateTimeUtils.formatCount(post.post.stats.commentCount),
114 style: TextStyle(
115 color: AppColors.textPrimary.withValues(alpha: 0.6),
116 fontSize: 13,
117 ),
118 ),
119 ],
120 ),
121 ),
122 ),
123 ),
124 const SizedBox(width: 8),
125
126 // Heart button
127 Consumer<VoteProvider>(
128 builder: (context, voteProvider, child) {
129 final isLiked = voteProvider.isLiked(post.post.uri);
130 final adjustedScore = voteProvider.getAdjustedScore(
131 post.post.uri,
132 post.post.stats.score,
133 );
134
135 return Semantics(
136 button: true,
137 label:
138 isLiked
139 ? 'Unlike post, $adjustedScore ${adjustedScore == 1 ? "like" : "likes"}'
140 : 'Like post, $adjustedScore ${adjustedScore == 1 ? "like" : "likes"}',
141 child: InkWell(
142 onTap: () async {
143 // Check authentication
144 final authProvider = context.read<AuthProvider>();
145 if (!authProvider.isAuthenticated) {
146 // Show sign-in dialog
147 final shouldSignIn = await SignInDialog.show(
148 context,
149 message: 'You need to sign in to like posts.',
150 );
151
152 if ((shouldSignIn ?? false) && context.mounted) {
153 // TODO: Navigate to sign-in screen
154 if (kDebugMode) {
155 debugPrint('Navigate to sign-in screen');
156 }
157 }
158 return;
159 }
160
161 // Light haptic feedback on both like and unlike
162 await HapticFeedback.lightImpact();
163
164 // Toggle vote with optimistic update
165 try {
166 await voteProvider.toggleVote(
167 postUri: post.post.uri,
168 postCid: post.post.cid,
169 );
170 } on Exception catch (e) {
171 if (kDebugMode) {
172 debugPrint('Failed to toggle vote: $e');
173 }
174 // TODO: Show error snackbar
175 }
176 },
177 child: Padding(
178 padding: const EdgeInsets.symmetric(
179 horizontal: 12,
180 vertical: 10,
181 ),
182 child: Row(
183 mainAxisSize: MainAxisSize.min,
184 children: [
185 AnimatedHeartIcon(
186 isLiked: isLiked,
187 color: AppColors.textPrimary.withValues(alpha: 0.6),
188 likedColor: const Color(0xFFFF0033),
189 ),
190 const SizedBox(width: 5),
191 Text(
192 DateTimeUtils.formatCount(adjustedScore),
193 style: TextStyle(
194 color: AppColors.textPrimary.withValues(
195 alpha: 0.6,
196 ),
197 fontSize: 13,
198 ),
199 ),
200 ],
201 ),
202 ),
203 ),
204 );
205 },
206 ),
207 ],
208 ),
209 ],
210 );
211 }
212}