Skip to content

Commit efed595

Browse files
committed
WIP: controller
1 parent 636f1b7 commit efed595

File tree

7 files changed

+241
-172
lines changed

7 files changed

+241
-172
lines changed

lib/src/base_spin_box.dart

Lines changed: 62 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import 'package:flutter/services.dart';
2424
import 'package:flutter/widgets.dart';
2525

26+
import 'spin_controller.dart';
2627
import 'spin_formatter.dart';
2728

2829
// ignore_for_file: public_member_api_docs
@@ -38,30 +39,20 @@ abstract class BaseSpinBox extends StatefulWidget {
3839
int get decimals;
3940
int get digits;
4041
ValueChanged<double>? get onChanged;
41-
bool Function(double value)? get canChange;
42-
VoidCallback? get beforeChange;
43-
VoidCallback? get afterChange;
4442
bool get readOnly;
4543
FocusNode? get focusNode;
44+
SpinController? get controller;
4645
}
4746

4847
mixin SpinBoxMixin<T extends BaseSpinBox> on State<T> {
49-
late double _value;
50-
late double _cachedValue;
48+
late final SpinController _controller;
49+
late final TextEditingController _editor;
5150
late final FocusNode _focusNode;
52-
late final TextEditingController _controller;
5351

54-
double get value => _value;
55-
bool get hasFocus => _focusNode.hasFocus;
52+
SpinController get controller => _controller;
53+
TextEditingController get editor => _editor;
5654
FocusNode get focusNode => _focusNode;
57-
TextEditingController get controller => _controller;
58-
SpinFormatter get formatter => SpinFormatter(
59-
min: widget.min, max: widget.max, decimals: widget.decimals);
60-
61-
static double _parseValue(String text) => double.tryParse(text) ?? 0;
62-
String _formatText(double value) {
63-
return value.toStringAsFixed(widget.decimals).padLeft(widget.digits, '0');
64-
}
55+
SpinFormatter get formatter => SpinFormatter(_controller);
6556

6657
Map<ShortcutActivator, VoidCallback> get bindings {
6758
return {
@@ -76,67 +67,67 @@ mixin SpinBoxMixin<T extends BaseSpinBox> on State<T> {
7667
};
7768
}
7869

70+
String _formatValue(double value) {
71+
return formatter.formatValue(value,
72+
decimals: widget.decimals, digits: widget.digits);
73+
}
74+
7975
@override
8076
void initState() {
8177
super.initState();
82-
_value = widget.value;
83-
_cachedValue = widget.value;
84-
_controller = TextEditingController(text: _formatText(_value));
85-
_controller.addListener(_updateValue);
78+
_controller = widget.controller ??
79+
SpinController(
80+
min: widget.min,
81+
max: widget.max,
82+
value: widget.value,
83+
decimals: widget.decimals,
84+
);
85+
_controller.addListener(_handleValueChange);
86+
_editor = TextEditingController(text: _formatValue(widget.value));
87+
_editor.addListener(_handleTextChange);
8688
_focusNode = widget.focusNode ?? FocusNode();
87-
_focusNode.addListener(_handleFocusChanged);
89+
_focusNode.addListener(_handleFocusChange);
8890
}
8991

9092
@override
9193
void dispose() {
92-
_focusNode.removeListener(_handleFocusChanged);
94+
_focusNode.removeListener(_handleFocusChange);
9395
if (widget.focusNode == null) {
9496
_focusNode.dispose();
9597
}
96-
_controller.dispose();
98+
_controller.removeListener(_handleValueChange);
99+
if (widget.controller == null) {
100+
_controller.dispose();
101+
}
102+
_editor.dispose();
97103
super.dispose();
98104
}
99105

100-
void _stepUp() => setValue(value + widget.step);
101-
void _stepDown() => setValue(value - widget.step);
102-
103-
void _pageStepUp() => setValue(value + widget.pageStep!);
104-
void _pageStepDown() => setValue(value - widget.pageStep!);
106+
void _stepUp() => controller.value += widget.step;
107+
void _stepDown() => controller.value -= widget.step;
105108

106-
void _updateValue() {
107-
final v = _parseValue(_controller.text);
108-
if (v == _value) return;
109+
void _pageStepUp() => controller.value += widget.pageStep!;
110+
void _pageStepDown() => controller.value -= widget.pageStep!;
109111

110-
if (widget.canChange?.call(v) == false) {
111-
controller.text = _formatText(_cachedValue);
112-
setState(() {
113-
_value = _cachedValue;
114-
});
115-
return;
116-
}
117-
118-
setState(() => _value = v);
119-
widget.onChanged?.call(v);
112+
void _handleValueChange() {
113+
widget.onChanged?.call(_controller.value);
114+
setState(() => _updateText(_controller.value));
120115
}
121116

122-
void setValue(double v) {
123-
final newValue = v.clamp(widget.min, widget.max);
124-
if (newValue == value) return;
125-
126-
if (widget.canChange?.call(newValue) == false) return;
127-
128-
widget.beforeChange?.call();
129-
setState(() => _updateController(value, newValue));
130-
widget.afterChange?.call();
117+
void _handleTextChange() {
118+
final value = _controller.parse(_editor.text);
119+
if (value != null && value >= controller.min && value <= controller.max) {
120+
_controller.value = value;
121+
}
131122
}
132123

133-
void _updateController(double oldValue, double newValue) {
134-
final text = _formatText(newValue);
135-
final selection = _controller.selection;
136-
final oldOffset = value.isNegative ? 1 : 0;
137-
final newOffset = _parseValue(text).isNegative ? 1 : 0;
124+
void _updateText(double value) {
125+
final text = _formatValue(value);
126+
final selection = _editor.selection;
127+
final oldOffset = _controller.value.isNegative ? 1 : 0;
128+
final newOffset = _controller.parse(text)?.isNegative == true ? 1 : 0;
138129

139-
_controller.value = _controller.value.copyWith(
130+
_editor.value = _editor.value.copyWith(
140131
text: text,
141132
selection: selection.copyWith(
142133
baseOffset: selection.baseOffset - oldOffset + newOffset,
@@ -146,37 +137,36 @@ mixin SpinBoxMixin<T extends BaseSpinBox> on State<T> {
146137
}
147138

148139
@protected
149-
void fixupValue(String value) {
150-
final v = _parseValue(value);
151-
if (value.isEmpty || (v < widget.min || v > widget.max)) {
152-
// will trigger notify to _updateValue()
153-
_controller.text = _formatText(_cachedValue);
154-
} else {
155-
_cachedValue = _value;
140+
void fixupValue(String text) {
141+
final value = _controller.parse(text);
142+
if (value == null) {
143+
_editor.text = _formatValue(_controller.value);
144+
} else if (value < _controller.min || value > _controller.max) {
145+
_controller.value = value.clamp(_controller.min, _controller.max);
156146
}
157147
}
158148

159-
void _handleFocusChanged() {
160-
if (hasFocus) {
149+
void _handleFocusChange() {
150+
if (focusNode.hasFocus) {
161151
setState(_selectAll);
162152
} else {
163-
fixupValue(_controller.text);
153+
fixupValue(_editor.text);
164154
}
165155
}
166156

167157
void _selectAll() {
168-
_controller.selection = _controller.selection
169-
.copyWith(baseOffset: 0, extentOffset: _controller.text.length);
158+
_editor.selection = _editor.selection
159+
.copyWith(baseOffset: 0, extentOffset: _editor.text.length);
170160
}
171161

172162
@override
173163
void didUpdateWidget(T oldWidget) {
174164
super.didUpdateWidget(oldWidget);
175165
if (oldWidget.value != widget.value) {
176-
_controller.removeListener(_updateValue);
177-
_value = _cachedValue = widget.value;
178-
_updateController(oldWidget.value, widget.value);
179-
_controller.addListener(_updateValue);
166+
_editor.removeListener(_handleTextChange);
167+
_controller.value = widget.value;
168+
_updateText(widget.value);
169+
_editor.addListener(_handleTextChange);
180170
}
181171
}
182172
}

lib/src/cupertino/spin_box.dart

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import 'package:flutter/cupertino.dart';
2424

2525
import '../base_spin_box.dart';
26+
import '../spin_controller.dart';
2627
import 'spin_button.dart';
2728

2829
part 'third_party/default_rounded_border.dart';
@@ -80,10 +81,8 @@ class CupertinoSpinBox extends BaseSpinBox {
8081
this.enableInteractiveSelection = true,
8182
this.spacing = 8,
8283
this.onChanged,
83-
this.canChange,
84-
this.beforeChange,
85-
this.afterChange,
8684
this.focusNode,
85+
this.controller,
8786
}) : assert(min <= max),
8887
keyboardType = keyboardType ??
8988
TextInputType.numberWithOptions(
@@ -199,18 +198,13 @@ class CupertinoSpinBox extends BaseSpinBox {
199198
@override
200199
final FocusNode? focusNode;
201200

202-
/// Called when the user has changed the value.
203-
@override
204-
final ValueChanged<double>? onChanged;
205-
201+
/// Controls the spinbox.
206202
@override
207-
final bool Function(double value)? canChange;
208-
209-
@override
210-
final VoidCallback? beforeChange;
203+
final SpinController? controller;
211204

205+
/// Called when the user has changed the value.
212206
@override
213-
final VoidCallback? afterChange;
207+
final ValueChanged<double>? onChanged;
214208

215209
/// See [CupertinoTextField.enabled].
216210
final bool enabled;
@@ -267,7 +261,7 @@ class _CupertinoSpinBoxState extends State<CupertinoSpinBox> with SpinBoxMixin {
267261
final textField = CallbackShortcuts(
268262
bindings: bindings,
269263
child: CupertinoTextField(
270-
controller: controller,
264+
controller: editor,
271265
style: widget.textStyle,
272266
textAlign: widget.textAlign,
273267
keyboardType: widget.keyboardType,
@@ -315,19 +309,19 @@ class _CupertinoSpinBoxState extends State<CupertinoSpinBox> with SpinBoxMixin {
315309
final incrementButton = CupertinoSpinButton(
316310
step: widget.step,
317311
icon: widget.incrementIcon,
318-
enabled: widget.enabled && value < widget.max,
312+
enabled: widget.enabled && controller.value < controller.max,
319313
interval: widget.interval,
320314
acceleration: widget.acceleration,
321-
onStep: (step) => setValue(value + step),
315+
onStep: (step) => controller.value += step,
322316
);
323317

324318
final decrementButton = CupertinoSpinButton(
325319
step: widget.step,
326320
icon: widget.decrementIcon,
327-
enabled: widget.enabled && value > widget.min,
321+
enabled: widget.enabled && controller.value > controller.min,
328322
interval: widget.interval,
329323
acceleration: widget.acceleration,
330-
onStep: (step) => setValue(value - step),
324+
onStep: (step) => controller.value -= step,
331325
);
332326

333327
if (isHorizontal) {

0 commit comments

Comments
 (0)