diff --git a/lib/screens/community/community_feed_screen.dart b/lib/screens/community/community_feed_screen.dart index e3d7d8c..b6025c6 100644 --- a/lib/screens/community/community_feed_screen.dart +++ b/lib/screens/community/community_feed_screen.dart @@ -5,8 +5,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; -import 'package:share_plus/share_plus.dart'; - import '../../constants/app_colors.dart'; import '../../models/community.dart'; import '../../models/post.dart'; @@ -20,6 +18,7 @@ import '../../utils/error_messages.dart'; import '../../widgets/community_header.dart'; import '../../widgets/loading_error_states.dart'; import '../../widgets/post_card.dart'; +import '../../widgets/share_button.dart'; /// Screen displaying a community's feed with header info /// @@ -390,32 +389,6 @@ class _CommunityFeedScreenState extends State { await _loadFeed(refresh: true); } - Future _handleShare() async { - if (_community == null) return; - - final handle = _community!.handle; - final communityUrl = 'https://coves.social/community/$handle'; - final subject = - 'Check out ${_community!.displayName ?? _community!.name} on Coves'; - - try { - await Share.share(communityUrl, subject: subject); - } on Exception catch (e) { - if (kDebugMode) { - debugPrint('Error sharing community: $e'); - } - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Failed to share. Please try again.'), - behavior: SnackBarBehavior.floating, - backgroundColor: AppColors.primary, - ), - ); - } - } - } - @override Widget build(BuildContext context) { // Loading community info @@ -463,9 +436,9 @@ class _CommunityFeedScreenState extends State { ), actions: [ _buildSubscribeButton(), - IconButton( - icon: const Icon(Icons.share_outlined), - onPressed: _handleShare, + const ShareButton( + useIconButton: true, + color: AppColors.textPrimary, tooltip: 'Share Community', ), ], diff --git a/lib/screens/home/post_detail_screen.dart b/lib/screens/home/post_detail_screen.dart index 123f2eb..6740e4f 100644 --- a/lib/screens/home/post_detail_screen.dart +++ b/lib/screens/home/post_detail_screen.dart @@ -15,7 +15,7 @@ import '../../utils/display_utils.dart'; import '../../utils/error_messages.dart'; import '../../widgets/comment_thread.dart'; import '../../widgets/comments_header.dart'; -import '../../widgets/icons/share_icon.dart'; +import '../../widgets/share_button.dart'; import '../../widgets/detailed_post_view.dart'; import '../../widgets/loading_error_states.dart'; import '../../widgets/post_action_bar.dart'; @@ -413,25 +413,6 @@ class _PostDetailScreenState extends State { ); } - /// Handle share button tap - Future _handleShare() async { - // Haptic feedback is non-essential, silently fail if unsupported - try { - await HapticFeedback.lightImpact(); - } on PlatformException { - // Haptics not supported on this platform - ignore - } - - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Share feature coming soon!'), - behavior: SnackBarBehavior.floating, - ), - ); - } - } - /// Handle menu action selection Future _handleMenuAction(String action) async { // Haptic feedback is non-essential, silently fail if unsupported @@ -776,9 +757,9 @@ class _PostDetailScreenState extends State { elevation: 0, pinned: true, actions: [ - IconButton( - icon: const ShareIcon(color: AppColors.textPrimary), - onPressed: _handleShare, + const ShareButton( + useIconButton: true, + color: AppColors.textPrimary, tooltip: 'Share', ), PopupMenuButton( diff --git a/lib/screens/home/profile_screen.dart b/lib/screens/home/profile_screen.dart index ececcd9..fa92d3f 100644 --- a/lib/screens/home/profile_screen.dart +++ b/lib/screens/home/profile_screen.dart @@ -3,8 +3,6 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; -import 'package:share_plus/share_plus.dart'; - import '../../constants/app_colors.dart'; import '../../models/comment.dart'; import '../../models/user_profile.dart'; @@ -15,6 +13,7 @@ import '../../widgets/loading_error_states.dart'; import '../../widgets/post_card.dart'; import '../../widgets/primary_button.dart'; import '../../widgets/profile_header.dart'; +import '../../widgets/share_button.dart'; import 'edit_profile_screen.dart'; /// Profile screen displaying user profile with header and posts @@ -139,16 +138,6 @@ class _ProfileScreenState extends State { ); } - void _handleShare() { - final profile = context.read().profile; - if (profile == null) return; - - final handle = profile.handle; - final profileUrl = 'https://coves.social/profile/$handle'; - final subject = 'Check out ${profile.displayNameOrHandle} on Coves'; - Share.share(profileUrl, subject: subject); - } - Future _handleSignOut() async { final authProvider = context.read(); await authProvider.signOut(); @@ -254,9 +243,9 @@ class _ProfileScreenState extends State { ), tooltip: 'Edit Profile', ), - IconButton( - icon: const Icon(Icons.share_outlined), - onPressed: _handleShare, + const ShareButton( + useIconButton: true, + color: AppColors.textPrimary, tooltip: 'Share Profile', ), IconButton( diff --git a/lib/widgets/icons/share_icon.dart b/lib/widgets/icons/share_icon.dart index eb9046b..68ca1cb 100644 --- a/lib/widgets/icons/share_icon.dart +++ b/lib/widgets/icons/share_icon.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; -/// Share icon widget (arrow out of box) +/// Share icon widget (arrow pointing right) /// -/// Arrow-out-of-box icon from Bluesky's design system. -/// Uses the modified version with rounded corners for a friendlier look. +/// ArrowShareRight icon from Bluesky's design system. +/// Uses the Stroke2_Corner2_Rounded variant for a consistent look. class ShareIcon extends StatelessWidget { const ShareIcon({this.size = 18, this.color, super.key}); @@ -24,7 +24,7 @@ class ShareIcon extends StatelessWidget { /// Custom painter for share icon /// -/// SVG path data from Bluesky's ArrowOutOfBoxModified icon component +/// SVG path data from Bluesky's ArrowShareRight_Stroke2_Corner2_Rounded class _ShareIconPainter extends CustomPainter { _ShareIconPainter({required this.color}); @@ -35,54 +35,58 @@ class _ShareIconPainter extends CustomPainter { final paint = Paint() ..color = color - ..style = PaintingStyle.fill; // Always fill - paths are pre-stroked + ..style = PaintingStyle.fill; // Scale factor to fit 24x24 viewBox into widget size final scale = size.width / 24.0; canvas.scale(scale); - // ArrowOutOfBoxModified_Stroke2_Corner2_Rounded path from Bluesky - // M20 13.75a1 1 0 0 1 1 1V18a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3v-3.25 - // a1 1 0 1 1 2 0V18 a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-3.25 - // a1 1 0 0 1 1-1ZM12 3a1 1 0 0 1 .707.293 l4.5 4.5 - // a1 1 0 1 1-1.414 1.414L13 6.414v8.836a1 1 0 1 1-2 0V6.414 - // L8.207 9.207a1 1 0 1 1-1.414-1.414l4.5-4.5A1 1 0 0 1 12 3Z + // ArrowShareRight_Stroke2_Corner2_Rounded path from Bluesky + // M11.839 4.744c0-1.488 1.724-2.277 2.846-1.364l.107.094 7.66 7.256.128.134 + // c.558.652.558 1.62 0 2.272l-.128.135-7.66 7.255c-1.115 1.057-2.953.267-2.953-1.27 + // v-2.748c-3.503.055-5.417.41-6.592.97-.997.474-1.525 1.122-2.084 2.14l-.243.46 + // c-.558 1.088-2.09.583-2.08-.515l.015-.748c.111-3.68.777-6.5 2.546-8.415 + // 1.83-1.98 4.63-2.771 8.438-2.884V4.744Zm2 3.256c0 .79-.604 1.41-1.341 1.494 + // l-.149.01c-3.9.057-6.147.813-7.48 2.254-.963 1.043-1.562 2.566-1.842 4.79 + // .38-.327.826-.622 1.361-.877 1.656-.788 4.08-1.14 7.938-1.169l.153.007 + // c.754.071 1.36.704 1.36 1.491v2.675L20.884 12l-7.045-6.676V8Z - // Box bottom part final path = Path() - ..moveTo(20, 13.75) - ..cubicTo(20.552, 13.75, 21, 14.198, 21, 14.75) - ..lineTo(21, 18) - ..cubicTo(21, 19.657, 19.657, 21, 18, 21) - ..lineTo(6, 21) - ..cubicTo(4.343, 21, 3, 19.657, 3, 18) - ..lineTo(3, 14.75) - ..cubicTo(3, 14.198, 3.448, 13.75, 4, 13.75) - ..cubicTo(4.552, 13.75, 5, 14.198, 5, 14.75) - ..lineTo(5, 18) - ..cubicTo(5, 18.552, 5.448, 19, 6, 19) - ..lineTo(18, 19) - ..cubicTo(18.552, 19, 19, 18.552, 19, 18) - ..lineTo(19, 14.75) - ..cubicTo(19, 14.198, 19.448, 13.75, 20, 13.75) + // Main shape + ..moveTo(11.839, 4.744) + ..cubicTo(11.839, 3.256, 13.563, 2.467, 14.685, 3.38) + ..lineTo(14.792, 3.474) + ..lineTo(22.452, 10.73) + ..lineTo(22.58, 10.864) + ..cubicTo(23.138, 11.516, 23.138, 12.484, 22.58, 13.136) + ..lineTo(22.452, 13.271) + ..lineTo(14.792, 20.526) + ..cubicTo(13.677, 21.583, 11.839, 20.793, 11.839, 19.256) + ..lineTo(11.839, 16.508) + ..cubicTo(8.336, 16.563, 6.422, 16.918, 5.247, 17.478) + ..cubicTo(4.25, 17.952, 3.722, 18.6, 3.163, 19.618) + ..lineTo(2.92, 20.078) + ..cubicTo(2.362, 21.166, 0.83, 20.661, 0.84, 19.563) + ..lineTo(0.855, 18.815) + ..cubicTo(0.966, 15.135, 1.632, 12.315, 3.401, 10.4) + ..cubicTo(5.231, 8.42, 8.031, 7.629, 11.839, 7.516) + ..lineTo(11.839, 4.744) ..close() - // Arrow - ..moveTo(12, 3) - ..cubicTo(12.265, 3, 12.52, 3.105, 12.707, 3.293) - ..lineTo(17.207, 7.793) - ..cubicTo(17.598, 8.184, 17.598, 8.817, 17.207, 9.207) - ..cubicTo(16.816, 9.598, 16.183, 9.598, 15.793, 9.207) - ..lineTo(13, 6.414) - ..lineTo(13, 15.25) - ..cubicTo(13, 15.802, 12.552, 16.25, 12, 16.25) - ..cubicTo(11.448, 16.25, 11, 15.802, 11, 15.25) - ..lineTo(11, 6.414) - ..lineTo(8.207, 9.207) - ..cubicTo(7.816, 9.598, 7.183, 9.598, 6.793, 9.207) - ..cubicTo(6.402, 8.816, 6.402, 8.183, 6.793, 7.793) - ..lineTo(11.293, 3.293) - ..cubicTo(11.48, 3.105, 11.735, 3, 12, 3) + // Inner cutout + ..moveTo(13.839, 8) + ..cubicTo(13.839, 8.79, 13.235, 9.41, 12.498, 9.494) + ..lineTo(12.349, 9.504) + ..cubicTo(8.449, 9.561, 6.202, 10.317, 4.869, 11.758) + ..cubicTo(3.906, 12.801, 3.307, 14.324, 3.027, 16.548) + ..cubicTo(3.407, 16.221, 3.853, 15.926, 4.388, 15.671) + ..cubicTo(6.044, 14.883, 8.468, 14.531, 12.326, 14.502) + ..lineTo(12.479, 14.509) + ..cubicTo(13.233, 14.58, 13.839, 15.213, 13.839, 16) + ..lineTo(13.839, 18.675) + ..lineTo(20.884, 12) + ..lineTo(13.839, 5.324) + ..lineTo(13.839, 8) ..close(); canvas.drawPath(path, paint); diff --git a/lib/widgets/post_card_actions.dart b/lib/widgets/post_card_actions.dart index 4376260..dc19b30 100644 --- a/lib/widgets/post_card_actions.dart +++ b/lib/widgets/post_card_actions.dart @@ -13,7 +13,7 @@ import '../services/api_exceptions.dart'; import '../services/coves_api_service.dart'; import '../utils/date_time_utils.dart'; import 'icons/animated_heart_icon.dart'; -import 'icons/share_icon.dart'; +import 'share_button.dart'; import 'sign_in_dialog.dart'; /// Action buttons row for post cards @@ -338,39 +338,7 @@ class _PostCardActionsState extends State { ), // Share button - Semantics( - button: true, - label: 'Share post', - child: InkWell( - onTap: () async { - try { - await HapticFeedback.lightImpact(); - } on PlatformException { - // Haptics not supported on this platform - ignore - } - - if (!context.mounted) { - return; - } - - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Share feature coming soon!'), - behavior: SnackBarBehavior.floating, - ), - ); - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 10, - ), - child: ShareIcon( - color: AppColors.textPrimary.withValues(alpha: 0.6), - ), - ), - ), - ), + const ShareButton(tooltip: 'Share post'), ], ), diff --git a/lib/widgets/share_button.dart b/lib/widgets/share_button.dart new file mode 100644 index 0000000..5553dca --- /dev/null +++ b/lib/widgets/share_button.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import '../constants/app_colors.dart'; +import 'icons/share_icon.dart'; + +/// Standardized share button used across the app +/// +/// Displays the ArrowShareRight icon from Bluesky's design system. +/// Shows a "Share coming soon!" snackbar when tapped. +class ShareButton extends StatelessWidget { + const ShareButton({ + this.size = 18, + this.color, + this.tooltip = 'Share', + this.padding = const EdgeInsets.symmetric(horizontal: 8, vertical: 10), + this.useIconButton = false, + super.key, + }); + + /// Size of the share icon + final double size; + + /// Color of the share icon (defaults to theme icon color with 0.6 opacity) + final Color? color; + + /// Tooltip text shown on long press + final String tooltip; + + /// Padding around the icon (ignored when useIconButton is true) + final EdgeInsets padding; + + /// Whether to use IconButton style (for app bars) vs InkWell style (for cards) + final bool useIconButton; + + Future _handleTap(BuildContext context) async { + try { + await HapticFeedback.lightImpact(); + } on PlatformException { + // Haptics not supported on this platform - ignore + } + + if (!context.mounted) return; + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Share coming soon!'), + behavior: SnackBarBehavior.floating, + ), + ); + } + + @override + Widget build(BuildContext context) { + final effectiveColor = + color ?? AppColors.textPrimary.withValues(alpha: 0.6); + + if (useIconButton) { + return IconButton( + icon: ShareIcon(size: size, color: effectiveColor), + onPressed: () => _handleTap(context), + tooltip: tooltip, + ); + } + + return Semantics( + button: true, + label: tooltip, + child: InkWell( + onTap: () => _handleTap(context), + borderRadius: BorderRadius.circular(8), + child: Padding( + padding: padding, + child: ShareIcon(size: size, color: effectiveColor), + ), + ), + ); + } +}