import 'package:flutter/material.dart'; import 'package:stackwallet/utilities/theme/stack_theme.dart'; class DraggableSwitchButton extends StatefulWidget { const DraggableSwitchButton({ Key? key, this.onItem, this.offItem, this.onValueChanged, required this.isOn, this.enabled = true, this.controller, }) : super(key: key); final Widget? onItem; final Widget? offItem; final void Function(bool)? onValueChanged; final bool isOn; final bool enabled; final DSBController? controller; @override DraggableSwitchButtonState createState() => DraggableSwitchButtonState(); } class DraggableSwitchButtonState extends State { late bool _isOn; bool get isOn => _isOn; late bool _enabled; late ValueNotifier valueListener; final tapAnimationDuration = const Duration(milliseconds: 150); bool _isDragging = false; Color _colorBG(bool isOn, bool enabled, double alpha) { if (enabled) { return Color.alphaBlend( StackTheme.instance.color.switchBGOn.withOpacity(alpha), StackTheme.instance.color.switchBGOff, ); } return StackTheme.instance.color.switchBGDisabled; } Color _colorFG(bool isOn, bool enabled, double alpha) { if (enabled) { return Color.alphaBlend( StackTheme.instance.color.switchCircleOn.withOpacity(alpha), StackTheme.instance.color.switchCircleOff, ); } return StackTheme.instance.color.switchCircleDisabled; } @override initState() { _isOn = widget.isOn; _enabled = widget.enabled; valueListener = _isOn ? ValueNotifier(1.0) : ValueNotifier(0.0); widget.controller?.activate = () { _isOn = !_isOn; // widget.onValueChanged?.call(_isOn); valueListener.value = _isOn ? 1.0 : 0.0; }; super.initState(); } @override void dispose() { valueListener.dispose(); super.dispose(); } @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); return GestureDetector( onTap: () { _isOn = !_isOn; widget.onValueChanged?.call(_isOn); valueListener.value = _isOn ? 1.0 : 0.0; }, child: LayoutBuilder( builder: (context, constraint) { return Stack( children: [ AnimatedBuilder( animation: valueListener, builder: (context, child) { return AnimatedContainer( duration: tapAnimationDuration, height: constraint.maxHeight, width: constraint.maxWidth, decoration: BoxDecoration( borderRadius: BorderRadius.circular( constraint.maxHeight / 2, ), color: _colorBG(_isOn, _enabled, valueListener.value), ), ); }, ), Builder( builder: (context) { final handle = GestureDetector( key: const Key("draggableSwitchButtonSwitch"), onHorizontalDragStart: (_) => _isDragging = true, onHorizontalDragUpdate: (details) { valueListener.value = (valueListener.value + details.delta.dx / constraint.maxWidth) .clamp(0.0, 1.0); }, onHorizontalDragEnd: (details) { bool oldValue = _isOn; if (valueListener.value > 0.5) { valueListener.value = 1.0; _isOn = true; } else { valueListener.value = 0.0; _isOn = false; } if (_isOn != oldValue) { widget.onValueChanged?.call(_isOn); } _isDragging = false; }, child: Padding( padding: const EdgeInsets.all(2), child: AnimatedBuilder( animation: valueListener, builder: (context, child) { return AnimatedContainer( duration: tapAnimationDuration, height: constraint.maxHeight - 4, width: constraint.maxWidth / 2 - 4, decoration: BoxDecoration( borderRadius: BorderRadius.circular( constraint.maxHeight / 2, ), color: _colorFG( _isOn, _enabled, valueListener.value), ), ); }, ), ), ); return AnimatedBuilder( animation: valueListener, builder: (context, child) { return AnimatedAlign( duration: _isDragging ? Duration.zero : tapAnimationDuration, alignment: Alignment(valueListener.value * 2 - 1, 0.5), child: child, ); }, child: handle, ); }, ), IgnorePointer( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ SizedBox( width: constraint.maxWidth / 2, child: Center( child: widget.onItem, ), ), SizedBox( width: constraint.maxWidth / 2, child: Center( child: widget.offItem, ), ), ], ), ), ], ); }, ), ); } } class DSBController { VoidCallback? activate; }