appflowy_popover.dart 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  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. class AppFlowyPopoverState extends State<AppFlowyPopover> {
  47. final LayerLink layerLink = LayerLink();
  48. OverlayEntry? _overlayEntry;
  49. bool hasMask = true;
  50. static AppFlowyPopoverState? _popoverWithMask;
  51. @override
  52. void initState() {
  53. widget.controller?.state = this;
  54. super.initState();
  55. }
  56. showOverlay() {
  57. debugPrint("show overlay");
  58. close();
  59. if (_popoverWithMask == null) {
  60. _popoverWithMask = this;
  61. } else {
  62. hasMask = false;
  63. }
  64. debugPrint("has mask: $hasMask");
  65. final newEntry = OverlayEntry(builder: (context) {
  66. final children = <Widget>[];
  67. if (hasMask) {
  68. children.add(_PopoverMask(
  69. decoration: widget.maskDecoration,
  70. onTap: () => close(),
  71. onExit: () => close(),
  72. ));
  73. }
  74. children.add(CompositedTransformFollower(
  75. link: layerLink,
  76. showWhenUnlinked: false,
  77. offset: widget.offset ?? Offset.zero,
  78. targetAnchor: widget.targetAnchor,
  79. followerAnchor: widget.followerAnchor,
  80. child: widget.popupBuilder(context),
  81. ));
  82. return Stack(children: children);
  83. });
  84. _overlayEntry = newEntry;
  85. Overlay.of(context)?.insert(newEntry);
  86. }
  87. close() {
  88. if (_overlayEntry != null) {
  89. _overlayEntry!.remove();
  90. _overlayEntry = null;
  91. if (_popoverWithMask == this) {
  92. _popoverWithMask = null;
  93. }
  94. }
  95. }
  96. @override
  97. void deactivate() {
  98. debugPrint("deactivate");
  99. close();
  100. super.deactivate();
  101. }
  102. @override
  103. Widget build(BuildContext context) {
  104. return CompositedTransformTarget(link: layerLink, child: widget.child);
  105. }
  106. }
  107. class _PopoverMask extends StatefulWidget {
  108. final void Function() onTap;
  109. final void Function()? onExit;
  110. final Decoration? decoration;
  111. const _PopoverMask(
  112. {Key? key, required this.onTap, this.onExit, this.decoration})
  113. : super(key: key);
  114. @override
  115. State<StatefulWidget> createState() => _PopoverMaskState();
  116. }
  117. class _PopoverMaskState extends State<_PopoverMask> {
  118. @override
  119. void initState() {
  120. HardwareKeyboard.instance.addHandler(_handleGlobalKeyEvent);
  121. super.initState();
  122. }
  123. bool _handleGlobalKeyEvent(KeyEvent event) {
  124. if (event.logicalKey == LogicalKeyboardKey.escape) {
  125. if (widget.onExit != null) {
  126. widget.onExit!();
  127. }
  128. return true;
  129. }
  130. return false;
  131. }
  132. @override
  133. void deactivate() {
  134. HardwareKeyboard.instance.removeHandler(_handleGlobalKeyEvent);
  135. super.deactivate();
  136. }
  137. @override
  138. Widget build(BuildContext context) {
  139. return GestureDetector(
  140. onTap: widget.onTap,
  141. child: Container(
  142. // decoration: widget.decoration,
  143. decoration: widget.decoration ??
  144. const BoxDecoration(
  145. color: Color.fromARGB(0, 244, 67, 54),
  146. ),
  147. ),
  148. );
  149. }
  150. }