at main 3.8 kB view raw
1import 'package:flutter/material.dart'; 2 3/// Reply/comment icon widget 4/// 5/// Speech bubble icon from Bluesky's design system. 6/// Supports both outline and filled states. 7class ReplyIcon extends StatelessWidget { 8 const ReplyIcon({this.size = 18, this.color, this.filled = false, super.key}); 9 10 final double size; 11 final Color? color; 12 final bool filled; 13 14 @override 15 Widget build(BuildContext context) { 16 final effectiveColor = 17 color ?? Theme.of(context).iconTheme.color ?? Colors.grey; 18 19 return CustomPaint( 20 size: Size(size, size), 21 painter: _ReplyIconPainter(color: effectiveColor, filled: filled), 22 ); 23 } 24} 25 26/// Custom painter for reply/comment icon 27/// 28/// SVG path data from Bluesky's Reply icon component 29class _ReplyIconPainter extends CustomPainter { 30 _ReplyIconPainter({required this.color, required this.filled}); 31 32 final Color color; 33 final bool filled; 34 35 @override 36 void paint(Canvas canvas, Size size) { 37 final paint = 38 Paint() 39 ..color = color 40 ..style = PaintingStyle.fill; // Always fill - paths are pre-stroked 41 42 // Scale factor to fit 24x24 viewBox into widget size 43 final scale = size.width / 24.0; 44 canvas.scale(scale); 45 46 final path = Path(); 47 48 if (filled) { 49 // Filled reply icon path from Bluesky 50 // M22.002 15a4 4 0 0 1-4 4h-4.648l-4.727 3.781A1.001 1.001 0 0 1 7.002 22 51 // v-3h-1a4 4 0 0 1-4-4V7a4 4 0 0 1 4-4h12a4 4 0 0 1 4 4v8Z 52 path 53 ..moveTo(22.002, 15) 54 ..cubicTo(22.002, 17.209, 20.211, 19, 18.002, 19) 55 ..lineTo(13.354, 19) 56 ..lineTo(8.627, 22.781) 57 ..cubicTo(8.243, 23.074, 7.683, 22.808, 7.627, 22.318) 58 ..lineTo(7.002, 22) 59 ..lineTo(7.002, 19) 60 ..lineTo(6.002, 19) 61 ..cubicTo(3.793, 19, 2.002, 17.209, 2.002, 15) 62 ..lineTo(2.002, 7) 63 ..cubicTo(2.002, 4.791, 3.793, 3, 6.002, 3) 64 ..lineTo(18.002, 3) 65 ..cubicTo(20.211, 3, 22.002, 4.791, 22.002, 7) 66 ..lineTo(22.002, 15) 67 ..close(); 68 } else { 69 // Outline reply icon path from Bluesky 70 // M20.002 7a2 2 0 0 0-2-2h-12a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2 71 // a1 1 0 0 1 1 1 v1.918l3.375-2.7a1 1 0 0 1 .625-.218h5 72 // a2 2 0 0 0 2-2V7Zm2 8a4 4 0 0 1-4 4 h-4.648l-4.727 3.781 73 // A1.001 1.001 0 0 1 7.002 22v-3h-1a4 4 0 0 1-4-4V7 74 // a4 4 0 0 1 4-4h12a4 4 0 0 1 4 4v8Z 75 76 // Inner shape 77 path 78 ..moveTo(20.002, 7) 79 ..cubicTo(20.002, 5.895, 19.107, 5, 18.002, 5) 80 ..lineTo(6.002, 5) 81 ..cubicTo(4.897, 5, 4.002, 5.895, 4.002, 7) 82 ..lineTo(4.002, 15) 83 ..cubicTo(4.002, 16.105, 4.897, 17, 6.002, 17) 84 ..lineTo(8.002, 17) 85 ..cubicTo(8.554, 17, 9.002, 17.448, 9.002, 18) 86 ..lineTo(9.002, 19.918) 87 ..lineTo(12.377, 17.218) 88 ..cubicTo(12.574, 17.073, 12.813, 17, 13.002, 17) 89 ..lineTo(18.002, 17) 90 ..cubicTo(19.107, 17, 20.002, 16.105, 20.002, 15) 91 ..lineTo(20.002, 7) 92 ..close() 93 // Outer shape 94 ..moveTo(22.002, 15) 95 ..cubicTo(22.002, 17.209, 20.211, 19, 18.002, 19) 96 ..lineTo(13.354, 19) 97 ..lineTo(8.627, 22.781) 98 ..cubicTo(8.243, 23.074, 7.683, 22.808, 7.627, 22.318) 99 ..lineTo(7.002, 22) 100 ..lineTo(7.002, 19) 101 ..lineTo(6.002, 19) 102 ..cubicTo(3.793, 19, 2.002, 17.209, 2.002, 15) 103 ..lineTo(2.002, 7) 104 ..cubicTo(2.002, 4.791, 3.793, 3, 6.002, 3) 105 ..lineTo(18.002, 3) 106 ..cubicTo(20.211, 3, 22.002, 4.791, 22.002, 7) 107 ..lineTo(22.002, 15) 108 ..close(); 109 } 110 111 canvas.drawPath(path, paint); 112 } 113 114 @override 115 bool shouldRepaint(_ReplyIconPainter oldDelegate) { 116 return oldDelegate.color != color || oldDelegate.filled != filled; 117 } 118}