import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:share_plus/share_plus.dart'; import '../../models/post.dart'; import '../../providers/api_provider.dart'; import '../../theme/app_theme.dart'; import '../sojorn_snackbar.dart'; /// Post actions with a vibrant, clear, and energetic design. /// /// Design Intent: /// - Actions are clear, tappable, and visually engaging. /// - Clear state changes: default (energetic) → active (highlighted). class PostActions extends ConsumerStatefulWidget { final Post post; final VoidCallback? onChain; final VoidCallback? onPostChanged; const PostActions({ super.key, required this.post, this.onChain, this.onPostChanged, }); @override ConsumerState createState() => _PostActionsState(); } class _PostActionsState extends ConsumerState { late bool _isLiked; late bool _isSaved; bool _isLiking = false; bool _isSaving = false; @override void initState() { super.initState(); _isLiked = widget.post.isLiked ?? false; _isSaved = widget.post.isSaved ?? false; } void _showError(String message) { sojornSnackbar.showError( context: context, message: message, ); } Future _toggleLike() async { if (_isLiking) return; setState(() { _isLiking = true; _isLiked = !_isLiked; }); final apiService = ref.read(apiServiceProvider); try { if (_isLiked) { await apiService.appreciatePost(widget.post.id); } else { await apiService.unappreciatePost(widget.post.id); } } catch (e) { if (mounted) { setState(() { _isLiked = !_isLiked; }); _showError(e.toString().replaceAll('Exception: ', '')); } } finally { if (mounted) { setState(() { _isLiking = false; }); } } } Future _toggleSave() async { if (_isSaving) return; setState(() { _isSaving = true; _isSaved = !_isSaved; }); final apiService = ref.read(apiServiceProvider); try { if (_isSaved) { await apiService.savePost(widget.post.id); } else { await apiService.unsavePost(widget.post.id); } } catch (e) { if (mounted) { setState(() { _isSaved = !_isSaved; }); _showError(e.toString().replaceAll('Exception: ', '')); } } finally { if (mounted) { setState(() { _isSaving = false; }); } } } Future _sharePost() async { final handle = widget.post.author?.handle ?? 'sojorn'; final text = '${widget.post.body}\n\n— @$handle on sojorn'; try { await Share.share(text); } catch (e) { _showError('Unable to share right now.'); } } @override Widget build(BuildContext context) { final allowChain = widget.post.allowChain && widget.post.visibility != 'private' && widget.onChain != null; return Row( mainAxisAlignment: MainAxisAlignment.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, ), 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(width: 24), _IconActionButton( icon: Icons.share_outlined, onPressed: _sharePost, ), const Spacer(), if (allowChain) _IconActionButton( icon: Icons.reply, onPressed: widget.onChain, ), ], ); } } /// Icon-only action button with large touch target. class _IconActionButton extends StatelessWidget { final IconData icon; final IconData? activeIcon; final VoidCallback? onPressed; final bool isActive; final bool isLoading; final Color? activeColor; const _IconActionButton({ required this.icon, this.activeIcon, this.onPressed, this.isActive = false, this.isLoading = false, this.activeColor, }); @override Widget build(BuildContext context) { final effectiveActiveColor = activeColor ?? AppTheme.brightNavy; final effectiveDefaultColor = AppTheme.royalPurple; final color = isActive ? effectiveActiveColor : effectiveDefaultColor; final displayIcon = isActive && activeIcon != null ? activeIcon! : icon; return AnimatedOpacity( opacity: isLoading ? 0.5 : 1.0, duration: const Duration(milliseconds: 200), child: IconButton( onPressed: onPressed, iconSize: 22.0, padding: const EdgeInsets.all(8.0), constraints: const BoxConstraints( minWidth: 44, minHeight: 44, ), icon: Icon( displayIcon, size: 22.0, color: color, ), ), ); } }