appflowy_popover.dart 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter/services.dart';
  3. class AppFlowyPopoverController {
  4. AppFlowyPopoverState? state;
  5. close() {
  6. state?.close();
  7. }
  8. show() {
  9. state?.showOverlay();
  10. }
  11. }
  12. class AppFlowyPopover extends StatefulWidget {
  13. final Widget child;
  14. final AppFlowyPopoverController? controller;
  15. final Offset? offset;
  16. final Decoration? maskDecoration;
  17. final Alignment targetAnchor;
  18. final Alignment followerAnchor;
  19. final Widget Function(BuildContext context) popupBuilder;
  20. const AppFlowyPopover({
  21. Key? key,
  22. required this.child,
  23. required this.popupBuilder,
  24. this.controller,
  25. this.offset,
  26. this.maskDecoration,
  27. this.targetAnchor = Alignment.topLeft,
  28. this.followerAnchor = Alignment.topLeft,
  29. }) : super(key: key);
  30. @override
  31. State<AppFlowyPopover> createState() => AppFlowyPopoverState();
  32. }
  33. final _globalPopovers = List<AppFlowyPopoverState>.empty(growable: true);
  34. class AppFlowyPopoverState extends State<AppFlowyPopover> {
  35. final LayerLink layerLink = LayerLink();
  36. OverlayEntry? _overlayEntry;
  37. bool hasMask = true;
  38. @override
  39. void initState() {
  40. widget.controller?.state = this;
  41. super.initState();
  42. }
  43. showOverlay() {
  44. debugPrint("show overlay");
  45. _overlayEntry?.remove();
  46. if (_globalPopovers.isNotEmpty) {
  47. hasMask = false;
  48. }
  49. debugPrint("has mask: $hasMask");
  50. final newEntry = OverlayEntry(builder: (context) {
  51. final children = <Widget>[];
  52. if (hasMask) {
  53. children.add(_PopoverMask(
  54. decoration: widget.maskDecoration,
  55. onTap: () => _closeAll(),
  56. onExit: () => _closeAll(),
  57. ));
  58. }
  59. children.add(CompositedTransformFollower(
  60. link: layerLink,
  61. offset: widget.offset ?? Offset.zero,
  62. targetAnchor: widget.targetAnchor,
  63. followerAnchor: widget.followerAnchor,
  64. child: widget.popupBuilder(context),
  65. ));
  66. return Stack(children: children);
  67. // return widget.popupBuilder(context);
  68. });
  69. _globalPopovers.add(this);
  70. _overlayEntry = newEntry;
  71. Overlay.of(context)?.insert(newEntry);
  72. }
  73. _closeAll() {
  74. final copiedArr = [..._globalPopovers];
  75. for (var i = copiedArr.length - 1; i >= 0; i--) {
  76. copiedArr[i].close();
  77. }
  78. _globalPopovers.clear();
  79. }
  80. close() {
  81. if (_globalPopovers.last == this) {
  82. _globalPopovers.removeLast();
  83. }
  84. _overlayEntry?.remove();
  85. _overlayEntry = null;
  86. }
  87. @override
  88. void dispose() {
  89. debugPrint("popover dispose");
  90. _overlayEntry?.remove();
  91. _overlayEntry = null;
  92. if (hasMask) {
  93. debugPrint("len: ${_globalPopovers.length}");
  94. }
  95. super.dispose();
  96. }
  97. @override
  98. Widget build(BuildContext context) {
  99. return CompositedTransformTarget(link: layerLink, child: widget.child);
  100. }
  101. }
  102. class _PopoverMask extends StatefulWidget {
  103. final void Function() onTap;
  104. final void Function()? onExit;
  105. final Decoration? decoration;
  106. const _PopoverMask(
  107. {Key? key, required this.onTap, this.onExit, this.decoration})
  108. : super(key: key);
  109. @override
  110. State<StatefulWidget> createState() => _PopoverMaskState();
  111. }
  112. class _PopoverMaskState extends State<_PopoverMask> {
  113. @override
  114. void initState() {
  115. HardwareKeyboard.instance.addHandler(_handleGlobalKeyEvent);
  116. super.initState();
  117. }
  118. bool _handleGlobalKeyEvent(KeyEvent event) {
  119. if (event.logicalKey == LogicalKeyboardKey.escape) {
  120. if (widget.onExit != null) {
  121. widget.onExit!();
  122. }
  123. return true;
  124. }
  125. return false;
  126. }
  127. @override
  128. void dispose() {
  129. HardwareKeyboard.instance.removeHandler(_handleGlobalKeyEvent);
  130. super.dispose();
  131. }
  132. @override
  133. Widget build(BuildContext context) {
  134. return GestureDetector(
  135. onTap: widget.onTap,
  136. child: Container(
  137. // decoration: widget.decoration,
  138. decoration: widget.decoration ??
  139. const BoxDecoration(
  140. color: Color.fromARGB(0, 244, 67, 54),
  141. ),
  142. ),
  143. );
  144. }
  145. }