From 723b34a736f21e445266efa09b57c0a7e3f71e92 Mon Sep 17 00:00:00 2001 From: appflowy Date: Tue, 20 Sep 2022 10:53:47 +0800 Subject: [PATCH] feat: enable remove one layer when click the empty space --- .../widgets/cell/date_cell/date_editor.dart | 2 + .../appflowy_popover/lib/src/mask.dart | 82 ++++++++++++++-- .../appflowy_popover/lib/src/popover.dart | 96 +++++++++---------- 3 files changed, 120 insertions(+), 60 deletions(-) diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_editor.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_editor.dart index 3385c83560..e1c9a3618c 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_editor.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_editor.dart @@ -341,6 +341,7 @@ class _CalDateTimeSettingState extends State<_CalDateTimeSetting> { List children = [ Popover( mutex: _popoverMutex, + asBarrier: true, triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click, offset: const Offset(20, 0), popupBuilder: (BuildContext context) { @@ -357,6 +358,7 @@ class _CalDateTimeSettingState extends State<_CalDateTimeSetting> { ), Popover( mutex: _popoverMutex, + asBarrier: true, triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click, offset: const Offset(20, 0), popupBuilder: (BuildContext context) { diff --git a/frontend/app_flowy/packages/appflowy_popover/lib/src/mask.dart b/frontend/app_flowy/packages/appflowy_popover/lib/src/mask.dart index 8ced8a0739..815321774f 100644 --- a/frontend/app_flowy/packages/appflowy_popover/lib/src/mask.dart +++ b/frontend/app_flowy/packages/appflowy_popover/lib/src/mask.dart @@ -1,24 +1,86 @@ +import 'dart:collection'; + +import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -class _PopoverMask extends StatefulWidget { +typedef EntryMap = LinkedHashMap; + +class RootOverlayEntry { + final EntryMap _entries = EntryMap(); + RootOverlayEntry(); + + void addEntry( + BuildContext context, + PopoverState newState, + OverlayEntry entry, + bool asBarrier, + ) { + _entries[newState] = OverlayEntryContext(entry, newState, asBarrier); + Overlay.of(context)?.insert(entry); + } + + bool contains(PopoverState oldState) { + return _entries.containsKey(oldState); + } + + void removeEntry(PopoverState oldState) { + if (_entries.isEmpty) return; + + final removedEntry = _entries.remove(oldState); + removedEntry?.overlayEntry.remove(); + } + + bool get isEmpty => _entries.isEmpty; + + bool get isNotEmpty => _entries.isNotEmpty; + + bool hasEntry() { + return _entries.isNotEmpty; + } + + PopoverState? popEntry() { + if (_entries.isEmpty) return null; + + final lastEntry = _entries.values.last; + _entries.remove(lastEntry.popoverState); + lastEntry.overlayEntry.remove(); + lastEntry.popoverState.widget.onClose?.call(); + + if (lastEntry.asBarrier) { + return lastEntry.popoverState; + } else { + return popEntry(); + } + } +} + +class OverlayEntryContext { + final bool asBarrier; + final PopoverState popoverState; + final OverlayEntry overlayEntry; + + OverlayEntryContext( + this.overlayEntry, + this.popoverState, + this.asBarrier, + ); +} + +class PopoverMask extends StatefulWidget { final void Function() onTap; final void Function()? onExit; final Decoration? decoration; - const _PopoverMask( - {Key? key, - required this.onTap, - this.onExit, - this.decoration = - const BoxDecoration(color: Color.fromARGB(0, 244, 67, 54))}) + const PopoverMask( + {Key? key, required this.onTap, this.onExit, this.decoration}) : super(key: key); @override State createState() => _PopoverMaskState(); } -class _PopoverMaskState extends State<_PopoverMask> { +class _PopoverMaskState extends State { @override void initState() { HardwareKeyboard.instance.addHandler(_handleGlobalKeyEvent); @@ -30,10 +92,10 @@ class _PopoverMaskState extends State<_PopoverMask> { if (widget.onExit != null) { widget.onExit!(); } - return true; + } else { + return false; } - return false; } @override diff --git a/frontend/app_flowy/packages/appflowy_popover/lib/src/popover.dart b/frontend/app_flowy/packages/appflowy_popover/lib/src/popover.dart index 3277d29890..e1e9481816 100644 --- a/frontend/app_flowy/packages/appflowy_popover/lib/src/popover.dart +++ b/frontend/app_flowy/packages/appflowy_popover/lib/src/popover.dart @@ -1,7 +1,6 @@ +import 'dart:async'; import 'package:appflowy_popover/src/layout.dart'; -import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; - import 'mask.dart'; import 'mutex.dart'; @@ -68,6 +67,8 @@ class Popover extends StatefulWidget { final void Function()? onClose; + final bool asBarrier; + /// The content area of the popover. final Widget child; @@ -77,11 +78,14 @@ class Popover extends StatefulWidget { required this.popupBuilder, this.controller, this.offset, - this.maskDecoration, + this.maskDecoration = const BoxDecoration( + color: Color.fromARGB(0, 244, 67, 54), + ), this.triggerActions = 0, this.direction = PopoverDirection.rightWithTopAligned, this.mutex, this.onClose, + this.asBarrier = false, }) : super(key: key); @override @@ -89,11 +93,9 @@ class Popover extends StatefulWidget { } class PopoverState extends State { + static final RootOverlayEntry _rootEntry = RootOverlayEntry(); final PopoverLink popoverLink = PopoverLink(); - OverlayEntry? _overlayEntry; - bool hasMask = true; - - static PopoverState? _popoverWithMask; + Timer? _debounceEnterRegionAction; @override void initState() { @@ -107,64 +109,51 @@ class PopoverState extends State { if (widget.mutex != null) { widget.mutex?.state = this; } - - if (_popoverWithMask == null) { - _popoverWithMask = this; - } else { - // hasMask = false; - } - + final shouldAddMask = _rootEntry.isEmpty; final newEntry = OverlayEntry(builder: (context) { final children = []; - - if (hasMask) { - children.add(PopoverMask( - decoration: widget.maskDecoration, - onTap: () => close(), - onExit: () => close(), - )); + if (shouldAddMask) { + children.add( + PopoverMask( + decoration: widget.maskDecoration, + onTap: () => _removeRootOverlay(), + onExit: () => _removeRootOverlay(), + ), + ); } - children.add(PopoverContainer( - direction: widget.direction, - popoverLink: popoverLink, - offset: widget.offset ?? Offset.zero, - popupBuilder: widget.popupBuilder, - onClose: () => close(), - onCloseAll: () => closeAll(), - )); + children.add( + PopoverContainer( + direction: widget.direction, + popoverLink: popoverLink, + offset: widget.offset ?? Offset.zero, + popupBuilder: widget.popupBuilder, + onClose: () => close(), + onCloseAll: () => _removeRootOverlay(), + ), + ); return Stack(children: children); }); - _overlayEntry = newEntry; - - final OverlayState s = Overlay.of(context)!; - - Overlay.of(context)?.insert(newEntry); + _rootEntry.addEntry(context, this, newEntry, widget.asBarrier); } void close() { - if (_overlayEntry != null) { - _overlayEntry!.remove(); - _overlayEntry = null; - - if (_popoverWithMask == this) { - _popoverWithMask = null; - } - + if (_rootEntry.contains(this)) { + _rootEntry.removeEntry(this); widget.onClose?.call(); } + } + + void _removeRootOverlay() { + _rootEntry.popEntry(); if (widget.mutex?.state == this) { widget.mutex?.removeState(); } } - void closeAll() { - _popoverWithMask?.close(); - } - @override void deactivate() { close(); @@ -185,10 +174,17 @@ class PopoverState extends State { } return MouseRegion( - onEnter: (PointerEnterEvent event) { - if (widget.triggerActions & PopoverTriggerFlags.hover != 0) { - showOverlay(); - } + onEnter: (event) { + _debounceEnterRegionAction = + Timer(const Duration(milliseconds: 200), () { + if (widget.triggerActions & PopoverTriggerFlags.hover != 0) { + showOverlay(); + } + }); + }, + onExit: (event) { + _debounceEnterRegionAction?.cancel(); + _debounceEnterRegionAction = null; }, child: Listener( child: widget.child,