ソースを参照

Merge pull request #903 from AppFlowy-IO/feat/flowy-overlay

Feat: appflowy_popover
Nathan.fooo 2 年 前
コミット
05b9bc50f6
100 ファイル変更2782 行追加833 行削除
  1. 10 4
      frontend/app_flowy/lib/plugins/board/presentation/board_page.dart
  2. 48 39
      frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_setting.dart
  3. 16 7
      frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_toolbar.dart
  4. 9 4
      frontend/app_flowy/lib/plugins/grid/presentation/grid_page.dart
  5. 35 7
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_accessory.dart
  6. 3 3
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_builder.dart
  7. 1 1
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_container.dart
  8. 29 18
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_cell.dart
  9. 88 115
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_editor.dart
  10. 49 11
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_cell.dart
  11. 68 59
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart
  12. 24 45
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/cell_editor.dart
  13. 77 21
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/url_cell.dart
  14. 22 32
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_cell.dart
  15. 53 45
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_cell_action_sheet.dart
  16. 29 33
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart
  17. 27 53
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_type_option_editor.dart
  18. 42 12
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/grid_header.dart
  19. 15 18
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/builder.dart
  20. 53 31
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/date.dart
  21. 10 7
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/multi_select.dart
  22. 36 29
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/number.dart
  23. 74 45
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/select_option.dart
  24. 10 7
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/single_select.dart
  25. 10 5
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/grid_row.dart
  26. 81 72
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart
  27. 49 41
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_property.dart
  28. 0 32
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_setting.dart
  29. 55 6
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_toolbar.dart
  30. 36 31
      frontend/app_flowy/lib/workspace/presentation/settings/settings_dialog.dart
  31. 30 0
      frontend/app_flowy/packages/appflowy_popover/.gitignore
  32. 10 0
      frontend/app_flowy/packages/appflowy_popover/.metadata
  33. 3 0
      frontend/app_flowy/packages/appflowy_popover/CHANGELOG.md
  34. 1 0
      frontend/app_flowy/packages/appflowy_popover/LICENSE
  35. 107 0
      frontend/app_flowy/packages/appflowy_popover/README.md
  36. 4 0
      frontend/app_flowy/packages/appflowy_popover/analysis_options.yaml
  37. 47 0
      frontend/app_flowy/packages/appflowy_popover/example/.gitignore
  38. 45 0
      frontend/app_flowy/packages/appflowy_popover/example/.metadata
  39. 16 0
      frontend/app_flowy/packages/appflowy_popover/example/README.md
  40. 29 0
      frontend/app_flowy/packages/appflowy_popover/example/analysis_options.yaml
  41. 13 0
      frontend/app_flowy/packages/appflowy_popover/example/android/.gitignore
  42. 71 0
      frontend/app_flowy/packages/appflowy_popover/example/android/app/build.gradle
  43. 8 0
      frontend/app_flowy/packages/appflowy_popover/example/android/app/src/debug/AndroidManifest.xml
  44. 34 0
      frontend/app_flowy/packages/appflowy_popover/example/android/app/src/main/AndroidManifest.xml
  45. 6 0
      frontend/app_flowy/packages/appflowy_popover/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt
  46. 12 0
      frontend/app_flowy/packages/appflowy_popover/example/android/app/src/main/res/drawable-v21/launch_background.xml
  47. 12 0
      frontend/app_flowy/packages/appflowy_popover/example/android/app/src/main/res/drawable/launch_background.xml
  48. BIN
      frontend/app_flowy/packages/appflowy_popover/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
  49. BIN
      frontend/app_flowy/packages/appflowy_popover/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
  50. BIN
      frontend/app_flowy/packages/appflowy_popover/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
  51. BIN
      frontend/app_flowy/packages/appflowy_popover/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  52. BIN
      frontend/app_flowy/packages/appflowy_popover/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  53. 18 0
      frontend/app_flowy/packages/appflowy_popover/example/android/app/src/main/res/values-night/styles.xml
  54. 18 0
      frontend/app_flowy/packages/appflowy_popover/example/android/app/src/main/res/values/styles.xml
  55. 8 0
      frontend/app_flowy/packages/appflowy_popover/example/android/app/src/profile/AndroidManifest.xml
  56. 31 0
      frontend/app_flowy/packages/appflowy_popover/example/android/build.gradle
  57. 3 0
      frontend/app_flowy/packages/appflowy_popover/example/android/gradle.properties
  58. 6 0
      frontend/app_flowy/packages/appflowy_popover/example/android/gradle/wrapper/gradle-wrapper.properties
  59. 11 0
      frontend/app_flowy/packages/appflowy_popover/example/android/settings.gradle
  60. 34 0
      frontend/app_flowy/packages/appflowy_popover/example/ios/.gitignore
  61. 26 0
      frontend/app_flowy/packages/appflowy_popover/example/ios/Flutter/AppFrameworkInfo.plist
  62. 1 0
      frontend/app_flowy/packages/appflowy_popover/example/ios/Flutter/Debug.xcconfig
  63. 1 0
      frontend/app_flowy/packages/appflowy_popover/example/ios/Flutter/Release.xcconfig
  64. 481 0
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner.xcodeproj/project.pbxproj
  65. 7 0
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  66. 8 0
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  67. 8 0
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
  68. 87 0
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
  69. 7 0
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner.xcworkspace/contents.xcworkspacedata
  70. 8 0
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  71. 8 0
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
  72. 13 0
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/AppDelegate.swift
  73. 122 0
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
  74. BIN
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
  75. BIN
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
  76. BIN
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
  77. BIN
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
  78. BIN
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
  79. BIN
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
  80. BIN
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
  81. BIN
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
  82. BIN
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
  83. BIN
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
  84. BIN
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
  85. BIN
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
  86. BIN
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
  87. BIN
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
  88. BIN
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
  89. 23 0
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
  90. BIN
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
  91. BIN
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/[email protected]
  92. BIN
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/[email protected]
  93. 5 0
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
  94. 37 0
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Base.lproj/LaunchScreen.storyboard
  95. 26 0
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Base.lproj/Main.storyboard
  96. 49 0
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Info.plist
  97. 1 0
      frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Runner-Bridging-Header.h
  98. 98 0
      frontend/app_flowy/packages/appflowy_popover/example/lib/example_button.dart
  99. 129 0
      frontend/app_flowy/packages/appflowy_popover/example/lib/main.dart
  100. 1 0
      frontend/app_flowy/packages/appflowy_popover/example/linux/.gitignore

+ 10 - 4
frontend/app_flowy/lib/plugins/board/presentation/board_page.dart

@@ -14,6 +14,7 @@ import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
 import 'package:flowy_infra_ui/widget/error_page.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
@@ -285,10 +286,15 @@ class _BoardContentState extends State<BoardContent> {
       rowCache: rowCache,
     );
 
-    RowDetailPage(
-      cellBuilder: GridCellBuilder(delegate: dataController),
-      dataController: dataController,
-    ).show(context);
+    FlowyOverlay.show(
+      context: context,
+      builder: (BuildContext context) {
+        return RowDetailPage(
+          cellBuilder: GridCellBuilder(delegate: dataController),
+          dataController: dataController,
+        );
+      },
+    );
   }
 }
 

+ 48 - 39
frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_setting.dart

@@ -2,7 +2,6 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'package:app_flowy/plugins/board/application/toolbar/board_setting_bloc.dart';
 import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
 import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart';
-import 'package:app_flowy/plugins/grid/presentation/widgets/toolbar/grid_group.dart';
 import 'package:app_flowy/plugins/grid/presentation/widgets/toolbar/grid_property.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/image.dart';
@@ -50,7 +49,7 @@ class BoardSettingList extends StatelessWidget {
             previous.selectedAction != current.selectedAction,
         listener: (context, state) {
           state.selectedAction.foldLeft(null, (_, action) {
-            FlowyOverlay.of(context).remove(identifier());
+            // FlowyOverlay.of(context).remove(identifier());
             onAction(action, settingContext);
           });
         },
@@ -84,43 +83,6 @@ class BoardSettingList extends StatelessWidget {
       ),
     );
   }
-
-  static void show(BuildContext context, BoardSettingContext settingContext) {
-    final list = BoardSettingList(
-      settingContext: settingContext,
-      onAction: (action, settingContext) {
-        switch (action) {
-          case BoardSettingAction.properties:
-            GridPropertyList(
-                    gridId: settingContext.viewId,
-                    fieldController: settingContext.fieldController)
-                .show(context);
-            break;
-          case BoardSettingAction.groups:
-            GridGroupList(
-                    viewId: settingContext.viewId,
-                    fieldController: settingContext.fieldController)
-                .show(context);
-            break;
-        }
-      },
-    );
-
-    FlowyOverlay.of(context).insertWithAnchor(
-      widget: OverlayContainer(
-        constraints: BoxConstraints.loose(const Size(140, 400)),
-        child: list,
-      ),
-      identifier: identifier(),
-      anchorContext: context,
-      anchorDirection: AnchorDirection.bottomRight,
-      style: FlowyOverlayStyle(blur: false),
-    );
-  }
-
-  static String identifier() {
-    return (BoardSettingList).toString();
-  }
 }
 
 class _SettingItem extends StatelessWidget {
@@ -177,3 +139,50 @@ extension _GridSettingExtension on BoardSettingAction {
     }
   }
 }
+
+class BoardSettingListPopover extends StatefulWidget {
+  final BoardSettingContext settingContext;
+
+  const BoardSettingListPopover({
+    Key? key,
+    required this.settingContext,
+  }) : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() => _BoardSettingListPopoverState();
+}
+
+class _BoardSettingListPopoverState extends State<BoardSettingListPopover> {
+  bool _showGridPropertyList = false;
+
+  @override
+  Widget build(BuildContext context) {
+    if (_showGridPropertyList) {
+      return OverlayContainer(
+        constraints: BoxConstraints.loose(const Size(260, 400)),
+        child: GridPropertyList(
+          gridId: widget.settingContext.viewId,
+          fieldController: widget.settingContext.fieldController,
+        ),
+      );
+    }
+
+    return OverlayContainer(
+      constraints: BoxConstraints.loose(const Size(140, 400)),
+      child: BoardSettingList(
+        settingContext: widget.settingContext,
+        onAction: (action, settingContext) {
+          switch (action) {
+            case BoardSettingAction.groups:
+              break;
+            case BoardSettingAction.properties:
+              setState(() {
+                _showGridPropertyList = true;
+              });
+              break;
+          }
+        },
+      ),
+    );
+  }
+}

+ 16 - 7
frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_toolbar.dart

@@ -1,4 +1,5 @@
 import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
+import 'package:appflowy_popover/popover.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/style_widget/icon_button.dart';
@@ -47,14 +48,22 @@ class _SettingButton extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     final theme = context.read<AppTheme>();
-    return FlowyIconButton(
-      hoverColor: theme.hover,
-      width: 22,
-      onPressed: () => BoardSettingList.show(context, settingContext),
-      icon: Padding(
-        padding: const EdgeInsets.symmetric(vertical: 3.0, horizontal: 3.0),
-        child: svgWidget("grid/setting/setting"),
+    return Popover(
+      triggerActions: PopoverTriggerActionFlags.click,
+      child: FlowyIconButton(
+        hoverColor: theme.hover,
+        width: 22,
+        onPressed: () {},
+        icon: Padding(
+          padding: const EdgeInsets.symmetric(vertical: 3.0, horizontal: 3.0),
+          child: svgWidget("grid/setting/setting"),
+        ),
       ),
+      popupBuilder: (BuildContext popoverContext) {
+        return BoardSettingListPopover(
+          settingContext: settingContext,
+        );
+      },
     );
   }
 }

+ 9 - 4
frontend/app_flowy/lib/plugins/grid/presentation/grid_page.dart

@@ -3,6 +3,7 @@ import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart'
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/plugins/grid/application/grid_bloc.dart';
 import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_scrollview.dart';
@@ -290,10 +291,14 @@ class _GridRowsState extends State<_GridRows> {
       rowCache: rowCache,
     );
 
-    RowDetailPage(
-      cellBuilder: cellBuilder,
-      dataController: dataController,
-    ).show(context);
+    FlowyOverlay.show(
+        context: context,
+        builder: (BuildContext context) {
+          return RowDetailPage(
+            cellBuilder: cellBuilder,
+            dataController: dataController,
+          );
+        });
   }
 }
 

+ 35 - 7
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_accessory.dart

@@ -20,14 +20,36 @@ class GridCellAccessoryBuildContext {
   });
 }
 
-abstract class GridCellAccessory implements Widget {
+class GridCellAccessoryBuilder {
+  final GlobalKey _key = GlobalKey();
+
+  final Widget Function(Key key) _builder;
+
+  GridCellAccessoryBuilder({required Widget Function(Key key) builder})
+      : _builder = builder;
+
+  Widget build() => _builder(_key);
+
+  void onTap() {
+    (_key.currentState as GridCellAccessoryState).onTap();
+  }
+
+  bool enable() {
+    if (_key.currentState == null) {
+      return true;
+    }
+    return (_key.currentState as GridCellAccessoryState).enable();
+  }
+}
+
+abstract class GridCellAccessoryState {
   void onTap();
 
   // The accessory will be hidden if enable() return false;
   bool enable() => true;
 }
 
-class PrimaryCellAccessory extends StatelessWidget with GridCellAccessory {
+class PrimaryCellAccessory extends StatefulWidget {
   final VoidCallback onTapCallback;
   final bool isCellEditing;
   const PrimaryCellAccessory({
@@ -36,9 +58,15 @@ class PrimaryCellAccessory extends StatelessWidget with GridCellAccessory {
     Key? key,
   }) : super(key: key);
 
+  @override
+  State<StatefulWidget> createState() => _PrimaryCellAccessoryState();
+}
+
+class _PrimaryCellAccessoryState extends State<PrimaryCellAccessory>
+    with GridCellAccessoryState {
   @override
   Widget build(BuildContext context) {
-    if (isCellEditing) {
+    if (widget.isCellEditing) {
       return const SizedBox();
     } else {
       final theme = context.watch<AppTheme>();
@@ -53,10 +81,10 @@ class PrimaryCellAccessory extends StatelessWidget with GridCellAccessory {
   }
 
   @override
-  void onTap() => onTapCallback();
+  void onTap() => widget.onTapCallback();
 
   @override
-  bool enable() => !isCellEditing;
+  bool enable() => !widget.isCellEditing;
 }
 
 class AccessoryHover extends StatefulWidget {
@@ -170,7 +198,7 @@ class _Background extends StatelessWidget {
 }
 
 class CellAccessoryContainer extends StatelessWidget {
-  final List<GridCellAccessory> accessories;
+  final List<GridCellAccessoryBuilder> accessories;
   const CellAccessoryContainer({required this.accessories, Key? key})
       : super(key: key);
 
@@ -186,7 +214,7 @@ class CellAccessoryContainer extends StatelessWidget {
           width: 26,
           height: 26,
           padding: const EdgeInsets.all(3),
-          child: accessory,
+          child: accessory.build(),
         ),
       );
       return GestureDetector(

+ 3 - 3
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_builder.dart

@@ -94,7 +94,7 @@ abstract class CellEditable {
   ValueNotifier<bool> get onCellEditing;
 }
 
-typedef AccessoryBuilder = List<GridCellAccessory> Function(
+typedef AccessoryBuilder = List<GridCellAccessoryBuilder> Function(
     GridCellAccessoryBuildContext buildContext);
 
 abstract class CellAccessory extends Widget {
@@ -125,8 +125,8 @@ abstract class GridCellWidget extends StatefulWidget
   final ValueNotifier<bool> onCellEditing = ValueNotifier<bool>(false);
 
   @override
-  List<GridCellAccessory> Function(GridCellAccessoryBuildContext buildContext)?
-      get accessoryBuilder => null;
+  List<GridCellAccessoryBuilder> Function(
+      GridCellAccessoryBuildContext buildContext)? get accessoryBuilder => null;
 
   @override
   final GridCellFocusListener beginFocus = GridCellFocusListener();

+ 1 - 1
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_container.dart

@@ -80,7 +80,7 @@ class CellContainer extends StatelessWidget {
 
 class _GridCellEnterRegion extends StatelessWidget {
   final Widget child;
-  final List<GridCellAccessory> accessories;
+  final List<GridCellAccessoryBuilder> accessories;
   const _GridCellEnterRegion(
       {required this.child, required this.accessories, Key? key})
       : super(key: key);

+ 29 - 18
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_cell.dart

@@ -3,6 +3,7 @@ import 'package:flutter/widgets.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/plugins/grid/application/prelude.dart';
+import 'package:appflowy_popover/popover.dart';
 
 import '../cell_builder.dart';
 import 'date_editor.dart';
@@ -39,10 +40,12 @@ class GridDateCell extends GridCellWidget {
 }
 
 class _DateCellState extends GridCellState<GridDateCell> {
+  late PopoverController _popover;
   late DateCellBloc _cellBloc;
 
   @override
   void initState() {
+    _popover = PopoverController();
     final cellController = widget.cellControllerBuilder.build();
     _cellBloc = getIt<DateCellBloc>(param1: cellController)
       ..add(const DateCellEvent.initial());
@@ -58,19 +61,34 @@ class _DateCellState extends GridCellState<GridDateCell> {
       value: _cellBloc,
       child: BlocBuilder<DateCellBloc, DateCellState>(
         builder: (context, state) {
-          return SizedBox.expand(
-            child: GestureDetector(
-              behavior: HitTestBehavior.opaque,
-              onTap: () => _showCalendar(context),
-              child: MouseRegion(
-                opaque: false,
-                cursor: SystemMouseCursors.click,
-                child: Align(
-                  alignment: alignment,
-                  child: FlowyText.medium(state.dateStr, fontSize: 12),
+          return Popover(
+            controller: _popover,
+            offset: const Offset(0, 20),
+            direction: PopoverDirection.bottomWithLeftAligned,
+            child: SizedBox.expand(
+              child: GestureDetector(
+                behavior: HitTestBehavior.opaque,
+                onTap: () => _showCalendar(context),
+                child: MouseRegion(
+                  opaque: false,
+                  cursor: SystemMouseCursors.click,
+                  child: Align(
+                    alignment: alignment,
+                    child: FlowyText.medium(state.dateStr, fontSize: 12),
+                  ),
                 ),
               ),
             ),
+            popupBuilder: (BuildContext popoverContent) {
+              final bloc = context.read<DateCellBloc>();
+              return DateCellEditor(
+                cellController: bloc.cellController.clone(),
+                onDismissed: () => widget.onCellEditing.value = false,
+              );
+            },
+            onClose: () {
+              widget.onCellEditing.value = false;
+            },
           );
         },
       ),
@@ -78,14 +96,7 @@ class _DateCellState extends GridCellState<GridDateCell> {
   }
 
   void _showCalendar(BuildContext context) {
-    final bloc = context.read<DateCellBloc>();
-    widget.onCellEditing.value = true;
-    final calendar =
-        DateCellEditor(onDismissed: () => widget.onCellEditing.value = false);
-    calendar.show(
-      context,
-      cellController: bloc.cellController.clone(),
-    );
+    _popover.show();
   }
 
   @override

+ 88 - 115
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_editor.dart

@@ -1,6 +1,7 @@
 import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'package:app_flowy/plugins/grid/application/cell/date_cal_bloc.dart';
 import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
+import 'package:appflowy_popover/popover.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
@@ -23,58 +24,54 @@ final kFirstDay = DateTime(kToday.year, kToday.month - 3, kToday.day);
 final kLastDay = DateTime(kToday.year, kToday.month + 3, kToday.day);
 const kMargin = EdgeInsets.symmetric(horizontal: 6, vertical: 10);
 
-class DateCellEditor with FlowyOverlayDelegate {
+class DateCellEditor extends StatefulWidget {
   final VoidCallback onDismissed;
+  final GridDateCellController cellController;
 
   const DateCellEditor({
+    Key? key,
     required this.onDismissed,
-  });
-
-  Future<void> show(
-    BuildContext context, {
-    required GridDateCellController cellController,
-  }) async {
-    DateCellEditor.remove(context);
+    required this.cellController,
+  }) : super(key: key);
 
-    final result =
-        await cellController.getFieldTypeOption(DateTypeOptionDataParser());
+  @override
+  State<StatefulWidget> createState() => _DateCellEditor();
+}
 
-    result.fold(
-      (dateTypeOptionPB) {
-        final calendar = _CellCalendarWidget(
-          cellContext: cellController,
-          dateTypeOptionPB: dateTypeOptionPB,
-        );
+class _DateCellEditor extends State<DateCellEditor> {
+  DateTypeOptionPB? _dateTypeOptionPB;
 
-        FlowyOverlay.of(context).insertWithAnchor(
-          widget: OverlayContainer(
-            constraints: BoxConstraints.loose(const Size(320, 500)),
-            child: calendar,
-          ),
-          identifier: DateCellEditor.identifier(),
-          anchorContext: context,
-          anchorDirection: AnchorDirection.leftWithCenterAligned,
-          style: FlowyOverlayStyle(blur: false),
-          delegate: this,
-        );
-      },
-      (err) => Log.error(err),
-    );
+  @override
+  void initState() {
+    super.initState();
+    _fetchData();
   }
 
-  static void remove(BuildContext context) {
-    FlowyOverlay.of(context).remove(identifier());
-  }
+  _fetchData() async {
+    final result = await widget.cellController
+        .getFieldTypeOption(DateTypeOptionDataParser());
 
-  static String identifier() {
-    return (DateCellEditor).toString();
+    result.fold((dateTypeOptionPB) {
+      setState(() {
+        _dateTypeOptionPB = dateTypeOptionPB;
+      });
+    }, (err) => Log.error(err));
   }
 
   @override
-  void didRemove() => onDismissed();
+  Widget build(BuildContext context) {
+    if (_dateTypeOptionPB == null) {
+      return Container();
+    }
 
-  @override
-  bool asBarrier() => true;
+    return OverlayContainer(
+      constraints: BoxConstraints.loose(const Size(320, 500)),
+      child: _CellCalendarWidget(
+        cellContext: widget.cellController,
+        dateTypeOptionPB: _dateTypeOptionPB!,
+      ),
+    );
+  }
 }
 
 class _CellCalendarWidget extends StatelessWidget {
@@ -169,17 +166,14 @@ class _CellCalendarWidget extends StatelessWidget {
             );
           },
           onDaySelected: (selectedDay, focusedDay) {
-            _CalDateTimeSetting.hide(context);
             context
                 .read<DateCalBloc>()
                 .add(DateCalEvent.selectDay(selectedDay));
           },
           onFormatChanged: (format) {
-            _CalDateTimeSetting.hide(context);
             context.read<DateCalBloc>().add(DateCalEvent.setCalFormat(format));
           },
           onPageChanged: (focusedDay) {
-            _CalDateTimeSetting.hide(context);
             context
                 .read<DateCalBloc>()
                 .add(DateCalEvent.setFocusedDay(focusedDay));
@@ -247,7 +241,6 @@ class _TimeTextFieldState extends State<_TimeTextField> {
     if (widget.bloc.state.dateTypeOptionPB.includeTime) {
       _focusNode.addListener(() {
         if (mounted) {
-          _CalDateTimeSetting.hide(context);
           widget.bloc.add(DateCalEvent.setTime(_controller.text));
         }
       });
@@ -304,29 +297,34 @@ class _DateTypeOptionButton extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
-    final title = "${LocaleKeys.grid_field_dateFormat.tr()} &${LocaleKeys.grid_field_timeFormat.tr()}";
+    final title =
+        "${LocaleKeys.grid_field_dateFormat.tr()} &${LocaleKeys.grid_field_timeFormat.tr()}";
     return BlocSelector<DateCalBloc, DateCalState, DateTypeOptionPB>(
       selector: (state) => state.dateTypeOptionPB,
       builder: (context, dateTypeOptionPB) {
-        return FlowyButton(
-          text: FlowyText.medium(title, fontSize: 12),
-          hoverColor: theme.hover,
-          margin: kMargin,
-          onTap: () => _showTimeSetting(dateTypeOptionPB, context),
-          rightIcon: svgWidget("grid/more", color: theme.iconColor),
+        return Popover(
+          triggerActions:
+              PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
+          offset: const Offset(20, 0),
+          child: FlowyButton(
+            text: FlowyText.medium(title, fontSize: 12),
+            hoverColor: theme.hover,
+            margin: kMargin,
+            rightIcon: svgWidget("grid/more", color: theme.iconColor),
+          ),
+          popupBuilder: (BuildContext popContext) {
+            return OverlayContainer(
+              constraints: BoxConstraints.loose(const Size(140, 100)),
+              child: _CalDateTimeSetting(
+                dateTypeOptionPB: dateTypeOptionPB,
+                onEvent: (event) => context.read<DateCalBloc>().add(event),
+              ),
+            );
+          },
         );
       },
     );
   }
-
-  void _showTimeSetting(
-      DateTypeOptionPB dateTypeOptionPB, BuildContext context) {
-    final setting = _CalDateTimeSetting(
-      dateTypeOptionPB: dateTypeOptionPB,
-      onEvent: (event) => context.read<DateCalBloc>().add(event),
-    );
-    setting.show(context);
-  }
 }
 
 class _CalDateTimeSetting extends StatefulWidget {
@@ -338,54 +336,48 @@ class _CalDateTimeSetting extends StatefulWidget {
 
   @override
   State<_CalDateTimeSetting> createState() => _CalDateTimeSettingState();
-
-  static String identifier() {
-    return (_CalDateTimeSetting).toString();
-  }
-
-  void show(BuildContext context) {
-    hide(context);
-    FlowyOverlay.of(context).insertWithAnchor(
-      widget: OverlayContainer(
-        constraints: BoxConstraints.loose(const Size(140, 100)),
-        child: this,
-      ),
-      identifier: _CalDateTimeSetting.identifier(),
-      anchorContext: context,
-      anchorDirection: AnchorDirection.rightWithCenterAligned,
-      anchorOffset: const Offset(20, 0),
-    );
-  }
-
-  static void hide(BuildContext context) {
-    FlowyOverlay.of(context).remove(identifier());
-  }
 }
 
 class _CalDateTimeSettingState extends State<_CalDateTimeSetting> {
   String? overlayIdentifier;
+  final _popoverMutex = PopoverMutex();
 
   @override
   Widget build(BuildContext context) {
     List<Widget> children = [
-      DateFormatButton(onTap: () {
-        final list = DateFormatList(
-          selectedFormat: widget.dateTypeOptionPB.dateFormat,
-          onSelected: (format) =>
-              widget.onEvent(DateCalEvent.setDateFormat(format)),
-        );
-        _showOverlay(context, list);
-      }),
-      TimeFormatButton(
-        timeFormat: widget.dateTypeOptionPB.timeFormat,
-        onTap: () {
-          final list = TimeFormatList(
-            selectedFormat: widget.dateTypeOptionPB.timeFormat,
-            onSelected: (format) =>
-                widget.onEvent(DateCalEvent.setTimeFormat(format)),
+      Popover(
+        mutex: _popoverMutex,
+        triggerActions:
+            PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
+        offset: const Offset(20, 0),
+        popupBuilder: (BuildContext context) {
+          return OverlayContainer(
+            constraints: BoxConstraints.loose(const Size(460, 440)),
+            child: DateFormatList(
+              selectedFormat: widget.dateTypeOptionPB.dateFormat,
+              onSelected: (format) =>
+                  widget.onEvent(DateCalEvent.setDateFormat(format)),
+            ),
+          );
+        },
+        child: const DateFormatButton(),
+      ),
+      Popover(
+        mutex: _popoverMutex,
+        triggerActions:
+            PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
+        offset: const Offset(20, 0),
+        popupBuilder: (BuildContext context) {
+          return OverlayContainer(
+            constraints: BoxConstraints.loose(const Size(460, 440)),
+            child: TimeFormatList(
+              selectedFormat: widget.dateTypeOptionPB.timeFormat,
+              onSelected: (format) =>
+                  widget.onEvent(DateCalEvent.setTimeFormat(format)),
+            ),
           );
-          _showOverlay(context, list);
         },
+        child: TimeFormatButton(timeFormat: widget.dateTypeOptionPB.timeFormat),
       ),
     ];
 
@@ -404,23 +396,4 @@ class _CalDateTimeSettingState extends State<_CalDateTimeSetting> {
       ),
     );
   }
-
-  void _showOverlay(BuildContext context, Widget child) {
-    if (overlayIdentifier != null) {
-      FlowyOverlay.of(context).remove(overlayIdentifier!);
-    }
-
-    overlayIdentifier = child.toString();
-    FlowyOverlay.of(context).insertWithAnchor(
-      widget: OverlayContainer(
-        constraints: BoxConstraints.loose(const Size(460, 440)),
-        child: child,
-      ),
-      identifier: overlayIdentifier!,
-      anchorContext: context,
-      anchorDirection: AnchorDirection.rightWithCenterAligned,
-      style: FlowyOverlayStyle(blur: false),
-      anchorOffset: const Offset(20, 0),
-    );
-  }
 }

+ 49 - 11
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_cell.dart

@@ -1,7 +1,9 @@
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/plugins/grid/application/prelude.dart';
+import 'package:appflowy_popover/popover.dart';
 
 import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 // ignore: unused_import
 import 'package:flowy_sdk/log.dart';
@@ -133,7 +135,7 @@ class _MultiSelectCellState extends State<GridMultiSelectCell> {
   }
 }
 
-class SelectOptionWrap extends StatelessWidget {
+class SelectOptionWrap extends StatefulWidget {
   final List<SelectOptionPB> selectOptions;
   final void Function(bool)? onFocus;
   final SelectOptionCellStyle? cellStyle;
@@ -146,15 +148,28 @@ class SelectOptionWrap extends StatelessWidget {
     Key? key,
   }) : super(key: key);
 
+  @override
+  State<StatefulWidget> createState() => _SelectOptionWrapState();
+}
+
+class _SelectOptionWrapState extends State<SelectOptionWrap> {
+  late PopoverController _popover;
+
+  @override
+  void initState() {
+    _popover = PopoverController();
+    super.initState();
+  }
+
   @override
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     final Widget child;
-    if (selectOptions.isEmpty && cellStyle != null) {
+    if (widget.selectOptions.isEmpty && widget.cellStyle != null) {
       child = Align(
         alignment: Alignment.centerLeft,
         child: FlowyText.medium(
-          cellStyle!.placeholder,
+          widget.cellStyle!.placeholder,
           fontSize: 14,
           color: theme.shader3,
         ),
@@ -165,7 +180,7 @@ class SelectOptionWrap extends StatelessWidget {
         child: Wrap(
           spacing: 4,
           runSpacing: 2,
-          children: selectOptions
+          children: widget.selectOptions
               .map((option) => SelectOptionTag.fromOption(
                     context: context,
                     option: option,
@@ -179,14 +194,37 @@ class SelectOptionWrap extends StatelessWidget {
       alignment: AlignmentDirectional.center,
       fit: StackFit.expand,
       children: [
-        child,
+        Popover(
+          controller: _popover,
+          offset: const Offset(0, 20),
+          direction: PopoverDirection.bottomWithLeftAligned,
+          // triggerActions: PopoverTriggerActionFlags.c,
+          popupBuilder: (BuildContext context) {
+            WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
+              widget.onFocus?.call(true);
+            });
+            return OverlayContainer(
+              constraints: BoxConstraints.loose(
+                  Size(SelectOptionCellEditor.editorPanelWidth, 300)),
+              child: SizedBox(
+                width: SelectOptionCellEditor.editorPanelWidth,
+                child: SelectOptionCellEditor(
+                  cellController: widget.cellControllerBuilder.build()
+                      as GridSelectOptionCellController,
+                  onDismissed: () {
+                    widget.onFocus?.call(false);
+                  },
+                ),
+              ),
+            );
+          },
+          onClose: () {
+            widget.onFocus?.call(false);
+          },
+          child: child,
+        ),
         InkWell(onTap: () {
-          onFocus?.call(true);
-          SelectOptionCellEditor.show(
-            context,
-            cellControllerBuilder.build() as GridSelectOptionCellController,
-            () => onFocus?.call(false),
-          );
+          _popover.show();
         }),
       ],
     );

+ 68 - 59
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart

@@ -1,6 +1,7 @@
 import 'dart:collection';
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
 import 'package:app_flowy/plugins/grid/application/cell/select_option_editor_bloc.dart';
+import 'package:appflowy_popover/popover.dart';
 
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
@@ -28,6 +29,8 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
   final GridSelectOptionCellController cellController;
   final VoidCallback onDismissed;
 
+  static double editorPanelWidth = 300;
+
   const SelectOptionCellEditor({
     required this.cellController,
     required this.onDismissed,
@@ -226,76 +229,82 @@ class _CreateOptionCell extends StatelessWidget {
   }
 }
 
-class _SelectOptionCell extends StatelessWidget {
+class _SelectOptionCell extends StatefulWidget {
   final SelectOptionPB option;
   final bool isSelected;
   const _SelectOptionCell(this.option, this.isSelected, {Key? key})
       : super(key: key);
 
+  @override
+  State<_SelectOptionCell> createState() => _SelectOptionCellState();
+}
+
+class _SelectOptionCellState extends State<_SelectOptionCell> {
+  late PopoverController _popoverController;
+
+  @override
+  void initState() {
+    _popoverController = PopoverController();
+    super.initState();
+  }
+
   @override
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
-    return SizedBox(
-      height: GridSize.typeOptionItemHeight,
-      child: Row(
-        children: [
-          Flexible(
-            fit: FlexFit.loose,
-            child: SelectOptionTagCell(
-              option: option,
-              onSelected: (option) {
-                context
-                    .read<SelectOptionCellEditorBloc>()
-                    .add(SelectOptionEditorEvent.selectOption(option.id));
-              },
-              children: [
-                if (isSelected)
-                  Padding(
-                    padding: const EdgeInsets.only(right: 6),
-                    child: svgWidget("grid/checkmark"),
-                  ),
-              ],
+    return Popover(
+      controller: _popoverController,
+      offset: const Offset(20, 0),
+      child: SizedBox(
+        height: GridSize.typeOptionItemHeight,
+        child: Row(
+          children: [
+            Flexible(
+              fit: FlexFit.loose,
+              child: SelectOptionTagCell(
+                option: widget.option,
+                onSelected: (option) {
+                  context
+                      .read<SelectOptionCellEditorBloc>()
+                      .add(SelectOptionEditorEvent.selectOption(option.id));
+                },
+                children: [
+                  if (widget.isSelected)
+                    Padding(
+                      padding: const EdgeInsets.only(right: 6),
+                      child: svgWidget("grid/checkmark"),
+                    ),
+                ],
+              ),
             ),
-          ),
-          FlowyIconButton(
-            width: 30,
-            onPressed: () => _showEditPannel(context),
-            iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
-            icon: svgWidget("editor/details", color: theme.iconColor),
-          )
-        ],
+            FlowyIconButton(
+              width: 30,
+              onPressed: () => _popoverController.show(),
+              iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
+              icon: svgWidget("editor/details", color: theme.iconColor),
+            )
+          ],
+        ),
       ),
-    );
-  }
-
-  void _showEditPannel(BuildContext context) {
-    final pannel = SelectOptionTypeOptionEditor(
-      option: option,
-      onDeleted: () {
-        context
-            .read<SelectOptionCellEditorBloc>()
-            .add(SelectOptionEditorEvent.deleteOption(option));
-      },
-      onUpdated: (updatedOption) {
-        context
-            .read<SelectOptionCellEditorBloc>()
-            .add(SelectOptionEditorEvent.updateOption(updatedOption));
+      popupBuilder: (BuildContext popoverContext) {
+        return OverlayContainer(
+          constraints: BoxConstraints.loose(const Size(200, 300)),
+          child: SelectOptionTypeOptionEditor(
+            option: widget.option,
+            onDeleted: () {
+              context
+                  .read<SelectOptionCellEditorBloc>()
+                  .add(SelectOptionEditorEvent.deleteOption(widget.option));
+            },
+            onUpdated: (updatedOption) {
+              context
+                  .read<SelectOptionCellEditorBloc>()
+                  .add(SelectOptionEditorEvent.updateOption(updatedOption));
+            },
+            key: ValueKey(widget.option
+                .id), // Use ValueKey to refresh the UI, otherwise, it will remain the old value.
+          ),
+        );
       },
-      key: ValueKey(option
-          .id), // Use ValueKey to refresh the UI, otherwise, it will remain the old value.
-    );
-    final overlayIdentifier = (SelectOptionTypeOptionEditor).toString();
-
-    FlowyOverlay.of(context).remove(overlayIdentifier);
-    FlowyOverlay.of(context).insertWithAnchor(
-      widget: OverlayContainer(
-        constraints: BoxConstraints.loose(const Size(200, 300)),
-        child: pannel,
-      ),
-      identifier: overlayIdentifier,
-      anchorContext: context,
-      anchorDirection: AnchorDirection.rightWithCenterAligned,
-      anchorOffset: Offset(2 * overlayContainerPadding.left, 0),
     );
   }
 }

+ 24 - 45
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/cell_editor.dart

@@ -6,56 +6,13 @@ import 'dart:async';
 
 import 'package:flutter_bloc/flutter_bloc.dart';
 
-class URLCellEditor extends StatefulWidget with FlowyOverlayDelegate {
+class URLCellEditor extends StatefulWidget {
   final GridURLCellController cellController;
-  final VoidCallback completed;
-  const URLCellEditor(
-      {required this.cellController, required this.completed, Key? key})
+  const URLCellEditor({required this.cellController, Key? key})
       : super(key: key);
 
   @override
   State<URLCellEditor> createState() => _URLCellEditorState();
-
-  static void show(
-    BuildContext context,
-    GridURLCellController cellContext,
-    VoidCallback completed,
-  ) {
-    FlowyOverlay.of(context).remove(identifier());
-    final editor = URLCellEditor(
-      cellController: cellContext,
-      completed: completed,
-    );
-
-    //
-    FlowyOverlay.of(context).insertWithAnchor(
-      widget: OverlayContainer(
-        constraints: BoxConstraints.loose(const Size(300, 160)),
-        child: SizedBox(
-          width: 200,
-          child: Padding(padding: const EdgeInsets.all(6), child: editor),
-        ),
-      ),
-      identifier: URLCellEditor.identifier(),
-      anchorContext: context,
-      anchorDirection: AnchorDirection.bottomWithCenterAligned,
-      delegate: editor,
-    );
-  }
-
-  static String identifier() {
-    return (URLCellEditor).toString();
-  }
-
-  @override
-  bool asBarrier() {
-    return true;
-  }
-
-  @override
-  void didRemove() {
-    completed();
-  }
 }
 
 class _URLCellEditorState extends State<URLCellEditor> {
@@ -114,3 +71,25 @@ class _URLCellEditorState extends State<URLCellEditor> {
     }
   }
 }
+
+class URLEditorPopover extends StatelessWidget {
+  final GridURLCellController cellController;
+  const URLEditorPopover({required this.cellController, Key? key})
+      : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return OverlayContainer(
+      constraints: BoxConstraints.loose(const Size(300, 160)),
+      child: SizedBox(
+        width: 200,
+        child: Padding(
+          padding: const EdgeInsets.all(6),
+          child: URLCellEditor(
+            cellController: cellController,
+          ),
+        ),
+      ),
+    );
+  }
+}

+ 77 - 21
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/url_cell.dart

@@ -2,6 +2,7 @@ import 'dart:async';
 import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'package:app_flowy/plugins/grid/application/cell/url_cell_bloc.dart';
 import 'package:app_flowy/workspace/presentation/home/toast.dart';
+import 'package:appflowy_popover/popover.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
@@ -48,27 +49,37 @@ class GridURLCell extends GridCellWidget {
   @override
   GridCellState<GridURLCell> createState() => _GridURLCellState();
 
-  GridCellAccessory accessoryFromType(
+  GridCellAccessoryBuilder accessoryFromType(
       GridURLCellAccessoryType ty, GridCellAccessoryBuildContext buildContext) {
     switch (ty) {
       case GridURLCellAccessoryType.edit:
         final cellController =
             cellControllerBuilder.build() as GridURLCellController;
-        return _EditURLAccessory(
+        return GridCellAccessoryBuilder(
+          builder: (Key key) => _EditURLAccessory(
+            key: key,
             cellContext: cellController,
-            anchorContext: buildContext.anchorContext);
+            anchorContext: buildContext.anchorContext,
+          ),
+        );
 
       case GridURLCellAccessoryType.copyURL:
         final cellContext =
             cellControllerBuilder.build() as GridURLCellController;
-        return _CopyURLAccessory(cellContext: cellContext);
+        return GridCellAccessoryBuilder(
+          builder: (Key key) => _CopyURLAccessory(
+            key: key,
+            cellContext: cellContext,
+          ),
+        );
     }
   }
 
   @override
-  List<GridCellAccessory> Function(GridCellAccessoryBuildContext buildContext)
+  List<GridCellAccessoryBuilder> Function(
+          GridCellAccessoryBuildContext buildContext)
       get accessoryBuilder => (buildContext) {
-            final List<GridCellAccessory> accessories = [];
+            final List<GridCellAccessoryBuilder> accessories = [];
             if (cellStyle != null) {
               accessories.addAll(cellStyle!.accessoryTypes.map((ty) {
                 return accessoryFromType(ty, buildContext);
@@ -86,6 +97,8 @@ class GridURLCell extends GridCellWidget {
 }
 
 class _GridURLCellState extends GridCellState<GridURLCell> {
+  final _popoverController = PopoverController();
+  GridURLCellController? _cellContext;
   late URLCellBloc _cellBloc;
 
   @override
@@ -116,14 +129,28 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
             ),
           );
 
-          return SizedBox.expand(
+          return Popover(
+            controller: _popoverController,
+            direction: PopoverDirection.bottomWithLeftAligned,
+            offset: const Offset(0, 20),
+            child: SizedBox.expand(
               child: GestureDetector(
-            child: Align(alignment: Alignment.centerLeft, child: richText),
-            onTap: () async {
-              final url = context.read<URLCellBloc>().state.url;
-              await _openUrlOrEdit(url);
+                child: Align(alignment: Alignment.centerLeft, child: richText),
+                onTap: () async {
+                  final url = context.read<URLCellBloc>().state.url;
+                  await _openUrlOrEdit(url);
+                },
+              ),
+            ),
+            popupBuilder: (BuildContext popoverContext) {
+              return URLEditorPopover(
+                cellController: _cellContext!,
+              );
+            },
+            onClose: () {
+              widget.onCellEditing.value = false;
             },
-          ));
+          );
         },
       ),
     );
@@ -140,12 +167,10 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
     if (url.isNotEmpty && await canLaunchUrl(uri)) {
       await launchUrl(uri);
     } else {
-      final cellContext =
+      _cellContext =
           widget.cellControllerBuilder.build() as GridURLCellController;
       widget.onCellEditing.value = true;
-      URLCellEditor.show(context, cellContext, () {
-        widget.onCellEditing.value = false;
-      });
+      _popoverController.show();
     }
   }
 
@@ -163,7 +188,7 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
   }
 }
 
-class _EditURLAccessory extends StatelessWidget with GridCellAccessory {
+class _EditURLAccessory extends StatefulWidget {
   final GridURLCellController cellContext;
   final BuildContext anchorContext;
   const _EditURLAccessory({
@@ -172,24 +197,55 @@ class _EditURLAccessory extends StatelessWidget with GridCellAccessory {
     Key? key,
   }) : super(key: key);
 
+  @override
+  State<StatefulWidget> createState() => _EditURLAccessoryState();
+}
+
+class _EditURLAccessoryState extends State<_EditURLAccessory>
+    with GridCellAccessoryState {
+  late PopoverController _popoverController;
+
+  @override
+  void initState() {
+    _popoverController = PopoverController();
+    super.initState();
+  }
+
   @override
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
-    return svgWidget("editor/edit", color: theme.iconColor);
+    return Popover(
+      controller: _popoverController,
+      direction: PopoverDirection.bottomWithLeftAligned,
+      triggerActions: PopoverTriggerActionFlags.click,
+      offset: const Offset(0, 20),
+      child: svgWidget("editor/edit", color: theme.iconColor),
+      popupBuilder: (BuildContext popoverContext) {
+        return URLEditorPopover(
+          cellController: widget.cellContext.clone(),
+        );
+      },
+    );
   }
 
   @override
   void onTap() {
-    URLCellEditor.show(anchorContext, cellContext, () {});
+    _popoverController.show();
   }
 }
 
-class _CopyURLAccessory extends StatelessWidget with GridCellAccessory {
+class _CopyURLAccessory extends StatefulWidget {
   final GridURLCellController cellContext;
   const _CopyURLAccessory({required this.cellContext, Key? key})
       : super(key: key);
 
   @override
+  State<StatefulWidget> createState() => _CopyURLAccessoryState();
+}
+
+class _CopyURLAccessoryState extends State<_CopyURLAccessory>
+    with GridCellAccessoryState {
+  @override
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     return svgWidget("editor/copy", color: theme.iconColor);
@@ -198,7 +254,7 @@ class _CopyURLAccessory extends StatelessWidget with GridCellAccessory {
   @override
   void onTap() {
     final content =
-        cellContext.getCellData(loadIfNotExist: false)?.content ?? "";
+        widget.cellContext.getCellData(loadIfNotExist: false)?.content ?? "";
     Clipboard.setData(ClipboardData(text: content));
     showMessageToast(LocaleKeys.grid_row_copyProperty.tr());
   }

+ 22 - 32
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_cell.dart

@@ -1,6 +1,6 @@
 import 'package:app_flowy/plugins/grid/application/field/field_cell_bloc.dart';
 import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
-import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
+import 'package:appflowy_popover/popover.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
@@ -13,23 +13,35 @@ import '../../layout/sizes.dart';
 import 'field_type_extension.dart';
 
 import 'field_cell_action_sheet.dart';
-import 'field_editor.dart';
 
 class GridFieldCell extends StatelessWidget {
   final GridFieldCellContext cellContext;
-  const GridFieldCell(this.cellContext, {Key? key}) : super(key: key);
+  const GridFieldCell({
+    Key? key,
+    required this.cellContext,
+  }) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
     return BlocProvider(
-      create: (context) => FieldCellBloc(cellContext: cellContext)
-        ..add(const FieldCellEvent.initial()),
+      create: (context) {
+        return FieldCellBloc(cellContext: cellContext);
+      },
       child: BlocBuilder<FieldCellBloc, FieldCellState>(
-        // buildWhen: (p, c) => p.field != c.field,
         builder: (context, state) {
-          final button = FieldCellButton(
-            field: state.field,
-            onTap: () => _showActionSheet(context),
+          final button = Popover(
+            direction: PopoverDirection.bottomWithLeftAligned,
+            triggerActions: PopoverTriggerActionFlags.click,
+            offset: const Offset(0, 10),
+            popupBuilder: (BuildContext context) {
+              return GridFieldCellActionSheet(
+                cellContext: cellContext,
+              );
+            },
+            child: FieldCellButton(
+              field: cellContext.field,
+              onTap: () {},
+            ),
           );
 
           const line = Positioned(
@@ -51,29 +63,6 @@ class GridFieldCell extends StatelessWidget {
       ),
     );
   }
-
-  void _showActionSheet(BuildContext context) {
-    final state = context.read<FieldCellBloc>().state;
-    GridFieldCellActionSheet(
-      cellContext:
-          GridFieldCellContext(gridId: state.gridId, field: state.field),
-      onEdited: () => _showFieldEditor(context),
-    ).show(context);
-  }
-
-  void _showFieldEditor(BuildContext context) {
-    final state = context.read<FieldCellBloc>().state;
-    final field = state.field;
-
-    FieldEditor(
-      gridId: state.gridId,
-      fieldName: field.name,
-      typeOptionLoader: FieldTypeOptionLoader(
-        gridId: state.gridId,
-        field: field,
-      ),
-    ).show(context);
-  }
 }
 
 class _GridHeaderCellContainer extends StatelessWidget {
@@ -119,6 +108,7 @@ class _DragToExpandLine extends StatelessWidget {
       child: GestureDetector(
         behavior: HitTestBehavior.opaque,
         onHorizontalDragUpdate: (value) {
+          debugPrint("update new width: ${value.delta.dx}");
           context
               .read<FieldCellBloc>()
               .add(FieldCellEvent.startUpdateWidth(value.delta.dx));

+ 53 - 45
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_cell_action_sheet.dart

@@ -1,3 +1,5 @@
+import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
+import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_editor.dart';
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/plugins/grid/application/prelude.dart';
 import 'package:flowy_infra/image.dart';
@@ -13,75 +15,81 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
 
 import '../../layout/sizes.dart';
 
-class GridFieldCellActionSheet extends StatelessWidget
-    with FlowyOverlayDelegate {
+class GridFieldCellActionSheet extends StatefulWidget {
   final GridFieldCellContext cellContext;
-  final VoidCallback onEdited;
-  const GridFieldCellActionSheet(
-      {required this.cellContext, required this.onEdited, Key? key})
+  const GridFieldCellActionSheet({required this.cellContext, Key? key})
       : super(key: key);
 
-  void show(BuildContext overlayContext) {
-    FlowyOverlay.of(overlayContext).insertWithAnchor(
-      widget: OverlayContainer(
-        constraints: BoxConstraints.loose(const Size(240, 200)),
-        child: this,
-      ),
-      identifier: GridFieldCellActionSheet.identifier(),
-      anchorContext: overlayContext,
-      anchorDirection: AnchorDirection.bottomWithLeftAligned,
-      delegate: this,
-    );
-  }
+  @override
+  State<StatefulWidget> createState() => _GridFieldCellActionSheetState();
+}
+
+class _GridFieldCellActionSheetState extends State<GridFieldCellActionSheet> {
+  bool _showFieldEditor = false;
 
   @override
   Widget build(BuildContext context) {
+    if (_showFieldEditor) {
+      final field = widget.cellContext.field;
+      return OverlayContainer(
+        constraints: BoxConstraints.loose(const Size(240, 200)),
+        child: FieldEditor(
+          gridId: widget.cellContext.gridId,
+          fieldName: field.name,
+          typeOptionLoader: FieldTypeOptionLoader(
+            gridId: widget.cellContext.gridId,
+            field: field,
+          ),
+        ),
+      );
+    }
     return BlocProvider(
-      create: (context) => getIt<FieldActionSheetBloc>(param1: cellContext),
-      child: SingleChildScrollView(
-        child: Column(
-          children: [
-            _EditFieldButton(
-              onEdited: () {
-                FlowyOverlay.of(context).remove(identifier());
-                onEdited();
-              },
-            ),
-            const VSpace(6),
-            _FieldOperationList(cellContext,
-                () => FlowyOverlay.of(context).remove(identifier())),
-          ],
+      create: (context) =>
+          getIt<FieldActionSheetBloc>(param1: widget.cellContext),
+      child: OverlayContainer(
+        constraints: BoxConstraints.loose(const Size(240, 200)),
+        child: SingleChildScrollView(
+          child: Column(
+            children: [
+              _EditFieldButton(
+                cellContext: widget.cellContext,
+                onTap: () {
+                  setState(() {
+                    _showFieldEditor = true;
+                  });
+                },
+              ),
+              const VSpace(6),
+              _FieldOperationList(widget.cellContext, () {}),
+            ],
+          ),
         ),
       ),
     );
   }
-
-  static String identifier() {
-    return (GridFieldCellActionSheet).toString();
-  }
-
-  @override
-  bool asBarrier() {
-    return true;
-  }
 }
 
 class _EditFieldButton extends StatelessWidget {
-  final Function() onEdited;
-  const _EditFieldButton({required this.onEdited, Key? key}) : super(key: key);
+  final GridFieldCellContext cellContext;
+  final void Function()? onTap;
+  const _EditFieldButton({required this.cellContext, Key? key, this.onTap})
+      : super(key: key);
 
   @override
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
+
     return BlocBuilder<FieldActionSheetBloc, FieldActionSheetState>(
       builder: (context, state) {
         return SizedBox(
           height: GridSize.typeOptionItemHeight,
           child: FlowyButton(
-            text: FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(),
-                fontSize: 12),
+            text: FlowyText.medium(
+              LocaleKeys.grid_field_editProperty.tr(),
+              fontSize: 12,
+            ),
             hoverColor: theme.hover,
-            onTap: onEdited,
+            onTap: onTap,
           ),
         );
       },

+ 29 - 33
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart

@@ -1,7 +1,7 @@
 import 'package:app_flowy/plugins/grid/application/field/field_editor_bloc.dart';
 import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
+import 'package:appflowy_popover/popover.dart';
 import 'package:easy_localization/easy_localization.dart';
-import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flutter/material.dart';
@@ -10,7 +10,7 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'field_name_input.dart';
 import 'field_type_option_editor.dart';
 
-class FieldEditor extends StatelessWidget with FlowyOverlayDelegate {
+class FieldEditor extends StatefulWidget {
   final String gridId;
   final String fieldName;
 
@@ -22,13 +22,26 @@ class FieldEditor extends StatelessWidget with FlowyOverlayDelegate {
     Key? key,
   }) : super(key: key);
 
+  @override
+  State<StatefulWidget> createState() => _FieldEditorState();
+}
+
+class _FieldEditorState extends State<FieldEditor> {
+  late PopoverMutex popoverMutex;
+
+  @override
+  void initState() {
+    popoverMutex = PopoverMutex();
+    super.initState();
+  }
+
   @override
   Widget build(BuildContext context) {
     return BlocProvider(
       create: (context) => FieldEditorBloc(
-        gridId: gridId,
-        fieldName: fieldName,
-        loader: typeOptionLoader,
+        gridId: widget.gridId,
+        fieldName: widget.fieldName,
+        loader: widget.typeOptionLoader,
       )..add(const FieldEditorEvent.initial()),
       child: BlocBuilder<FieldEditorBloc, FieldEditorState>(
         buildWhen: (p, c) => false,
@@ -41,42 +54,22 @@ class FieldEditor extends StatelessWidget with FlowyOverlayDelegate {
               const VSpace(10),
               const _FieldNameCell(),
               const VSpace(10),
-              const _FieldTypeOptionCell(),
+              _FieldTypeOptionCell(popoverMutex: popoverMutex),
             ],
           );
         },
       ),
     );
   }
-
-  void show(
-    BuildContext context, {
-    AnchorDirection anchorDirection = AnchorDirection.bottomWithLeftAligned,
-  }) {
-    FlowyOverlay.of(context).remove(identifier());
-    FlowyOverlay.of(context).insertWithAnchor(
-      widget: OverlayContainer(
-        constraints: BoxConstraints.loose(const Size(280, 400)),
-        child: this,
-      ),
-      identifier: identifier(),
-      anchorContext: context,
-      anchorDirection: anchorDirection,
-      style: FlowyOverlayStyle(blur: false),
-      delegate: this,
-    );
-  }
-
-  static String identifier() {
-    return (FieldEditor).toString();
-  }
-
-  @override
-  bool asBarrier() => true;
 }
 
 class _FieldTypeOptionCell extends StatelessWidget {
-  const _FieldTypeOptionCell({Key? key}) : super(key: key);
+  final PopoverMutex popoverMutex;
+
+  const _FieldTypeOptionCell({
+    Key? key,
+    required this.popoverMutex,
+  }) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
@@ -88,7 +81,10 @@ class _FieldTypeOptionCell extends StatelessWidget {
           (fieldContext) {
             final dataController =
                 context.read<FieldEditorBloc>().dataController;
-            return FieldTypeOptionEditor(dataController: dataController);
+            return FieldTypeOptionEditor(
+              dataController: dataController,
+              popoverMutex: popoverMutex,
+            );
           },
         );
       },

+ 27 - 53
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_type_option_editor.dart

@@ -1,5 +1,6 @@
 import 'dart:typed_data';
 import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_data_controller.dart';
+import 'package:appflowy_popover/popover.dart';
 import 'package:dartz/dartz.dart' show Either;
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
@@ -23,30 +24,25 @@ typedef SwitchToFieldCallback
   FieldType fieldType,
 );
 
-class FieldTypeOptionEditor extends StatefulWidget {
+class FieldTypeOptionEditor extends StatelessWidget {
   final TypeOptionDataController dataController;
+  final PopoverMutex popoverMutex;
 
   const FieldTypeOptionEditor({
     required this.dataController,
+    required this.popoverMutex,
     Key? key,
   }) : super(key: key);
 
-  @override
-  State<FieldTypeOptionEditor> createState() => _FieldTypeOptionEditorState();
-}
-
-class _FieldTypeOptionEditorState extends State<FieldTypeOptionEditor> {
-  String? currentOverlayIdentifier;
-
   @override
   Widget build(BuildContext context) {
     return BlocProvider(
-      create: (context) => FieldTypeOptionEditBloc(widget.dataController)
+      create: (context) => FieldTypeOptionEditBloc(dataController)
         ..add(const FieldTypeOptionEditEvent.initial()),
       child: BlocBuilder<FieldTypeOptionEditBloc, FieldTypeOptionEditState>(
         builder: (context, state) {
           List<Widget> children = [
-            _switchFieldTypeButton(context, widget.dataController.field)
+            _switchFieldTypeButton(context, dataController.field)
           ];
           final typeOptionWidget =
               _typeOptionWidget(context: context, state: state);
@@ -68,18 +64,28 @@ class _FieldTypeOptionEditorState extends State<FieldTypeOptionEditor> {
     final theme = context.watch<AppTheme>();
     return SizedBox(
       height: GridSize.typeOptionItemHeight,
-      child: FlowyButton(
-        text: FlowyText.medium(field.fieldType.title(), fontSize: 12),
-        margin: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
-        hoverColor: theme.hover,
-        onTap: () {
+      child: Popover(
+        triggerActions:
+            PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
+        mutex: popoverMutex,
+        offset: const Offset(20, 0),
+        popupBuilder: (context) {
           final list = FieldTypeList(onSelectField: (newFieldType) {
-            widget.dataController.switchToField(newFieldType);
+            dataController.switchToField(newFieldType);
           });
-          _showOverlay(context, list);
+          return OverlayContainer(
+            constraints: BoxConstraints.loose(const Size(460, 440)),
+            child: list,
+          );
         },
-        leftIcon: svgWidget(field.fieldType.iconName(), color: theme.iconColor),
-        rightIcon: svgWidget("grid/more", color: theme.iconColor),
+        child: FlowyButton(
+          text: FlowyText.medium(field.fieldType.title(), fontSize: 12),
+          margin: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
+          hoverColor: theme.hover,
+          leftIcon:
+              svgWidget(field.fieldType.iconName(), color: theme.iconColor),
+          rightIcon: svgWidget("grid/more", color: theme.iconColor),
+        ),
       ),
     );
   }
@@ -88,44 +94,12 @@ class _FieldTypeOptionEditorState extends State<FieldTypeOptionEditor> {
     required BuildContext context,
     required FieldTypeOptionEditState state,
   }) {
-    final overlayDelegate = TypeOptionOverlayDelegate(
-      showOverlay: _showOverlay,
-      hideOverlay: _hideOverlay,
-    );
-
     return makeTypeOptionWidget(
       context: context,
-      overlayDelegate: overlayDelegate,
-      dataController: widget.dataController,
+      dataController: dataController,
+      popoverMutex: popoverMutex,
     );
   }
-
-  void _showOverlay(BuildContext context, Widget child,
-      {VoidCallback? onRemoved}) {
-    final identifier = child.toString();
-    if (currentOverlayIdentifier != null) {
-      FlowyOverlay.of(context).remove(currentOverlayIdentifier!);
-    }
-
-    currentOverlayIdentifier = identifier;
-    FlowyOverlay.of(context).insertWithAnchor(
-      widget: OverlayContainer(
-        constraints: BoxConstraints.loose(const Size(460, 440)),
-        child: child,
-      ),
-      identifier: identifier,
-      anchorContext: context,
-      anchorDirection: AnchorDirection.leftWithCenterAligned,
-      style: FlowyOverlayStyle(blur: false),
-      anchorOffset: const Offset(-20, 0),
-    );
-  }
-
-  void _hideOverlay(BuildContext context) {
-    if (currentOverlayIdentifier != null) {
-      FlowyOverlay.of(context).remove(currentOverlayIdentifier!);
-    }
-  }
 }
 
 abstract class TypeOptionWidget extends StatelessWidget {

+ 42 - 12
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/grid_header.dart

@@ -4,8 +4,10 @@ import 'package:app_flowy/plugins/grid/application/field/type_option/type_option
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/plugins/grid/application/prelude.dart';
 import 'package:easy_localization/easy_localization.dart';
+import 'package:appflowy_popover/popover.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
@@ -75,6 +77,21 @@ class _GridHeader extends StatefulWidget {
 }
 
 class _GridHeaderState extends State<_GridHeader> {
+  final Map<String, ValueKey<String>> _gridMap = {};
+
+  /// This is a workaround for [ReorderableRow].
+  /// [ReorderableRow] warps the child's key with a [GlobalKey].
+  /// It will trigger the child's widget's to recreate.
+  /// The state will lose.
+  _getKeyById(String id) {
+    if (_gridMap.containsKey(id)) {
+      return _gridMap[id];
+    }
+    final newKey = ValueKey(id);
+    _gridMap[id] = newKey;
+    return newKey;
+  }
+
   @override
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
@@ -85,7 +102,10 @@ class _GridHeaderState extends State<_GridHeader> {
             .where((field) => field.visibility)
             .map((field) =>
                 GridFieldCellContext(gridId: widget.gridId, field: field.field))
-            .map((ctx) => GridFieldCell(ctx, key: ValueKey(ctx.field.id)))
+            .map((ctx) => GridFieldCell(
+                  key: _getKeyById(ctx.field.id),
+                  cellContext: ctx,
+                ))
             .toList();
 
         return Container(
@@ -156,18 +176,28 @@ class CreateFieldButton extends StatelessWidget {
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
 
-    return FlowyButton(
-      text: FlowyText.medium(
-        LocaleKeys.grid_field_newColumn.tr(),
-        fontSize: 12,
+    return Popover(
+      triggerActions: PopoverTriggerActionFlags.click,
+      direction: PopoverDirection.bottomWithRightAligned,
+      child: FlowyButton(
+        text: FlowyText.medium(
+          LocaleKeys.grid_field_newColumn.tr(),
+          fontSize: 12,
+        ),
+        hoverColor: theme.shader6,
+        onTap: () {},
+        leftIcon: svgWidget("home/add"),
       ),
-      hoverColor: theme.shader6,
-      onTap: () => FieldEditor(
-        gridId: gridId,
-        fieldName: "",
-        typeOptionLoader: NewFieldTypeOptionLoader(gridId: gridId),
-      ).show(context),
-      leftIcon: svgWidget("home/add"),
+      popupBuilder: (BuildContext popover) {
+        return OverlayContainer(
+          constraints: BoxConstraints.loose(const Size(240, 200)),
+          child: FieldEditor(
+            gridId: gridId,
+            fieldName: "",
+            typeOptionLoader: NewFieldTypeOptionLoader(gridId: gridId),
+          ),
+        );
+      },
     );
   }
 }

+ 15 - 18
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/builder.dart

@@ -3,6 +3,7 @@ import 'dart:typed_data';
 import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
 import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
 import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_data_controller.dart';
+import 'package:appflowy_popover/popover.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/multi_select_type_option.pb.dart';
@@ -46,19 +47,16 @@ abstract class TypeOptionWidgetBuilder {
 Widget? makeTypeOptionWidget({
   required BuildContext context,
   required TypeOptionDataController dataController,
-  required TypeOptionOverlayDelegate overlayDelegate,
+  required PopoverMutex popoverMutex,
 }) {
   final builder = makeTypeOptionWidgetBuilder(
-    dataController: dataController,
-    overlayDelegate: overlayDelegate,
-  );
+      dataController: dataController, popoverMutex: popoverMutex);
   return builder.build(context);
 }
 
-TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder({
-  required TypeOptionDataController dataController,
-  required TypeOptionOverlayDelegate overlayDelegate,
-}) {
+TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder(
+    {required TypeOptionDataController dataController,
+    required PopoverMutex popoverMutex}) {
   final gridId = dataController.gridId;
   final fieldType = dataController.field.fieldType;
 
@@ -73,13 +71,12 @@ TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder({
       );
     case FieldType.DateTime:
       return DateTypeOptionWidgetBuilder(
-        makeTypeOptionContextWithDataController<DateTypeOptionPB>(
-          gridId: gridId,
-          fieldType: fieldType,
-          dataController: dataController,
-        ),
-        overlayDelegate,
-      );
+          makeTypeOptionContextWithDataController<DateTypeOptionPB>(
+            gridId: gridId,
+            fieldType: fieldType,
+            dataController: dataController,
+          ),
+          popoverMutex);
     case FieldType.SingleSelect:
       return SingleSelectTypeOptionWidgetBuilder(
         makeTypeOptionContextWithDataController<SingleSelectTypeOptionPB>(
@@ -87,7 +84,7 @@ TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder({
           fieldType: fieldType,
           dataController: dataController,
         ),
-        overlayDelegate,
+        popoverMutex,
       );
     case FieldType.MultiSelect:
       return MultiSelectTypeOptionWidgetBuilder(
@@ -96,7 +93,7 @@ TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder({
           fieldType: fieldType,
           dataController: dataController,
         ),
-        overlayDelegate,
+        popoverMutex,
       );
     case FieldType.Number:
       return NumberTypeOptionWidgetBuilder(
@@ -105,7 +102,7 @@ TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder({
           fieldType: fieldType,
           dataController: dataController,
         ),
-        overlayDelegate,
+        popoverMutex,
       );
     case FieldType.RichText:
       return RichTextTypeOptionWidgetBuilder(

+ 53 - 31
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/date.dart

@@ -11,6 +11,7 @@ import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:appflowy_popover/popover.dart';
 import '../../../layout/sizes.dart';
 import '../field_type_option_editor.dart';
 import 'builder.dart';
@@ -20,10 +21,10 @@ class DateTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
 
   DateTypeOptionWidgetBuilder(
     DateTypeOptionContext typeOptionContext,
-    TypeOptionOverlayDelegate overlayDelegate,
+    PopoverMutex popoverMutex,
   ) : _widget = DateTypeOptionWidget(
           typeOptionContext: typeOptionContext,
-          overlayDelegate: overlayDelegate,
+          popoverMutex: popoverMutex,
         );
 
   @override
@@ -34,11 +35,10 @@ class DateTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
 
 class DateTypeOptionWidget extends TypeOptionWidget {
   final DateTypeOptionContext typeOptionContext;
-  final TypeOptionOverlayDelegate overlayDelegate;
-
+  final PopoverMutex popoverMutex;
   const DateTypeOptionWidget({
     required this.typeOptionContext,
-    required this.overlayDelegate,
+    required this.popoverMutex,
     Key? key,
   }) : super(key: key);
 
@@ -62,39 +62,58 @@ class DateTypeOptionWidget extends TypeOptionWidget {
   }
 
   Widget _renderDateFormatButton(BuildContext context, DateFormat dataFormat) {
-    return DateFormatButton(onTap: () {
-      final list = DateFormatList(
-        selectedFormat: dataFormat,
-        onSelected: (format) {
-          context
-              .read<DateTypeOptionBloc>()
-              .add(DateTypeOptionEvent.didSelectDateFormat(format));
-        },
-      );
-      overlayDelegate.showOverlay(context, list);
-    });
-  }
-
-  Widget _renderTimeFormatButton(BuildContext context, TimeFormat timeFormat) {
-    return TimeFormatButton(
-      timeFormat: timeFormat,
-      onTap: () {
-        final list = TimeFormatList(
-            selectedFormat: timeFormat,
+    return Popover(
+      mutex: popoverMutex,
+      triggerActions:
+          PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
+      offset: const Offset(20, 0),
+      popupBuilder: (popoverContext) {
+        return OverlayContainer(
+          constraints: BoxConstraints.loose(const Size(460, 440)),
+          child: DateFormatList(
+            selectedFormat: dataFormat,
             onSelected: (format) {
               context
                   .read<DateTypeOptionBloc>()
-                  .add(DateTypeOptionEvent.didSelectTimeFormat(format));
-            });
-        overlayDelegate.showOverlay(context, list);
+                  .add(DateTypeOptionEvent.didSelectDateFormat(format));
+              PopoverContainerState.of(popoverContext).closeAll();
+            },
+          ),
+        );
       },
+      child: const DateFormatButton(),
+    );
+  }
+
+  Widget _renderTimeFormatButton(BuildContext context, TimeFormat timeFormat) {
+    return Popover(
+      mutex: popoverMutex,
+      triggerActions:
+          PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
+      offset: const Offset(20, 0),
+      popupBuilder: (BuildContext popoverContext) {
+        return OverlayContainer(
+          constraints: BoxConstraints.loose(const Size(460, 440)),
+          child: TimeFormatList(
+              selectedFormat: timeFormat,
+              onSelected: (format) {
+                context
+                    .read<DateTypeOptionBloc>()
+                    .add(DateTypeOptionEvent.didSelectTimeFormat(format));
+                PopoverContainerState.of(popoverContext).closeAll();
+              }),
+        );
+      },
+      child: TimeFormatButton(timeFormat: timeFormat),
     );
   }
 }
 
 class DateFormatButton extends StatelessWidget {
-  final VoidCallback onTap;
-  const DateFormatButton({required this.onTap, Key? key}) : super(key: key);
+  final VoidCallback? onTap;
+  final void Function(bool)? onHover;
+  const DateFormatButton({this.onTap, this.onHover, Key? key})
+      : super(key: key);
 
   @override
   Widget build(BuildContext context) {
@@ -107,6 +126,7 @@ class DateFormatButton extends StatelessWidget {
         margin: GridSize.typeOptionContentInsets,
         hoverColor: theme.hover,
         onTap: onTap,
+        onHover: onHover,
         rightIcon: svgWidget("grid/more", color: theme.iconColor),
       ),
     );
@@ -115,9 +135,10 @@ class DateFormatButton extends StatelessWidget {
 
 class TimeFormatButton extends StatelessWidget {
   final TimeFormat timeFormat;
-  final VoidCallback onTap;
+  final VoidCallback? onTap;
+  final void Function(bool)? onHover;
   const TimeFormatButton(
-      {required this.timeFormat, required this.onTap, Key? key})
+      {required this.timeFormat, this.onTap, this.onHover, Key? key})
       : super(key: key);
 
   @override
@@ -131,6 +152,7 @@ class TimeFormatButton extends StatelessWidget {
         margin: GridSize.typeOptionContentInsets,
         hoverColor: theme.hover,
         onTap: onTap,
+        onHover: onHover,
         rightIcon: svgWidget("grid/more", color: theme.iconColor),
       ),
     );

+ 10 - 7
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/multi_select.dart

@@ -1,6 +1,7 @@
 import 'package:app_flowy/plugins/grid/application/field/type_option/multi_select_type_option.dart';
 import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
 import 'package:flutter/material.dart';
+import 'package:appflowy_popover/popover.dart';
 
 import '../field_type_option_editor.dart';
 import 'builder.dart';
@@ -11,14 +12,14 @@ class MultiSelectTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
 
   MultiSelectTypeOptionWidgetBuilder(
     MultiSelectTypeOptionContext typeOptionContext,
-    TypeOptionOverlayDelegate overlayDelegate,
+    PopoverMutex popoverMutex,
   ) : _widget = MultiSelectTypeOptionWidget(
           selectOptionAction: MultiSelectAction(
             fieldId: typeOptionContext.fieldId,
             gridId: typeOptionContext.gridId,
             typeOptionContext: typeOptionContext,
           ),
-          overlayDelegate: overlayDelegate,
+          popoverMutex: popoverMutex,
         );
 
   @override
@@ -27,20 +28,22 @@ class MultiSelectTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
 
 class MultiSelectTypeOptionWidget extends TypeOptionWidget {
   final MultiSelectAction selectOptionAction;
-  final TypeOptionOverlayDelegate overlayDelegate;
+  final PopoverMutex? popoverMutex;
 
   const MultiSelectTypeOptionWidget({
-    required this.selectOptionAction,
-    required this.overlayDelegate,
     Key? key,
+    required this.selectOptionAction,
+    this.popoverMutex,
   }) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
     return SelectOptionTypeOptionWidget(
       options: selectOptionAction.typeOption.options,
-      beginEdit: () => overlayDelegate.hideOverlay(context),
-      overlayDelegate: overlayDelegate,
+      beginEdit: () {
+        PopoverContainerState.of(context).closeAll();
+      },
+      popoverMutex: popoverMutex,
       typeOptionAction: selectOptionAction,
       // key: ValueKey(state.typeOption.hashCode),
     );

+ 36 - 29
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/number.dart

@@ -1,6 +1,7 @@
 import 'package:app_flowy/plugins/grid/application/field/type_option/number_bloc.dart';
 import 'package:app_flowy/plugins/grid/application/field/type_option/number_format_bloc.dart';
 import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
+import 'package:appflowy_popover/popover.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@@ -23,10 +24,10 @@ class NumberTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
 
   NumberTypeOptionWidgetBuilder(
     NumberTypeOptionContext typeOptionContext,
-    TypeOptionOverlayDelegate overlayDelegate,
+    PopoverMutex popoverMutex,
   ) : _widget = NumberTypeOptionWidget(
           typeOptionContext: typeOptionContext,
-          overlayDelegate: overlayDelegate,
+          popoverMutex: popoverMutex,
         );
 
   @override
@@ -34,11 +35,11 @@ class NumberTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
 }
 
 class NumberTypeOptionWidget extends TypeOptionWidget {
-  final TypeOptionOverlayDelegate overlayDelegate;
   final NumberTypeOptionContext typeOptionContext;
+  final PopoverMutex popoverMutex;
   const NumberTypeOptionWidget({
     required this.typeOptionContext,
-    required this.overlayDelegate,
+    required this.popoverMutex,
     Key? key,
   }) : super(key: key);
 
@@ -54,34 +55,40 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
           listener: (context, state) =>
               typeOptionContext.typeOption = state.typeOption,
           builder: (context, state) {
-            return FlowyButton(
-              text: Row(
-                children: [
-                  FlowyText.medium(LocaleKeys.grid_field_numberFormat.tr(),
-                      fontSize: 12),
-                  // const HSpace(6),
-                  const Spacer(),
-                  FlowyText.regular(state.typeOption.format.title(),
-                      fontSize: 12),
-                ],
+            return Popover(
+              mutex: popoverMutex,
+              triggerActions: PopoverTriggerActionFlags.hover |
+                  PopoverTriggerActionFlags.click,
+              offset: const Offset(20, 0),
+              child: FlowyButton(
+                margin: GridSize.typeOptionContentInsets,
+                hoverColor: theme.hover,
+                rightIcon: svgWidget("grid/more", color: theme.iconColor),
+                text: Row(
+                  children: [
+                    FlowyText.medium(LocaleKeys.grid_field_numberFormat.tr(),
+                        fontSize: 12),
+                    // const HSpace(6),
+                    const Spacer(),
+                    FlowyText.regular(state.typeOption.format.title(),
+                        fontSize: 12),
+                  ],
+                ),
               ),
-              margin: GridSize.typeOptionContentInsets,
-              hoverColor: theme.hover,
-              onTap: () {
-                final list = NumberFormatList(
-                  onSelected: (format) {
-                    context
-                        .read<NumberTypeOptionBloc>()
-                        .add(NumberTypeOptionEvent.didSelectFormat(format));
-                  },
-                  selectedFormat: state.typeOption.format,
-                );
-                overlayDelegate.showOverlay(
-                  context,
-                  list,
+              popupBuilder: (BuildContext popoverContext) {
+                return OverlayContainer(
+                  constraints: BoxConstraints.loose(const Size(460, 440)),
+                  child: NumberFormatList(
+                    onSelected: (format) {
+                      context
+                          .read<NumberTypeOptionBloc>()
+                          .add(NumberTypeOptionEvent.didSelectFormat(format));
+                      PopoverContainerState.of(popoverContext).closeAll();
+                    },
+                    selectedFormat: state.typeOption.format,
+                  ),
                 );
               },
-              rightIcon: svgWidget("grid/more", color: theme.iconColor),
             );
           },
         ),

+ 74 - 45
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/select_option.dart

@@ -1,6 +1,8 @@
 import 'package:app_flowy/plugins/grid/application/field/type_option/select_option_type_option_bloc.dart';
+import 'package:appflowy_popover/popover.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
@@ -13,20 +15,19 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
 import '../../../layout/sizes.dart';
 import '../../cell/select_option_cell/extension.dart';
 import '../../common/text_field.dart';
-import 'builder.dart';
 import 'select_option_editor.dart';
 
 class SelectOptionTypeOptionWidget extends StatelessWidget {
   final List<SelectOptionPB> options;
   final VoidCallback beginEdit;
-  final TypeOptionOverlayDelegate overlayDelegate;
   final ISelectOptionAction typeOptionAction;
+  final PopoverMutex? popoverMutex;
 
   const SelectOptionTypeOptionWidget({
     required this.options,
     required this.beginEdit,
-    required this.overlayDelegate,
     required this.typeOptionAction,
+    this.popoverMutex,
     Key? key,
   }) : super(key: key);
 
@@ -50,7 +51,7 @@ class SelectOptionTypeOptionWidget extends StatelessWidget {
               ),
             if (state.options.isEmpty && !state.isEditingOption)
               const _AddOptionButton(),
-            _OptionList(overlayDelegate)
+            _OptionList(popoverMutex: popoverMutex)
           ];
 
           return Column(children: children);
@@ -111,8 +112,8 @@ class _OptionTitleButton extends StatelessWidget {
 }
 
 class _OptionList extends StatelessWidget {
-  final TypeOptionOverlayDelegate delegate;
-  const _OptionList(this.delegate, {Key? key}) : super(key: key);
+  final PopoverMutex? popoverMutex;
+  const _OptionList({Key? key, this.popoverMutex}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
@@ -122,7 +123,11 @@ class _OptionList extends StatelessWidget {
       },
       builder: (context, state) {
         final cells = state.options.map((option) {
-          return _makeOptionCell(context, option);
+          return _makeOptionCell(
+            context: context,
+            option: option,
+            popoverMutex: popoverMutex,
+          );
         }).toList();
 
         return ListView.separated(
@@ -140,57 +145,81 @@ class _OptionList extends StatelessWidget {
     );
   }
 
-  _OptionCell _makeOptionCell(BuildContext context, SelectOptionPB option) {
+  _OptionCell _makeOptionCell({
+    required BuildContext context,
+    required SelectOptionPB option,
+    PopoverMutex? popoverMutex,
+  }) {
     return _OptionCell(
       option: option,
-      onSelected: (option) {
-        final pannel = SelectOptionTypeOptionEditor(
-          option: option,
-          onDeleted: () {
-            delegate.hideOverlay(context);
-            context
-                .read<SelectOptionTypeOptionBloc>()
-                .add(SelectOptionTypeOptionEvent.deleteOption(option));
-          },
-          onUpdated: (updatedOption) {
-            delegate.hideOverlay(context);
-            context
-                .read<SelectOptionTypeOptionBloc>()
-                .add(SelectOptionTypeOptionEvent.updateOption(updatedOption));
-          },
-          key: ValueKey(option.id),
-        );
-        delegate.showOverlay(context, pannel);
-      },
+      popoverMutex: popoverMutex,
     );
   }
 }
 
-class _OptionCell extends StatelessWidget {
+class _OptionCell extends StatefulWidget {
   final SelectOptionPB option;
-  final Function(SelectOptionPB) onSelected;
-  const _OptionCell({
-    required this.option,
-    required this.onSelected,
-    Key? key,
-  }) : super(key: key);
+  final PopoverMutex? popoverMutex;
+  const _OptionCell({required this.option, Key? key, this.popoverMutex})
+      : super(key: key);
+
+  @override
+  State<_OptionCell> createState() => _OptionCellState();
+}
+
+class _OptionCellState extends State<_OptionCell> {
+  late PopoverController _popoverController;
+
+  @override
+  void initState() {
+    _popoverController = PopoverController();
+    super.initState();
+  }
 
   @override
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
 
-    return SizedBox(
-      height: GridSize.typeOptionItemHeight,
-      child: SelectOptionTagCell(
-        option: option,
-        onSelected: onSelected,
-        children: [
-          svgWidget(
-            "grid/details",
-            color: theme.iconColor,
-          ),
-        ],
+    return Popover(
+      controller: _popoverController,
+      mutex: widget.popoverMutex,
+      offset: const Offset(20, 0),
+      child: SizedBox(
+        height: GridSize.typeOptionItemHeight,
+        child: SelectOptionTagCell(
+          option: widget.option,
+          onSelected: (SelectOptionPB pb) {
+            _popoverController.show();
+          },
+          children: [
+            svgWidget(
+              "grid/details",
+              color: theme.iconColor,
+            ),
+          ],
+        ),
       ),
+      popupBuilder: (BuildContext popoverContext) {
+        return OverlayContainer(
+          constraints: BoxConstraints.loose(const Size(460, 440)),
+          child: SelectOptionTypeOptionEditor(
+            option: widget.option,
+            onDeleted: () {
+              context
+                  .read<SelectOptionTypeOptionBloc>()
+                  .add(SelectOptionTypeOptionEvent.deleteOption(widget.option));
+              PopoverContainerState.of(popoverContext).closeAll();
+            },
+            onUpdated: (updatedOption) {
+              context
+                  .read<SelectOptionTypeOptionBloc>()
+                  .add(SelectOptionTypeOptionEvent.updateOption(updatedOption));
+              PopoverContainerState.of(popoverContext).closeAll();
+            },
+            key: ValueKey(widget.option.id),
+          ),
+        );
+      },
     );
   }
 }

+ 10 - 7
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/single_select.dart

@@ -2,6 +2,7 @@ import 'package:app_flowy/plugins/grid/application/field/type_option/single_sele
 import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
 import 'package:flutter/material.dart';
 import '../field_type_option_editor.dart';
+import 'package:appflowy_popover/popover.dart';
 import 'builder.dart';
 import 'select_option.dart';
 
@@ -10,14 +11,14 @@ class SingleSelectTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
 
   SingleSelectTypeOptionWidgetBuilder(
     SingleSelectTypeOptionContext singleSelectTypeOption,
-    TypeOptionOverlayDelegate overlayDelegate,
+    PopoverMutex popoverMutex,
   ) : _widget = SingleSelectTypeOptionWidget(
           selectOptionAction: SingleSelectAction(
             fieldId: singleSelectTypeOption.fieldId,
             gridId: singleSelectTypeOption.gridId,
             typeOptionContext: singleSelectTypeOption,
           ),
-          overlayDelegate: overlayDelegate,
+          popoverMutex: popoverMutex,
         );
 
   @override
@@ -26,20 +27,22 @@ class SingleSelectTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
 
 class SingleSelectTypeOptionWidget extends TypeOptionWidget {
   final SingleSelectAction selectOptionAction;
-  final TypeOptionOverlayDelegate overlayDelegate;
+  final PopoverMutex? popoverMutex;
 
   const SingleSelectTypeOptionWidget({
-    required this.selectOptionAction,
-    required this.overlayDelegate,
     Key? key,
+    required this.selectOptionAction,
+    this.popoverMutex,
   }) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
     return SelectOptionTypeOptionWidget(
       options: selectOptionAction.typeOption.options,
-      beginEdit: () => overlayDelegate.hideOverlay(context),
-      overlayDelegate: overlayDelegate,
+      beginEdit: () {
+        PopoverContainerState.of(context).closeAll();
+      },
+      popoverMutex: popoverMutex,
       typeOptionAction: selectOptionAction,
       // key: ValueKey(state.typeOption.hashCode),
     );

+ 10 - 5
frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/grid_row.dart

@@ -194,12 +194,17 @@ class RowContent extends StatelessWidget {
               Provider.of<RegionStateNotifier>(context, listen: false),
           accessoryBuilder: (buildContext) {
             final builder = child.accessoryBuilder;
-            List<GridCellAccessory> accessories = [];
+            List<GridCellAccessoryBuilder> accessories = [];
             if (cellId.fieldContext.isPrimary) {
-              accessories.add(PrimaryCellAccessory(
-                onTapCallback: onExpand,
-                isCellEditing: buildContext.isCellEditing,
-              ));
+              accessories.add(
+                GridCellAccessoryBuilder(
+                  builder: (key) => PrimaryCellAccessory(
+                    key: key,
+                    onTapCallback: onExpand,
+                    isCellEditing: buildContext.isCellEditing,
+                  ),
+                ),
+              );
             }
 
             if (builder != null) {

+ 81 - 72
frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart

@@ -15,6 +15,7 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:appflowy_popover/popover.dart';
 
 import '../../layout/sizes.dart';
 import '../cell/cell_accessory.dart';
@@ -35,23 +36,6 @@ class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate {
   @override
   State<RowDetailPage> createState() => _RowDetailPageState();
 
-  void show(BuildContext context) async {
-    final windowSize = MediaQuery.of(context).size;
-    final size = windowSize * 0.5;
-    FlowyOverlay.of(context).insertWithRect(
-      widget: OverlayContainer(
-        constraints: BoxConstraints.tight(size),
-        child: this,
-      ),
-      identifier: RowDetailPage.identifier(),
-      anchorPosition: Offset(-size.width / 2.0, -size.height / 2.0),
-      anchorSize: windowSize,
-      anchorDirection: AnchorDirection.center,
-      style: FlowyOverlayStyle(blur: false),
-      delegate: this,
-    );
-  }
-
   static String identifier() {
     return (RowDetailPage).toString();
   }
@@ -60,31 +44,33 @@ class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate {
 class _RowDetailPageState extends State<RowDetailPage> {
   @override
   Widget build(BuildContext context) {
-    return BlocProvider(
-      create: (context) {
-        final bloc = RowDetailBloc(
-          dataController: widget.dataController,
-        );
-        bloc.add(const RowDetailEvent.initial());
-        return bloc;
-      },
-      child: Padding(
-        padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 20),
-        child: Column(
-          children: [
-            SizedBox(
-              height: 30,
-              child: Row(
-                children: const [Spacer(), _CloseButton()],
+    return FlowyDialog(
+      child: BlocProvider(
+        create: (context) {
+          final bloc = RowDetailBloc(
+            dataController: widget.dataController,
+          );
+          bloc.add(const RowDetailEvent.initial());
+          return bloc;
+        },
+        child: Padding(
+          padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 20),
+          child: Column(
+            children: [
+              SizedBox(
+                height: 40,
+                child: Row(
+                  children: const [Spacer(), _CloseButton()],
+                ),
               ),
-            ),
-            Expanded(
-              child: _PropertyList(
-                cellBuilder: widget.cellBuilder,
-                viewId: widget.dataController.rowInfo.gridId,
+              Expanded(
+                child: _PropertyList(
+                  cellBuilder: widget.cellBuilder,
+                  viewId: widget.dataController.rowInfo.gridId,
+                ),
               ),
-            ),
-          ],
+            ],
+          ),
         ),
       ),
     );
@@ -99,8 +85,9 @@ class _CloseButton extends StatelessWidget {
     final theme = context.watch<AppTheme>();
     return FlowyIconButton(
       width: 24,
-      onPressed: () =>
-          FlowyOverlay.of(context).remove(RowDetailPage.identifier()),
+      onPressed: () {
+        FlowyOverlay.pop(context);
+      },
       iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2),
       icon: svgWidget("home/close", color: theme.iconColor),
     );
@@ -161,26 +148,35 @@ class _CreateFieldButton extends StatelessWidget {
   Widget build(BuildContext context) {
     final theme = context.read<AppTheme>();
 
-    return SizedBox(
-      height: 40,
-      child: FlowyButton(
-        text: FlowyText.medium(
-          LocaleKeys.grid_field_newColumn.tr(),
-          fontSize: 12,
+    return Popover(
+      triggerActions: PopoverTriggerActionFlags.click,
+      child: SizedBox(
+        height: 40,
+        child: FlowyButton(
+          text: FlowyText.medium(
+            LocaleKeys.grid_field_newColumn.tr(),
+            fontSize: 12,
+          ),
+          hoverColor: theme.shader6,
+          onTap: () {},
+          leftIcon: svgWidget("home/add"),
         ),
-        hoverColor: theme.shader6,
-        onTap: () => FieldEditor(
-          gridId: viewId,
-          fieldName: "",
-          typeOptionLoader: NewFieldTypeOptionLoader(gridId: viewId),
-        ).show(context),
-        leftIcon: svgWidget("home/add"),
       ),
+      popupBuilder: (BuildContext context) {
+        return OverlayContainer(
+          constraints: BoxConstraints.loose(const Size(240, 200)),
+          child: FieldEditor(
+            gridId: viewId,
+            fieldName: "",
+            typeOptionLoader: NewFieldTypeOptionLoader(gridId: viewId),
+          ),
+        );
+      },
     );
   }
 }
 
-class _RowDetailCell extends StatelessWidget {
+class _RowDetailCell extends StatefulWidget {
   final GridCellIdentifier cellId;
   final GridCellBuilder cellBuilder;
   const _RowDetailCell({
@@ -189,11 +185,18 @@ class _RowDetailCell extends StatelessWidget {
     Key? key,
   }) : super(key: key);
 
+  @override
+  State<StatefulWidget> createState() => _RowDetailCellState();
+}
+
+class _RowDetailCellState extends State<_RowDetailCell> {
+  final PopoverController popover = PopoverController();
+
   @override
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
-    final style = _customCellStyle(theme, cellId.fieldType);
-    final cell = cellBuilder.build(cellId, style: style);
+    final style = _customCellStyle(theme, widget.cellId.fieldType);
+    final cell = widget.cellBuilder.build(widget.cellId, style: style);
 
     final gesture = GestureDetector(
       behavior: HitTestBehavior.translucent,
@@ -214,9 +217,26 @@ class _RowDetailCell extends StatelessWidget {
           children: [
             SizedBox(
               width: 150,
-              child: FieldCellButton(
-                field: cellId.fieldContext.field,
-                onTap: () => _showFieldEditor(context),
+              child: Popover(
+                controller: popover,
+                offset: const Offset(20, 0),
+                popupBuilder: (context) {
+                  return OverlayContainer(
+                    constraints: BoxConstraints.loose(const Size(240, 200)),
+                    child: FieldEditor(
+                      gridId: widget.cellId.gridId,
+                      fieldName: widget.cellId.fieldContext.field.name,
+                      typeOptionLoader: FieldTypeOptionLoader(
+                        gridId: widget.cellId.gridId,
+                        field: widget.cellId.fieldContext.field,
+                      ),
+                    ),
+                  );
+                },
+                child: FieldCellButton(
+                  field: widget.cellId.fieldContext.field,
+                  onTap: () => popover.show(),
+                ),
               ),
             ),
             const HSpace(10),
@@ -226,17 +246,6 @@ class _RowDetailCell extends StatelessWidget {
       ),
     );
   }
-
-  void _showFieldEditor(BuildContext context) {
-    FieldEditor(
-      gridId: cellId.gridId,
-      fieldName: cellId.fieldContext.name,
-      typeOptionLoader: FieldTypeOptionLoader(
-        gridId: cellId.gridId,
-        field: cellId.fieldContext.field,
-      ),
-    ).show(context);
-  }
 }
 
 GridCellStyle? _customCellStyle(AppTheme theme, FieldType fieldType) {

+ 49 - 41
frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_property.dart

@@ -1,7 +1,9 @@
 import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
+import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_editor.dart';
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/plugins/grid/application/setting/property_bloc.dart';
 import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_type_extension.dart';
+import 'package:appflowy_popover/popover.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@@ -15,9 +17,8 @@ import 'package:styled_widget/styled_widget.dart';
 
 import '../../../application/field/field_controller.dart';
 import '../../layout/sizes.dart';
-import '../header/field_editor.dart';
 
-class GridPropertyList extends StatelessWidget with FlowyOverlayDelegate {
+class GridPropertyList extends StatefulWidget {
   final String gridId;
   final GridFieldController fieldController;
   const GridPropertyList({
@@ -26,34 +27,38 @@ class GridPropertyList extends StatelessWidget with FlowyOverlayDelegate {
     Key? key,
   }) : super(key: key);
 
-  void show(BuildContext context) {
-    FlowyOverlay.of(context).insertWithAnchor(
-      widget: OverlayContainer(
-        constraints: BoxConstraints.loose(const Size(260, 400)),
-        child: this,
-      ),
-      identifier: identifier(),
-      anchorContext: context,
-      anchorDirection: AnchorDirection.bottomRight,
-      style: FlowyOverlayStyle(blur: false),
-      delegate: this,
-    );
+  @override
+  State<StatefulWidget> createState() => _GridPropertyListState();
+}
+
+class _GridPropertyListState extends State<GridPropertyList> {
+  late PopoverMutex _popoverMutex;
+
+  @override
+  void initState() {
+    _popoverMutex = PopoverMutex();
+    super.initState();
   }
 
   @override
   Widget build(BuildContext context) {
     return BlocProvider(
-      create: (context) =>
-          getIt<GridPropertyBloc>(param1: gridId, param2: fieldController)
-            ..add(const GridPropertyEvent.initial()),
+      create: (context) => getIt<GridPropertyBloc>(
+          param1: widget.gridId, param2: widget.fieldController)
+        ..add(const GridPropertyEvent.initial()),
       child: BlocBuilder<GridPropertyBloc, GridPropertyState>(
         builder: (context, state) {
           final cells = state.fieldContexts.map((field) {
             return _GridPropertyCell(
-                gridId: gridId, fieldContext: field, key: ValueKey(field.id));
+              popoverMutex: _popoverMutex,
+              gridId: widget.gridId,
+              fieldContext: field,
+              key: ValueKey(field.id),
+            );
           }).toList();
 
           return ListView.separated(
+            controller: ScrollController(),
             shrinkWrap: true,
             itemCount: cells.length,
             itemBuilder: (BuildContext context, int index) {
@@ -67,21 +72,18 @@ class GridPropertyList extends StatelessWidget with FlowyOverlayDelegate {
       ),
     );
   }
-
-  String identifier() {
-    return (GridPropertyList).toString();
-  }
-
-  @override
-  bool asBarrier() => true;
 }
 
 class _GridPropertyCell extends StatelessWidget {
   final GridFieldContext fieldContext;
   final String gridId;
-  const _GridPropertyCell(
-      {required this.gridId, required this.fieldContext, Key? key})
-      : super(key: key);
+  final PopoverMutex popoverMutex;
+  const _GridPropertyCell({
+    required this.gridId,
+    required this.fieldContext,
+    required this.popoverMutex,
+    Key? key,
+  }) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
@@ -113,21 +115,27 @@ class _GridPropertyCell extends StatelessWidget {
     );
   }
 
-  FlowyButton _editFieldButton(AppTheme theme, BuildContext context) {
-    return FlowyButton(
-      text: FlowyText.medium(fieldContext.name, fontSize: 12),
-      hoverColor: theme.hover,
-      leftIcon:
-          svgWidget(fieldContext.fieldType.iconName(), color: theme.iconColor),
-      onTap: () {
-        FieldEditor(
-          gridId: gridId,
-          fieldName: fieldContext.name,
-          typeOptionLoader: FieldTypeOptionLoader(
+  Widget _editFieldButton(AppTheme theme, BuildContext context) {
+    return Popover(
+      mutex: popoverMutex,
+      triggerActions: PopoverTriggerActionFlags.click,
+      offset: const Offset(20, 0),
+      child: FlowyButton(
+        text: FlowyText.medium(fieldContext.name, fontSize: 12),
+        hoverColor: theme.hover,
+        leftIcon: svgWidget(fieldContext.fieldType.iconName(),
+            color: theme.iconColor),
+      ),
+      popupBuilder: (BuildContext context) {
+        return OverlayContainer(
+          constraints: BoxConstraints.loose(const Size(240, 200)),
+          child: FieldEditor(
             gridId: gridId,
-            field: fieldContext.field,
+            fieldName: fieldContext.name,
+            typeOptionLoader: FieldTypeOptionLoader(
+                gridId: gridId, field: fieldContext.field),
           ),
-        ).show(context, anchorDirection: AnchorDirection.bottomRight);
+        );
       },
     );
   }

+ 0 - 32
frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_setting.dart

@@ -13,7 +13,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:app_flowy/generated/locale_keys.g.dart';
 import '../../../application/field/field_controller.dart';
 import '../../layout/sizes.dart';
-import 'grid_property.dart';
 
 class GridSettingContext {
   final String gridId;
@@ -32,37 +31,6 @@ class GridSettingList extends StatelessWidget {
       {required this.settingContext, required this.onAction, Key? key})
       : super(key: key);
 
-  static void show(BuildContext context, GridSettingContext settingContext) {
-    final list = GridSettingList(
-      settingContext: settingContext,
-      onAction: (action, settingContext) {
-        switch (action) {
-          case GridSettingAction.filter:
-            break;
-          case GridSettingAction.sortBy:
-            break;
-          case GridSettingAction.properties:
-            GridPropertyList(
-                    gridId: settingContext.gridId,
-                    fieldController: settingContext.fieldController)
-                .show(context);
-            break;
-        }
-      },
-    );
-
-    FlowyOverlay.of(context).insertWithAnchor(
-      widget: OverlayContainer(
-        constraints: BoxConstraints.loose(const Size(140, 400)),
-        child: list,
-      ),
-      identifier: list.identifier(),
-      anchorContext: context,
-      anchorDirection: AnchorDirection.bottomRight,
-      style: FlowyOverlayStyle(blur: false),
-    );
-  }
-
   @override
   Widget build(BuildContext context) {
     return BlocProvider(

+ 55 - 6
frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_toolbar.dart

@@ -1,5 +1,8 @@
+import 'package:appflowy_popover/popover.dart';
+import 'package:app_flowy/plugins/grid/application/setting/setting_bloc.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/style_widget/extension.dart';
 import 'package:flowy_infra_ui/style_widget/icon_button.dart';
 import 'package:flutter/material.dart';
@@ -7,6 +10,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 
 import '../../../application/field/field_controller.dart';
 import '../../layout/sizes.dart';
+import 'grid_property.dart';
 import 'grid_setting.dart';
 
 class GridToolbarContext {
@@ -49,12 +53,57 @@ class _SettingButton extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
-    return FlowyIconButton(
-      hoverColor: theme.hover,
-      width: 22,
-      onPressed: () => GridSettingList.show(context, settingContext),
-      icon:
-          svgWidget("grid/setting/setting").padding(horizontal: 3, vertical: 3),
+    return Popover(
+      triggerActions: PopoverTriggerActionFlags.click,
+      offset: const Offset(0, 10),
+      child: FlowyIconButton(
+        width: 22,
+        hoverColor: theme.hover,
+        icon: svgWidget("grid/setting/setting")
+            .padding(horizontal: 3, vertical: 3),
+      ),
+      popupBuilder: (BuildContext context) {
+        return _GridSettingListPopover(settingContext: settingContext);
+      },
+    );
+  }
+}
+
+class _GridSettingListPopover extends StatefulWidget {
+  final GridSettingContext settingContext;
+
+  const _GridSettingListPopover({Key? key, required this.settingContext})
+      : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() => _GridSettingListPopoverState();
+}
+
+class _GridSettingListPopoverState extends State<_GridSettingListPopover> {
+  GridSettingAction? _action;
+
+  @override
+  Widget build(BuildContext context) {
+    if (_action == GridSettingAction.properties) {
+      return OverlayContainer(
+        constraints: BoxConstraints.loose(const Size(260, 400)),
+        child: GridPropertyList(
+          gridId: widget.settingContext.gridId,
+          fieldController: widget.settingContext.fieldController,
+        ),
+      );
+    }
+
+    return OverlayContainer(
+      constraints: BoxConstraints.loose(const Size(140, 400)),
+      child: GridSettingList(
+        settingContext: widget.settingContext,
+        onAction: (action, settingContext) {
+          setState(() {
+            _action = action;
+          });
+        },
+      ),
     );
   }
 }

+ 36 - 31
frontend/app_flowy/lib/workspace/presentation/settings/settings_dialog.dart

@@ -6,6 +6,7 @@ import 'package:app_flowy/workspace/presentation/settings/widgets/settings_langu
 import 'package:app_flowy/workspace/presentation/settings/widgets/settings_user_view.dart';
 import 'package:app_flowy/workspace/presentation/settings/widgets/settings_menu.dart';
 import 'package:app_flowy/workspace/application/settings/settings_dialog_bloc.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
@@ -28,46 +29,50 @@ class SettingsDialog extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return BlocProvider<SettingsDialogBloc>(
-        create: (context) => getIt<SettingsDialogBloc>(param1: user)..add(const SettingsDialogEvent.initial()),
+        create: (context) => getIt<SettingsDialogBloc>(param1: user)
+          ..add(const SettingsDialogEvent.initial()),
         child: BlocBuilder<SettingsDialogBloc, SettingsDialogState>(
             builder: (context, state) => ChangeNotifierProvider.value(
-                  value: Provider.of<AppearanceSettingModel>(context, listen: true),
-                  child: AlertDialog(
-                    shape: RoundedRectangleBorder(
-                      borderRadius: BorderRadius.circular(10),
-                    ),
+                  value: Provider.of<AppearanceSettingModel>(context,
+                      listen: true),
+                  child: FlowyDialog(
                     title: Text(
                       LocaleKeys.settings_title.tr(),
                       style: const TextStyle(
                         fontWeight: FontWeight.bold,
                       ),
                     ),
-                    content: ConstrainedBox(
-                      constraints: const BoxConstraints(
-                        maxHeight: 600,
-                        minWidth: 600,
-                        maxWidth: 1000,
-                      ),
-                      child: Row(
-                        crossAxisAlignment: CrossAxisAlignment.start,
-                        children: [
-                          SizedBox(
-                            width: 200,
-                            child: SettingsMenu(
-                              changeSelectedIndex: (index) {
-                                context.read<SettingsDialogBloc>().add(SettingsDialogEvent.setViewIndex(index));
-                              },
-                              currentIndex: context.read<SettingsDialogBloc>().state.viewIndex,
-                            ),
+                    child: Row(
+                      crossAxisAlignment: CrossAxisAlignment.start,
+                      children: [
+                        SizedBox(
+                          width: 200,
+                          child: SettingsMenu(
+                            changeSelectedIndex: (index) {
+                              context
+                                  .read<SettingsDialogBloc>()
+                                  .add(SettingsDialogEvent.setViewIndex(index));
+                            },
+                            currentIndex: context
+                                .read<SettingsDialogBloc>()
+                                .state
+                                .viewIndex,
                           ),
-                          const VerticalDivider(),
-                          const SizedBox(width: 10),
-                          Expanded(
-                            child: getSettingsView(context.read<SettingsDialogBloc>().state.viewIndex,
-                                context.read<SettingsDialogBloc>().state.userProfile),
-                          )
-                        ],
-                      ),
+                        ),
+                        const VerticalDivider(),
+                        const SizedBox(width: 10),
+                        Expanded(
+                          child: getSettingsView(
+                              context
+                                  .read<SettingsDialogBloc>()
+                                  .state
+                                  .viewIndex,
+                              context
+                                  .read<SettingsDialogBloc>()
+                                  .state
+                                  .userProfile),
+                        )
+                      ],
                     ),
                   ),
                 )));

+ 30 - 0
frontend/app_flowy/packages/appflowy_popover/.gitignore

@@ -0,0 +1,30 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
+/pubspec.lock
+**/doc/api/
+.dart_tool/
+.packages
+build/

+ 10 - 0
frontend/app_flowy/packages/appflowy_popover/.metadata

@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+  revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+  channel: stable
+
+project_type: package

+ 3 - 0
frontend/app_flowy/packages/appflowy_popover/CHANGELOG.md

@@ -0,0 +1,3 @@
+## 0.0.1
+
+* TODO: Describe initial release.

+ 1 - 0
frontend/app_flowy/packages/appflowy_popover/LICENSE

@@ -0,0 +1 @@
+TODO: Add your license here.

+ 107 - 0
frontend/app_flowy/packages/appflowy_popover/README.md

@@ -0,0 +1,107 @@
+# AppFlowy Popover
+
+A Popover can be used to display some content on top of another.
+
+It can be used to display a dropdown menu.
+
+> A popover is a transient view that appears above other content onscreen when you tap a control or in an area. Typically, a popover includes an arrow pointing to the location from which it emerged. Popovers can be nonmodal or modal. A nonmodal popover is dismissed by tapping another part of the screen or a button on the popover. A modal popover is dismissed by tapping a Cancel or other button on the popover.
+
+Source: [Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/ios/views/popovers/).
+
+## Features
+
+- Basic popover style
+- Follow the target automatically
+- Nested popover support
+- Exclusive API
+
+![](./screenshot.png)
+
+## Example
+
+```dart
+Popover(
+  // Define how to trigger the popover
+  triggerActions: PopoverTriggerActionFlags.click,
+  child: TextButton(child: Text("Popover"), onPressed: () {}),
+  // Define the direction of the popover
+  direction: PopoverDirection.bottomWithLeftAligned,
+  popupBuilder(BuildContext context) {
+    return PopoverMenu();
+  },
+);
+```
+
+### Trigger the popover manually
+
+Sometimes, if you want to trigger the popover manually, you can use a `PopoverController`.
+
+```dart
+class MyWidgetState extends State<GridDateCell> {
+  late PopoverController _popover;
+
+  @override
+  void initState() {
+    _popover = PopoverController();
+    super.initState();
+  }
+
+  // triggered by another widget
+  _onClick() {
+    _popover.show();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Popover(
+      controller: _popover,
+      ...
+    )
+  }
+}
+```
+
+### Make several popovers exclusive
+
+The popover has a mechanism to make sure there are only one popover is shown in a group of popovers.
+It's called `PopoverMutex`.
+
+If you pass the same mutex object to the popovers, there will be only one popover is triggered.
+
+```dart
+class MyWidgetState extends State<GridDateCell> {
+  final _popoverMutex = PopoverMutex();
+
+  @override
+  Widget build(BuildContext context) {
+    return Row(
+      children: [
+        Popover(
+          mutex: _popoverMutex,
+          ...
+        ),
+        Popover(
+          mutex: _popoverMutex,
+          ...
+        ),
+        Popover(
+          mutex: _popoverMutex,
+          ...
+        ),
+      ]
+    )
+  }
+}
+```
+
+## API
+
+| Param          | Description                                                      | Type                                    |
+| -------------- | ---------------------------------------------------------------- | --------------------------------------- |
+| offset         | The offset between the popover and the child                     | `Offset`                                |
+| popupBuilder   | The function used to build the popover                           | `Widget Function(BuildContext context)` |
+| triggerActions | Define the actions about how to trigger the popover              | `int`                                   |
+| mutex          | If multiple popovers are exclusive, pass the same mutex to them. | `PopoverMutex`                          |
+| direction      | The direction where the popover should be placed                 | `PopoverDirection`                      |
+| onClose        | The callback will be called after the popover is closed          | `void Function()`                       |
+| child          | The child to trigger the popover                                 | `Widget`                                |

+ 4 - 0
frontend/app_flowy/packages/appflowy_popover/analysis_options.yaml

@@ -0,0 +1,4 @@
+include: package:flutter_lints/flutter.yaml
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options

+ 47 - 0
frontend/app_flowy/packages/appflowy_popover/example/.gitignore

@@ -0,0 +1,47 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Web related
+lib/generated_plugin_registrant.dart
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release

+ 45 - 0
frontend/app_flowy/packages/appflowy_popover/example/.metadata

@@ -0,0 +1,45 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled.
+
+version:
+  revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+  channel: stable
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+  platforms:
+    - platform: root
+      create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+      base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+    - platform: android
+      create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+      base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+    - platform: ios
+      create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+      base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+    - platform: linux
+      create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+      base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+    - platform: macos
+      create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+      base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+    - platform: web
+      create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+      base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+    - platform: windows
+      create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+      base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+
+  # User provided section
+
+  # List of Local paths (relative to this file) that should be
+  # ignored by the migrate tool.
+  #
+  # Files that are not part of the templates will be ignored by default.
+  unmanaged_files:
+    - 'lib/main.dart'
+    - 'ios/Runner.xcodeproj/project.pbxproj'

+ 16 - 0
frontend/app_flowy/packages/appflowy_popover/example/README.md

@@ -0,0 +1,16 @@
+# example
+
+A new Flutter project.
+
+## Getting Started
+
+This project is a starting point for a Flutter application.
+
+A few resources to get you started if this is your first Flutter project:
+
+- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
+- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
+
+For help getting started with Flutter development, view the
+[online documentation](https://docs.flutter.dev/), which offers tutorials,
+samples, guidance on mobile development, and a full API reference.

+ 29 - 0
frontend/app_flowy/packages/appflowy_popover/example/analysis_options.yaml

@@ -0,0 +1,29 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+  # The lint rules applied to this project can be customized in the
+  # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+  # included above or to enable additional rules. A list of all available lints
+  # and their documentation is published at
+  # https://dart-lang.github.io/linter/lints/index.html.
+  #
+  # Instead of disabling a lint rule for the entire project in the
+  # section below, it can also be suppressed for a single line of code
+  # or a specific dart file by using the `// ignore: name_of_lint` and
+  # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+  # producing the lint.
+  rules:
+    # avoid_print: false  # Uncomment to disable the `avoid_print` rule
+    # prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options

+ 13 - 0
frontend/app_flowy/packages/appflowy_popover/example/android/.gitignore

@@ -0,0 +1,13 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+key.properties
+**/*.keystore
+**/*.jks

+ 71 - 0
frontend/app_flowy/packages/appflowy_popover/example/android/app/build.gradle

@@ -0,0 +1,71 @@
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+    localPropertiesFile.withReader('UTF-8') { reader ->
+        localProperties.load(reader)
+    }
+}
+
+def flutterRoot = localProperties.getProperty('flutter.sdk')
+if (flutterRoot == null) {
+    throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+    flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+    flutterVersionName = '1.0'
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+android {
+    compileSdkVersion flutter.compileSdkVersion
+    ndkVersion flutter.ndkVersion
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+
+    sourceSets {
+        main.java.srcDirs += 'src/main/kotlin'
+    }
+
+    defaultConfig {
+        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+        applicationId "com.example.example"
+        // You can update the following values to match your application needs.
+        // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
+        minSdkVersion flutter.minSdkVersion
+        targetSdkVersion flutter.targetSdkVersion
+        versionCode flutterVersionCode.toInteger()
+        versionName flutterVersionName
+    }
+
+    buildTypes {
+        release {
+            // TODO: Add your own signing config for the release build.
+            // Signing with the debug keys for now, so `flutter run --release` works.
+            signingConfig signingConfigs.debug
+        }
+    }
+}
+
+flutter {
+    source '../..'
+}
+
+dependencies {
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+}

+ 8 - 0
frontend/app_flowy/packages/appflowy_popover/example/android/app/src/debug/AndroidManifest.xml

@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.example">
+    <!-- The INTERNET permission is required for development. Specifically,
+         the Flutter tool needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>

+ 34 - 0
frontend/app_flowy/packages/appflowy_popover/example/android/app/src/main/AndroidManifest.xml

@@ -0,0 +1,34 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.example">
+   <application
+        android:label="example"
+        android:name="${applicationName}"
+        android:icon="@mipmap/ic_launcher">
+        <activity
+            android:name=".MainActivity"
+            android:exported="true"
+            android:launchMode="singleTop"
+            android:theme="@style/LaunchTheme"
+            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
+            android:hardwareAccelerated="true"
+            android:windowSoftInputMode="adjustResize">
+            <!-- Specifies an Android theme to apply to this Activity as soon as
+                 the Android process has started. This theme is visible to the user
+                 while the Flutter UI initializes. After that, this theme continues
+                 to determine the Window background behind the Flutter UI. -->
+            <meta-data
+              android:name="io.flutter.embedding.android.NormalTheme"
+              android:resource="@style/NormalTheme"
+              />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <!-- Don't delete the meta-data below.
+             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
+        <meta-data
+            android:name="flutterEmbedding"
+            android:value="2" />
+    </application>
+</manifest>

+ 6 - 0
frontend/app_flowy/packages/appflowy_popover/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt

@@ -0,0 +1,6 @@
+package com.example.example
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}

+ 12 - 0
frontend/app_flowy/packages/appflowy_popover/example/android/app/src/main/res/drawable-v21/launch_background.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="?android:colorBackground" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>

+ 12 - 0
frontend/app_flowy/packages/appflowy_popover/example/android/app/src/main/res/drawable/launch_background.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:color/white" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>

BIN
frontend/app_flowy/packages/appflowy_popover/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png


BIN
frontend/app_flowy/packages/appflowy_popover/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png


BIN
frontend/app_flowy/packages/appflowy_popover/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png


BIN
frontend/app_flowy/packages/appflowy_popover/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png


BIN
frontend/app_flowy/packages/appflowy_popover/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


+ 18 - 0
frontend/app_flowy/packages/appflowy_popover/example/android/app/src/main/res/values-night/styles.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             the Flutter engine draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>

+ 18 - 0
frontend/app_flowy/packages/appflowy_popover/example/android/app/src/main/res/values/styles.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             the Flutter engine draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>

+ 8 - 0
frontend/app_flowy/packages/appflowy_popover/example/android/app/src/profile/AndroidManifest.xml

@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.example">
+    <!-- The INTERNET permission is required for development. Specifically,
+         the Flutter tool needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>

+ 31 - 0
frontend/app_flowy/packages/appflowy_popover/example/android/build.gradle

@@ -0,0 +1,31 @@
+buildscript {
+    ext.kotlin_version = '1.6.10'
+    repositories {
+        google()
+        mavenCentral()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:7.1.2'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+    project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+    project.evaluationDependsOn(':app')
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}

+ 3 - 0
frontend/app_flowy/packages/appflowy_popover/example/android/gradle.properties

@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true

+ 6 - 0
frontend/app_flowy/packages/appflowy_popover/example/android/gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+#Fri Jun 23 08:50:38 CEST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip

+ 11 - 0
frontend/app_flowy/packages/appflowy_popover/example/android/settings.gradle

@@ -0,0 +1,11 @@
+include ':app'
+
+def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
+def properties = new Properties()
+
+assert localPropertiesFile.exists()
+localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
+
+def flutterSdkPath = properties.getProperty("flutter.sdk")
+assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"

+ 34 - 0
frontend/app_flowy/packages/appflowy_popover/example/ios/.gitignore

@@ -0,0 +1,34 @@
+**/dgph
+*.mode1v3
+*.mode2v3
+*.moved-aside
+*.pbxuser
+*.perspectivev3
+**/*sync/
+.sconsign.dblite
+.tags*
+**/.vagrant/
+**/DerivedData/
+Icon?
+**/Pods/
+**/.symlinks/
+profile
+xcuserdata
+**/.generated/
+Flutter/App.framework
+Flutter/Flutter.framework
+Flutter/Flutter.podspec
+Flutter/Generated.xcconfig
+Flutter/ephemeral/
+Flutter/app.flx
+Flutter/app.zip
+Flutter/flutter_assets/
+Flutter/flutter_export_environment.sh
+ServiceDefinitions.json
+Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
+!default.mode1v3
+!default.mode2v3
+!default.pbxuser
+!default.perspectivev3

+ 26 - 0
frontend/app_flowy/packages/appflowy_popover/example/ios/Flutter/AppFrameworkInfo.plist

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>CFBundleDevelopmentRegion</key>
+  <string>en</string>
+  <key>CFBundleExecutable</key>
+  <string>App</string>
+  <key>CFBundleIdentifier</key>
+  <string>io.flutter.flutter.app</string>
+  <key>CFBundleInfoDictionaryVersion</key>
+  <string>6.0</string>
+  <key>CFBundleName</key>
+  <string>App</string>
+  <key>CFBundlePackageType</key>
+  <string>FMWK</string>
+  <key>CFBundleShortVersionString</key>
+  <string>1.0</string>
+  <key>CFBundleSignature</key>
+  <string>????</string>
+  <key>CFBundleVersion</key>
+  <string>1.0</string>
+  <key>MinimumOSVersion</key>
+  <string>9.0</string>
+</dict>
+</plist>

+ 1 - 0
frontend/app_flowy/packages/appflowy_popover/example/ios/Flutter/Debug.xcconfig

@@ -0,0 +1 @@
+#include "Generated.xcconfig"

+ 1 - 0
frontend/app_flowy/packages/appflowy_popover/example/ios/Flutter/Release.xcconfig

@@ -0,0 +1 @@
+#include "Generated.xcconfig"

+ 481 - 0
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner.xcodeproj/project.pbxproj

@@ -0,0 +1,481 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 50;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+		3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
+		74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
+		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
+		97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
+		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+		9705A1C41CF9048500538489 /* Embed Frameworks */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = "";
+			dstSubfolderSpec = 10;
+			files = (
+			);
+			name = "Embed Frameworks";
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+		1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
+		1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
+		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
+		74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
+		74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
+		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
+		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
+		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
+		97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
+		97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+		97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
+		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		97C146EB1CF9000F007C117D /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		9740EEB11CF90186004384FC /* Flutter */ = {
+			isa = PBXGroup;
+			children = (
+				3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
+				9740EEB21CF90195004384FC /* Debug.xcconfig */,
+				7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+				9740EEB31CF90195004384FC /* Generated.xcconfig */,
+			);
+			name = Flutter;
+			sourceTree = "<group>";
+		};
+		97C146E51CF9000F007C117D = {
+			isa = PBXGroup;
+			children = (
+				9740EEB11CF90186004384FC /* Flutter */,
+				97C146F01CF9000F007C117D /* Runner */,
+				97C146EF1CF9000F007C117D /* Products */,
+			);
+			sourceTree = "<group>";
+		};
+		97C146EF1CF9000F007C117D /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				97C146EE1CF9000F007C117D /* Runner.app */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		97C146F01CF9000F007C117D /* Runner */ = {
+			isa = PBXGroup;
+			children = (
+				97C146FA1CF9000F007C117D /* Main.storyboard */,
+				97C146FD1CF9000F007C117D /* Assets.xcassets */,
+				97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
+				97C147021CF9000F007C117D /* Info.plist */,
+				1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
+				1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
+				74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
+				74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
+			);
+			path = Runner;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		97C146ED1CF9000F007C117D /* Runner */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
+			buildPhases = (
+				9740EEB61CF901F6004384FC /* Run Script */,
+				97C146EA1CF9000F007C117D /* Sources */,
+				97C146EB1CF9000F007C117D /* Frameworks */,
+				97C146EC1CF9000F007C117D /* Resources */,
+				9705A1C41CF9048500538489 /* Embed Frameworks */,
+				3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = Runner;
+			productName = Runner;
+			productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
+			productType = "com.apple.product-type.application";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		97C146E61CF9000F007C117D /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastUpgradeCheck = 1300;
+				ORGANIZATIONNAME = "";
+				TargetAttributes = {
+					97C146ED1CF9000F007C117D = {
+						CreatedOnToolsVersion = 7.3.1;
+						LastSwiftMigration = 1100;
+					};
+				};
+			};
+			buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
+			compatibilityVersion = "Xcode 9.3";
+			developmentRegion = en;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+				Base,
+			);
+			mainGroup = 97C146E51CF9000F007C117D;
+			productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				97C146ED1CF9000F007C117D /* Runner */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		97C146EC1CF9000F007C117D /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
+				3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
+				97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
+				97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+		3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "Thin Binary";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
+		};
+		9740EEB61CF901F6004384FC /* Run Script */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "Run Script";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
+		};
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		97C146EA1CF9000F007C117D /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
+				1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+		97C146FA1CF9000F007C117D /* Main.storyboard */ = {
+			isa = PBXVariantGroup;
+			children = (
+				97C146FB1CF9000F007C117D /* Base */,
+			);
+			name = Main.storyboard;
+			sourceTree = "<group>";
+		};
+		97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
+			isa = PBXVariantGroup;
+			children = (
+				97C147001CF9000F007C117D /* Base */,
+			);
+			name = LaunchScreen.storyboard;
+			sourceTree = "<group>";
+		};
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+		249021D3217E4FDB00AE95B9 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = iphoneos;
+				SUPPORTED_PLATFORMS = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VALIDATE_PRODUCT = YES;
+			};
+			name = Profile;
+		};
+		249021D4217E4FDB00AE95B9 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+				ENABLE_BITCODE = NO;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+				SWIFT_VERSION = 5.0;
+				VERSIONING_SYSTEM = "apple-generic";
+			};
+			name = Profile;
+		};
+		97C147031CF9000F007C117D /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Debug;
+		};
+		97C147041CF9000F007C117D /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = iphoneos;
+				SUPPORTED_PLATFORMS = iphoneos;
+				SWIFT_COMPILATION_MODE = wholemodule;
+				SWIFT_OPTIMIZATION_LEVEL = "-O";
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VALIDATE_PRODUCT = YES;
+			};
+			name = Release;
+		};
+		97C147061CF9000F007C117D /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+				ENABLE_BITCODE = NO;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+				SWIFT_VERSION = 5.0;
+				VERSIONING_SYSTEM = "apple-generic";
+			};
+			name = Debug;
+		};
+		97C147071CF9000F007C117D /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+				ENABLE_BITCODE = NO;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+				SWIFT_VERSION = 5.0;
+				VERSIONING_SYSTEM = "apple-generic";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				97C147031CF9000F007C117D /* Debug */,
+				97C147041CF9000F007C117D /* Release */,
+				249021D3217E4FDB00AE95B9 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				97C147061CF9000F007C117D /* Debug */,
+				97C147071CF9000F007C117D /* Release */,
+				249021D4217E4FDB00AE95B9 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 97C146E61CF9000F007C117D /* Project object */;
+}

+ 7 - 0
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "self:">
+   </FileRef>
+</Workspace>

+ 8 - 0
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>

+ 8 - 0
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>PreviewsEnabled</key>
+	<false/>
+</dict>
+</plist>

+ 87 - 0
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1300"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+               BuildableName = "Runner.app"
+               BlueprintName = "Runner"
+               ReferencedContainer = "container:Runner.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Profile"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 7 - 0
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner.xcworkspace/contents.xcworkspacedata

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "group:Runner.xcodeproj">
+   </FileRef>
+</Workspace>

+ 8 - 0
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>

+ 8 - 0
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>PreviewsEnabled</key>
+	<false/>
+</dict>
+</plist>

+ 13 - 0
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/AppDelegate.swift

@@ -0,0 +1,13 @@
+import UIKit
+import Flutter
+
+@UIApplicationMain
+@objc class AppDelegate: FlutterAppDelegate {
+  override func application(
+    _ application: UIApplication,
+    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
+  ) -> Bool {
+    GeneratedPluginRegistrant.register(with: self)
+    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
+  }
+}

+ 122 - 0
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json

@@ -0,0 +1,122 @@
+{
+  "images" : [
+    {
+      "size" : "20x20",
+      "idiom" : "iphone",
+      "filename" : "[email protected]",
+      "scale" : "2x"
+    },
+    {
+      "size" : "20x20",
+      "idiom" : "iphone",
+      "filename" : "[email protected]",
+      "scale" : "3x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "[email protected]",
+      "scale" : "1x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "[email protected]",
+      "scale" : "2x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "[email protected]",
+      "scale" : "3x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "iphone",
+      "filename" : "[email protected]",
+      "scale" : "2x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "iphone",
+      "filename" : "[email protected]",
+      "scale" : "3x"
+    },
+    {
+      "size" : "60x60",
+      "idiom" : "iphone",
+      "filename" : "[email protected]",
+      "scale" : "2x"
+    },
+    {
+      "size" : "60x60",
+      "idiom" : "iphone",
+      "filename" : "[email protected]",
+      "scale" : "3x"
+    },
+    {
+      "size" : "20x20",
+      "idiom" : "ipad",
+      "filename" : "[email protected]",
+      "scale" : "1x"
+    },
+    {
+      "size" : "20x20",
+      "idiom" : "ipad",
+      "filename" : "[email protected]",
+      "scale" : "2x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "ipad",
+      "filename" : "[email protected]",
+      "scale" : "1x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "ipad",
+      "filename" : "[email protected]",
+      "scale" : "2x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "ipad",
+      "filename" : "[email protected]",
+      "scale" : "1x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "ipad",
+      "filename" : "[email protected]",
+      "scale" : "2x"
+    },
+    {
+      "size" : "76x76",
+      "idiom" : "ipad",
+      "filename" : "[email protected]",
+      "scale" : "1x"
+    },
+    {
+      "size" : "76x76",
+      "idiom" : "ipad",
+      "filename" : "[email protected]",
+      "scale" : "2x"
+    },
+    {
+      "size" : "83.5x83.5",
+      "idiom" : "ipad",
+      "filename" : "[email protected]",
+      "scale" : "2x"
+    },
+    {
+      "size" : "1024x1024",
+      "idiom" : "ios-marketing",
+      "filename" : "[email protected]",
+      "scale" : "1x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}

BIN
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]


+ 23 - 0
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json

@@ -0,0 +1,23 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "filename" : "LaunchImage.png",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "[email protected]",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "[email protected]",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}

BIN
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png


BIN
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/[email protected]


BIN
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/[email protected]


+ 5 - 0
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md

@@ -0,0 +1,5 @@
+# Launch Screen Assets
+
+You can customize the launch screen with your own desired assets by replacing the image files in this directory.
+
+You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

+ 37 - 0
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Base.lproj/LaunchScreen.storyboard

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
+    </dependencies>
+    <scenes>
+        <!--View Controller-->
+        <scene sceneID="EHf-IW-A2E">
+            <objects>
+                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
+                        <viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
+                            </imageView>
+                        </subviews>
+                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                        <constraints>
+                            <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
+                            <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
+                        </constraints>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="53" y="375"/>
+        </scene>
+    </scenes>
+    <resources>
+        <image name="LaunchImage" width="168" height="185"/>
+    </resources>
+</document>

+ 26 - 0
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Base.lproj/Main.storyboard

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
+    </dependencies>
+    <scenes>
+        <!--Flutter View Controller-->
+        <scene sceneID="tne-QT-ifu">
+            <objects>
+                <viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
+                        <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+                        <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+            </objects>
+        </scene>
+    </scenes>
+</document>

+ 49 - 0
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Info.plist

@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleDisplayName</key>
+	<string>Example</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>example</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>$(FLUTTER_BUILD_NAME)</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>$(FLUTTER_BUILD_NUMBER)</string>
+	<key>LSRequiresIPhoneOS</key>
+	<true/>
+	<key>UILaunchStoryboardName</key>
+	<string>LaunchScreen</string>
+	<key>UIMainStoryboardFile</key>
+	<string>Main</string>
+	<key>UISupportedInterfaceOrientations</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UISupportedInterfaceOrientations~ipad</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UIViewControllerBasedStatusBarAppearance</key>
+	<false/>
+	<key>CADisableMinimumFrameDurationOnPhone</key>
+	<true/>
+</dict>
+</plist>

+ 1 - 0
frontend/app_flowy/packages/appflowy_popover/example/ios/Runner/Runner-Bridging-Header.h

@@ -0,0 +1 @@
+#import "GeneratedPluginRegistrant.h"

+ 98 - 0
frontend/app_flowy/packages/appflowy_popover/example/lib/example_button.dart

@@ -0,0 +1,98 @@
+import 'package:flutter/material.dart';
+import 'package:appflowy_popover/popover.dart';
+
+class PopoverMenu extends StatefulWidget {
+  const PopoverMenu({Key? key}) : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() => _PopoverMenuState();
+}
+
+class _PopoverMenuState extends State<PopoverMenu> {
+  final PopoverMutex popOverMutex = PopoverMutex();
+
+  @override
+  Widget build(BuildContext context) {
+    return Material(
+        type: MaterialType.transparency,
+        child: Container(
+          width: 200,
+          height: 200,
+          decoration: BoxDecoration(
+            color: Colors.white,
+            borderRadius: const BorderRadius.all(Radius.circular(8)),
+            boxShadow: [
+              BoxShadow(
+                color: Colors.grey.withOpacity(0.5),
+                spreadRadius: 5,
+                blurRadius: 7,
+                offset: const Offset(0, 3), // changes position of shadow
+              ),
+            ],
+          ),
+          child: ListView(children: [
+            Container(
+              margin: const EdgeInsets.all(8),
+              child: const Text("Popover",
+                  style: TextStyle(
+                      fontSize: 14,
+                      color: Colors.black,
+                      fontStyle: null,
+                      decoration: null)),
+            ),
+            Popover(
+              triggerActions: PopoverTriggerActionFlags.hover |
+                  PopoverTriggerActionFlags.click,
+              mutex: popOverMutex,
+              offset: const Offset(10, 0),
+              popupBuilder: (BuildContext context) {
+                return const PopoverMenu();
+              },
+              child: TextButton(
+                onPressed: () {},
+                child: const Text("First"),
+              ),
+            ),
+            Popover(
+              triggerActions: PopoverTriggerActionFlags.hover |
+                  PopoverTriggerActionFlags.click,
+              mutex: popOverMutex,
+              offset: const Offset(10, 0),
+              popupBuilder: (BuildContext context) {
+                return const PopoverMenu();
+              },
+              child: TextButton(
+                onPressed: () {},
+                child: const Text("Second"),
+              ),
+            ),
+          ]),
+        ));
+  }
+}
+
+class ExampleButton extends StatelessWidget {
+  final String label;
+  final Offset? offset;
+  final PopoverDirection? direction;
+
+  const ExampleButton({
+    Key? key,
+    required this.label,
+    this.direction,
+    this.offset = Offset.zero,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Popover(
+      triggerActions: PopoverTriggerActionFlags.click,
+      offset: offset,
+      direction: direction ?? PopoverDirection.rightWithTopAligned,
+      child: TextButton(child: Text(label), onPressed: () {}),
+      popupBuilder: (BuildContext context) {
+        return const PopoverMenu();
+      },
+    );
+  }
+}

+ 129 - 0
frontend/app_flowy/packages/appflowy_popover/example/lib/main.dart

@@ -0,0 +1,129 @@
+import 'package:appflowy_popover/popover.dart';
+import 'package:flutter/material.dart';
+import "./example_button.dart";
+
+void main() {
+  runApp(const MyApp());
+}
+
+class MyApp extends StatelessWidget {
+  const MyApp({Key? key}) : super(key: key);
+
+  // This widget is the root of your application.
+  @override
+  Widget build(BuildContext context) {
+    return MaterialApp(
+      title: 'Flutter Demo',
+      theme: ThemeData(
+        // This is the theme of your application.
+        //
+        // Try running your application with "flutter run". You'll see the
+        // application has a blue toolbar. Then, without quitting the app, try
+        // changing the primarySwatch below to Colors.green and then invoke
+        // "hot reload" (press "r" in the console where you ran "flutter run",
+        // or simply save your changes to "hot reload" in a Flutter IDE).
+        // Notice that the counter didn't reset back to zero; the application
+        // is not restarted.
+        primarySwatch: Colors.blue,
+      ),
+      home: const MyHomePage(title: 'AppFlowy Popover Example'),
+    );
+  }
+}
+
+class MyHomePage extends StatefulWidget {
+  const MyHomePage({Key? key, required this.title}) : super(key: key);
+
+  // This widget is the home page of your application. It is stateful, meaning
+  // that it has a State object (defined below) that contains fields that affect
+  // how it looks.
+
+  // This class is the configuration for the state. It holds the values (in this
+  // case the title) provided by the parent (in this case the App widget) and
+  // used by the build method of the State. Fields in a Widget subclass are
+  // always marked "final".
+
+  final String title;
+
+  @override
+  State<MyHomePage> createState() => _MyHomePageState();
+}
+
+class _MyHomePageState extends State<MyHomePage> {
+  @override
+  Widget build(BuildContext context) {
+    // This method is rerun every time setState is called, for instance as done
+    // by the _incrementCounter method above.
+    //
+    // The Flutter framework has been optimized to make rerunning build methods
+    // fast, so that you can just rebuild anything that needs updating rather
+    // than having to individually change instances of widgets.
+    return Scaffold(
+      appBar: AppBar(
+        // Here we take the value from the MyHomePage object that was created by
+        // the App.build method, and use it to set our appbar title.
+        title: Text(widget.title),
+      ),
+      body: Row(children: [
+        Column(children: [
+          const ExampleButton(
+            label: "Left top",
+            offset: Offset(0, 10),
+            direction: PopoverDirection.bottomWithLeftAligned,
+          ),
+          Expanded(child: Container()),
+          const ExampleButton(
+            label: "Left bottom",
+            offset: Offset(0, -10),
+            direction: PopoverDirection.topWithLeftAligned,
+          ),
+        ]),
+        Expanded(
+          child: Column(
+            mainAxisAlignment: MainAxisAlignment.center,
+            children: <Widget>[
+              const ExampleButton(
+                label: "Top",
+                offset: Offset(0, 10),
+                direction: PopoverDirection.bottomWithCenterAligned,
+              ),
+              Expanded(
+                child: Column(
+                  mainAxisAlignment: MainAxisAlignment.center,
+                  crossAxisAlignment: CrossAxisAlignment.center,
+                  children: const [
+                    ExampleButton(
+                      label: "Central",
+                      offset: Offset(0, 10),
+                      direction: PopoverDirection.bottomWithCenterAligned,
+                    ),
+                  ],
+                ),
+              ),
+              const ExampleButton(
+                label: "Bottom",
+                offset: Offset(0, -10),
+                direction: PopoverDirection.topWithCenterAligned,
+              ),
+            ],
+          ),
+        ),
+        Column(
+          children: [
+            const ExampleButton(
+              label: "Right top",
+              offset: Offset(0, 10),
+              direction: PopoverDirection.bottomWithRightAligned,
+            ),
+            Expanded(child: Container()),
+            const ExampleButton(
+              label: "Right bottom",
+              offset: Offset(0, -10),
+              direction: PopoverDirection.topWithRightAligned,
+            ),
+          ],
+        )
+      ]),
+    );
+  }
+}

+ 1 - 0
frontend/app_flowy/packages/appflowy_popover/example/linux/.gitignore

@@ -0,0 +1 @@
+flutter/ephemeral

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません