import 'dart:math' as math; import 'package:flutter/material.dart'; import '../util/extension_helper.dart'; enum SlideDirection { up, down } class AudioPanel extends StatefulWidget { final Widget miniPanel; final Widget expandedPanel; final Widget? optionPanel; final double minHeight; final double maxHeight; final double? expandHeight; AudioPanel( {required this.miniPanel, required this.expandedPanel, this.optionPanel, this.minHeight = 70, this.maxHeight = 300, this.expandHeight, Key? key}) : super(key: key); @override AudioPanelState createState() => AudioPanelState(); } class AudioPanelState extends State with TickerProviderStateMixin { double? initSize; late double _startdy; double _move = 0; late AnimationController _controller; late AnimationController _slowController; late Animation _animation; SlideDirection? _slideDirection; double? _expandHeight; @override void initState() { initSize = widget.minHeight; _controller = AnimationController(vsync: this, duration: Duration(milliseconds: 50)) ..addListener(() { if (mounted) setState(() {}); }); _slowController = AnimationController(vsync: this, duration: Duration(milliseconds: 200)) ..addListener(() { if (mounted) setState(() {}); }); _animation = Tween(begin: 0, end: initSize).animate(_slowController); _controller.forward(); _slideDirection = SlideDirection.up; super.initState(); _expandHeight = widget.expandHeight; } @override void dispose() { _controller.dispose(); _slowController.dispose(); super.dispose(); } @override void didUpdateWidget(AudioPanel oldWidget) { if (oldWidget.maxHeight != widget.maxHeight) { setState(() { _expandHeight = widget.expandHeight; }); } super.didUpdateWidget(oldWidget); } double? _getHeight() { if (_animation.value >= _expandHeight) { return _expandHeight; } else if (_animation.value <= widget.minHeight) { return widget.minHeight; } else { return _animation.value; } } @override Widget build(BuildContext context) { return Stack(children: [ Container( child: (_animation.value > widget.minHeight + 30) ? Positioned.fill( child: GestureDetector( onTap: backToMini, child: Container( color: context.background.withOpacity( 0.9 * math.min(_animation.value / widget.maxHeight, 1)), ), ), ) : Center(), ), Align( alignment: Alignment.bottomCenter, child: GestureDetector( onVerticalDragStart: _start, onVerticalDragUpdate: _update, onVerticalDragEnd: (event) => _end(), child: Container( height: _getHeight(), child: _animation.value < widget.minHeight + 30 ? Container( color: context.primaryColor, child: Opacity( opacity: _animation.value > widget.minHeight ? (widget.minHeight + 30 - _animation.value) / 40 : 1, child: widget.miniPanel, ), ) : Container( decoration: BoxDecoration( color: context.primaryColor, borderRadius: BorderRadius.only( topLeft: Radius.circular(16.0), topRight: Radius.circular(16.0)), boxShadow: [ BoxShadow( offset: Offset(0, -1), blurRadius: 1, color: context.brightness == Brightness.light ? Colors.grey[400]!.withOpacity(0.5) : Colors.grey[800]!, ), BoxShadow( offset: Offset(-1, 0), blurRadius: 1, color: context.brightness == Brightness.light ? Colors.grey[400]!.withOpacity(0.5) : Colors.grey[800]!, ), BoxShadow( offset: Offset(1, 0), blurRadius: 1, color: context.brightness == Brightness.light ? Colors.grey[400]!.withOpacity(0.5) : Colors.grey[800]!, ), ], ), child: SingleChildScrollView( physics: const NeverScrollableScrollPhysics(), child: Opacity( opacity: _animation.value < (widget.maxHeight - 50) ? (_animation.value - widget.minHeight) / (widget.maxHeight - widget.minHeight - 50) : 1, child: SizedBox( height: math.max(widget.maxHeight, math.min(_animation.value, _expandHeight!)), child: Column( mainAxisSize: MainAxisSize.min, children: [ SizedBox(height: 16), Expanded(child: widget.expandedPanel), ], ), ), ), ), ), ), ), ), ]); } backToMini() { setState(() { _animation = Tween(begin: initSize, end: widget.minHeight) .animate(_slowController); initSize = widget.minHeight; }); _slowController.forward(); } scrollToTop() { setState(() { _animation = Tween(begin: initSize, end: _expandHeight) .animate(_slowController); initSize = _expandHeight; }); _slowController.forward(); } _start(DragStartDetails event) { setState(() { _startdy = event.localPosition.dy; _animation = Tween(begin: initSize, end: initSize).animate(_controller); }); _controller.forward(); } _update(DragUpdateDetails event) { setState(() { _move = _startdy - event.localPosition.dy; _animation = Tween(begin: initSize, end: initSize! + _move) .animate(_controller); _slideDirection = _move > 0 ? SlideDirection.up : SlideDirection.down; }); _controller.forward(); } _end() async { if (_slideDirection == SlideDirection.up) { if (_move > 50) { if (_animation.value > widget.maxHeight + 20) { setState(() { _animation = Tween(begin: _animation.value, end: _expandHeight) .animate(_slowController); initSize = _expandHeight; }); _slowController.forward(); } else { setState(() { _animation = Tween(begin: widget.maxHeight, end: widget.maxHeight) .animate(_controller); initSize = widget.maxHeight; }); _controller.forward(); } } else { setState(() { _animation = Tween(begin: _animation.value, end: widget.minHeight) .animate(_controller); initSize = widget.minHeight; }); _controller.forward(); } } else if (_slideDirection == SlideDirection.down) { if (_move > -50) { if (_animation.value > widget.maxHeight) { setState(() { _animation = Tween(begin: _animation.value, end: _expandHeight) .animate(_slowController); initSize = _expandHeight; }); } else { setState(() { _animation = Tween(begin: _animation.value, end: widget.maxHeight) .animate(_slowController); initSize = widget.maxHeight; }); } _slowController.forward(); } else { if (_animation.value > widget.maxHeight) { setState(() { _animation = Tween(begin: _animation.value, end: widget.maxHeight) .animate(_slowController); initSize = widget.maxHeight; }); } else { setState(() { _animation = Tween(begin: _animation.value, end: widget.minHeight) .animate(_controller); initSize = widget.minHeight; }); } _controller.forward(); } } if (_animation.value >= _expandHeight) { setState(() { initSize = _expandHeight; }); } else if (_animation.value < widget.minHeight) { setState(() { initSize = widget.minHeight; }); } } } class _AudioPanelRoute extends StatefulWidget { _AudioPanelRoute({this.expandPanel, this.height, Key? key}) : super(key: key); final Widget? expandPanel; final double? height; @override __AudioPanelRouteState createState() => __AudioPanelRouteState(); } class __AudioPanelRouteState extends State<_AudioPanelRoute> { @override Widget build(BuildContext context) { return MediaQuery.removePadding( context: context, removeTop: true, child: Scaffold( body: Stack(children: [ Container( child: Positioned.fill( child: GestureDetector( onTap: () => Navigator.pop(context), // child: // Container( // color: Theme.of(context) // .background // .withOpacity(0.8), // //), ), ), ), Align( alignment: Alignment.bottomCenter, child: Container( height: widget.height, decoration: BoxDecoration( color: context.primaryColor, boxShadow: [ BoxShadow( offset: Offset(0, -1), blurRadius: 1, color: context.brightness == Brightness.light ? Colors.grey[400]!.withOpacity(0.5) : Colors.grey[800]!, ), ], ), child: SingleChildScrollView( physics: const NeverScrollableScrollPhysics(), child: SizedBox( height: 300, child: widget.expandPanel, ), ), ), ), ]), ), ); } }