Browse Source

feat: enable remove one layer when click the empty space

appflowy 2 năm trước cách đây
mục cha
commit
723b34a736

+ 2 - 0
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_editor.dart

@@ -341,6 +341,7 @@ class _CalDateTimeSettingState extends State<_CalDateTimeSetting> {
     List<Widget> 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) {

+ 72 - 10
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<PopoverState, OverlayEntryContext>;
+
+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<StatefulWidget> createState() => _PopoverMaskState();
 }
 
-class _PopoverMaskState extends State<_PopoverMask> {
+class _PopoverMaskState extends State<PopoverMask> {
   @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

+ 46 - 50
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<Popover> {
+  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<Popover> {
     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 = <Widget>[];
-
-      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<Popover> {
     }
 
     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,