flutter_wordle/lib/validated_number_input.dart
2025-06-19 13:38:17 +02:00

147 lines
4.1 KiB
Dart

import 'package:flutter/material.dart';
class ValidatedNumberInput extends StatefulWidget {
final int? minValue;
final int? maxValue;
final ValueChanged<int>? onValueChanged;
final int? defaultValue;
final ValueChanged<bool>? onValidityChanged;
const ValidatedNumberInput({
super.key,
this.minValue,
this.maxValue,
this.onValueChanged,
this.defaultValue,
this.onValidityChanged,
});
@override
_ValidatedNumberInputState createState() => _ValidatedNumberInputState();
}
class _ValidatedNumberInputState extends State<ValidatedNumberInput> {
late int currentValue;
late TextEditingController _controller;
bool isValid = true;
@override
void initState() {
super.initState();
currentValue = widget.defaultValue ?? 0;
_controller = TextEditingController(text: currentValue.toString());
_validateInput(currentValue);
}
@override
void didUpdateWidget(covariant ValidatedNumberInput oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.defaultValue != null && widget.defaultValue != currentValue) {
setState(() {
currentValue = widget.defaultValue!;
_controller.text = currentValue.toString();
_validateInput(currentValue);
});
}
}
void _incrementValue() {
setState(() {
currentValue++;
_controller.text = currentValue.toString();
_validateInput(currentValue);
widget.onValueChanged?.call(currentValue);
});
}
void _decrementValue() {
setState(() {
currentValue--;
_controller.text = currentValue.toString();
_validateInput(currentValue);
widget.onValueChanged?.call(currentValue);
});
}
void _updateValueFromText(String value) {
String sanitizedValue = value.replaceAll(RegExp(r'[^0-9]'), '');
int? newValue = int.tryParse(sanitizedValue);
if (newValue != null) {
setState(() {
currentValue = newValue;
_controller.text = currentValue.toString();
_controller.selection = TextSelection.fromPosition(
TextPosition(offset: _controller.text.length),
);
_validateInput(currentValue);
widget.onValueChanged?.call(currentValue);
});
} else {
_controller.text = currentValue.toString();
_controller.selection = TextSelection.fromPosition(
TextPosition(offset: _controller.text.length),
);
}
}
void _validateInput(int value) {
int minValue = widget.minValue ?? double.negativeInfinity.toInt();
int maxValue = widget.maxValue ?? double.maxFinite.toInt();
bool newIsValid = value >= minValue && value <= maxValue;
if (newIsValid != isValid) {
isValid = newIsValid;
WidgetsBinding.instance.addPostFrameCallback((_) {
widget.onValidityChanged?.call(isValid);
});
}
}
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: BoxDecoration(
color: Colors.grey[800],
borderRadius: BorderRadius.circular(30),
border: Border.all(
color: isValid ? Colors.grey : Colors.red,
width: 2.0,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.remove),
onPressed: _decrementValue,
constraints: const BoxConstraints(),
padding: EdgeInsets.zero,
),
SizedBox(
width: 60,
child: TextFormField(
controller: _controller,
textAlign: TextAlign.center,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 8.0),
),
onChanged: (value) {
_updateValueFromText(value);
},
),
),
IconButton(
icon: const Icon(Icons.add),
onPressed: _incrementValue,
constraints: const BoxConstraints(),
padding: EdgeInsets.zero,
),
],
),
);
}
}