diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ca0e86..2bed6be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ +## 2.2.2 + +- Made the popup dismissible + ## 2.2.1 + - Added marking to datetimepicker ## 2.2.0 diff --git a/README.md b/README.md index 7abe3a6..34fa63f 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ A picker that is placed in the top of the screen. You are able to select a day for a given week. When it is dragged down you are able to select a day for a given month. -![Dragdown date time picker GIF](dragdown_date_time_picker.gif) +![Dragdown date time picker GIF](dropdown_date_time_picker.gif) ### OverlayDateTimePicker diff --git a/example/pubspec.lock b/example/pubspec.lock index 8eaa6be..d6d2aa1 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -61,7 +61,7 @@ packages: path: ".." relative: true source: path - version: "2.2.0" + version: "2.2.2" flutter_lints: dependency: "direct dev" description: diff --git a/lib/src/overlay_date_time_picker.dart b/lib/src/overlay_date_time_picker.dart index 8a815ad..78c302a 100644 --- a/lib/src/overlay_date_time_picker.dart +++ b/lib/src/overlay_date_time_picker.dart @@ -102,29 +102,8 @@ class _OverlayDateTimePickerState extends State { bool _isShown = false; - late OverlayState? _overlayState; + _DropdownRoute? _dropdownRoute; - late final OverlayEntry _overlay = OverlayEntry( - builder: (context) { - var box = buttonKey.currentContext?.findRenderObject() as RenderBox; - var offset = _calculateOffset( - alignment: widget.alignment, - position: box.localToGlobal( - const Offset(0, 0), - ), - buttonSize: box.size, - overlaySize: widget.size, - ); - return Positioned( - top: offset.dy, - left: offset.dx, - child: Material( - color: Colors.transparent, - child: _buildOverlay(context), - ), - ); - }, - ); late final DateTimePickerController _dateTimePickerController = DateTimePickerController( highlightToday: widget.highlightToday, @@ -137,7 +116,7 @@ class _OverlayDateTimePickerState extends State { onTapDayCallBack: (date) { widget.onTapDay?.call(date); if (widget.closeOnSelectDate) { - _toggleOverlay(); + Navigator.of(context).pop(); } }, browsingDate: widget.initialDate ?? DateTime.now(), @@ -146,65 +125,61 @@ class _OverlayDateTimePickerState extends State { @override void dispose() { - if (_overlay.mounted) _overlay.remove(); - _overlay.dispose(); - _overlayState = null; _dateTimePickerController.dispose(); super.dispose(); } - void _toggleOverlay() { - if (mounted && (_overlayState?.mounted ?? false)) { - setState(() { - if (!_isShown) { - _overlayState?.insert(_overlay); - } else { - _overlay.remove(); - } - _isShown = !_isShown; - }); + void _onPressed() async { + if (!mounted) return; + setState(() { + _isShown = !_isShown; + }); + final TextDirection? textDirection = Directionality.maybeOf(context); + + final NavigatorState navigator = Navigator.of(context); + + final RenderBox buttonBox = context.findRenderObject()! as RenderBox; + final Rect buttonRect = buttonBox.localToGlobal(Offset.zero, + ancestor: navigator.context.findRenderObject()) & + buttonBox.size; + + _dropdownRoute = _DropdownRoute( + child: _buildOverlay(context), + barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, + buttonRect: + EdgeInsets.zero.resolve(textDirection).inflateRect(buttonRect), + alignment: widget.alignment, + menuSize: widget.size, + ); + await navigator.push(_dropdownRoute!); + if (!mounted) { + return; } + + _close(); } - Offset _calculateOffset({ - required Alignment alignment, - required Offset position, - required Size buttonSize, - required Size overlaySize, - }) { - double offsetX = 0; - double offsetY = 0; - - offsetX = position.dx + // adds the world x position of the button - buttonSize.width * - 0.5 - // centers the pivot of the button of the calculation to the center of the button - overlaySize.width * - 0.5 + // centers the pivot of the overlay of the calculation to the center of the overlay - (overlaySize.width + buttonSize.width) * 0.5 * alignment.x; - offsetY = position.dy + // adds the world y position of the button - buttonSize.height * 0.5 - - overlaySize.height * 0.5 + - (overlaySize.height + buttonSize.height) * 0.5 * alignment.y; - - return Offset(offsetX, offsetY); + void _close() { + if (!mounted) return; + setState(() { + _isShown = false; + }); } @override Widget build(BuildContext context) { - _overlayState = Overlay.of(context); - if (widget.buttonBuilder != null) { return widget.buttonBuilder!.call( buttonKey, () { - _toggleOverlay(); + _onPressed(); }, ); } return ElevatedButton( key: buttonKey, onPressed: () { - _toggleOverlay(); + _onPressed(); }, child: widget.child); } @@ -284,3 +259,86 @@ class _OverlayDateTimePickerState extends State { }); } } + +class _DropdownRoute extends PopupRoute { + _DropdownRoute({ + required this.barrierLabel, + required this.child, + required this.alignment, + required this.menuSize, + required this.buttonRect, + }); + + final Widget child; + final Alignment alignment; + final Size menuSize; + final Rect buttonRect; + + @override + Color? get barrierColor => null; + + @override + bool get barrierDismissible => true; + + @override + final String? barrierLabel; + + @override + Widget buildPage(BuildContext context, Animation animation, + Animation secondaryAnimation) { + return CustomSingleChildLayout( + delegate: _DropdownMenuLayoutDelegate( + buttonRect: buttonRect, + menuSize: menuSize, + alignment: alignment, + ), + child: Material(child: child), + ); + } + + @override + Duration get transitionDuration => const Duration(milliseconds: 100); + + void dismiss() { + navigator?.pop(); + } +} + +class _DropdownMenuLayoutDelegate extends SingleChildLayoutDelegate { + _DropdownMenuLayoutDelegate({ + required this.buttonRect, + required this.menuSize, + required this.alignment, + }); + + final Rect buttonRect; + final Size menuSize; + final Alignment alignment; + + @override + BoxConstraints getConstraintsForChild(BoxConstraints constraints) { + return BoxConstraints( + minWidth: menuSize.width, + maxWidth: menuSize.width, + minHeight: menuSize.height, + maxHeight: menuSize.height, + ); + } + + @override + Offset getPositionForChild(Size size, Size childSize) { + Rect pop = Offset.zero & childSize; + pop.center; + return buttonRect.center - + pop.center + + Offset( + (childSize.width + buttonRect.width) * 0.5 * alignment.x, + (childSize.height + buttonRect.height) * 0.5 * alignment.y, + ); + } + + @override + bool shouldRelayout(_DropdownMenuLayoutDelegate oldDelegate) { + return buttonRect != oldDelegate.buttonRect; + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 3484419..d814525 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_date_time_picker description: A Flutter package for date and time picker. -version: 2.2.1 +version: 2.2.2 homepage: https://iconica.nl/ environment: