import 'dart:async'; import 'dart:io'; import 'package:diplomaticquarterapp/pages/conference/clipped_video.dart'; import 'package:flutter/material.dart'; class DraggablePublisher extends StatefulWidget { final Size availableScreenSize; final Widget child; final double scaleFactor; final Stream onButtonBarVisible; final Stream onButtonBarHeight; const DraggablePublisher({ Key key, @required this.availableScreenSize, this.child, @required this.onButtonBarVisible, @required this.onButtonBarHeight, /// The portion of the screen the DraggableWidget should use. this.scaleFactor = .25, }) : assert(scaleFactor != null && scaleFactor > 0 && scaleFactor <= .4), assert(availableScreenSize != null), assert(onButtonBarVisible != null), assert(onButtonBarHeight != null), super(key: key); @override _DraggablePublisherState createState() => _DraggablePublisherState(); } class _DraggablePublisherState extends State { bool _isButtonBarVisible = true; double _buttonBarHeight = 0; double _width; double _height; double _top; double _left; double _viewPaddingTop; double _viewPaddingBottom; final double _padding = 8.0; final Duration _duration300ms = const Duration(milliseconds: 300); final Duration _duration0ms = const Duration(milliseconds: 0); Duration _duration; StreamSubscription _streamSubscription; StreamSubscription _streamHeightSubscription; @override void initState() { super.initState(); _duration = _duration300ms; _width = widget.availableScreenSize.width * widget.scaleFactor; _height = _width * (widget.availableScreenSize.height / widget.availableScreenSize.width); _top = widget.availableScreenSize.height - (_buttonBarHeight + _padding) - _height; _left = widget.availableScreenSize.width - _padding - _width; _streamSubscription = widget.onButtonBarVisible.listen(_buttonBarVisible); _streamHeightSubscription = widget.onButtonBarHeight.listen(_getButtonBarHeight); } @override void didChangeDependencies() { final mediaQuery = MediaQuery.of(context); _viewPaddingTop = mediaQuery.viewPadding.top; _viewPaddingBottom = mediaQuery.viewPadding.bottom; super.didChangeDependencies(); } @override void dispose() { _streamSubscription.cancel(); _streamHeightSubscription.cancel(); super.dispose(); } void _getButtonBarHeight(double height) { setState(() { _buttonBarHeight = height; _positionWidget(); }); } void _buttonBarVisible(bool visible) { if (!mounted) { return; } setState(() { _isButtonBarVisible = visible; if (_duration == _duration300ms) { // only position the widget when we are not currently dragging it around _positionWidget(); } }); } @override Widget build(BuildContext context) { return AnimatedPositioned( top: _top, left: _left, width: _width, height: _height, duration: _duration, child: Listener( onPointerDown: (_) => _duration = _duration0ms, onPointerMove: (PointerMoveEvent event) { setState(() { _left = (_left + event.delta.dx).roundToDouble(); _top = (_top + event.delta.dy).roundToDouble(); }); }, onPointerUp: (_) => _positionWidget(), onPointerCancel: (_) => _positionWidget(), child: ClippedVideo( height: _height, width: _width, child: widget.child, ), ), ); } double _getCurrentStatusBarHeight() { if (_isButtonBarVisible) { return _viewPaddingTop; } final _defaultViewPaddingTop = Platform.isIOS ? 20.0 : Platform.isAndroid ? 24.0 : 0.0; if (_viewPaddingTop > _defaultViewPaddingTop) { // There must be a hardware notch in the display. return _viewPaddingTop; } return 0.0; } double _getCurrentButtonBarHeight() { if (_isButtonBarVisible) { return _buttonBarHeight + _viewPaddingBottom; } return _viewPaddingBottom; } void _positionWidget() { // Determine the center of the object being dragged so we can decide // in which corner the object should be placed. var dx = (_width / 2) + _left; dx = dx < 0 ? 0 : dx >= widget.availableScreenSize.width ? widget.availableScreenSize.width - 1 : dx; var dy = (_height / 2) + _top; dy = dy < 0 ? 0 : dy >= widget.availableScreenSize.height ? widget.availableScreenSize.height - 1 : dy; final draggableCenter = Offset(dx, dy); setState(() { _duration = _duration300ms; if (Rect.fromLTRB(0, 0, widget.availableScreenSize.width / 2, widget.availableScreenSize.height / 2).contains(draggableCenter)) { // Top-left _top = _getCurrentStatusBarHeight() + _padding; _left = _padding; } else if (Rect.fromLTRB(widget.availableScreenSize.width / 2, 0, widget.availableScreenSize.width, widget.availableScreenSize.height / 2).contains(draggableCenter)) { // Top-right _top = _getCurrentStatusBarHeight() + _padding; _left = widget.availableScreenSize.width - _padding - _width; } else if (Rect.fromLTRB(0, widget.availableScreenSize.height / 2, widget.availableScreenSize.width / 2, widget.availableScreenSize.height).contains(draggableCenter)) { // Bottom-left _top = widget.availableScreenSize.height - (_getCurrentButtonBarHeight() + _padding) - _height; _left = _padding; } else if (Rect.fromLTRB(widget.availableScreenSize.width / 2, widget.availableScreenSize.height / 2, widget.availableScreenSize.width, widget.availableScreenSize.height).contains(draggableCenter)) { // Bottom-right _top = widget.availableScreenSize.height - (_getCurrentButtonBarHeight() + _padding) - _height; _left = widget.availableScreenSize.width - _padding - _width; } }); } }