import 'package:flutter/material.dart'; typedef StepperCallbackFuture = Future Function(int apply, int total); class StepperView extends StatefulWidget { final double height; final Color foregroundColor; final Color backgroundColor; final double buttonPadding; final int initialNumber; final int maxNumber; final int minNumber; final StepperCallbackFuture? counterCallback; final Function? increaseCallback; final Function? decreaseCallback; StepperView({this.initialNumber = 1, this.minNumber = 1, required this.maxNumber, @required this.counterCallback, this.increaseCallback, this.decreaseCallback, this.height = 25, required this.foregroundColor, required this.backgroundColor, this.buttonPadding = 1}){ assert((this.initialNumber >= this.minNumber && this.initialNumber <= this.maxNumber)); } @override _StepperViewState createState() => _StepperViewState(); } class _StepperViewState extends State { late int _currentCount; late StepperCallbackFuture _counterCallback; late Function _increaseCallback; late Function _decreaseCallback; @override void initState() { _currentCount = widget.initialNumber ?? 1; _counterCallback = widget.counterCallback!; _increaseCallback = widget.increaseCallback ?? () {}; _decreaseCallback = widget.decreaseCallback ?? () {}; super.initState(); } bool loadingInc = false; bool loadingDec = false; @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(widget.buttonPadding), decoration: BoxDecoration( borderRadius: BorderRadius.circular((widget.height/2) + (widget.buttonPadding*2)), color: widget.backgroundColor, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _createDecrementButton( ((_currentCount > widget.minNumber) ? () => _decrement() : null) as VoidCallback, ), Container( width: 25, child: Center( child: Text( _currentCount.toString(), style: TextStyle( fontSize: 12, fontWeight: FontWeight.normal, height: 1.5, color: Colors.black, ) ), ) ), _createIncrementButton( ((_currentCount < widget.maxNumber) ? () => _increment() : null) as VoidCallback ), ], ), ); } void _increment() async{ doInc({required bool can}){ if(can) setState(() { _currentCount++; _increaseCallback(); }); } if (_currentCount < widget.maxNumber){ if(_counterCallback == null) doInc(can: true); else { setState(() => loadingInc = true); var result = (await _counterCallback(1,_currentCount)); doInc(can: result); setState(() => loadingInc = false); } } } void _decrement() async{ doDec({required bool can}){ if(can) setState(() { _currentCount--; _decreaseCallback(); }); } if (_currentCount > widget.minNumber) { if(_counterCallback == null) doDec(can: true); else { setState(() => loadingDec = true); var result = (await _counterCallback(-1,_currentCount)); doDec(can: result); setState(() => loadingDec = false); } } } Widget _createIncrementButton(VoidCallback onPressed,) { return Stack( children: [ RawMaterialButton( materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, constraints: BoxConstraints(minWidth: widget.height, minHeight: widget.height), onPressed: onPressed, elevation: 2.0, fillColor: widget.foregroundColor, child: Icon( Icons.add, color: (_currentCount < widget.maxNumber) ? Colors.grey[700] : Colors.grey[400], size: 12.0, ), shape: CircleBorder(), ), if(loadingInc) Container( decoration: BoxDecoration( color: Colors.white.withOpacity(0.8), borderRadius: BorderRadius.circular(widget.height/2), ), height: widget.height, width: widget.height, child: CircularProgressIndicator( strokeWidth: 3, valueColor: AlwaysStoppedAnimation(Colors.green) ) ) ], ); } Widget _createDecrementButton(VoidCallback onPressed) { return Stack( children: [ RawMaterialButton( materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, constraints: BoxConstraints(minWidth: widget.height, minHeight: widget.height), onPressed: onPressed, elevation: 2.0, fillColor: widget.foregroundColor, child: Icon( Icons.remove, color: (_currentCount > widget.minNumber) ? Colors.grey[700] : Colors.grey[400], size: 12.0, ), shape: CircleBorder(), ), if(loadingDec) Container( decoration: BoxDecoration( color: Colors.white.withOpacity(0.8), borderRadius: BorderRadius.circular(widget.height/2), ), height: widget.height, width: widget.height, child: CircularProgressIndicator( strokeWidth: 3, valueColor: AlwaysStoppedAnimation(Colors.red) ) ) ], ); } }