2323import 'package:flutter/services.dart' ;
2424import 'package:flutter/widgets.dart' ;
2525
26+ import 'spin_controller.dart' ;
2627import 'spin_formatter.dart' ;
2728
2829// ignore_for_file: public_member_api_docs
@@ -43,98 +44,86 @@ abstract class BaseSpinBox extends StatefulWidget {
4344 VoidCallback ? get afterChange;
4445 bool get readOnly;
4546 FocusNode ? get focusNode;
47+ SpinController ? get controller;
4648}
4749
4850mixin SpinBoxMixin <T extends BaseSpinBox > on State <T > {
49- late double _value ;
50- late double _cachedValue ;
51+ late final SpinController _controller ;
52+ late final TextEditingController _editor ;
5153 late final FocusNode _focusNode;
52- late final TextEditingController _controller;
5354
54- double get value => _value ;
55- bool get hasFocus => _focusNode.hasFocus ;
55+ SpinController get controller => _controller ;
56+ TextEditingController get editor => _editor ;
5657 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- }
58+ TextInputFormatter get formatter => SpinFormatter (_controller);
6559
6660 Map <ShortcutActivator , VoidCallback > get bindings {
6761 return {
6862 // ### TODO: use SingleActivator fixed in Flutter 2.10+
6963 // https://github.com/flutter/flutter/issues/92717
70- LogicalKeySet (LogicalKeyboardKey .arrowUp): _stepUp ,
71- LogicalKeySet (LogicalKeyboardKey .arrowDown): _stepDown ,
72- LogicalKeySet (LogicalKeyboardKey .pageUp): _pageStepUp ,
73- LogicalKeySet (LogicalKeyboardKey .pageDown): _pageStepDown ,
64+ LogicalKeySet (LogicalKeyboardKey .arrowUp): _controller.stepUp ,
65+ LogicalKeySet (LogicalKeyboardKey .arrowDown): _controller.stepDown ,
66+ LogicalKeySet (LogicalKeyboardKey .pageUp): _controller.pageStepUp ,
67+ LogicalKeySet (LogicalKeyboardKey .pageDown): _controller.pageStepDown ,
7468 };
7569 }
7670
7771 @override
7872 void initState () {
7973 super .initState ();
80- _value = widget.value;
81- _cachedValue = widget.value;
82- _controller = TextEditingController (text: _formatText (_value));
83- _controller.addListener (_updateValue);
74+ _controller = widget.controller ??
75+ SpinController (
76+ min: widget.min,
77+ max: widget.max,
78+ step: widget.step,
79+ pageStep: widget.pageStep,
80+ value: widget.value,
81+ decimals: widget.decimals,
82+ digits: widget.digits,
83+ canChange: widget.canChange,
84+ beforeChange: widget.beforeChange,
85+ afterChange: widget.afterChange,
86+ );
87+ _controller.addListener (_handleValueChange);
88+ _editor = TextEditingController (text: _controller.format (widget.value));
89+ _editor.addListener (_handleTextChange);
8490 _focusNode = widget.focusNode ?? FocusNode ();
85- _focusNode.addListener (_handleFocusChanged );
91+ _focusNode.addListener (_handleFocusChange );
8692 }
8793
8894 @override
8995 void dispose () {
90- _focusNode.removeListener (_handleFocusChanged );
96+ _focusNode.removeListener (_handleFocusChange );
9197 if (widget.focusNode == null ) {
9298 _focusNode.dispose ();
9399 }
94- _controller.dispose ();
100+ _controller.removeListener (_handleValueChange);
101+ if (widget.controller == null ) {
102+ _controller.dispose ();
103+ }
104+ _editor.dispose ();
95105 super .dispose ();
96106 }
97107
98- void _stepUp () => setValue (value + widget.step);
99- void _stepDown () => setValue (value - widget.step);
100-
101- void _pageStepUp () => setValue (value + widget.pageStep);
102- void _pageStepDown () => setValue (value - widget.pageStep);
103-
104- void _updateValue () {
105- final v = _parseValue (_controller.text);
106- if (v == _value) return ;
107-
108- if (widget.canChange? .call (v) == false ) {
109- controller.text = _formatText (_cachedValue);
110- setState (() {
111- _value = _cachedValue;
112- });
113- return ;
114- }
115-
116- setState (() => _value = v);
117- widget.onChanged? .call (v);
108+ void _handleValueChange () {
109+ widget.onChanged? .call (_controller.value);
110+ setState (() => _updateText (_controller.value));
118111 }
119112
120- void setValue (double v) {
121- final newValue = v.clamp (widget.min, widget.max);
122- if (newValue == value) return ;
123-
124- if (widget.canChange? .call (newValue) == false ) return ;
125-
126- widget.beforeChange? .call ();
127- setState (() => _updateController (value, newValue));
128- widget.afterChange? .call ();
113+ void _handleTextChange () {
114+ final value = _controller.parse (_editor.text);
115+ if (value != null && value >= controller.min && value <= controller.max) {
116+ _controller.value = value;
117+ }
129118 }
130119
131- void _updateController ( double oldValue, double newValue) {
132- final text = _formatText (newValue);
133- final selection = _controller .selection;
134- final oldOffset = value.isNegative ? 1 : 0 ;
135- final newOffset = _parseValue (text).isNegative ? 1 : 0 ;
120+ void _updateText ( double newValue) {
121+ final text = _controller. format (newValue);
122+ final selection = _editor .selection;
123+ final oldOffset = _controller. value.isNegative ? 1 : 0 ;
124+ final newOffset = _controller. parse (text)? .isNegative == true ? 1 : 0 ;
136125
137- _controller .value = _controller .value.copyWith (
126+ _editor .value = _editor .value.copyWith (
138127 text: text,
139128 selection: selection.copyWith (
140129 baseOffset: selection.baseOffset - oldOffset + newOffset,
@@ -144,37 +133,36 @@ mixin SpinBoxMixin<T extends BaseSpinBox> on State<T> {
144133 }
145134
146135 @protected
147- void fixupValue (String value) {
148- final v = _parseValue (value);
149- if (value.isEmpty || (v < widget.min || v > widget.max)) {
150- // will trigger notify to _updateValue()
151- _controller.text = _formatText (_cachedValue);
152- } else {
153- _cachedValue = _value;
136+ void fixupValue (String text) {
137+ final v = _controller.parse (text);
138+ if (v == null ) {
139+ _editor.text = _controller.format (_controller.value);
140+ } else if (v < _controller.min || v > _controller.max) {
141+ _controller.value = v.clamp (_controller.min, _controller.max);
154142 }
155143 }
156144
157- void _handleFocusChanged () {
158- if (hasFocus) {
145+ void _handleFocusChange () {
146+ if (focusNode. hasFocus) {
159147 setState (_selectAll);
160148 } else {
161- fixupValue (_controller .text);
149+ fixupValue (_editor .text);
162150 }
163151 }
164152
165153 void _selectAll () {
166- _controller .selection = _controller .selection
167- .copyWith (baseOffset: 0 , extentOffset: _controller .text.length);
154+ _editor .selection = _editor .selection
155+ .copyWith (baseOffset: 0 , extentOffset: _editor .text.length);
168156 }
169157
170158 @override
171159 void didUpdateWidget (T oldWidget) {
172160 super .didUpdateWidget (oldWidget);
173161 if (oldWidget.value != widget.value) {
174- _controller .removeListener (_updateValue );
175- _value = _cachedValue = widget.value;
176- _updateController (oldWidget.value, widget.value);
177- _controller .addListener (_updateValue );
162+ _editor .removeListener (_handleTextChange );
163+ _controller.value = widget.value;
164+ _updateText ( widget.value);
165+ _editor .addListener (_handleTextChange );
178166 }
179167 }
180168}
0 commit comments