130 lines
3.3 KiB
Dart
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();
|
|
}
|
|
}
|