Main coves client
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}