11library load_switch;
22
33import 'package:flutter/material.dart' ;
4+ import 'package:load_switch/src/load_switch_controller.dart' ;
45import 'package:load_switch/src/spin_styles.dart' ;
56import 'package:load_switch/src/spinner.dart' ;
67
78class LoadSwitch extends StatefulWidget {
89 const LoadSwitch ({
9- required this .value,
10- required this .future,
11- required this .onChange,
12- required this .onTap,
10+ this .value,
11+ this .future,
12+ this .onChange,
13+ this .onTap,
14+ this .controller,
1315 this .style = SpinStyle .material,
1416 this .onError,
1517 this .width = 95 ,
@@ -23,13 +25,26 @@ class LoadSwitch extends StatefulWidget {
2325 this .curveIn,
2426 this .curveOut,
2527 this .isLoading,
26- this .isActive = true ,
28+ this .isActive,
2729 Key ? key,
2830 }) : assert (width >= height, "Width can't be less than the height." ),
2931 assert (thumbSizeRatio > 0 && thumbSizeRatio <= 1 ,
3032 "Thumb size ratio must be between 0 and 1." ),
33+ assert (
34+ (controller == null &&
35+ value != null &&
36+ future != null &&
37+ onChange != null &&
38+ onTap != null ) ||
39+ (controller != null ),
40+ "Either provide a controller or all of value, future, onChange, and onTap" ),
3141 super (key: key);
3242
43+ /// Controller to manage the switch state programmatically.
44+ ///
45+ /// If provided, this controller takes precedence over the other state-related properties.
46+ final LoadSwitchController ? controller;
47+
3348 /// The width of the switch. Must be greater or equal to height. Defaults to 95.
3449 final double width;
3550
@@ -39,7 +54,7 @@ class LoadSwitch extends StatefulWidget {
3954 /// The main [Future] with [bool] return type, which triggers when tapping the switch.
4055 ///
4156 /// The returned value represents the new state of the switch.
42- final Future <bool > Function () future;
57+ final Future <bool > Function ()? future;
4358
4459 /// The width of the loading spinner. Defaults to 2.
4560 final double spinStrokeWidth;
@@ -48,13 +63,13 @@ class LoadSwitch extends StatefulWidget {
4863 final Color Function (bool value)? spinColor;
4964
5065 /// The callback when [future] has finished loading. Returns the response [bool] of the [future] .
51- final Function (bool ) onChange;
66+ final Function (bool )? onChange;
5267
5368 /// Tap callback which returns the current value of the switch.
54- final Function (bool ) onTap;
69+ final Function (bool )? onTap;
5570
5671 /// Current value of the switch.
57- final bool value;
72+ final bool ? value;
5873
5974 /// The duration of the switch animation. Defaults to 250ms.
6075 final Duration ? animationDuration;
@@ -65,7 +80,7 @@ class LoadSwitch extends StatefulWidget {
6580 /// The curve of the switch animation when going out loading state.
6681 final Curve ? curveOut;
6782
68- /// The decoration of the thumb .
83+ /// The decoration of the switch .
6984 final Decoration Function (bool value, bool isActive)? switchDecoration;
7085
7186 /// The decoration of the thumb.
@@ -75,7 +90,7 @@ class LoadSwitch extends StatefulWidget {
7590 final bool ? isLoading;
7691
7792 /// Whether the toggle is active or not. If null, the switch will be active.
78- final bool isActive;
93+ final bool ? isActive;
7994
8095 /// The ratio of the thumb size to the switch size. Defaults to 1.
8196 final double thumbSizeRatio;
@@ -85,49 +100,107 @@ class LoadSwitch extends StatefulWidget {
85100
86101 /// The style of the loading spinner. Defaults to [SpinStyle.material] .
87102 final SpinStyle style;
103+
88104 @override
89105 State <LoadSwitch > createState () => _LoadSwitchState ();
90106}
91107
92108class _LoadSwitchState extends State <LoadSwitch > with TickerProviderStateMixin {
93- late ValueNotifier < bool > _isLoading ;
94- late ValueNotifier < bool > _switchValue ;
109+ late LoadSwitchController _controller ;
110+ bool _internalController = false ;
95111
96112 @override
97113 void initState () {
98114 super .initState ();
99- _switchValue = ValueNotifier (widget.value);
100- _isLoading = ValueNotifier (widget.isLoading ?? false );
115+ _initializeController ();
116+ }
117+
118+ void _initializeController () {
119+ if (widget.controller != null ) {
120+ _controller = widget.controller! ;
121+ } else {
122+ _internalController = true ;
123+ _controller = LoadSwitchController (
124+ initialValue: widget.value! ,
125+ isLoading: widget.isLoading ?? false ,
126+ isActive: widget.isActive ?? true ,
127+ );
128+ }
129+ _controller.addListener (_handleControllerChange);
130+ }
131+
132+ void _handleControllerChange () {
133+ setState (() {
134+ // Update the UI when the controller values change
135+ });
101136 }
102137
103138 @override
104139 void didUpdateWidget (LoadSwitch oldWidget) {
105140 super .didUpdateWidget (oldWidget);
106- if (oldWidget.value != widget.value ||
107- oldWidget.isLoading != widget.isLoading) {
108- _switchValue.value = widget.value;
109- _isLoading.value = widget.isLoading ?? _isLoading.value;
141+
142+ if (oldWidget.controller != widget.controller) {
143+ oldWidget.controller? .removeListener (_handleControllerChange);
144+ _controller.removeListener (_handleControllerChange);
145+
146+ if (_internalController) {
147+ _controller.dispose ();
148+ }
149+
150+ _initializeController ();
151+ } else if (_internalController) {
152+ // Update internal controller values if they changed externally
153+ if (widget.value != null && _controller.value != widget.value) {
154+ _controller.value = widget.value! ;
155+ }
156+ if (widget.isLoading != null &&
157+ _controller.isLoading != widget.isLoading) {
158+ _controller.isLoading = widget.isLoading! ;
159+ }
160+ if (widget.isActive != null && _controller.isActive != widget.isActive) {
161+ _controller.isActive = widget.isActive! ;
162+ }
110163 }
111164 }
112165
113166 Future <void > _handleToggle () async {
114- widget.onTap (_switchValue.value);
115-
116- if (_isLoading.value || ! widget.isActive) {
117- return ;
167+ if (widget.onTap != null ) {
168+ widget.onTap !(_controller.value);
118169 }
119170
120- _isLoading.value = true ;
121-
122- try {
123- _switchValue.value = await widget.future.call ();
124- } catch (error) {
125- widget.onError? .call (error);
126- } finally {
127- _isLoading.value = false ;
171+ if (! _controller.isLoading && _controller.isActive) {
172+ if (_internalController) {
173+ _controller.isLoading = true ;
174+
175+ try {
176+ _controller.value = await widget.future !();
177+ if (widget.onChange != null ) {
178+ widget.onChange !(_controller.value);
179+ }
180+ } catch (error) {
181+ if (widget.onError != null ) {
182+ widget.onError !(error);
183+ }
184+ } finally {
185+ _controller.isLoading = false ;
186+ }
187+ } else {
188+ await _controller.executeWithLoading (
189+ widget.future ?? () async => ! _controller.value,
190+ onChange: widget.onChange,
191+ onError: widget.onError,
192+ );
193+ }
128194 }
195+ }
129196
130- widget.onChange (_switchValue.value);
197+ @override
198+ void dispose () {
199+ _controller.removeListener (_handleControllerChange);
200+ if (_internalController) {
201+ _controller.dispose ();
202+ }
203+ super .dispose ();
131204 }
132205
133206 @override
@@ -136,51 +209,43 @@ class _LoadSwitchState extends State<LoadSwitch> with TickerProviderStateMixin {
136209 final collapsedWidth = switchSize;
137210 final expandedWidth = widget.width;
138211
212+ final value = _controller.value;
213+ final loading = _controller.isLoading;
214+ final isActive = _controller.isActive;
215+
139216 return GestureDetector (
140217 onTap: _handleToggle,
141- child: ValueListenableBuilder <bool >(
142- valueListenable: _isLoading,
143- builder: (_, loading, __) {
144- final curve = loading
145- ? widget.curveIn ?? Curves .easeIn
146- : widget.curveOut ?? Curves .easeInOut;
147- return AnimatedContainer (
148- width: loading ? collapsedWidth : expandedWidth,
149- height: switchSize,
150- duration:
151- widget.animationDuration ?? const Duration (milliseconds: 250 ),
152- curve: curve,
153- decoration: widget.switchDecoration
154- ? .call (_switchValue.value, widget.isActive) ??
155- _defaultSwitchDecoration (
156- _switchValue.value, widget.isActive, switchSize),
157- child: ValueListenableBuilder <bool >(
158- valueListenable: _switchValue,
159- builder: (_, value, __) {
160- final thumbSize = switchSize * widget.thumbSizeRatio;
161- final thumbPadding = (switchSize - thumbSize) / 2 ;
162- return Stack (
163- children: [
164- AnimatedAlign (
165- alignment: loading
166- ? Alignment .center
167- : value
168- ? Alignment .centerRight
169- : Alignment .centerLeft,
170- duration: widget.animationDuration ??
171- const Duration (milliseconds: 250 ),
172- curve: curve,
173- child: Padding (
174- padding: EdgeInsets .all (thumbPadding),
175- child: _buildThumb (loading, value, thumbSize, this ),
176- ),
177- ),
178- ],
179- );
180- },
218+ child: AnimatedContainer (
219+ width: loading ? collapsedWidth : expandedWidth,
220+ height: switchSize,
221+ duration: widget.animationDuration ?? const Duration (milliseconds: 250 ),
222+ curve: loading
223+ ? widget.curveIn ?? Curves .easeIn
224+ : widget.curveOut ?? Curves .easeInOut,
225+ decoration: widget.switchDecoration? .call (value, isActive) ??
226+ _defaultSwitchDecoration (value, isActive, switchSize),
227+ child: Stack (
228+ children: [
229+ AnimatedAlign (
230+ alignment: loading
231+ ? Alignment .center
232+ : value
233+ ? Alignment .centerRight
234+ : Alignment .centerLeft,
235+ duration:
236+ widget.animationDuration ?? const Duration (milliseconds: 250 ),
237+ curve: loading
238+ ? widget.curveIn ?? Curves .easeIn
239+ : widget.curveOut ?? Curves .easeInOut,
240+ child: Padding (
241+ padding: EdgeInsets .all (
242+ (switchSize - (switchSize * widget.thumbSizeRatio)) / 2 ),
243+ child: _buildThumb (
244+ loading, value, switchSize * widget.thumbSizeRatio),
245+ ),
181246 ),
182- );
183- } ,
247+ ],
248+ ) ,
184249 ),
185250 );
186251 }
@@ -205,14 +270,13 @@ class _LoadSwitchState extends State<LoadSwitch> with TickerProviderStateMixin {
205270 );
206271 }
207272
208- Widget _buildThumb (
209- bool loading, bool value, double size, TickerProvider vsync) {
273+ Widget _buildThumb (bool loading, bool value, double size) {
210274 return Container (
211275 width: size,
212276 height: size,
213- decoration: widget.thumbDecoration? .call (value, widget .isActive) ??
277+ decoration: widget.thumbDecoration? .call (value, _controller .isActive) ??
214278 BoxDecoration (
215- color: loading ? Colors .white : Colors .white,
279+ color: Colors .white,
216280 shape: BoxShape .circle,
217281 boxShadow: [
218282 BoxShadow (
@@ -226,7 +290,7 @@ class _LoadSwitchState extends State<LoadSwitch> with TickerProviderStateMixin {
226290 child: loading
227291 ? buildSpinner (
228292 style: widget.style,
229- vsync: vsync ,
293+ vsync: this ,
230294 value: value,
231295 size: widget.height,
232296 width: widget.spinStrokeWidth,
0 commit comments