Files
fitnote/lib/widget/number_input.dart
2025-12-02 14:09:56 +08:00

130 lines
3.3 KiB
Dart

import 'package:fitnote/widget/record/common.dart';
import 'package:flutter/material.dart';
class NumberInput extends StatefulWidget {
final num? value;
final ValueChanged<num?>? onChanged;
final double min;
final double max;
final double step;
final int precision;
final String unit;
const NumberInput({
Key? key,
this.value,
this.onChanged,
this.min = double.negativeInfinity,
this.max = double.infinity,
this.step = 1,
this.precision = 2,
this.unit = ''
}) : super(key: key);
@override
State<NumberInput> createState() => _NumberInputState();
}
class _NumberInputState extends State<NumberInput> {
late TextEditingController _controller;
late num _currentValue;
final Color disabledColor = Colors.grey.shade400;
final double width = 150;
final double height = 36;
@override
void initState() {
super.initState();
_currentValue = widget.value ?? 0.0;
_controller = TextEditingController(text: _formatValue(_currentValue));
}
@override
void didUpdateWidget(NumberInput oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.value != oldWidget.value && widget.value != _currentValue) {
_currentValue = widget.value ?? 0.0;
_controller.text = _formatValue(_currentValue);
}
}
String _formatValue(num value) {
if (widget.precision == 0) return value.toInt().toString();
return value.toStringAsFixed(widget.precision);
}
void _handleValueChange(num newValue) {
num clampedValue = newValue.clamp(widget.min, widget.max);
if (widget.precision > 0) {
clampedValue = double.parse(
clampedValue.toStringAsFixed(widget.precision),
);
}
setState(() {
_currentValue = clampedValue;
_controller.text = _formatValue(clampedValue);
});
widget.onChanged?.call(clampedValue);
}
Widget _buildControlButton({
required IconData icon,
required VoidCallback? onPressed,
required bool disabled,
}) {
final colors = Theme.of(context).colorScheme;
return Container(
width: height,
height: height,
child: Material(
color: disabled ? disabledColor : colors.primary.withAlpha(100),
child: InkWell(
onTap: disabled ? null : onPressed,
borderRadius: BorderRadius.circular(4),
child: Icon(icon, size: 18, color: colors.onSurface),
),
),
);
}
@override
Widget build(BuildContext context) {
return Container(
width: width,
decoration: buildBoxDecoration(context),
child: IntrinsicWidth(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
_buildControlButton(
icon: Icons.remove,
onPressed: () => _handleValueChange(_currentValue - widget.step),
disabled: _currentValue <= widget.min,
),
Expanded(
child: Text(
'${_currentValue} ${widget.unit}',
textAlign: TextAlign.center,
),
),
_buildControlButton(
icon: Icons.add,
onPressed: () => _handleValueChange(_currentValue + widget.step),
disabled: _currentValue >= widget.max,
),
],
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}