appflowy_popover.dart 4.4 KB

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