Line data Source code
1 : import 'package:basf_flutter_components/basf_flutter_components.dart';
2 : import 'package:flutter/material.dart';
3 :
4 : /// {@template basf_slider_button}
5 : /// A BASF-styled slider button
6 : /// {@endtemplate}
7 : class SliderButton extends StatefulWidget {
8 : /// {@macro basf_slider_button}
9 1 : const SliderButton({
10 : super.key,
11 : this.height = 70,
12 : this.width = 300,
13 : this.backgroundColor = Colors.white,
14 : this.backgroundColorEnd,
15 : this.foregroundColor,
16 : this.iconColor = Colors.white,
17 : this.shadow,
18 : this.sliderButtonContent,
19 : this.text,
20 : this.textStyle,
21 : required this.onConfirmation,
22 : this.foregroundShape,
23 : this.backgroundShape,
24 : this.stickToEnd = false,
25 : this.animationCurve,
26 : }) : assert(
27 3 : height >= 25 && width >= 250,
28 : '''Height must be be greater or equal to 25, and width greater or equal to 250''',
29 : );
30 :
31 : /// Height of the slider
32 : final double height;
33 :
34 : /// Width of the slider
35 : final double width;
36 :
37 : /// Background color of the slider
38 : final Color backgroundColor;
39 :
40 : /// Background color of the slider on the end
41 : final Color? backgroundColorEnd;
42 :
43 : /// Background color of the slider on the foreground
44 : final Color? foregroundColor;
45 :
46 : /// Icon color of the icon in the slider
47 : final Color iconColor;
48 :
49 : /// An optional slider cuntent as a widget
50 : final Widget? sliderButtonContent;
51 :
52 : /// Slider shadows
53 : final BoxShadow? shadow;
54 :
55 : /// Slider text
56 : final String? text;
57 :
58 : /// Slider text style
59 : final TextStyle? textStyle;
60 :
61 : /// Action to be performed when the slider is fully slided
62 : final VoidCallback onConfirmation;
63 :
64 : /// Shape of the slider
65 : final BorderRadius? foregroundShape;
66 :
67 : /// Shape of the slider's background
68 : final BorderRadius? backgroundShape;
69 :
70 : /// Wheter or not the slider should stick to the end of the shape on drag
71 : final bool stickToEnd;
72 :
73 : /// Curve of the slider animation
74 : final Curve? animationCurve;
75 :
76 1 : @override
77 : State<StatefulWidget> createState() {
78 1 : return _SliderButtonState();
79 : }
80 : }
81 :
82 : class _SliderButtonState extends State<SliderButton> {
83 : double _position = 0;
84 : int _duration = 0;
85 :
86 1 : @override
87 : Widget build(BuildContext context) {
88 1 : return AnimatedContainer(
89 2 : duration: Duration(milliseconds: _duration),
90 : curve: Curves.ease,
91 2 : height: widget.height,
92 2 : width: widget.width,
93 : padding: const EdgeInsets.all(5),
94 1 : decoration: BoxDecoration(
95 3 : borderRadius: widget.backgroundShape ?? BasfThemes.defaultBorderRadius,
96 2 : color: widget.backgroundColorEnd != null
97 1 : ? _calculateBackground()
98 2 : : widget.backgroundColor,
99 1 : boxShadow: [
100 2 : widget.shadow ??
101 : const BoxShadow(
102 : color: Colors.black38,
103 : offset: Offset(0, 1),
104 : blurRadius: 2,
105 : ),
106 : ],
107 : ),
108 1 : child: Stack(
109 1 : children: [
110 1 : _text(),
111 1 : _backgroundOverlay(),
112 1 : _sliderButton(),
113 : ],
114 : ),
115 : );
116 : }
117 :
118 1 : Widget _text() {
119 1 : return Center(
120 1 : child: Text(
121 2 : widget.text ?? '',
122 2 : style: widget.textStyle ??
123 : const TextStyle(
124 : color: Colors.black26,
125 : fontWeight: FontWeight.bold,
126 : ),
127 : maxLines: 2,
128 : overflow: TextOverflow.ellipsis,
129 : ),
130 : );
131 : }
132 :
133 1 : Widget _backgroundOverlay() {
134 1 : return Positioned(
135 3 : left: widget.height / 2,
136 1 : child: AnimatedContainer(
137 3 : height: widget.height - 10,
138 1 : width: _getPosition(),
139 2 : duration: Duration(milliseconds: _duration),
140 : curve: Curves.ease,
141 1 : decoration: BoxDecoration(
142 2 : borderRadius: widget.backgroundShape ??
143 4 : BorderRadius.all(Radius.circular(widget.height)),
144 2 : color: widget.backgroundColorEnd != null
145 1 : ? _calculateBackground()
146 2 : : widget.backgroundColor,
147 : ),
148 : ),
149 : );
150 : }
151 :
152 1 : Widget _sliderButton() {
153 1 : return AnimatedPositioned(
154 2 : duration: Duration(milliseconds: _duration),
155 2 : curve: widget.animationCurve ?? Curves.easeOutCubic,
156 1 : left: _getPosition(),
157 : top: 0,
158 1 : child: GestureDetector(
159 1 : onPanUpdate: _updatePosition,
160 1 : onPanEnd: _sliderReleased,
161 1 : child: Container(
162 3 : height: widget.height - 10,
163 3 : width: widget.height - 10,
164 1 : decoration: BoxDecoration(
165 : borderRadius:
166 3 : widget.foregroundShape ?? BasfThemes.defaultBorderRadius,
167 5 : color: widget.foregroundColor ?? Theme.of(context).primaryColor,
168 : ),
169 2 : child: widget.sliderButtonContent ??
170 1 : Icon(
171 : Icons.navigate_next,
172 2 : color: widget.iconColor,
173 : ),
174 : ),
175 : ),
176 : );
177 : }
178 :
179 1 : double _getPosition() {
180 2 : if (_position < 0) {
181 : return 0;
182 7 : } else if (_position > widget.width - widget.height) {
183 0 : return widget.width - widget.height;
184 : } else {
185 1 : return _position;
186 : }
187 : }
188 :
189 1 : void _updatePosition(dynamic details) {
190 1 : if (details is DragEndDetails) {
191 2 : setState(() {
192 1 : _duration = 600;
193 9 : if (widget.stickToEnd && _position > widget.width - widget.height) {
194 6 : _position = widget.width - widget.height;
195 : } else {
196 1 : _position = 0;
197 : }
198 : });
199 1 : } else if (details is DragUpdateDetails) {
200 2 : setState(() {
201 1 : _duration = 0;
202 7 : _position = details.localPosition.dx - (widget.height / 2);
203 : });
204 : }
205 : }
206 :
207 1 : void _sliderReleased(DragEndDetails details) {
208 7 : if (_position > widget.width - widget.height) {
209 3 : widget.onConfirmation();
210 : }
211 1 : _updatePosition(details);
212 : }
213 :
214 1 : Color _calculateBackground() {
215 2 : if (widget.backgroundColorEnd != null) {
216 : double percent;
217 :
218 : // calculates the percentage of the position of the slider
219 7 : if (_position > widget.width - widget.height) {
220 : percent = 1.0;
221 8 : } else if (_position / (widget.width - widget.height) > 0) {
222 7 : percent = _position / (widget.width - widget.height);
223 : } else {
224 : percent = 0.0;
225 : }
226 :
227 3 : final red = widget.backgroundColorEnd!.red;
228 3 : final green = widget.backgroundColorEnd!.green;
229 3 : final blue = widget.backgroundColorEnd!.blue;
230 :
231 1 : return Color.alphaBlend(
232 1 : Color.fromRGBO(red, green, blue, percent),
233 2 : widget.backgroundColor,
234 : );
235 : } else {
236 0 : return widget.backgroundColor;
237 : }
238 : }
239 : }
|