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