feat: enable remove one layer when click the empty space

This commit is contained in:
appflowy 2022-09-20 10:53:47 +08:00
parent d1737d35fe
commit 723b34a736
3 changed files with 120 additions and 60 deletions

View File

@ -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) {

View File

@ -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

View File

@ -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,