Line data Source code
1 : library rate;
2 :
3 : import 'package:flutter/material.dart';
4 :
5 : typedef IconBuilder = Icon Function(double value, int index);
6 :
7 : class Rate extends StatefulWidget {
8 : /// Allows half a start to be selectable, like 2.5 stars. Defaults to `false`
9 : final bool allowHalf;
10 :
11 : /// Allows clearing if clicked in the same points. Defaults to `true`
12 : final bool allowClear;
13 :
14 : /// If read only, click is blocked
15 : final bool readOnly;
16 :
17 : /// Size of the icon
18 : final double iconSize;
19 :
20 : /// Color of the icon
21 : final Color color;
22 :
23 : /// Initial value, defaults to `0`
24 : final double initialValue;
25 :
26 : /// Function called whenever the rating changes
27 : final void Function(double value)? onChange;
28 :
29 : /// Custom icon builder, in case you need something more customizable
30 : final IconBuilder? iconBuilder;
31 :
32 1 : const Rate({
33 : Key? key,
34 : this.allowHalf = false,
35 : this.allowClear = true,
36 : this.readOnly = false,
37 : this.iconSize = 24,
38 : this.color = Colors.yellow,
39 : this.initialValue = 0.0,
40 : this.onChange,
41 : this.iconBuilder,
42 : }) : super(key: key); // coverage:ignore-line
43 :
44 1 : @override
45 1 : State<Rate> createState() => _RateState();
46 : }
47 :
48 : class _RateState extends State<Rate> {
49 : double _value = 0;
50 : double? _hoverValue;
51 :
52 1 : @override
53 : void initState() {
54 1 : super.initState();
55 3 : _value = widget.initialValue;
56 : }
57 :
58 1 : void _valueChangeAction(double value) {
59 4 : final newValue = widget.allowClear && _value == value ? 0.0 : value;
60 :
61 1 : if (mounted) {
62 3 : setState(() => _value = newValue);
63 : }
64 :
65 2 : widget.onChange?.call(newValue);
66 : }
67 :
68 1 : @override
69 : Widget build(BuildContext context) {
70 1 : return Row(
71 : mainAxisSize: MainAxisSize.min,
72 1 : children: [
73 1 : _buildStar(0),
74 1 : _buildStar(1),
75 1 : _buildStar(2),
76 1 : _buildStar(3),
77 1 : _buildStar(4),
78 : ],
79 : );
80 : }
81 :
82 1 : Widget _buildStar(int index) {
83 : var icon =
84 3 : widget.iconBuilder?.call(_value, index) ?? _defaultIconBuilder(index);
85 :
86 1 : final iconSize = icon.size ?? widget.iconSize;
87 :
88 1 : return GestureDetector(
89 2 : key: Key('star_rate_$index'),
90 2 : onTapDown: widget.readOnly
91 : ? null
92 1 : : (details) {
93 5 : if (details.localPosition.dx < ((iconSize / 2) + 1) &&
94 2 : widget.allowHalf) {
95 2 : _valueChangeAction(index + 0.5);
96 : } else {
97 2 : _valueChangeAction(index + 1);
98 : }
99 : },
100 1 : child: MouseRegion(
101 2 : cursor: widget.readOnly ? MouseCursor.defer : SystemMouseCursors.click,
102 2 : onHover: widget.readOnly
103 : ? null
104 1 : : (event) {
105 5 : if (event.localPosition.dx < ((iconSize / 2) + 1) &&
106 2 : widget.allowHalf) {
107 4 : setState(() => _hoverValue = index + 0.5);
108 : } else {
109 4 : setState(() => _hoverValue = index + 1);
110 : }
111 : },
112 2 : onExit: widget.readOnly
113 : ? null
114 1 : : (_) {
115 2 : setState(() {
116 1 : _hoverValue = null;
117 : });
118 : },
119 : child: icon,
120 : ),
121 : );
122 : }
123 :
124 1 : Icon _defaultIconBuilder(int index) {
125 4 : var icon = (_hoverValue ?? _value) > index.toDouble()
126 : ? Icons.star
127 : : Icons.star_border;
128 :
129 6 : if (widget.allowHalf && (_hoverValue ?? _value) == (index + 0.5)) {
130 : icon = Icons.star_half;
131 : }
132 :
133 5 : return Icon(icon, size: widget.iconSize, color: widget.color);
134 : }
135 : }
|