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';
6import 'package:share_plus/share_plus.dart';
7
8import '../constants/app_colors.dart';
9import '../models/post.dart';
10import '../providers/auth_provider.dart';
11import '../providers/vote_provider.dart';
12import '../utils/date_time_utils.dart';
13import 'icons/animated_heart_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: () async {
71 // Add haptic feedback
72 await HapticFeedback.lightImpact();
73
74 // Share post title and URI
75 final postUri = post.post.uri;
76 final title = post.post.title ?? 'Check out this post';
77 await Share.share('$title\n\n$postUri', subject: title);
78 },
79 child: Padding(
80 padding: const EdgeInsets.symmetric(
81 horizontal: 8,
82 vertical: 10,
83 ),
84 child: ShareIcon(
85 color: AppColors.textPrimary.withValues(alpha: 0.6),
86 ),
87 ),
88 ),
89 ),
90 ],
91 ),
92
93 // Right side: Comment and heart
94 Row(
95 mainAxisSize: MainAxisSize.min,
96 children: [
97 // Comment button (hidden in detail view)
98 if (showCommentButton) ...[
99 Builder(
100 builder: (context) {
101 final count = post.post.stats.commentCount;
102 final commentText = count == 1 ? 'comment' : 'comments';
103 return Semantics(
104 button: true,
105 label: 'View $count $commentText',
106 child: InkWell(
107 onTap: () {
108 // Navigate to post detail screen (ALL post types)
109 final encodedUri = Uri.encodeComponent(post.post.uri);
110 context.push('/post/$encodedUri', extra: post);
111 },
112 child: Padding(
113 padding: const EdgeInsets.symmetric(
114 horizontal: 12,
115 vertical: 10,
116 ),
117 child: Row(
118 mainAxisSize: MainAxisSize.min,
119 children: [
120 Icon(
121 Icons.chat_bubble_outline,
122 size: 20,
123 color: AppColors.textPrimary.withValues(
124 alpha: 0.6,
125 ),
126 ),
127 const SizedBox(width: 5),
128 Text(
129 DateTimeUtils.formatCount(count),
130 style: TextStyle(
131 color: AppColors.textPrimary.withValues(
132 alpha: 0.6,
133 ),
134 fontSize: 13,
135 ),
136 ),
137 ],
138 ),
139 ),
140 ),
141 );
142 },
143 ),
144 const SizedBox(width: 8),
145 ],
146
147 // Heart button
148 Consumer<VoteProvider>(
149 builder: (context, voteProvider, child) {
150 final isLiked = voteProvider.isLiked(post.post.uri);
151 final adjustedScore = voteProvider.getAdjustedScore(
152 post.post.uri,
153 post.post.stats.score,
154 );
155
156 return Semantics(
157 button: true,
158 label:
159 isLiked
160 ? 'Unlike post, $adjustedScore '
161 '${adjustedScore == 1 ? "like" : "likes"}'
162 : 'Like post, $adjustedScore '
163 '${adjustedScore == 1 ? "like" : "likes"}',
164 child: InkWell(
165 onTap: () async {
166 // Check authentication
167 final authProvider = context.read<AuthProvider>();
168 if (!authProvider.isAuthenticated) {
169 // Show sign-in dialog
170 final shouldSignIn = await SignInDialog.show(
171 context,
172 message: 'You need to sign in to like posts.',
173 );
174
175 if ((shouldSignIn ?? false) && context.mounted) {
176 // TODO: Navigate to sign-in screen
177 if (kDebugMode) {
178 debugPrint('Navigate to sign-in screen');
179 }
180 }
181 return;
182 }
183
184 // Light haptic feedback on both like and unlike
185 await HapticFeedback.lightImpact();
186
187 // Toggle vote with optimistic update
188 try {
189 await voteProvider.toggleVote(
190 postUri: post.post.uri,
191 postCid: post.post.cid,
192 );
193 } on Exception catch (e) {
194 if (kDebugMode) {
195 debugPrint('Failed to toggle vote: $e');
196 }
197 // TODO: Show error snackbar
198 }
199 },
200 child: Padding(
201 padding: const EdgeInsets.symmetric(
202 horizontal: 12,
203 vertical: 10,
204 ),
205 child: Row(
206 mainAxisSize: MainAxisSize.min,
207 children: [
208 AnimatedHeartIcon(
209 isLiked: isLiked,
210 color: AppColors.textPrimary.withValues(alpha: 0.6),
211 likedColor: const Color(0xFFFF0033),
212 ),
213 const SizedBox(width: 5),
214 Text(
215 DateTimeUtils.formatCount(adjustedScore),
216 style: TextStyle(
217 color: AppColors.textPrimary.withValues(
218 alpha: 0.6,
219 ),
220 fontSize: 13,
221 ),
222 ),
223 ],
224 ),
225 ),
226 ),
227 );
228 },
229 ),
230 ],
231 ),
232 ],
233 );
234 }
235}