dev
dart 155 lines 5.05 KB
Raw
1 import 'package:cake_wallet/typography.dart';
2 import 'package:flutter/material.dart';
3 import 'package:flutter/services.dart';
4
5 class NumberTextField extends StatefulWidget {
6 final TextEditingController? controller;
7 final FocusNode? focusNode;
8 final int min;
9 final int max;
10 final int step;
11 final double arrowsWidth;
12 final double arrowsHeight;
13 final EdgeInsets contentPadding;
14 final double borderWidth;
15 final ValueChanged<int?>? onChanged;
16
17 const NumberTextField({
18 Key? key,
19 this.controller,
20 this.focusNode,
21 this.min = 0,
22 this.max = 999,
23 this.step = 1,
24 this.arrowsWidth = 24,
25 this.arrowsHeight = kMinInteractiveDimension,
26 this.contentPadding = const EdgeInsets.symmetric(horizontal: 8),
27 this.borderWidth = 2,
28 this.onChanged,
29 }) : super(key: key);
30
31 @override
32 State<StatefulWidget> createState() => _NumberTextFieldState();
33 }
34
35 class _NumberTextFieldState extends State<NumberTextField> {
36 late TextEditingController _controller;
37 late FocusNode _focusNode;
38 bool _canGoUp = false;
39 bool _canGoDown = false;
40
41 @override
42 void initState() {
43 super.initState();
44 _controller = widget.controller ?? TextEditingController();
45 _focusNode = widget.focusNode ?? FocusNode();
46 _updateArrows(int.tryParse(_controller.text));
47 }
48
49 @override
50 void didUpdateWidget(covariant NumberTextField oldWidget) {
51 super.didUpdateWidget(oldWidget);
52 _controller = widget.controller ?? _controller;
53 _focusNode = widget.focusNode ?? _focusNode;
54 _updateArrows(int.tryParse(_controller.text));
55 }
56
57 @override
58 Widget build(BuildContext context) => TextField(
59 style: Theme.of(context).textTheme.titleMedium!,
60 enableInteractiveSelection: false,
61 textAlign: TextAlign.center,
62 textAlignVertical: TextAlignVertical.bottom,
63 controller: _controller,
64 focusNode: _focusNode,
65 textInputAction: TextInputAction.done,
66 keyboardType: TextInputType.number,
67 maxLength: widget.max.toString().length + (widget.min.isNegative ? 1 : 0),
68 decoration: InputDecoration(
69 border: InputBorder.none,
70 contentPadding: EdgeInsets.all(0),
71 fillColor: Colors.transparent,
72 counterText: '',
73 isDense: true,
74 filled: true,
75 suffixIconConstraints: BoxConstraints(minWidth: 0, minHeight: 0),
76 prefixIconConstraints: BoxConstraints(minWidth: 0, minHeight: 0),
77 prefixIcon: Material(
78 type: MaterialType.transparency,
79 child: InkWell(
80 child: Container(
81 width: widget.arrowsWidth,
82 alignment: Alignment.bottomCenter,
83 child: Icon(Icons.keyboard_arrow_left_outlined, size: widget.arrowsWidth)),
84 onTap: _canGoDown ? () => _update(false) : null)),
85 suffixIcon: Material(
86 type: MaterialType.transparency,
87 child: InkWell(
88 child: Container(
89 width: widget.arrowsWidth,
90 alignment: Alignment.bottomCenter,
91 child: Icon(Icons.keyboard_arrow_right_outlined, size: widget.arrowsWidth)),
92 onTap: _canGoUp ? () => _update(true) : null))),
93 maxLines: 1,
94 onChanged: (value) {
95 final intValue = int.tryParse(value);
96 widget.onChanged?.call(intValue);
97 _updateArrows(intValue);
98 },
99 inputFormatters: [_NumberTextInputFormatter(widget.min, widget.max)]);
100
101 void _update(bool up) {
102 var intValue = int.tryParse(_controller.text);
103 intValue == null ? intValue = widget.min : intValue += up ? widget.step : -widget.step;
104 intValue = intValue.clamp(widget.min, widget.max); // Ensure intValue is within range
105 _controller.text = intValue.toString();
106
107 // Manually call the onChanged callback after updating the controller's text
108 widget.onChanged?.call(intValue);
109
110 _updateArrows(intValue);
111 _focusNode.requestFocus();
112 }
113
114 void _updateArrows(int? value) {
115 final canGoUp = value == null || value < widget.max;
116 final canGoDown = value == null || value > widget.min;
117 if (_canGoUp != canGoUp || _canGoDown != canGoDown)
118 setState(() {
119 _canGoUp = canGoUp;
120 _canGoDown = canGoDown;
121 });
122 }
123 }
124
125 class _NumberTextInputFormatter extends TextInputFormatter {
126 final int min;
127 final int max;
128
129 _NumberTextInputFormatter(this.min, this.max);
130
131 @override
132 TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
133 if (const ['-', ''].contains(newValue.text)) return newValue;
134 final intValue = int.tryParse(newValue.text);
135 if (intValue == null) return oldValue;
136
137 if (intValue < min) {
138 final text = min.toString();
139 return TextEditingValue(
140 text: text,
141 selection: TextSelection.collapsed(offset: text.length),
142 );
143 }
144
145 if (intValue > max) {
146 final text = max.toString();
147 return TextEditingValue(
148 text: text,
149 selection: TextSelection.collapsed(offset: text.length),
150 );
151 }
152
153 return newValue.copyWith(text: intValue.toString());
154 }
155 }