Skip to content

Commit be22079

Browse files
committed
✨ Refactor LoadSwitch to support LoadSwitchController and improve state management
Closes #5
1 parent 7bb715a commit be22079

1 file changed

Lines changed: 143 additions & 79 deletions

File tree

lib/src/load_switch.dart

Lines changed: 143 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
library load_switch;
22

33
import 'package:flutter/material.dart';
4+
import 'package:load_switch/src/load_switch_controller.dart';
45
import 'package:load_switch/src/spin_styles.dart';
56
import 'package:load_switch/src/spinner.dart';
67

78
class 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

92108
class _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

Comments
 (0)