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