diff --git a/sojorn_app/lib/widgets/post/post_actions.dart b/sojorn_app/lib/widgets/post/post_actions.dart index 399ebde..bc5ada1 100644 --- a/sojorn_app/lib/widgets/post/post_actions.dart +++ b/sojorn_app/lib/widgets/post/post_actions.dart @@ -1,11 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:share_plus/share_plus.dart'; +import 'package:google_fonts/google_fonts.dart'; import '../../models/post.dart'; import '../../providers/api_provider.dart'; import '../../theme/app_theme.dart'; import '../sojorn_snackbar.dart'; +import '../reactions/reaction_strip.dart'; /// Post actions with a vibrant, clear, and energetic design. /// @@ -33,12 +35,26 @@ class _PostActionsState extends ConsumerState { late bool _isSaved; bool _isLiking = false; bool _isSaving = false; + + // Reaction state + final Map _reactionCounts = {}; + final Set _myReactions = {}; @override void initState() { super.initState(); _isLiked = widget.post.isLiked ?? false; _isSaved = widget.post.isSaved ?? false; + _seedReactionState(); + } + + void _seedReactionState() { + if (widget.post.reactions != null) { + _reactionCounts.addAll(widget.post.reactions!); + } + if (widget.post.myReactions != null) { + _myReactions.addAll(widget.post.myReactions!); + } } void _showError(String message) { @@ -121,42 +137,147 @@ class _PostActionsState extends ConsumerState { } } + Future _toggleReaction(String emoji) async { + final previousCounts = Map.from(_reactionCounts); + final previousMine = Set.from(_myReactions); + + setState(() { + if (_myReactions.contains(emoji)) { + _myReactions.remove(emoji); + final next = (_reactionCounts[emoji] ?? 1) - 1; + if (next <= 0) { + _reactionCounts.remove(emoji); + } else { + _reactionCounts[emoji] = next; + } + } else { + if (_myReactions.isNotEmpty) { + final previousEmoji = _myReactions.first; + _myReactions.clear(); + final prevCount = (_reactionCounts[previousEmoji] ?? 1) - 1; + if (prevCount <= 0) { + _reactionCounts.remove(previousEmoji); + } else { + _reactionCounts[previousEmoji] = prevCount; + } + } + _myReactions.add(emoji); + _reactionCounts[emoji] = (_reactionCounts[emoji] ?? 0) + 1; + } + }); + + try { + final api = ref.read(apiServiceProvider); + final response = await api.toggleReaction(widget.post.id, emoji); + if (!mounted) return; + final updatedCounts = response['reactions'] as Map?; + final updatedMine = response['my_reactions'] as List?; + + if (updatedCounts != null) { + setState(() { + _reactionCounts.clear(); + _reactionCounts.addAll( + updatedCounts.map((key, value) => MapEntry(key, value as int)) + ); + }); + } + if (updatedMine != null) { + setState(() { + _myReactions.clear(); + _myReactions.addAll(updatedMine.map((item) => item.toString())); + }); + } + } catch (_) { + if (mounted) { + setState(() { + _reactionCounts.clear(); + _reactionCounts.addAll(previousCounts); + _myReactions.clear(); + _myReactions.addAll(previousMine); + }); + } + } + } + @override Widget build(BuildContext context) { final allowChain = widget.post.allowChain && widget.post.visibility != 'private' && widget.onChain != null; - return Row( - mainAxisAlignment: MainAxisAlignment.start, + return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Icon-only action buttons with Row layout - _IconActionButton( - icon: Icons.favorite_border, - activeIcon: Icons.favorite, - isActive: _isLiked, - isLoading: _isLiking, - onPressed: _isLiking ? null : _toggleLike, - activeColor: AppTheme.brightNavy, + // Reactions section - full width + ReactionStrip( + reactions: _reactionCounts, + myReactions: _myReactions, + reactionUsers: {}, + onToggle: (emoji) => _toggleReaction(emoji), + onAdd: () => _toggleReaction('❤️'), // Default to heart for now ), - const SizedBox(width: 24), - _IconActionButton( - icon: Icons.bookmark_border, - activeIcon: Icons.bookmark, - isActive: _isSaved, - isLoading: _isSaving, - onPressed: _isSaving ? null : _toggleSave, - activeColor: AppTheme.brightNavy, + const SizedBox(height: 16), + // Actions row - left aligned + Row( + children: [ + if (allowChain) + Expanded( + child: ElevatedButton.icon( + onPressed: widget.onChain, + icon: const Icon(Icons.reply, size: 18), + label: const Text('Reply'), + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.brightNavy, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + ), + if (allowChain) const SizedBox(width: 12), + IconButton( + onPressed: _isLiking ? null : _toggleLike, + icon: Icon( + _isLiked ? Icons.favorite : Icons.favorite_border, + color: _isLiked ? Colors.red : AppTheme.textSecondary, + ), + style: IconButton.styleFrom( + backgroundColor: AppTheme.navyBlue.withValues(alpha: 0.08), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + const SizedBox(width: 8), + IconButton( + onPressed: _isSaving ? null : _toggleSave, + icon: Icon( + _isSaved ? Icons.bookmark : Icons.bookmark_border, + color: _isSaved ? AppTheme.brightNavy : AppTheme.textSecondary, + ), + style: IconButton.styleFrom( + backgroundColor: AppTheme.navyBlue.withValues(alpha: 0.08), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + const SizedBox(width: 8), + IconButton( + onPressed: _sharePost, + icon: Icon( + Icons.share_outlined, + color: AppTheme.textSecondary, + ), + style: IconButton.styleFrom( + backgroundColor: AppTheme.navyBlue.withValues(alpha: 0.08), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + ], ), - const SizedBox(width: 24), - _IconActionButton( - icon: Icons.share_outlined, - onPressed: _sharePost, - ), - const Spacer(), - if (allowChain) - _IconActionButton( - icon: Icons.reply, - onPressed: widget.onChain, - ), ], ); } diff --git a/sojorn_app/lib/widgets/sojorn_post_card.dart b/sojorn_app/lib/widgets/sojorn_post_card.dart index 6b43fe7..3a391f5 100644 --- a/sojorn_app/lib/widgets/sojorn_post_card.dart +++ b/sojorn_app/lib/widgets/sojorn_post_card.dart @@ -81,9 +81,25 @@ class sojornPostCard extends StatelessWidget { highlightColor: Colors.transparent, child: Container( padding: _padding, + decoration: BoxDecoration( + color: AppTheme.cardSurface, + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: AppTheme.brightNavy, + width: 2, + ), + boxShadow: [ + BoxShadow( + color: AppTheme.brightNavy.withValues(alpha: 0.18), + blurRadius: 24, + offset: const Offset(0, 8), + ), + ], + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + const SizedBox(height: 4), // Header row with menu Row( crossAxisAlignment: CrossAxisAlignment.start, @@ -101,12 +117,7 @@ class sojornPostCard extends StatelessWidget { ), ], ), - - // Chain context (if parent exists and not in detail view) - if (post.chainParent != null && mode != PostViewMode.detail) - _buildChainContext(context), - - const SizedBox(height: AppTheme.spacingMd), + const SizedBox(height: 16), // Body text PostBody( @@ -117,13 +128,15 @@ class sojornPostCard extends StatelessWidget { ), // Media (if available) - if (post.imageUrl != null && post.imageUrl!.isNotEmpty) + if (post.imageUrl != null && post.imageUrl!.isNotEmpty) ...[ + const SizedBox(height: 16), PostMedia( post: post, mode: mode, ), + ], - const SizedBox(height: AppTheme.spacingLg), + const SizedBox(height: 20), // Actions PostActions( @@ -137,43 +150,4 @@ class sojornPostCard extends StatelessWidget { ), ); } - - Widget _buildChainContext(BuildContext context) { - final parent = post.chainParent; - if (parent == null) return const SizedBox.shrink(); - - return Container( - margin: const EdgeInsets.only(bottom: AppTheme.spacingSm), - padding: const EdgeInsets.all(AppTheme.spacingSm), - decoration: BoxDecoration( - color: AppTheme.queenPink.withOpacity(0.15), - borderRadius: BorderRadius.circular(AppTheme.radiusSm), - border: Border( - left: BorderSide( - color: AppTheme.egyptianBlue.withOpacity(0.5), - width: 2, - ), - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Icon( - Icons.subdirectory_arrow_right, - color: AppTheme.royalPurple.withOpacity(0.7), - size: 16, - ), - const SizedBox(width: 6), - Text( - "Replying to @${parent.author?.handle ?? 'unknown'}", - style: AppTheme.textTheme.labelSmall?.copyWith( - color: AppTheme.royalPurple, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ); - } }