danvick / flutter_touch_spin

Simple number input spinner Widget for Flutter.
https://pub.dev/packages/flutter_touch_spin
MIT License
11 stars 22 forks source link

Keyboard option in FormBuilderTouchSpin #5

Open danielweil opened 3 years ago

danielweil commented 3 years ago

Is there a way to include a numeric keyboard when touching the field in the middle? If I still want to use the spinner but I have to type a big number.

Thanks! Love your package!

awhitford commented 3 years ago

If the middle Text widget was replaced with a TextField, then the keyboard should become visible. 🤔

danvick commented 3 years ago

@awhitford mind taking this one?

aaronjudd commented 2 years ago

Maybe not a frequent request, and I guess it's pretty easy to override.. and this issue is quite old. However, having a default option to edit the entry, in addition to touch / spin would be really nice.

myThorsten commented 1 year ago

Would be nice to have this feature

divan commented 1 month ago

If the middle Text widget was replaced with a TextField then the keyboard should become visible.

And if use onChanged in TextField widget will be recreated every time the value changes, which means that the cursor and selection will be reset.

I ended up updating value only on onEditingComplete, onSubmitted and onTapOutside, but haven't properly tested it yet in all situations.

My modified version look like this:

class TouchSpin extends StatefulWidget {
  final num value;
  final num min;
  final num max;
  final num step;
  final double iconSize;
  final ValueChanged<num>? onChanged;
  final NumberFormat? displayFormat;
  final Icon subtractIcon;
  final Icon addIcon;
  final EdgeInsetsGeometry iconPadding;
  final TextStyle? textStyle;
  final Color? iconActiveColor;
  final Color? iconDisabledColor;
  final bool enabled;

  const TouchSpin({
    Key? key,
    this.value = 1.0,
    this.onChanged,
    this.min = 1.0,
    this.max = 9999999.0,
    this.step = 1.0,
    this.iconSize = 24.0,
    this.displayFormat,
    this.subtractIcon = const Icon(Icons.remove),
    this.addIcon = const Icon(Icons.add),
    this.iconPadding = const EdgeInsets.all(0),
    this.textStyle,
    this.iconActiveColor,
    this.iconDisabledColor,
    this.enabled = true,
  }) : super(key: key);

  @override
  TouchSpinState createState() => TouchSpinState();
}

class TouchSpinState extends State<TouchSpin> {
  late num _value;
  final TextEditingController _controller = TextEditingController();

  bool get minusBtnDisabled =>
      _value <= widget.min ||
      _value - widget.step < widget.min ||
      !widget.enabled;

  bool get addBtnDisabled =>
      _value >= widget.max ||
      _value + widget.step > widget.max ||
      !widget.enabled;

  @override
  void initState() {
    super.initState();
    _value = widget.value;
    _controller.text = _value.toString();
  }

  @override
  void didUpdateWidget(TouchSpin oldWidget) {
    super.didUpdateWidget(oldWidget);

    if (oldWidget.value != widget.value) {
      setState(() {
        _value = widget.value;
        _controller.text = _value.toString();
      });
      widget.onChanged?.call(widget.value);
    }
  }

  Color? _spinButtonColor(bool btnDisabled) => btnDisabled
      ? widget.iconDisabledColor ?? Theme.of(context).disabledColor
      : widget.iconActiveColor ?? Theme.of(context).textTheme.labelLarge?.color;

  void _adjustValue(num adjustment) {
    num newVal = _value + adjustment;
    setState(() {
      _value = newVal;
      _controller.text = _value.toString();
    });
    widget.onChanged?.call(newVal);
  }

  void _onTextChanged() {
    final newValue = _controller.text.isEmpty
        ? widget.min
        : num.tryParse(_controller.text) ?? widget.min;
    print(newValue);
    if (newValue != _value &&
        newValue >= widget.min &&
        newValue <= widget.max) {
      setState(() {
        _value = newValue;
      });
      widget.onChanged?.call(newValue);
    } else {
      _controller.text = _value.toString();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: <Widget>[
        IconButton(
          padding: widget.iconPadding,
          iconSize: widget.iconSize,
          color: _spinButtonColor(minusBtnDisabled),
          icon: widget.subtractIcon,
          onPressed: minusBtnDisabled ? null : () => _adjustValue(-widget.step),
        ),
        SizedBox(
          width: 50, 
          child: TextField(
            decoration: null,
            controller: _controller,
            keyboardType: TextInputType.number,
            inputFormatters: [FilteringTextInputFormatter.digitsOnly],
            textAlign: TextAlign.center,
            style: widget.textStyle,
            onEditingComplete: () => _onTextChanged(),
            onSubmitted: (_) => _onTextChanged(),
            onTapOutside: (_) => _onTextChanged(),
          ),
        ),
        IconButton(
          padding: widget.iconPadding,
          iconSize: widget.iconSize,
          color: _spinButtonColor(addBtnDisabled),
          icon: widget.addIcon,
          onPressed: addBtnDisabled ? null : () => _adjustValue(widget.step),
        ),
      ],
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}