Skip to content

Commit 723b34a

Browse files
committed
feat: enable remove one layer when click the empty space
1 parent d1737d3 commit 723b34a

File tree

3 files changed

+120
-60
lines changed

3 files changed

+120
-60
lines changed

frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_editor.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ class _CalDateTimeSettingState extends State<_CalDateTimeSetting> {
341341
List<Widget> children = [
342342
Popover(
343343
mutex: _popoverMutex,
344+
asBarrier: true,
344345
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
345346
offset: const Offset(20, 0),
346347
popupBuilder: (BuildContext context) {
@@ -357,6 +358,7 @@ class _CalDateTimeSettingState extends State<_CalDateTimeSetting> {
357358
),
358359
Popover(
359360
mutex: _popoverMutex,
361+
asBarrier: true,
360362
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
361363
offset: const Offset(20, 0),
362364
popupBuilder: (BuildContext context) {

frontend/app_flowy/packages/appflowy_popover/lib/src/mask.dart

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,86 @@
1+
import 'dart:collection';
2+
3+
import 'package:appflowy_popover/appflowy_popover.dart';
14
import 'package:flutter/material.dart';
25
import 'package:flutter/services.dart';
36

4-
class _PopoverMask extends StatefulWidget {
7+
typedef EntryMap = LinkedHashMap<PopoverState, OverlayEntryContext>;
8+
9+
class RootOverlayEntry {
10+
final EntryMap _entries = EntryMap();
11+
RootOverlayEntry();
12+
13+
void addEntry(
14+
BuildContext context,
15+
PopoverState newState,
16+
OverlayEntry entry,
17+
bool asBarrier,
18+
) {
19+
_entries[newState] = OverlayEntryContext(entry, newState, asBarrier);
20+
Overlay.of(context)?.insert(entry);
21+
}
22+
23+
bool contains(PopoverState oldState) {
24+
return _entries.containsKey(oldState);
25+
}
26+
27+
void removeEntry(PopoverState oldState) {
28+
if (_entries.isEmpty) return;
29+
30+
final removedEntry = _entries.remove(oldState);
31+
removedEntry?.overlayEntry.remove();
32+
}
33+
34+
bool get isEmpty => _entries.isEmpty;
35+
36+
bool get isNotEmpty => _entries.isNotEmpty;
37+
38+
bool hasEntry() {
39+
return _entries.isNotEmpty;
40+
}
41+
42+
PopoverState? popEntry() {
43+
if (_entries.isEmpty) return null;
44+
45+
final lastEntry = _entries.values.last;
46+
_entries.remove(lastEntry.popoverState);
47+
lastEntry.overlayEntry.remove();
48+
lastEntry.popoverState.widget.onClose?.call();
49+
50+
if (lastEntry.asBarrier) {
51+
return lastEntry.popoverState;
52+
} else {
53+
return popEntry();
54+
}
55+
}
56+
}
57+
58+
class OverlayEntryContext {
59+
final bool asBarrier;
60+
final PopoverState popoverState;
61+
final OverlayEntry overlayEntry;
62+
63+
OverlayEntryContext(
64+
this.overlayEntry,
65+
this.popoverState,
66+
this.asBarrier,
67+
);
68+
}
69+
70+
class PopoverMask extends StatefulWidget {
571
final void Function() onTap;
672
final void Function()? onExit;
773
final Decoration? decoration;
874

9-
const _PopoverMask(
10-
{Key? key,
11-
required this.onTap,
12-
this.onExit,
13-
this.decoration =
14-
const BoxDecoration(color: Color.fromARGB(0, 244, 67, 54))})
75+
const PopoverMask(
76+
{Key? key, required this.onTap, this.onExit, this.decoration})
1577
: super(key: key);
1678

1779
@override
1880
State<StatefulWidget> createState() => _PopoverMaskState();
1981
}
2082

21-
class _PopoverMaskState extends State<_PopoverMask> {
83+
class _PopoverMaskState extends State<PopoverMask> {
2284
@override
2385
void initState() {
2486
HardwareKeyboard.instance.addHandler(_handleGlobalKeyEvent);
@@ -30,10 +92,10 @@ class _PopoverMaskState extends State<_PopoverMask> {
3092
if (widget.onExit != null) {
3193
widget.onExit!();
3294
}
33-
3495
return true;
96+
} else {
97+
return false;
3598
}
36-
return false;
3799
}
38100

39101
@override

frontend/app_flowy/packages/appflowy_popover/lib/src/popover.dart

Lines changed: 46 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1+
import 'dart:async';
12
import 'package:appflowy_popover/src/layout.dart';
2-
import 'package:flutter/gestures.dart';
33
import 'package:flutter/material.dart';
4-
54
import 'mask.dart';
65
import 'mutex.dart';
76

@@ -68,6 +67,8 @@ class Popover extends StatefulWidget {
6867

6968
final void Function()? onClose;
7069

70+
final bool asBarrier;
71+
7172
/// The content area of the popover.
7273
final Widget child;
7374

@@ -77,23 +78,24 @@ class Popover extends StatefulWidget {
7778
required this.popupBuilder,
7879
this.controller,
7980
this.offset,
80-
this.maskDecoration,
81+
this.maskDecoration = const BoxDecoration(
82+
color: Color.fromARGB(0, 244, 67, 54),
83+
),
8184
this.triggerActions = 0,
8285
this.direction = PopoverDirection.rightWithTopAligned,
8386
this.mutex,
8487
this.onClose,
88+
this.asBarrier = false,
8589
}) : super(key: key);
8690

8791
@override
8892
State<Popover> createState() => PopoverState();
8993
}
9094

9195
class PopoverState extends State<Popover> {
96+
static final RootOverlayEntry _rootEntry = RootOverlayEntry();
9297
final PopoverLink popoverLink = PopoverLink();
93-
OverlayEntry? _overlayEntry;
94-
bool hasMask = true;
95-
96-
static PopoverState? _popoverWithMask;
98+
Timer? _debounceEnterRegionAction;
9799

98100
@override
99101
void initState() {
@@ -107,64 +109,51 @@ class PopoverState extends State<Popover> {
107109
if (widget.mutex != null) {
108110
widget.mutex?.state = this;
109111
}
110-
111-
if (_popoverWithMask == null) {
112-
_popoverWithMask = this;
113-
} else {
114-
// hasMask = false;
115-
}
116-
112+
final shouldAddMask = _rootEntry.isEmpty;
117113
final newEntry = OverlayEntry(builder: (context) {
118114
final children = <Widget>[];
119-
120-
if (hasMask) {
121-
children.add(PopoverMask(
122-
decoration: widget.maskDecoration,
123-
onTap: () => close(),
124-
onExit: () => close(),
125-
));
115+
if (shouldAddMask) {
116+
children.add(
117+
PopoverMask(
118+
decoration: widget.maskDecoration,
119+
onTap: () => _removeRootOverlay(),
120+
onExit: () => _removeRootOverlay(),
121+
),
122+
);
126123
}
127124

128-
children.add(PopoverContainer(
129-
direction: widget.direction,
130-
popoverLink: popoverLink,
131-
offset: widget.offset ?? Offset.zero,
132-
popupBuilder: widget.popupBuilder,
133-
onClose: () => close(),
134-
onCloseAll: () => closeAll(),
135-
));
125+
children.add(
126+
PopoverContainer(
127+
direction: widget.direction,
128+
popoverLink: popoverLink,
129+
offset: widget.offset ?? Offset.zero,
130+
popupBuilder: widget.popupBuilder,
131+
onClose: () => close(),
132+
onCloseAll: () => _removeRootOverlay(),
133+
),
134+
);
136135

137136
return Stack(children: children);
138137
});
139138

140-
_overlayEntry = newEntry;
141-
142-
final OverlayState s = Overlay.of(context)!;
143-
144-
Overlay.of(context)?.insert(newEntry);
139+
_rootEntry.addEntry(context, this, newEntry, widget.asBarrier);
145140
}
146141

147142
void close() {
148-
if (_overlayEntry != null) {
149-
_overlayEntry!.remove();
150-
_overlayEntry = null;
151-
152-
if (_popoverWithMask == this) {
153-
_popoverWithMask = null;
154-
}
155-
143+
if (_rootEntry.contains(this)) {
144+
_rootEntry.removeEntry(this);
156145
widget.onClose?.call();
157146
}
147+
}
148+
149+
void _removeRootOverlay() {
150+
_rootEntry.popEntry();
158151

159152
if (widget.mutex?.state == this) {
160153
widget.mutex?.removeState();
161154
}
162155
}
163156

164-
void closeAll() {
165-
_popoverWithMask?.close();
166-
}
167-
168157
@override
169158
void deactivate() {
170159
close();
@@ -185,10 +174,17 @@ class PopoverState extends State<Popover> {
185174
}
186175

187176
return MouseRegion(
188-
onEnter: (PointerEnterEvent event) {
189-
if (widget.triggerActions & PopoverTriggerFlags.hover != 0) {
190-
showOverlay();
191-
}
177+
onEnter: (event) {
178+
_debounceEnterRegionAction =
179+
Timer(const Duration(milliseconds: 200), () {
180+
if (widget.triggerActions & PopoverTriggerFlags.hover != 0) {
181+
showOverlay();
182+
}
183+
});
184+
},
185+
onExit: (event) {
186+
_debounceEnterRegionAction?.cancel();
187+
_debounceEnterRegionAction = null;
192188
},
193189
child: Listener(
194190
child: widget.child,

0 commit comments

Comments
 (0)