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:
124 AppColors.textPrimary.withValues(
125 alpha: 0.6,
126 ),
127 ),
128 const SizedBox(width: 5),
129 Text(
130 DateTimeUtils.formatCount(count),
131 style: TextStyle(
132 color:
133 AppColors.textPrimary.withValues(
134 alpha: 0.6,
135 ),
136 fontSize: 13,
137 ),
138 ),
139 ],
140 ),
141 ),
142 ),
143 );
144 },
145 ),
146 const SizedBox(width: 8),
147 ],
148
149 // Heart button
150 Consumer<VoteProvider>(
151 builder: (context, voteProvider, child) {
152 final isLiked = voteProvider.isLiked(post.post.uri);
153 final adjustedScore = voteProvider.getAdjustedScore(
154 post.post.uri,
155 post.post.stats.score,
156 );
157
158 return Semantics(
159 button: true,
160 label:
161 isLiked
162 ? 'Unlike post, $adjustedScore '
163 '${adjustedScore == 1 ? "like" : "likes"}'
164 : 'Like post, $adjustedScore '
165 '${adjustedScore == 1 ? "like" : "likes"}',
166 child: InkWell(
167 onTap: () async {
168 // Check authentication
169 final authProvider = context.read<AuthProvider>();
170 if (!authProvider.isAuthenticated) {
171 // Show sign-in dialog
172 final shouldSignIn = await SignInDialog.show(
173 context,
174 message: 'You need to sign in to like posts.',
175 );
176
177 if ((shouldSignIn ?? false) && context.mounted) {
178 // TODO: Navigate to sign-in screen
179 if (kDebugMode) {
180 debugPrint('Navigate to sign-in screen');
181 }
182 }
183 return;
184 }
185
186 // Light haptic feedback on both like and unlike
187 await HapticFeedback.lightImpact();
188
189 // Toggle vote with optimistic update
190 try {
191 await voteProvider.toggleVote(
192 postUri: post.post.uri,
193 postCid: post.post.cid,
194 );
195 } on Exception catch (e) {
196 if (kDebugMode) {
197 debugPrint('Failed to toggle vote: $e');
198 }
199 // TODO: Show error snackbar
200 }
201 },
202 child: Padding(
203 padding: const EdgeInsets.symmetric(
204 horizontal: 12,
205 vertical: 10,
206 ),
207 child: Row(
208 mainAxisSize: MainAxisSize.min,
209 children: [
210 AnimatedHeartIcon(
211 isLiked: isLiked,
212 color: AppColors.textPrimary.withValues(alpha: 0.6),
213 likedColor: const Color(0xFFFF0033),
214 ),
215 const SizedBox(width: 5),
216 Text(
217 DateTimeUtils.formatCount(adjustedScore),
218 style: TextStyle(
219 color: AppColors.textPrimary.withValues(
220 alpha: 0.6,
221 ),
222 fontSize: 13,
223 ),
224 ),
225 ],
226 ),
227 ),
228 ),
229 );
230 },
231 ),
232 ],
233 ),
234 ],
235 );
236 }
237}