quantity_input.dart 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. // ignore_for_file: library_private_types_in_public_api, depend_on_referenced_packages
  2. import 'package:intl/intl.dart';
  3. import '../icon_button_widget/icon_button_widget.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:flutter/services.dart';
  6. /// quantity input type
  7. /// [int] default mask type, to format value as int
  8. /// [double] to format value as double
  9. enum QuantityInputType {
  10. int,
  11. double,
  12. }
  13. class QuantityInput extends StatefulWidget {
  14. /// Has to be an int or double depending on QuantityInputType variable
  15. final dynamic value;
  16. /// Detects changes to the input and sends value through param
  17. final Function(String) onChanged;
  18. /// The value that is incremented or decremented each time the user presses a button
  19. final dynamic step;
  20. /// The number of decimal places that can be displayed for double input
  21. final int decimalDigits;
  22. /// Set min value to be displayed in input
  23. final dynamic minValue;
  24. /// Set max value to be displayed in input
  25. final dynamic maxValue;
  26. /// The width of the textfield input
  27. final double inputWidth;
  28. /// Sets color for increment and decrement buttons
  29. final Color? buttonColor;
  30. /// Sets color for icons inside increment and decrement buttons
  31. final Color? iconColor;
  32. /// Sets label for input
  33. final String label;
  34. /// Determines if the input will be readOnly
  35. final bool readOnly;
  36. /// If set to true, the input can accept the value 0
  37. final bool acceptsZero;
  38. /// If set to true, the input can accept negative values
  39. final bool acceptsNegatives;
  40. /// Determines if the input will manage values as int or double
  41. final QuantityInputType? type;
  42. /// Sets custom InputDecoration to the widget TextFormField
  43. final InputDecoration? decoration;
  44. /// Sets elevation to increment and decrement buttons
  45. final double elevation;
  46. /// Creates a widget that can be used to manage number inputs
  47. ///
  48. /// Widget can manage integer or double values
  49. const QuantityInput(
  50. {super.key,
  51. required this.value,
  52. required this.onChanged,
  53. this.step = 1,
  54. this.decimalDigits = 1,
  55. this.buttonColor,
  56. this.iconColor,
  57. this.label = '',
  58. this.readOnly = false,
  59. this.acceptsNegatives = false,
  60. this.acceptsZero = false,
  61. this.minValue = 1,
  62. this.maxValue = 100000,
  63. this.type = QuantityInputType.int,
  64. this.inputWidth = 80,
  65. this.decoration,
  66. this.elevation = 5})
  67. : assert(decimalDigits > 0,
  68. 'Decimal digits cannot be set to zero or negative value'),
  69. assert(!(acceptsZero && acceptsNegatives),
  70. 'acceptsZero and acceptsNegatives cannot be simultaneously true');
  71. @override
  72. _QuantityInputState createState() => _QuantityInputState();
  73. }
  74. class _QuantityInputState extends State<QuantityInput> {
  75. final TextEditingController _controller = TextEditingController();
  76. @override
  77. void initState() {
  78. super.initState();
  79. _controller.text = valueFormatter(widget.value);
  80. }
  81. String valueFormatter(dynamic value) {
  82. String extraZeros = '';
  83. if (widget.decimalDigits > 0 && widget.type == QuantityInputType.double) {
  84. extraZeros = '.';
  85. extraZeros = extraZeros.padRight(widget.decimalDigits + 1, '0');
  86. }
  87. NumberFormat formatter = NumberFormat(
  88. '#,###,###,###,###,###,###,###,###,##0$extraZeros', 'en_US');
  89. return formatter.format(value);
  90. }
  91. String parseNewDouble(String value) {
  92. String fullPartOfDouble =
  93. value.substring(0, value.length - widget.decimalDigits);
  94. String decimalPartOfDouble =
  95. value.substring(value.length - widget.decimalDigits);
  96. String newFullValue = '$fullPartOfDouble.$decimalPartOfDouble';
  97. return newFullValue;
  98. }
  99. @override
  100. Widget build(BuildContext context) {
  101. return Padding(
  102. padding: const EdgeInsets.symmetric(vertical: 5),
  103. child: Column(
  104. mainAxisSize: MainAxisSize.min,
  105. mainAxisAlignment: MainAxisAlignment.start,
  106. crossAxisAlignment: CrossAxisAlignment.start,
  107. children: [
  108. Visibility(
  109. visible: widget.label.isNotEmpty,
  110. child: Padding(
  111. padding: const EdgeInsets.only(bottom: 5),
  112. child: Text(widget.label,
  113. textAlign: TextAlign.start,
  114. style: TextStyle(
  115. color: Colors.grey[500],
  116. fontSize: 13,
  117. fontWeight: FontWeight.w500)))),
  118. Row(
  119. mainAxisSize: MainAxisSize.min,
  120. mainAxisAlignment: MainAxisAlignment.start,
  121. crossAxisAlignment: CrossAxisAlignment.start,
  122. children: [
  123. IconButtonWidget(
  124. icon: Icons.remove,
  125. iconColor: widget.iconColor,
  126. buttonColor: widget.buttonColor,
  127. elevation: widget.elevation,
  128. onTap: () {
  129. dynamic currentValue = 0;
  130. if (widget.acceptsNegatives ||
  131. ((widget.value - widget.step) > 0 &&
  132. widget.type == QuantityInputType.double) ||
  133. ((widget.value - widget.step) >= 1 &&
  134. widget.type == QuantityInputType.int) ||
  135. (widget.acceptsZero &&
  136. (widget.value - widget.step) == 0)) {
  137. currentValue = widget.value - widget.step;
  138. } else {
  139. currentValue = widget.value;
  140. }
  141. String formattedValue = valueFormatter(currentValue);
  142. _controller.value = TextEditingValue(
  143. text: formattedValue,
  144. selection: TextSelection.collapsed(
  145. offset: formattedValue.length));
  146. widget.onChanged(formattedValue);
  147. }),
  148. SizedBox(
  149. height: 38,
  150. width: widget.inputWidth,
  151. child: Padding(
  152. padding: const EdgeInsets.symmetric(horizontal: 5),
  153. child: TextFormField(
  154. controller: _controller,
  155. textAlign: TextAlign.center,
  156. decoration: widget.decoration,
  157. readOnly: widget.readOnly,
  158. keyboardType: TextInputType.number,
  159. inputFormatters: [
  160. TextInputFormatter.withFunction(
  161. (oldValue, newValue) {
  162. String simpleValue = '';
  163. String formattedValue = '';
  164. if (newValue.text
  165. .contains(RegExp(r'[a-zA-Z]'))) {
  166. return oldValue;
  167. }
  168. if (widget.type ==
  169. QuantityInputType.double) {
  170. simpleValue = newValue.text
  171. .replaceAll(',', '')
  172. .replaceAll('.', '');
  173. if (simpleValue.length == 1) {
  174. simpleValue =
  175. simpleValue.padLeft(1, '0');
  176. }
  177. formattedValue = valueFormatter(
  178. double.parse(
  179. parseNewDouble(simpleValue)));
  180. } else {
  181. if (newValue.text.isEmpty) {
  182. formattedValue = '0';
  183. } else {
  184. simpleValue =
  185. newValue.text.replaceAll(',', '');
  186. formattedValue = valueFormatter(
  187. int.parse(simpleValue));
  188. }
  189. }
  190. return TextEditingValue(
  191. text: formattedValue,
  192. selection: TextSelection.collapsed(
  193. offset: formattedValue.length));
  194. })
  195. ],
  196. onChanged: (value) =>
  197. widget.onChanged(value)))),
  198. IconButtonWidget(
  199. icon: Icons.add,
  200. iconColor: widget.iconColor,
  201. buttonColor: widget.buttonColor,
  202. elevation: widget.elevation,
  203. onTap: () {
  204. dynamic currentValue = 0;
  205. if (widget.maxValue >= widget.value &&
  206. widget.maxValue >= widget.value + widget.step) {
  207. currentValue = widget.value + widget.step;
  208. } else {
  209. currentValue = widget.value;
  210. }
  211. String formattedValue = valueFormatter(currentValue);
  212. _controller.value = TextEditingValue(
  213. text: formattedValue,
  214. selection: TextSelection.collapsed(
  215. offset: formattedValue.length));
  216. widget.onChanged(formattedValue);
  217. })
  218. ])
  219. ]));
  220. }
  221. }