Selaa lähdekoodia

Merge branch 'main' into main

Nathan.fooo 2 vuotta sitten
vanhempi
commit
1967913db6
84 muutettua tiedostoa jossa 1665 lisäystä ja 1416 poistoa
  1. 16 12
      frontend/Makefile.toml
  2. 2 2
      frontend/app_flowy/assets/translations/en.json
  3. 3 3
      frontend/app_flowy/assets/translations/es-VE.json
  4. 3 3
      frontend/app_flowy/assets/translations/fr-FR.json
  5. 2 2
      frontend/app_flowy/assets/translations/id-ID.json
  6. 2 2
      frontend/app_flowy/assets/translations/ja-JP.json
  7. 229 231
      frontend/app_flowy/assets/translations/ru-RU.json
  8. 2 2
      frontend/app_flowy/assets/translations/zh-CN.json
  9. 2 2
      frontend/app_flowy/assets/translations/zh-TW.json
  10. 2 2
      frontend/app_flowy/lib/plugins/board/presentation/card/board_cell.dart
  11. 0 1
      frontend/app_flowy/lib/plugins/board/presentation/card/board_number_cell.dart
  12. 56 35
      frontend/app_flowy/lib/plugins/board/presentation/card/board_select_option_cell.dart
  13. 113 58
      frontend/app_flowy/lib/plugins/board/presentation/card/card.dart
  14. 74 0
      frontend/app_flowy/lib/plugins/board/presentation/card/container/accessory.dart
  15. 21 74
      frontend/app_flowy/lib/plugins/board/presentation/card/container/card_container.dart
  16. 1 2
      frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_setting.dart
  17. 3 3
      frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_toolbar.dart
  18. 4 2
      frontend/app_flowy/lib/plugins/grid/application/field/field_editor_bloc.dart
  19. 7 4
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_cell.dart
  20. 17 24
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_editor.dart
  21. 54 56
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_cell.dart
  22. 53 61
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart
  23. 41 21
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/text_field.dart
  24. 4 4
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/url_cell.dart
  25. 12 4
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_cell.dart
  26. 30 30
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_cell_action_sheet.dart
  27. 121 61
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart
  28. 0 56
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_name_input.dart
  29. 1 5
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_type_list.dart
  30. 5 4
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_type_option_editor.dart
  31. 5 4
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/grid_header.dart
  32. 4 2
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/builder.dart
  33. 17 29
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/date.dart
  34. 1 1
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/multi_select.dart
  35. 5 11
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/number.dart
  36. 5 4
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/select_option.dart
  37. 1 1
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/select_option_editor.dart
  38. 1 1
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/single_select.dart
  39. 68 30
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/grid_row.dart
  40. 2 33
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_action_sheet.dart
  41. 97 78
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart
  42. 0 1
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_group.dart
  43. 8 6
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_property.dart
  44. 0 2
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_setting.dart
  45. 3 3
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_toolbar.dart
  46. 6 6
      frontend/app_flowy/lib/plugins/trash/trash.dart
  47. 16 9
      frontend/app_flowy/lib/workspace/application/appearance.dart
  48. 0 39
      frontend/app_flowy/lib/workspace/presentation/widgets/dialogs.dart
  49. 0 63
      frontend/app_flowy/lib/workspace/presentation/widgets/pop_up_window.dart
  50. 0 3
      frontend/app_flowy/macos/Runner.xcodeproj/project.pbxproj
  51. 2 6
      frontend/app_flowy/packages/appflowy_editor/README.md
  52. 22 15
      frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md
  53. BIN
      frontend/app_flowy/packages/appflowy_editor/documentation/images/appflowy-editor-example.gif
  54. BIN
      frontend/app_flowy/packages/appflowy_editor/documentation/images/appflowy_editor_example.mp4
  55. BIN
      frontend/app_flowy/packages/appflowy_editor/documentation/images/customize_a_component.gif
  56. BIN
      frontend/app_flowy/packages/appflowy_editor/documentation/images/customize_a_shortcut_event_after.gif
  57. BIN
      frontend/app_flowy/packages/appflowy_editor/documentation/images/customize_a_shortcut_event_before.gif
  58. BIN
      frontend/app_flowy/packages/appflowy_editor/documentation/images/customizing_a_component.gif
  59. BIN
      frontend/app_flowy/packages/appflowy_editor/documentation/images/customizing_a_shortcut_event_after.gif
  60. BIN
      frontend/app_flowy/packages/appflowy_editor/documentation/images/customizing_a_shortcut_event_before.gif
  61. 4 3
      frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart
  62. 53 0
      frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic.dart
  63. 0 45
      frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic_key_event_handler.dart
  64. 7 6
      frontend/app_flowy/packages/appflowy_editor/example/macos/Runner/Base.lproj/MainMenu.xib
  65. 1 0
      frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart
  66. 10 11
      frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/whitespace_handler.dart
  67. 40 0
      frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/white_space_handler_test.dart
  68. 6 6
      frontend/app_flowy/packages/appflowy_popover/example/lib/example_button.dart
  69. 1 1
      frontend/app_flowy/packages/appflowy_popover/example/lib/main.dart
  70. 5 0
      frontend/app_flowy/packages/appflowy_popover/lib/appflowy_popover.dart
  71. 0 0
      frontend/app_flowy/packages/appflowy_popover/lib/src/follower.dart
  72. 13 1
      frontend/app_flowy/packages/appflowy_popover/lib/src/layout.dart
  73. 116 0
      frontend/app_flowy/packages/appflowy_popover/lib/src/mask.dart
  74. 51 0
      frontend/app_flowy/packages/appflowy_popover/lib/src/mutex.dart
  75. 72 138
      frontend/app_flowy/packages/appflowy_popover/lib/src/popover.dart
  76. 6 3
      frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/appflowy_stype_popover.dart
  77. 30 9
      frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/text.dart
  78. 0 2
      frontend/app_flowy/packages/flowy_infra_ui/pubspec.yaml
  79. 15 1
      frontend/app_flowy/pubspec.lock
  80. 14 12
      frontend/rust-lib/dart-ffi/Cargo.toml
  81. 17 14
      frontend/rust-lib/flowy-database/Cargo.toml
  82. 52 44
      frontend/rust-lib/flowy-sdk/Cargo.toml
  83. 1 0
      frontend/rust-lib/flowy-text-block/src/editor.rs
  84. 8 7
      frontend/rust-lib/lib-sqlite/Cargo.toml

+ 16 - 12
frontend/Makefile.toml

@@ -14,7 +14,7 @@ extend = [
 on_error_task = "catch"
 on_error_task = "catch"
 
 
 [tasks.catch]
 [tasks.catch]
-run_task = {name = ["restore-crate-type"]}
+run_task = { name = ["restore-crate-type"] }
 
 
 [env]
 [env]
 RUST_LOG = "info"
 RUST_LOG = "info"
@@ -42,8 +42,8 @@ PRODUCT_NAME = "AppFlowy"
 CRATE_TYPE = "staticlib"
 CRATE_TYPE = "staticlib"
 SDK_EXT = "a"
 SDK_EXT = "a"
 APP_ENVIRONMENT = "local"
 APP_ENVIRONMENT = "local"
-FLUTTER_FLOWY_SDK_PATH="app_flowy/packages/flowy_sdk"
-PROTOBUF_DERIVE_CACHE="../shared-lib/flowy-derive/src/derive_cache/derive_cache.rs"
+FLUTTER_FLOWY_SDK_PATH = "app_flowy/packages/flowy_sdk"
+PROTOBUF_DERIVE_CACHE = "../shared-lib/flowy-derive/src/derive_cache/derive_cache.rs"
 
 
 [env.development-mac-arm64]
 [env.development-mac-arm64]
 RUST_LOG = "info"
 RUST_LOG = "info"
@@ -139,8 +139,7 @@ LINUX_ARCH = "arm64"
 APP_ENVIRONMENT = "production"
 APP_ENVIRONMENT = "production"
 
 
 [tasks.echo_env]
 [tasks.echo_env]
-script = [
-    '''
+script = ['''
     echo "-------- Env Parameters --------"
     echo "-------- Env Parameters --------"
     echo CRATE_TYPE: ${CRATE_TYPE}
     echo CRATE_TYPE: ${CRATE_TYPE}
     echo BUILD_FLAG: ${BUILD_FLAG}
     echo BUILD_FLAG: ${BUILD_FLAG}
@@ -151,8 +150,7 @@ script = [
     echo APP_ENVIRONMENT: ${APP_ENVIRONMENT}
     echo APP_ENVIRONMENT: ${APP_ENVIRONMENT}
     echo ${platforms}
     echo ${platforms}
     echo ${BUILD_ARCHS}
     echo ${BUILD_ARCHS}
-    '''
-]
+    ''']
 script_runner = "@shell"
 script_runner = "@shell"
 
 
 [env.production-ios]
 [env.production-ios]
@@ -166,6 +164,14 @@ BUILD_FLAG = "debug"
 TARGET_OS = "android"
 TARGET_OS = "android"
 CRATE_TYPE = "cdylib"
 CRATE_TYPE = "cdylib"
 FLUTTER_OUTPUT_DIR = "Debug"
 FLUTTER_OUTPUT_DIR = "Debug"
+FEATURES = "flutter,openssl_vendored"
+
+[env.production-android]
+BUILD_FLAG = "release"
+TARGET_OS = "android"
+CRATE_TYPE = "cdylib"
+FLUTTER_OUTPUT_DIR = "Release"
+FEATURES = "flutter,openssl_vendored"
 
 
 [tasks.setup-crate-type]
 [tasks.setup-crate-type]
 private = true
 private = true
@@ -192,11 +198,9 @@ script = [
 script_runner = "@duckscript"
 script_runner = "@duckscript"
 
 
 [tasks.test-build]
 [tasks.test-build]
-condition = { env_set = [ "FLUTTER_FLOWY_SDK_PATH"] }
-script = [
-    """
+condition = { env_set = ["FLUTTER_FLOWY_SDK_PATH"] }
+script = ["""
       cd ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/flowy-net
       cd ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/flowy-net
       cargo build -vv --features=dart
       cargo build -vv --features=dart
-      """,
-]
+      """]
 script_runner = "@shell"
 script_runner = "@shell"

+ 2 - 2
frontend/app_flowy/assets/translations/en.json

@@ -213,8 +213,8 @@
       "aquaColor": "Aqua",
       "aquaColor": "Aqua",
       "blueColor": "Blue",
       "blueColor": "Blue",
       "deleteTag": "Delete tag",
       "deleteTag": "Delete tag",
-      "colorPannelTitle": "Colors",
-      "pannelTitle": "Select an option or create one",
+      "colorPanelTitle": "Colors",
+      "panelTitle": "Select an option or create one",
       "searchOption": "Search for an option"
       "searchOption": "Search for an option"
     },
     },
     "menuName": "Grid"
     "menuName": "Grid"

+ 3 - 3
frontend/app_flowy/assets/translations/es-VE.json

@@ -201,8 +201,8 @@
       "aquaColor": "Agua",
       "aquaColor": "Agua",
       "blueColor": "Azul",
       "blueColor": "Azul",
       "deleteTag": "Borrar etiqueta",
       "deleteTag": "Borrar etiqueta",
-      "colorPannelTitle": "Colores",
-      "pannelTitle": "Selecciona una opción o crea una",
+      "colorPanelTitle": "Colores",
+      "panelTitle": "Selecciona una opción o crea una",
       "searchOption": "Buscar una opción"
       "searchOption": "Buscar una opción"
     },
     },
     "menuName": "Grid"
     "menuName": "Grid"
@@ -218,4 +218,4 @@
     "openSidebar": "Open sidebar",
     "openSidebar": "Open sidebar",
     "closeSidebar": "Close sidebar"
     "closeSidebar": "Close sidebar"
   }
   }
-}
+}

+ 3 - 3
frontend/app_flowy/assets/translations/fr-FR.json

@@ -199,8 +199,8 @@
       "aquaColor": "Aqua",
       "aquaColor": "Aqua",
       "blueColor": "Bleu",
       "blueColor": "Bleu",
       "deleteTag": "Supprimer l'étiquette",
       "deleteTag": "Supprimer l'étiquette",
-      "colorPannelTitle": "Couleurs",
-      "pannelTitle": "Sélectionnez une option ou créez-en une",
+      "colorPanelTitle": "Couleurs",
+      "panelTitle": "Sélectionnez une option ou créez-en une",
       "searchOption": "Rechercher une option"
       "searchOption": "Rechercher une option"
     },
     },
     "menuName": "Grille"
     "menuName": "Grille"
@@ -212,4 +212,4 @@
       "timeHintTextInTwentyFourHour": "12:00"
       "timeHintTextInTwentyFourHour": "12:00"
     }
     }
   }
   }
-}
+}

+ 2 - 2
frontend/app_flowy/assets/translations/id-ID.json

@@ -202,8 +202,8 @@
       "aquaColor": "Air",
       "aquaColor": "Air",
       "blueColor": "Biru",
       "blueColor": "Biru",
       "deleteTag": "Hapus tag",
       "deleteTag": "Hapus tag",
-      "colorPannelTitle": "Warna",
-      "pannelTitle": "Pilih opsi atau buat baru",
+      "colorPanelTitle": "Warna",
+      "panelTitle": "Pilih opsi atau buat baru",
       "searchOption": "Cari opsi"
       "searchOption": "Cari opsi"
     },
     },
     "menuName": "Grid"
     "menuName": "Grid"

+ 2 - 2
frontend/app_flowy/assets/translations/ja-JP.json

@@ -191,8 +191,8 @@
       "aquaColor": "水色",
       "aquaColor": "水色",
       "blueColor": "青",
       "blueColor": "青",
       "deleteTag": "選択候補を削除",
       "deleteTag": "選択候補を削除",
-      "colorPannelTitle": "色",
-      "pannelTitle": "選択候補を検索 または 作成する",
+      "colorPanelTitle": "色",
+      "panelTitle": "選択候補を検索 または 作成する",
       "searchOption": "選択候補を検索"
       "searchOption": "選択候補を検索"
     }
     }
   },
   },

+ 229 - 231
frontend/app_flowy/assets/translations/ru-RU.json

@@ -1,235 +1,233 @@
 {
 {
-    "appName": "AppFlowy",
-    "defaultUsername": "Я",
-    "welcomeText": "Добро пожаловать в @:appName",
-    "githubStarText": "Поставить звезду на GitHub",
-    "subscribeNewsletterText": "Подписаться на рассылку",
-    "letsGoButtonText": "Начнём",
-    "title": "Заголовок",
-    "signUp": {
-      "buttonText": "Зарегистрироваться",
-      "title": "Регистрация в @:appName",
-      "getStartedText": "Начать",
-      "emptyPasswordError": "Пароль не может быть пустым",
-      "repeatPasswordEmptyError": "Повтор пароля не может быть пустым",
-      "unmatchedPasswordError": "Пароли не совпадают",
-      "alreadyHaveAnAccount": "Уже есть аккаунт?",
-      "emailHint": "Электронная почта",
-      "passwordHint": "Пароль",
-      "repeatPasswordHint": "Повторите пароль"
-    },
-    "signIn": {
-      "loginTitle": "Войти в @:appName",
-      "loginButtonText": "Войти",
-      "buttonText": "Авторизация",
-      "forgotPassword": "Забыли пароль?",
-      "emailHint": "Электронная почта",
-      "passwordHint": "Пароль",
-      "dontHaveAnAccount": "Нет аккаунта?",
-      "repeatPasswordEmptyError": "Повтор пароля не может быть пустым",
-      "unmatchedPasswordError": "Пароли не совпадают"
-    },
-    "workspace": {
-      "create": "Создать рабочее пространство",
-      "hint": "рабочее пространство",
-      "notFoundError": "Нет такого рабочего пространства"
-    },
-    "shareAction": {
-      "buttonText": "Поделиться",
-      "workInProgress": "В разработке",
-      "markdown": "Markdown",
-      "copyLink": "Скопировать ссылку"
-    },
-    "disclosureAction": {
-      "rename": "Переименовать",
-      "delete": "Удалить",
-      "duplicate": "Дублировать"
-    },
-    "blankPageTitle": "Пустая страница",
-    "newPageText": "Новая страница",
-    "trash": {
-      "text": "Корзина",
-      "restoreAll": "Восстановить всё",
-      "deleteAll": "Очистить",
-      "pageHeader": {
-        "fileName": "Имя",
-        "lastModified": "Последнее изменение",
-        "created": "Создан"
-      }
-    },
-    "deletePagePrompt": {
-      "text": "Эта страница в Корзине",
-      "restore": "Восстановить страницу",
-      "deletePermanent": "Удалить навсегда"
-    },
-    "dialogCreatePageNameHint": "Имя",
-    "questionBubble": {
-      "whatsNew": "Что нового?",
-      "help": "Помощь",
-      "debug": {
-        "name": "Отладочная информация",
-        "success": "Скопировано в буфер обмена!",
-        "fail": "Не получилось скопировать"
-      }
-    },
-    "menuAppHeader": {
-      "addPageTooltip": "Быстро добавить новую страницу",
-      "defaultNewPageName": "Без заголовка",
-      "renameDialog": "Переименовать"
-    },
-    "toolbar": {
-      "undo": "Отменить",
-      "redo": "Повторить",
-      "bold": "Жирный",
-      "italic": "Курсив",
-      "underline": "Подчёркнутый",
-      "strike": "Зачёркнутый",
-      "numList": "Нумерованный список",
-      "bulletList": "Маркированный список",
-      "checkList": "Список To-Do",
-      "inlineCode": "Код",
-      "quote": "Цитата",
-      "header": "Заголовок",
-      "highlight": "Выделение"
-    },
-    "tooltip": {
-      "lightMode": "Переключиться в светлую тему",
-      "darkMode": "Переключиться в тёмную тему",
-      "openAsPage": "Открыть как страницу",
-      "addNewRow": "Добавить новую строку",
-      "openMenu": "Открыть меню"
-    },
-    "sideBar": {
-      "closeSidebar": "Закрыть боковое меню",
-      "openSidebar": "Открыть боковое меню"
-    },
-    "notifications": {
-      "export": {
-        "markdown": "Заметка экспортирована в Markdown",
-        "path": "Документы/flowy"
-      }
-    },
-    "contactsPage": {
-      "title": "Контакты",
-      "whatsHappening": "Что происходит на этой неделе?",
-      "addContact": "Новый контакт",
-      "editContact": "Редактировать"
-    },
-    "button": {
-      "OK": "OK",
-      "Cancel": "Отмена",
-      "signIn": "Войти",
-      "signOut": "Выйти",
-      "complete": "Завершить",
-      "save": "Сохранить"
-    },
-    "label": {
-      "welcome": "Добро пожаловать!",
-      "firstName": "Имя",
-      "middleName": "Отчество",
-      "lastName": "Фамилия",
-      "stepX": "Этап {X}"
-    },
-    "oAuth": {
-      "err": {
-        "failedTitle": "Ошибка подключения к аккаунту.",
-        "failedMsg": "Убедитесь, что вы завершили вход в своём браузере."
-      },
-      "google": {
-        "title": "Вход через Google",
-        "instruction1": "Чтобы импортировать ваши Google Контакты, вам нужно будет авторизовать приложение через браузер.",
-        "instruction2": "Скопируйте этот код в буфер обмена (нажав кнопку или выделив текст):",
-        "instruction3": "Пройдите по ссылке и введите этот код:",
-        "instruction4": "Нажмите на кнопку ниже, когда завершите вход:"
-      }
-    },
+  "appName": "AppFlowy",
+  "defaultUsername": "Я",
+  "welcomeText": "Добро пожаловать в @:appName",
+  "githubStarText": "Поставить звезду на GitHub",
+  "subscribeNewsletterText": "Подписаться на рассылку",
+  "letsGoButtonText": "Начнём",
+  "title": "Заголовок",
+  "signUp": {
+    "buttonText": "Зарегистрироваться",
+    "title": "Регистрация в @:appName",
+    "getStartedText": "Начать",
+    "emptyPasswordError": "Пароль не может быть пустым",
+    "repeatPasswordEmptyError": "Повтор пароля не может быть пустым",
+    "unmatchedPasswordError": "Пароли не совпадают",
+    "alreadyHaveAnAccount": "Уже есть аккаунт?",
+    "emailHint": "Электронная почта",
+    "passwordHint": "Пароль",
+    "repeatPasswordHint": "Повторите пароль"
+  },
+  "signIn": {
+    "loginTitle": "Войти в @:appName",
+    "loginButtonText": "Войти",
+    "buttonText": "Авторизация",
+    "forgotPassword": "Забыли пароль?",
+    "emailHint": "Электронная почта",
+    "passwordHint": "Пароль",
+    "dontHaveAnAccount": "Нет аккаунта?",
+    "repeatPasswordEmptyError": "Повтор пароля не может быть пустым",
+    "unmatchedPasswordError": "Пароли не совпадают"
+  },
+  "workspace": {
+    "create": "Создать рабочее пространство",
+    "hint": "рабочее пространство",
+    "notFoundError": "Нет такого рабочего пространства"
+  },
+  "shareAction": {
+    "buttonText": "Поделиться",
+    "workInProgress": "В разработке",
+    "markdown": "Markdown",
+    "copyLink": "Скопировать ссылку"
+  },
+  "disclosureAction": {
+    "rename": "Переименовать",
+    "delete": "Удалить",
+    "duplicate": "Дублировать"
+  },
+  "blankPageTitle": "Пустая страница",
+  "newPageText": "Новая страница",
+  "trash": {
+    "text": "Корзина",
+    "restoreAll": "Восстановить всё",
+    "deleteAll": "Очистить",
+    "pageHeader": {
+      "fileName": "Имя",
+      "lastModified": "Последнее изменение",
+      "created": "Создан"
+    }
+  },
+  "deletePagePrompt": {
+    "text": "Эта страница в Корзине",
+    "restore": "Восстановить страницу",
+    "deletePermanent": "Удалить навсегда"
+  },
+  "dialogCreatePageNameHint": "Имя",
+  "questionBubble": {
+    "whatsNew": "Что нового?",
+    "help": "Помощь",
+    "debug": {
+      "name": "Отладочная информация",
+      "success": "Скопировано в буфер обмена!",
+      "fail": "Не получилось скопировать"
+    }
+  },
+  "menuAppHeader": {
+    "addPageTooltip": "Быстро добавить новую страницу",
+    "defaultNewPageName": "Без заголовка",
+    "renameDialog": "Переименовать"
+  },
+  "toolbar": {
+    "undo": "Отменить",
+    "redo": "Повторить",
+    "bold": "Жирный",
+    "italic": "Курсив",
+    "underline": "Подчёркнутый",
+    "strike": "Зачёркнутый",
+    "numList": "Нумерованный список",
+    "bulletList": "Маркированный список",
+    "checkList": "Список To-Do",
+    "inlineCode": "Код",
+    "quote": "Цитата",
+    "header": "Заголовок",
+    "highlight": "Выделение"
+  },
+  "tooltip": {
+    "darkMode": "Переключиться в тёмную тему",
+     "openAsPage": "Открыть как страницу",
+     "addNewRow": "Добавить новую строку",
+     "openMenu": "Открыть меню"
+  },
+  "sideBar": {
+    "closeSidebar": "Закрыть боковое меню",
+    "openSidebar": "Открыть боковое меню"
+  },
+  "notifications": {
+    "export": {
+      "markdown": "Заметка экспортирована в Markdown",
+      "path": "Документы/flowy"
+    }
+  },
+  "contactsPage": {
+    "title": "Контакты",
+    "whatsHappening": "Что происходит на этой неделе?",
+    "addContact": "Новый контакт",
+    "editContact": "Редактировать"
+  },
+  "button": {
+    "OK": "OK",
+    "Cancel": "Отмена",
+    "signIn": "Войти",
+    "signOut": "Выйти",
+    "complete": "Завершить",
+    "save": "Сохранить"
+  },
+  "label": {
+    "welcome": "Добро пожаловать!",
+    "firstName": "Имя",
+    "middleName": "Отчество",
+    "lastName": "Фамилия",
+    "stepX": "Этап {X}"
+  },
+  "oAuth": {
+    "err": {
+      "failedTitle": "Ошибка подключения к аккаунту.",
+      "failedMsg": "Убедитесь, что вы завершили вход в своём браузере."
+    },
+    "google": {
+      "title": "Вход через Google",
+      "instruction1": "Чтобы импортировать ваши Google Контакты, вам нужно будет авторизовать приложение через браузер.",
+      "instruction2": "Скопируйте этот код в буфер обмена (нажав кнопку или выделив текст):",
+      "instruction3": "Пройдите по ссылке и введите этот код:",
+      "instruction4": "Нажмите на кнопку ниже, когда завершите вход:"
+    }
+  },
+  "settings": {
+    "title": "Настройки",
+    "menu": {
+      "appearance": "Внешний вид",
+      "language": "Язык",
+      "user": "Пользователь",
+      "open": "Открыть настройки"
+    },
+    "appearance": {
+      "lightLabel": "Светлая",
+      "darkLabel": "Тёмная"
+    }
+  },
+  "grid": {
     "settings": {
     "settings": {
-      "title": "Настройки",
-      "menu": {
-        "appearance": "Внешний вид",
-        "language": "Язык",
-        "user": "Пользователь",
-        "open": "Открыть настройки"
-      },
-      "appearance": {
-        "lightLabel": "Светлая",
-        "darkLabel": "Тёмная"
-      }
-    },
-    "grid": {
-      "settings": {
-        "filter": "Фильтр",
-        "sortBy": "Сортировать",
-        "Properties": "Свойства",
-        "group": "Группировать"
-      },
-      "field": {
-        "hide": "Скрыть",
-        "insertLeft": "Вставить слева",
-        "insertRight": "Вставить справа",
-        "duplicate": "Дублировать",
-        "delete": "Удалить",
-        "textFieldName": "Текст",
-        "checkboxFieldName": "Чекбокс",
-        "dateFieldName": "Дата",
-        "numberFieldName": "Число",
-        "singleSelectFieldName": "Выбор",
-        "multiSelectFieldName": "Выбор нескольких",
-        "urlFieldName": "URL",
-        "numberFormat": " Формат числа",
-        "dateFormat": " Формат даты",
-        "includeTime": " Время",
-        "dateFormatFriendly": "День Месяц, Год",
-        "dateFormatISO": "Год-Месяц-День",
-        "dateFormatLocal": "Год/Месяц/День",
-        "dateFormatUS": "Год/Месяц/День",
-        "timeFormat": " Форматировать время",
-        "invalidTimeFormat": "Неверный формат",
-        "timeFormatTwelveHour": "12 часов",
-        "timeFormatTwentyFourHour": "24 часа",
-        "addSelectOption": "Добавить вариант",
-        "optionTitle": "Варианты",
-        "addOption": "Добавить",
-        "editProperty": "Редактировать свойство",
-        "newColumn": "Добавить колонку",
-        "deleteFieldPromptMessage": "Вы уверены? Свойство будет удалено"
-      },
-      "row": {
-        "duplicate": "Дублировать",
-        "delete": "Удалить",
-        "textPlaceholder": "Пусто",
-        "copyProperty": "Свойство скопировано",
-        "count": "Количество"
-      },
-      "selectOption": {
-        "create": "Создать",
-        "purpleColor": "Фиолетовый",
-        "pinkColor": "Розовый",
-        "lightPinkColor": "Светло-розовый",
-        "orangeColor": "Оранжевый",
-        "yellowColor": "Желтый",
-        "limeColor": "Ярко-зелёный",
-        "greenColor": "Зелёный",
-        "aquaColor": "Бирюзовый",
-        "blueColor": "Синий",
-        "deleteTag": "Удалить вариант",
-        "colorPannelTitle": "Цвета",
-        "pannelTitle": "Выберите или создайте вариант",
-        "searchOption": "Поиск"
-      },
-      "menuName": "Сетка"
-    },
-    "document": {
-      "menuName": "Документ",
-      "date": {
-        "timeHintTextInTwelveHour": "12:00 AM",
-        "timeHintTextInTwentyFourHour": "12:00"
-      }
-    },
-    "board": {
-      "column": {
-        "create_new_card": "Создать"
-      }
+      "filter": "Фильтр",
+      "sortBy": "Сортировать",
+      "Properties": "Свойства",
+      "group": "Группировать"
+    },
+    "field": {
+      "hide": "Скрыть",
+      "insertLeft": "Вставить слева",
+      "insertRight": "Вставить справа",
+      "duplicate": "Дублировать",
+      "delete": "Удалить",
+      "textFieldName": "Текст",
+      "checkboxFieldName": "Чекбокс",
+      "dateFieldName": "Дата",
+      "numberFieldName": "Число",
+      "singleSelectFieldName": "Выбор",
+      "multiSelectFieldName": "Выбор нескольких",
+      "urlFieldName": "URL",
+      "numberFormat": " Формат числа",
+      "dateFormat": " Формат даты",
+      "includeTime": " Время",
+      "dateFormatFriendly": "День Месяц, Год",
+      "dateFormatISO": "Год-Месяц-День",
+      "dateFormatLocal": "Год/Месяц/День",
+      "dateFormatUS": "Год/Месяц/День",
+      "timeFormat": " Форматировать время",
+      "invalidTimeFormat": "Неверный формат",
+      "timeFormatTwelveHour": "12 часов",
+      "timeFormatTwentyFourHour": "24 часа",
+      "addSelectOption": "Добавить вариант",
+      "optionTitle": "Варианты",
+      "addOption": "Добавить",
+      "editProperty": "Редактировать свойство",
+      "newColumn": "Добавить колонку",
+      "deleteFieldPromptMessage": "Вы уверены? Свойство будет удалено"
+    },
+    "row": {
+      "duplicate": "Дублировать",
+      "delete": "Удалить",
+      "textPlaceholder": "Пусто",
+      "copyProperty": "Свойство скопировано",
+      "count": "Количество"
+    },
+    "selectOption": {
+      "create": "Создать",
+      "purpleColor": "Фиолетовый",
+      "pinkColor": "Розовый",
+      "lightPinkColor": "Светло-розовый",
+      "orangeColor": "Оранжевый",
+      "yellowColor": "Желтый",
+      "limeColor": "Ярко-зелёный",
+      "greenColor": "Зелёный",
+      "aquaColor": "Бирюзовый",
+      "blueColor": "Синий",
+      "deleteTag": "Удалить вариант",
+      "colorPanelTitle": "Цвета",
+      "panelTitle": "Выберите или создайте вариант",
+      "searchOption": "Поиск"
+    },
+    "menuName": "Сетка"
+  },
+  "document": {
+    "menuName": "Документ",
+    "date": {
+      "timeHintTextInTwelveHour": "12:00 AM",
+      "timeHintTextInTwentyFourHour": "12:00"
+    }
+  },
+  "board": {
+    "column": {
+      "create_new_card": "Создать"
     }
     }
   }
   }
-  
+}

+ 2 - 2
frontend/app_flowy/assets/translations/zh-CN.json

@@ -206,8 +206,8 @@
       "aquaColor": "水蓝色",
       "aquaColor": "水蓝色",
       "blueColor": "蓝色",
       "blueColor": "蓝色",
       "deleteTag": "删除标签",
       "deleteTag": "删除标签",
-      "colorPannelTitle": "颜色",
-      "pannelTitle": "选择或新建一个标签",
+      "colorPanelTitle": "颜色",
+      "panelTitle": "选择或新建一个标签",
       "searchOption": "搜索标签"
       "searchOption": "搜索标签"
     },
     },
     "menuName": "网格"
     "menuName": "网格"

+ 2 - 2
frontend/app_flowy/assets/translations/zh-TW.json

@@ -202,8 +202,8 @@
       "aquaColor": "水藍色",
       "aquaColor": "水藍色",
       "blueColor": "藍色",
       "blueColor": "藍色",
       "deleteTag": "刪除標籤",
       "deleteTag": "刪除標籤",
-      "colorPannelTitle": "顏色",
-      "pannelTitle": "搜尋或建立選項",
+      "colorPanelTitle": "顏色",
+      "panelTitle": "搜尋或建立選項",
       "searchOption": "搜尋選項"
       "searchOption": "搜尋選項"
     },
     },
     "menuName": "網格"
     "menuName": "網格"

+ 2 - 2
frontend/app_flowy/lib/plugins/board/presentation/card/board_cell.dart

@@ -23,7 +23,7 @@ class EditableRowNotifier {
   EditableRowNotifier({required bool isEditing})
   EditableRowNotifier({required bool isEditing})
       : isEditing = ValueNotifier(isEditing);
       : isEditing = ValueNotifier(isEditing);
 
 
-  void insertCell(
+  void bindCell(
     GridCellIdentifier cellIdentifier,
     GridCellIdentifier cellIdentifier,
     EditableCellNotifier notifier,
     EditableCellNotifier notifier,
   ) {
   ) {
@@ -59,7 +59,7 @@ class EditableRowNotifier {
     _cells.values.first.isCellEditing.value = false;
     _cells.values.first.isCellEditing.value = false;
   }
   }
 
 
-  void clear() {
+  void unbind() {
     for (final notifier in _cells.values) {
     for (final notifier in _cells.values) {
       notifier.dispose();
       notifier.dispose();
     }
     }

+ 0 - 1
frontend/app_flowy/lib/plugins/board/presentation/card/board_number_cell.dart

@@ -3,7 +3,6 @@ import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_servic
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
-
 import 'define.dart';
 import 'define.dart';
 
 
 class BoardNumberCell extends StatefulWidget {
 class BoardNumberCell extends StatefulWidget {

+ 56 - 35
frontend/app_flowy/lib/plugins/board/presentation/card/board_select_option_cell.dart

@@ -2,6 +2,8 @@ import 'package:app_flowy/plugins/board/application/card/board_select_option_cel
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
 import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart';
 import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart';
 import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart';
 import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
 
@@ -26,9 +28,11 @@ class BoardSelectOptionCell extends StatefulWidget with EditableCell {
 
 
 class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
 class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
   late BoardSelectOptionCellBloc _cellBloc;
   late BoardSelectOptionCellBloc _cellBloc;
+  late PopoverController _popover;
 
 
   @override
   @override
   void initState() {
   void initState() {
+    _popover = PopoverController();
     final cellController =
     final cellController =
         widget.cellControllerBuilder.build() as GridSelectOptionCellController;
         widget.cellControllerBuilder.build() as GridSelectOptionCellController;
     _cellBloc = BoardSelectOptionCellBloc(cellController: cellController)
     _cellBloc = BoardSelectOptionCellBloc(cellController: cellController)
@@ -41,43 +45,60 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
     return BlocProvider.value(
     return BlocProvider.value(
       value: _cellBloc,
       value: _cellBloc,
       child: BlocBuilder<BoardSelectOptionCellBloc, BoardSelectOptionCellState>(
       child: BlocBuilder<BoardSelectOptionCellBloc, BoardSelectOptionCellState>(
-        buildWhen: (previous, current) {
-          return previous.selectedOptions != current.selectedOptions;
-        },
-        builder: (context, state) {
-          if (state.selectedOptions
-                  .where((element) => element.id == widget.groupId)
-                  .isNotEmpty ||
-              state.selectedOptions.isEmpty) {
-            return const SizedBox();
-          } else {
-            final children = state.selectedOptions
-                .map(
-                  (option) => SelectOptionTag.fromOption(
-                    context: context,
-                    option: option,
-                    onSelected: () {
-                      SelectOptionCellEditor.show(
-                        context: context,
-                        cellController: widget.cellControllerBuilder.build()
-                            as GridSelectOptionCellController,
-                      );
-                    },
-                  ),
-                )
-                .toList();
+          buildWhen: (previous, current) {
+        return previous.selectedOptions != current.selectedOptions;
+      }, builder: (context, state) {
+        // Returns SizedBox if the content of the cell is empty
+        if (_isEmpty(state)) return const SizedBox();
 
 
-            return IntrinsicHeight(
-              child: Padding(
-                padding: const EdgeInsets.symmetric(vertical: 6),
-                child: SizedBox.expand(
-                  child: Wrap(spacing: 4, runSpacing: 2, children: children),
-                ),
-              ),
+        final children = state.selectedOptions.map(
+          (option) {
+            final tag = SelectOptionTag.fromOption(
+              context: context,
+              option: option,
+              onSelected: () => _popover.show(),
             );
             );
-          }
-        },
-      ),
+            return _wrapPopover(tag);
+          },
+        ).toList();
+
+        return IntrinsicHeight(
+          child: Padding(
+            padding: const EdgeInsets.symmetric(vertical: 6),
+            child: SizedBox.expand(
+              child: Wrap(spacing: 4, runSpacing: 2, children: children),
+            ),
+          ),
+        );
+      }),
+    );
+  }
+
+  bool _isEmpty(BoardSelectOptionCellState state) {
+    // The cell should hide if the option id is equal to the groupId.
+    final isInGroup = state.selectedOptions
+        .where((element) => element.id == widget.groupId)
+        .isNotEmpty;
+    return isInGroup || state.selectedOptions.isEmpty;
+  }
+
+  Widget _wrapPopover(Widget child) {
+    final constraints = BoxConstraints.loose(Size(
+      SelectOptionCellEditor.editorPanelWidth,
+      300,
+    ));
+    return AppFlowyPopover(
+      controller: _popover,
+      constraints: constraints,
+      direction: PopoverDirection.bottomWithLeftAligned,
+      popupBuilder: (BuildContext context) {
+        return SelectOptionCellEditor(
+          cellController: widget.cellControllerBuilder.build()
+              as GridSelectOptionCellController,
+        );
+      },
+      onClose: () {},
+      child: child,
     );
     );
   }
   }
 
 

+ 113 - 58
frontend/app_flowy/lib/plugins/board/presentation/card/card.dart

@@ -1,16 +1,17 @@
 import 'package:app_flowy/plugins/board/application/card/card_bloc.dart';
 import 'package:app_flowy/plugins/board/application/card/card_bloc.dart';
 import 'package:app_flowy/plugins/board/application/card/card_data_controller.dart';
 import 'package:app_flowy/plugins/board/application/card/card_data_controller.dart';
-import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
 import 'package:app_flowy/plugins/grid/presentation/widgets/row/row_action_sheet.dart';
 import 'package:app_flowy/plugins/grid/presentation/widgets/row/row_action_sheet.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/theme.dart';
-import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'board_cell.dart';
 import 'board_cell.dart';
 import 'card_cell_builder.dart';
 import 'card_cell_builder.dart';
-import 'card_container.dart';
+import 'container/accessory.dart';
+import 'container/card_container.dart';
 
 
 class BoardCard extends StatefulWidget {
 class BoardCard extends StatefulWidget {
   final String gridId;
   final String gridId;
@@ -39,6 +40,8 @@ class BoardCard extends StatefulWidget {
 class _BoardCardState extends State<BoardCard> {
 class _BoardCardState extends State<BoardCard> {
   late BoardCardBloc _cardBloc;
   late BoardCardBloc _cardBloc;
   late EditableRowNotifier rowNotifier;
   late EditableRowNotifier rowNotifier;
+  late PopoverController popoverController;
+  AccessoryType? accessoryType;
 
 
   @override
   @override
   void initState() {
   void initState() {
@@ -55,6 +58,7 @@ class _BoardCardState extends State<BoardCard> {
       _cardBloc.add(BoardCardEvent.setIsEditing(rowNotifier.isEditing.value));
       _cardBloc.add(BoardCardEvent.setIsEditing(rowNotifier.isEditing.value));
     });
     });
 
 
+    popoverController = PopoverController();
     super.initState();
     super.initState();
   }
   }
 
 
@@ -64,31 +68,42 @@ class _BoardCardState extends State<BoardCard> {
       value: _cardBloc,
       value: _cardBloc,
       child: BlocBuilder<BoardCardBloc, BoardCardState>(
       child: BlocBuilder<BoardCardBloc, BoardCardState>(
         buildWhen: (previous, current) {
         buildWhen: (previous, current) {
+          // Rebuild when:
+          // 1.If the length of the cells is not the same
+          // 2.isEditing changed
           if (previous.cells.length != current.cells.length ||
           if (previous.cells.length != current.cells.length ||
               previous.isEditing != current.isEditing) {
               previous.isEditing != current.isEditing) {
             return true;
             return true;
           }
           }
+
+          // 3.Compare the content of the cells. The cells consists of
+          // list of [BoardCellEquatable] that extends the [Equatable].
           return !listEquals(previous.cells, current.cells);
           return !listEquals(previous.cells, current.cells);
         },
         },
         builder: (context, state) {
         builder: (context, state) {
-          return BoardCardContainer(
-            buildAccessoryWhen: () => state.isEditing == false,
-            accessoryBuilder: (context) {
-              return [
-                _CardEditOption(
-                  startEditing: () => rowNotifier.becomeFirstResponder(),
-                ),
-                const _CardMoreOption(),
-              ];
-            },
-            onTap: (context) {
-              widget.openCard(context);
-            },
-            child: Column(
-              mainAxisSize: MainAxisSize.min,
-              children: _makeCells(
-                context,
-                state.cells.map((cell) => cell.identifier).toList(),
+          return AppFlowyPopover(
+            controller: popoverController,
+            constraints: BoxConstraints.loose(const Size(140, 200)),
+            direction: PopoverDirection.rightWithCenterAligned,
+            popupBuilder: (popoverContext) => _handlePopoverBuilder(
+              context,
+              popoverContext,
+            ),
+            child: BoardCardContainer(
+              buildAccessoryWhen: () => state.isEditing == false,
+              accessoryBuilder: (context) {
+                return [
+                  _CardEditOption(rowNotifier: rowNotifier),
+                  _CardMoreOption(),
+                ];
+              },
+              openAccessory: _handleOpenAccessory,
+              openCard: (context) => widget.openCard(context),
+              child: _CellColumn(
+                groupId: widget.groupId,
+                rowNotifier: rowNotifier,
+                cellBuilder: widget.cellBuilder,
+                cells: state.cells,
               ),
               ),
             ),
             ),
           );
           );
@@ -97,36 +112,86 @@ class _BoardCardState extends State<BoardCard> {
     );
     );
   }
   }
 
 
+  void _handleOpenAccessory(AccessoryType newAccessoryType) {
+    accessoryType = newAccessoryType;
+    switch (newAccessoryType) {
+      case AccessoryType.edit:
+        break;
+      case AccessoryType.more:
+        popoverController.show();
+        break;
+    }
+  }
+
+  Widget _handlePopoverBuilder(
+    BuildContext context,
+    BuildContext popoverContext,
+  ) {
+    switch (accessoryType!) {
+      case AccessoryType.edit:
+        throw UnimplementedError();
+      case AccessoryType.more:
+        return GridRowActionSheet(
+            rowData: context.read<BoardCardBloc>().rowInfo());
+    }
+  }
+
+  @override
+  Future<void> dispose() async {
+    rowNotifier.dispose();
+    _cardBloc.close();
+    super.dispose();
+  }
+}
+
+class _CellColumn extends StatelessWidget {
+  final String groupId;
+  final BoardCellBuilder cellBuilder;
+  final EditableRowNotifier rowNotifier;
+  final List<BoardCellEquatable> cells;
+  const _CellColumn({
+    required this.groupId,
+    required this.rowNotifier,
+    required this.cellBuilder,
+    required this.cells,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Column(
+      mainAxisSize: MainAxisSize.min,
+      children: _makeCells(context, cells),
+    );
+  }
+
   List<Widget> _makeCells(
   List<Widget> _makeCells(
     BuildContext context,
     BuildContext context,
-    List<GridCellIdentifier> cells,
+    List<BoardCellEquatable> cells,
   ) {
   ) {
     final List<Widget> children = [];
     final List<Widget> children = [];
-    rowNotifier.clear();
+    // Remove all the cell listeners.
+    rowNotifier.unbind();
+
     cells.asMap().forEach(
     cells.asMap().forEach(
-      (int index, GridCellIdentifier cellId) {
-        EditableCellNotifier cellNotifier;
+      (int index, BoardCellEquatable cell) {
+        final isEditing = index == 0 ? rowNotifier.isEditing.value : false;
+        final cellNotifier = EditableCellNotifier(isEditing: isEditing);
+
         if (index == 0) {
         if (index == 0) {
           // Only use the first cell to receive user's input when click the edit
           // Only use the first cell to receive user's input when click the edit
           // button
           // button
-          cellNotifier = EditableCellNotifier(
-            isEditing: rowNotifier.isEditing.value,
-          );
-          rowNotifier.insertCell(cellId, cellNotifier);
-        } else {
-          cellNotifier = EditableCellNotifier();
+          rowNotifier.bindCell(cell.identifier, cellNotifier);
         }
         }
 
 
-        Widget child = widget.cellBuilder.buildCell(
-          widget.groupId,
-          cellId,
-          cellNotifier,
-        );
-
-        child = Padding(
-          key: cellId.key(),
+        final child = Padding(
+          key: cell.identifier.key(),
           padding: const EdgeInsets.only(left: 4, right: 4),
           padding: const EdgeInsets.only(left: 4, right: 4),
-          child: child,
+          child: cellBuilder.buildCell(
+            groupId,
+            cell.identifier,
+            cellNotifier,
+          ),
         );
         );
 
 
         children.add(child);
         children.add(child);
@@ -134,17 +199,10 @@ class _BoardCardState extends State<BoardCard> {
     );
     );
     return children;
     return children;
   }
   }
-
-  @override
-  Future<void> dispose() async {
-    rowNotifier.dispose();
-    _cardBloc.close();
-    super.dispose();
-  }
 }
 }
 
 
 class _CardMoreOption extends StatelessWidget with CardAccessory {
 class _CardMoreOption extends StatelessWidget with CardAccessory {
-  const _CardMoreOption({Key? key}) : super(key: key);
+  _CardMoreOption({Key? key}) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
@@ -156,17 +214,13 @@ class _CardMoreOption extends StatelessWidget with CardAccessory {
   }
   }
 
 
   @override
   @override
-  void onTap(BuildContext context) {
-    GridRowActionSheet(
-      rowData: context.read<BoardCardBloc>().rowInfo(),
-    ).show(context, direction: AnchorDirection.bottomWithCenterAligned);
-  }
+  AccessoryType get type => AccessoryType.more;
 }
 }
 
 
 class _CardEditOption extends StatelessWidget with CardAccessory {
 class _CardEditOption extends StatelessWidget with CardAccessory {
-  final VoidCallback startEditing;
+  final EditableRowNotifier rowNotifier;
   const _CardEditOption({
   const _CardEditOption({
-    required this.startEditing,
+    required this.rowNotifier,
     Key? key,
     Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
@@ -182,7 +236,8 @@ class _CardEditOption extends StatelessWidget with CardAccessory {
   }
   }
 
 
   @override
   @override
-  void onTap(BuildContext context) {
-    startEditing();
-  }
+  void onTap(BuildContext context) => rowNotifier.becomeFirstResponder();
+
+  @override
+  AccessoryType get type => AccessoryType.edit;
 }
 }

+ 74 - 0
frontend/app_flowy/lib/plugins/board/presentation/card/container/accessory.dart

@@ -0,0 +1,74 @@
+import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/style_widget/hover.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+enum AccessoryType {
+  edit,
+  more,
+}
+
+abstract class CardAccessory implements Widget {
+  AccessoryType get type;
+  void onTap(BuildContext context) {}
+}
+
+typedef CardAccessoryBuilder = List<CardAccessory> Function(
+  BuildContext buildContext,
+);
+
+class CardAccessoryContainer extends StatelessWidget {
+  final void Function(AccessoryType) onTapAccessory;
+  final List<CardAccessory> accessories;
+  const CardAccessoryContainer({
+    required this.accessories,
+    required this.onTapAccessory,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.read<AppTheme>();
+    final children = accessories.map((accessory) {
+      return GestureDetector(
+        behavior: HitTestBehavior.opaque,
+        onTap: () {
+          accessory.onTap(context);
+          onTapAccessory(accessory.type);
+        },
+        child: _wrapHover(theme, accessory),
+      );
+    }).toList();
+    return _wrapDecoration(context, Row(children: children));
+  }
+
+  FlowyHover _wrapHover(AppTheme theme, CardAccessory accessory) {
+    return FlowyHover(
+      style: HoverStyle(
+        hoverColor: theme.hover,
+        backgroundColor: theme.surface,
+        borderRadius: BorderRadius.zero,
+      ),
+      builder: (_, onHover) => SizedBox(
+        width: 24,
+        height: 24,
+        child: accessory,
+      ),
+    );
+  }
+
+  Widget _wrapDecoration(BuildContext context, Widget child) {
+    final theme = context.read<AppTheme>();
+    final borderSide = BorderSide(color: theme.shader6, width: 1.0);
+    final decoration = BoxDecoration(
+      color: Colors.transparent,
+      border: Border.fromBorderSide(borderSide),
+      borderRadius: const BorderRadius.all(Radius.circular(4)),
+    );
+    return Container(
+      clipBehavior: Clip.hardEdge,
+      decoration: decoration,
+      child: child,
+    );
+  }
+}

+ 21 - 74
frontend/app_flowy/lib/plugins/board/presentation/card/card_container.dart → frontend/app_flowy/lib/plugins/board/presentation/card/container/card_container.dart

@@ -1,17 +1,19 @@
-import 'package:flowy_infra/theme.dart';
-import 'package:flowy_infra_ui/style_widget/hover.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
 import 'package:provider/provider.dart';
 import 'package:styled_widget/styled_widget.dart';
 import 'package:styled_widget/styled_widget.dart';
 
 
+import 'accessory.dart';
+
 class BoardCardContainer extends StatelessWidget {
 class BoardCardContainer extends StatelessWidget {
   final Widget child;
   final Widget child;
   final CardAccessoryBuilder? accessoryBuilder;
   final CardAccessoryBuilder? accessoryBuilder;
   final bool Function()? buildAccessoryWhen;
   final bool Function()? buildAccessoryWhen;
-  final void Function(BuildContext) onTap;
+  final void Function(BuildContext) openCard;
+  final void Function(AccessoryType) openAccessory;
   const BoardCardContainer({
   const BoardCardContainer({
     required this.child,
     required this.child,
-    required this.onTap,
+    required this.openCard,
+    required this.openAccessory,
     this.accessoryBuilder,
     this.accessoryBuilder,
     this.buildAccessoryWhen,
     this.buildAccessoryWhen,
     Key? key,
     Key? key,
@@ -34,13 +36,14 @@ class BoardCardContainer extends StatelessWidget {
             if (accessories.isNotEmpty) {
             if (accessories.isNotEmpty) {
               container = _CardEnterRegion(
               container = _CardEnterRegion(
                 accessories: accessories,
                 accessories: accessories,
+                onTapAccessory: openAccessory,
                 child: container,
                 child: container,
               );
               );
             }
             }
           }
           }
 
 
           return GestureDetector(
           return GestureDetector(
-            onTap: () => onTap(context),
+            onTap: () => openCard(context),
             child: Padding(
             child: Padding(
               padding: const EdgeInsets.all(8),
               padding: const EdgeInsets.all(8),
               child: ConstrainedBox(
               child: ConstrainedBox(
@@ -55,75 +58,16 @@ class BoardCardContainer extends StatelessWidget {
   }
   }
 }
 }
 
 
-abstract class CardAccessory implements Widget {
-  void onTap(BuildContext context);
-}
-
-typedef CardAccessoryBuilder = List<CardAccessory> Function(
-  BuildContext buildContext,
-);
-
-class CardAccessoryContainer extends StatelessWidget {
-  final List<CardAccessory> accessories;
-  const CardAccessoryContainer({required this.accessories, Key? key})
-      : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    final theme = context.read<AppTheme>();
-    final children = accessories.map((accessory) {
-      final hover = FlowyHover(
-        style: HoverStyle(
-          hoverColor: theme.hover,
-          backgroundColor: theme.surface,
-          borderRadius: BorderRadius.zero,
-        ),
-        builder: (_, onHover) => SizedBox(
-          width: 24,
-          height: 24,
-          child: accessory,
-        ),
-      );
-      return GestureDetector(
-        behavior: HitTestBehavior.opaque,
-        onTap: () => accessory.onTap(context),
-        child: hover,
-      );
-    }).toList();
-
-    return Container(
-      clipBehavior: Clip.hardEdge,
-      decoration: _makeBoxDecoration(context),
-      child: Row(children: children),
-    );
-  }
-}
-
-BoxDecoration _makeBoxDecoration(BuildContext context) {
-  final theme = context.read<AppTheme>();
-  final borderSide = BorderSide(color: theme.shader6, width: 1.0);
-  return BoxDecoration(
-    color: Colors.transparent,
-    border: Border.fromBorderSide(borderSide),
-    // boxShadow: const [
-    //   BoxShadow(
-    //     color: Colors.transparent,
-    //     spreadRadius: 0,
-    //     blurRadius: 5,
-    //     offset: Offset.zero,
-    //   )
-    // ],
-
-    borderRadius: const BorderRadius.all(Radius.circular(4)),
-  );
-}
-
 class _CardEnterRegion extends StatelessWidget {
 class _CardEnterRegion extends StatelessWidget {
   final Widget child;
   final Widget child;
   final List<CardAccessory> accessories;
   final List<CardAccessory> accessories;
-  const _CardEnterRegion(
-      {required this.child, required this.accessories, Key? key})
-      : super(key: key);
+  final void Function(AccessoryType) onTapAccessory;
+  const _CardEnterRegion({
+    required this.child,
+    required this.accessories,
+    required this.onTapAccessory,
+    Key? key,
+  }) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
@@ -132,9 +76,12 @@ class _CardEnterRegion extends StatelessWidget {
       builder: (context, onEnter, _) {
       builder: (context, onEnter, _) {
         List<Widget> children = [child];
         List<Widget> children = [child];
         if (onEnter) {
         if (onEnter) {
-          children.add(CardAccessoryContainer(
-            accessories: accessories,
-          ).positioned(right: 0));
+          children.add(
+            CardAccessoryContainer(
+              accessories: accessories,
+              onTapAccessory: onTapAccessory,
+            ).positioned(right: 0),
+          );
         }
         }
 
 
         return MouseRegion(
         return MouseRegion(

+ 1 - 2
frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_setting.dart

@@ -4,7 +4,7 @@ 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/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_group.dart';
 import 'package:app_flowy/plugins/grid/presentation/widgets/toolbar/grid_property.dart';
 import 'package:app_flowy/plugins/grid/presentation/widgets/toolbar/grid_property.dart';
-import 'package:appflowy_popover/popover.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/theme.dart';
@@ -50,7 +50,6 @@ class BoardSettingList extends StatelessWidget {
             previous.selectedAction != current.selectedAction,
             previous.selectedAction != current.selectedAction,
         listener: (context, state) {
         listener: (context, state) {
           state.selectedAction.foldLeft(null, (_, action) {
           state.selectedAction.foldLeft(null, (_, action) {
-            // FlowyOverlay.of(context).remove(identifier());
             onAction(action, settingContext);
             onAction(action, settingContext);
           });
           });
         },
         },

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

@@ -1,5 +1,5 @@
 import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
 import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
-import 'package:appflowy_popover/popover.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@@ -62,10 +62,10 @@ class _SettingButtonState extends State<_SettingButton> {
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final theme = context.read<AppTheme>();
     final theme = context.read<AppTheme>();
-    return AppFlowyStylePopover(
+    return AppFlowyPopover(
       controller: popoverController,
       controller: popoverController,
       constraints: BoxConstraints.loose(const Size(260, 400)),
       constraints: BoxConstraints.loose(const Size(260, 400)),
-      triggerActions: PopoverTriggerActionFlags.click,
+      triggerActions: PopoverTriggerFlags.click,
       child: FlowyIconButton(
       child: FlowyIconButton(
         hoverColor: theme.hover,
         hoverColor: theme.hover,
         width: 22,
         width: 22,

+ 4 - 2
frontend/app_flowy/lib/plugins/grid/application/field/field_editor_bloc.dart

@@ -33,8 +33,10 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
             await dataController.loadTypeOptionData();
             await dataController.loadTypeOptionData();
           },
           },
           updateName: (name) {
           updateName: (name) {
-            dataController.fieldName = name;
-            emit(state.copyWith(name: name));
+            if (state.name != name) {
+              dataController.fieldName = name;
+              emit(state.copyWith(name: name));
+            }
           },
           },
           didReceiveFieldChanged: (FieldPB field) {
           didReceiveFieldChanged: (FieldPB field) {
             emit(state.copyWith(
             emit(state.copyWith(

+ 7 - 4
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_cell.dart

@@ -4,7 +4,7 @@ import 'package:flutter/widgets.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/plugins/grid/application/prelude.dart';
 import 'package:app_flowy/plugins/grid/application/prelude.dart';
-import 'package:appflowy_popover/popover.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 
 
 import '../cell_builder.dart';
 import '../cell_builder.dart';
 import 'date_editor.dart';
 import 'date_editor.dart';
@@ -58,12 +58,12 @@ class _DateCellState extends GridCellState<GridDateCell> {
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final alignment = widget.cellStyle != null
     final alignment = widget.cellStyle != null
         ? widget.cellStyle!.alignment
         ? widget.cellStyle!.alignment
-        : Alignment.center;
+        : Alignment.centerLeft;
     return BlocProvider.value(
     return BlocProvider.value(
       value: _cellBloc,
       value: _cellBloc,
       child: BlocBuilder<DateCellBloc, DateCellState>(
       child: BlocBuilder<DateCellBloc, DateCellState>(
         builder: (context, state) {
         builder: (context, state) {
-          return AppFlowyStylePopover(
+          return AppFlowyPopover(
             controller: _popover,
             controller: _popover,
             offset: const Offset(0, 20),
             offset: const Offset(0, 20),
             direction: PopoverDirection.bottomWithLeftAligned,
             direction: PopoverDirection.bottomWithLeftAligned,
@@ -77,7 +77,10 @@ class _DateCellState extends GridCellState<GridDateCell> {
                   cursor: SystemMouseCursors.click,
                   cursor: SystemMouseCursors.click,
                   child: Align(
                   child: Align(
                     alignment: alignment,
                     alignment: alignment,
-                    child: FlowyText.medium(state.dateStr, fontSize: 12),
+                    child: FlowyText.medium(
+                      state.dateStr,
+                      fontSize: 12,
+                    ),
                   ),
                   ),
                 ),
                 ),
               ),
               ),

+ 17 - 24
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_editor.dart

@@ -1,7 +1,7 @@
 import 'package:app_flowy/generated/locale_keys.g.dart';
 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/cell/date_cal_bloc.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_context.dart';
-import 'package:appflowy_popover/popover.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/theme.dart';
@@ -299,9 +299,8 @@ class _DateTypeOptionButton extends StatelessWidget {
     return BlocSelector<DateCalBloc, DateCalState, DateTypeOptionPB>(
     return BlocSelector<DateCalBloc, DateCalState, DateTypeOptionPB>(
       selector: (state) => state.dateTypeOptionPB,
       selector: (state) => state.dateTypeOptionPB,
       builder: (context, dateTypeOptionPB) {
       builder: (context, dateTypeOptionPB) {
-        return AppFlowyStylePopover(
-          triggerActions:
-              PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
+        return AppFlowyPopover(
+          triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
           offset: const Offset(20, 0),
           offset: const Offset(20, 0),
           constraints: BoxConstraints.loose(const Size(140, 100)),
           constraints: BoxConstraints.loose(const Size(140, 100)),
           child: FlowyButton(
           child: FlowyButton(
@@ -340,36 +339,30 @@ class _CalDateTimeSettingState extends State<_CalDateTimeSetting> {
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     List<Widget> children = [
     List<Widget> children = [
-      Popover(
+      AppFlowyPopover(
         mutex: _popoverMutex,
         mutex: _popoverMutex,
-        triggerActions:
-            PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
+        asBarrier: true,
+        triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
         offset: const Offset(20, 0),
         offset: const Offset(20, 0),
         popupBuilder: (BuildContext context) {
         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)),
-            ),
+          return DateFormatList(
+            selectedFormat: widget.dateTypeOptionPB.dateFormat,
+            onSelected: (format) =>
+                widget.onEvent(DateCalEvent.setDateFormat(format)),
           );
           );
         },
         },
         child: const DateFormatButton(),
         child: const DateFormatButton(),
       ),
       ),
-      Popover(
+      AppFlowyPopover(
         mutex: _popoverMutex,
         mutex: _popoverMutex,
-        triggerActions:
-            PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
+        asBarrier: true,
+        triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
         offset: const Offset(20, 0),
         offset: const Offset(20, 0),
         popupBuilder: (BuildContext context) {
         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)),
-            ),
+          return TimeFormatList(
+            selectedFormat: widget.dateTypeOptionPB.timeFormat,
+            onSelected: (format) =>
+                widget.onEvent(DateCalEvent.setTimeFormat(format)),
           );
           );
         },
         },
         child: TimeFormatButton(timeFormat: widget.dateTypeOptionPB.timeFormat),
         child: TimeFormatButton(timeFormat: widget.dateTypeOptionPB.timeFormat),

+ 54 - 56
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_cell.dart

@@ -1,6 +1,6 @@
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/plugins/grid/application/prelude.dart';
 import 'package:app_flowy/plugins/grid/application/prelude.dart';
-import 'package:appflowy_popover/popover.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 
 
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@@ -164,67 +164,65 @@ class _SelectOptionWrapState extends State<SelectOptionWrap> {
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     final theme = context.watch<AppTheme>();
-    final Widget child;
-    if (widget.selectOptions.isEmpty && widget.cellStyle != null) {
-      child = Align(
-        alignment: Alignment.centerLeft,
-        child: FlowyText.medium(
-          widget.cellStyle!.placeholder,
-          fontSize: 14,
-          color: theme.shader3,
-        ),
-      );
-    } else {
-      child = Align(
-        alignment: Alignment.centerLeft,
-        child: Wrap(
-          spacing: 4,
-          runSpacing: 2,
-          children: widget.selectOptions
-              .map((option) => SelectOptionTag.fromOption(
-                    context: context,
-                    option: option,
-                  ))
-              .toList(),
-        ),
-      );
-    }
+    Widget child = _buildOptions(theme, context);
 
 
     return Stack(
     return Stack(
       alignment: AlignmentDirectional.center,
       alignment: AlignmentDirectional.center,
       fit: StackFit.expand,
       fit: StackFit.expand,
       children: [
       children: [
-        AppFlowyStylePopover(
-          controller: _popover,
-          constraints: BoxConstraints.loose(
-              Size(SelectOptionCellEditor.editorPanelWidth, 300)),
-          offset: const Offset(0, 20),
-          direction: PopoverDirection.bottomWithLeftAligned,
-          // triggerActions: PopoverTriggerActionFlags.c,
-          popupBuilder: (BuildContext context) {
-            WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
-              widget.onFocus?.call(true);
-            });
-            return 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: () {
-          _popover.show();
-        }),
+        _wrapPopover(child),
+        InkWell(onTap: () => _popover.show()),
       ],
       ],
     );
     );
   }
   }
+
+  Widget _wrapPopover(Widget child) {
+    final constraints = BoxConstraints.loose(Size(
+      SelectOptionCellEditor.editorPanelWidth,
+      300,
+    ));
+    return AppFlowyPopover(
+      controller: _popover,
+      constraints: constraints,
+      direction: PopoverDirection.bottomWithLeftAligned,
+      popupBuilder: (BuildContext context) {
+        WidgetsBinding.instance.addPostFrameCallback((_) {
+          widget.onFocus?.call(true);
+        });
+        return SelectOptionCellEditor(
+          cellController: widget.cellControllerBuilder.build()
+              as GridSelectOptionCellController,
+        );
+      },
+      onClose: () => widget.onFocus?.call(false),
+      child: child,
+    );
+  }
+
+  Widget _buildOptions(AppTheme theme, BuildContext context) {
+    final Widget child;
+    if (widget.selectOptions.isEmpty && widget.cellStyle != null) {
+      child = FlowyText.medium(
+        widget.cellStyle!.placeholder,
+        fontSize: 14,
+        color: theme.shader3,
+      );
+    } else {
+      final children = widget.selectOptions.map(
+        (option) {
+          return SelectOptionTag.fromOption(
+            context: context,
+            option: option,
+          );
+        },
+      ).toList();
+
+      child = Wrap(
+        spacing: 4,
+        runSpacing: 2,
+        children: children,
+      );
+    }
+    return Align(alignment: Alignment.centerLeft, child: child);
+  }
 }
 }

+ 53 - 61
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart

@@ -1,7 +1,7 @@
 import 'dart:collection';
 import 'dart:collection';
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
 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:app_flowy/plugins/grid/application/cell/select_option_editor_bloc.dart';
-import 'package:appflowy_popover/popover.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 
 
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/theme.dart';
@@ -25,83 +25,61 @@ import 'text_field.dart';
 
 
 const double _editorPanelWidth = 300;
 const double _editorPanelWidth = 300;
 
 
-class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
+class SelectOptionCellEditor extends StatefulWidget {
   final GridSelectOptionCellController cellController;
   final GridSelectOptionCellController cellController;
-  final VoidCallback? onDismissed;
-
   static double editorPanelWidth = 300;
   static double editorPanelWidth = 300;
 
 
-  const SelectOptionCellEditor({
-    required this.cellController,
-    this.onDismissed,
-    Key? key,
-  }) : super(key: key);
+  const SelectOptionCellEditor({required this.cellController, Key? key})
+      : super(key: key);
+
+  @override
+  State<SelectOptionCellEditor> createState() => _SelectOptionCellEditorState();
+}
+
+class _SelectOptionCellEditorState extends State<SelectOptionCellEditor> {
+  late PopoverMutex popoverMutex;
+
+  @override
+  void initState() {
+    popoverMutex = PopoverMutex();
+    super.initState();
+  }
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return BlocProvider(
     return BlocProvider(
       create: (context) => SelectOptionCellEditorBloc(
       create: (context) => SelectOptionCellEditorBloc(
-        cellController: cellController,
+        cellController: widget.cellController,
       )..add(const SelectOptionEditorEvent.initial()),
       )..add(const SelectOptionEditorEvent.initial()),
       child: BlocBuilder<SelectOptionCellEditorBloc, SelectOptionEditorState>(
       child: BlocBuilder<SelectOptionCellEditorBloc, SelectOptionEditorState>(
         builder: (context, state) {
         builder: (context, state) {
           return CustomScrollView(
           return CustomScrollView(
             shrinkWrap: true,
             shrinkWrap: true,
             slivers: [
             slivers: [
-              SliverToBoxAdapter(child: _TextField()),
+              SliverToBoxAdapter(
+                child: _TextField(popoverMutex: popoverMutex),
+              ),
               const SliverToBoxAdapter(child: VSpace(6)),
               const SliverToBoxAdapter(child: VSpace(6)),
               const SliverToBoxAdapter(child: TypeOptionSeparator()),
               const SliverToBoxAdapter(child: TypeOptionSeparator()),
               const SliverToBoxAdapter(child: VSpace(6)),
               const SliverToBoxAdapter(child: VSpace(6)),
               const SliverToBoxAdapter(child: _Title()),
               const SliverToBoxAdapter(child: _Title()),
-              const SliverToBoxAdapter(child: _OptionList()),
+              SliverToBoxAdapter(
+                child: _OptionList(popoverMutex: popoverMutex),
+              ),
             ],
             ],
           );
           );
         },
         },
       ),
       ),
     );
     );
   }
   }
-
-  static void show({
-    required BuildContext context,
-    required GridSelectOptionCellController cellController,
-    VoidCallback? onDismissed,
-  }) {
-    SelectOptionCellEditor.remove(context);
-    final editor = SelectOptionCellEditor(
-      cellController: cellController,
-      onDismissed: onDismissed,
-    );
-
-    //
-    FlowyOverlay.of(context).insertWithAnchor(
-      widget: OverlayContainer(
-        constraints: BoxConstraints.loose(const Size(_editorPanelWidth, 300)),
-        child: SizedBox(width: _editorPanelWidth, child: editor),
-      ),
-      identifier: SelectOptionCellEditor.identifier(),
-      anchorContext: context,
-      anchorDirection: AnchorDirection.bottomWithCenterAligned,
-      delegate: editor,
-    );
-  }
-
-  static void remove(BuildContext context) {
-    FlowyOverlay.of(context).remove(identifier());
-  }
-
-  static String identifier() {
-    return (SelectOptionCellEditor).toString();
-  }
-
-  @override
-  bool asBarrier() => true;
-
-  @override
-  void didRemove() => onDismissed?.call();
 }
 }
 
 
 class _OptionList extends StatelessWidget {
 class _OptionList extends StatelessWidget {
-  const _OptionList({Key? key}) : super(key: key);
+  final PopoverMutex popoverMutex;
+  const _OptionList({
+    required this.popoverMutex,
+    Key? key,
+  }) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
@@ -110,7 +88,10 @@ class _OptionList extends StatelessWidget {
         List<Widget> cells = [];
         List<Widget> cells = [];
         cells.addAll(state.options.map((option) {
         cells.addAll(state.options.map((option) {
           return _SelectOptionCell(
           return _SelectOptionCell(
-              option, state.selectedOptions.contains(option));
+            option: option,
+            isSelected: state.selectedOptions.contains(option),
+            popoverMutex: popoverMutex,
+          );
         }).toList());
         }).toList());
 
 
         state.createOption.fold(
         state.createOption.fold(
@@ -143,9 +124,13 @@ class _OptionList extends StatelessWidget {
 }
 }
 
 
 class _TextField extends StatelessWidget {
 class _TextField extends StatelessWidget {
+  final PopoverMutex popoverMutex;
   final TextfieldTagsController _tagController = TextfieldTagsController();
   final TextfieldTagsController _tagController = TextfieldTagsController();
 
 
-  _TextField({Key? key}) : super(key: key);
+  _TextField({
+    required this.popoverMutex,
+    Key? key,
+  }) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
@@ -163,8 +148,7 @@ class _TextField extends StatelessWidget {
             selectedOptionMap: optionMap,
             selectedOptionMap: optionMap,
             distanceToText: _editorPanelWidth * 0.7,
             distanceToText: _editorPanelWidth * 0.7,
             tagController: _tagController,
             tagController: _tagController,
-            onClick: () => FlowyOverlay.of(context)
-                .remove(SelectOptionTypeOptionEditor.identifier),
+            onClick: () => popoverMutex.close(),
             newText: (text) {
             newText: (text) {
               context
               context
                   .read<SelectOptionCellEditorBloc>()
                   .read<SelectOptionCellEditorBloc>()
@@ -193,7 +177,7 @@ class _Title extends StatelessWidget {
       child: Padding(
       child: Padding(
         padding: const EdgeInsets.symmetric(horizontal: 6),
         padding: const EdgeInsets.symmetric(horizontal: 6),
         child: FlowyText.medium(
         child: FlowyText.medium(
-          LocaleKeys.grid_selectOption_pannelTitle.tr(),
+          LocaleKeys.grid_selectOption_panelTitle.tr(),
           fontSize: 12,
           fontSize: 12,
           color: theme.shader3,
           color: theme.shader3,
         ),
         ),
@@ -231,9 +215,14 @@ class _CreateOptionCell extends StatelessWidget {
 
 
 class _SelectOptionCell extends StatefulWidget {
 class _SelectOptionCell extends StatefulWidget {
   final SelectOptionPB option;
   final SelectOptionPB option;
+  final PopoverMutex popoverMutex;
   final bool isSelected;
   final bool isSelected;
-  const _SelectOptionCell(this.option, this.isSelected, {Key? key})
-      : super(key: key);
+  const _SelectOptionCell({
+    required this.option,
+    required this.isSelected,
+    required this.popoverMutex,
+    Key? key,
+  }) : super(key: key);
 
 
   @override
   @override
   State<_SelectOptionCell> createState() => _SelectOptionCellState();
   State<_SelectOptionCell> createState() => _SelectOptionCellState();
@@ -251,10 +240,12 @@ class _SelectOptionCellState extends State<_SelectOptionCell> {
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     final theme = context.watch<AppTheme>();
-    return AppFlowyStylePopover(
+    return AppFlowyPopover(
       controller: _popoverController,
       controller: _popoverController,
       offset: const Offset(20, 0),
       offset: const Offset(20, 0),
+      asBarrier: true,
       constraints: BoxConstraints.loose(const Size(200, 300)),
       constraints: BoxConstraints.loose(const Size(200, 300)),
+      mutex: widget.popoverMutex,
       child: SizedBox(
       child: SizedBox(
         height: GridSize.typeOptionItemHeight,
         height: GridSize.typeOptionItemHeight,
         child: Row(
         child: Row(
@@ -299,8 +290,9 @@ class _SelectOptionCellState extends State<_SelectOptionCell> {
                 .read<SelectOptionCellEditorBloc>()
                 .read<SelectOptionCellEditorBloc>()
                 .add(SelectOptionEditorEvent.updateOption(updatedOption));
                 .add(SelectOptionEditorEvent.updateOption(updatedOption));
           },
           },
-          key: ValueKey(widget.option
-              .id), // Use ValueKey to refresh the UI, otherwise, it will remain the old value.
+          key: ValueKey(
+            widget.option.id,
+          ), // Use ValueKey to refresh the UI, otherwise, it will remain the old value.
         );
         );
       },
       },
     );
     );

+ 41 - 21
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/text_field.dart

@@ -11,20 +11,17 @@ import 'package:textfield_tags/textfield_tags.dart';
 
 
 import 'extension.dart';
 import 'extension.dart';
 
 
-class SelectOptionTextField extends StatelessWidget {
-  final FocusNode _focusNode;
-  final TextEditingController _controller;
+class SelectOptionTextField extends StatefulWidget {
   final TextfieldTagsController tagController;
   final TextfieldTagsController tagController;
   final List<SelectOptionPB> options;
   final List<SelectOptionPB> options;
   final LinkedHashMap<String, SelectOptionPB> selectedOptionMap;
   final LinkedHashMap<String, SelectOptionPB> selectedOptionMap;
-
   final double distanceToText;
   final double distanceToText;
 
 
   final Function(String) onNewTag;
   final Function(String) onNewTag;
   final Function(String) newText;
   final Function(String) newText;
   final VoidCallback? onClick;
   final VoidCallback? onClick;
 
 
-  SelectOptionTextField({
+  const SelectOptionTextField({
     required this.options,
     required this.options,
     required this.selectedOptionMap,
     required this.selectedOptionMap,
     required this.distanceToText,
     required this.distanceToText,
@@ -35,33 +32,55 @@ class SelectOptionTextField extends StatelessWidget {
     TextEditingController? textController,
     TextEditingController? textController,
     FocusNode? focusNode,
     FocusNode? focusNode,
     Key? key,
     Key? key,
-  })  : _controller = textController ?? TextEditingController(),
-        _focusNode = focusNode ?? FocusNode(),
-        super(key: key);
+  }) : super(key: key);
+
+  @override
+  State<SelectOptionTextField> createState() => _SelectOptionTextFieldState();
+}
+
+class _SelectOptionTextFieldState extends State<SelectOptionTextField> {
+  late FocusNode focusNode;
+  late TextEditingController controller;
+
+  @override
+  void initState() {
+    focusNode = FocusNode();
+    controller = TextEditingController();
+
+    WidgetsBinding.instance.addPostFrameCallback((_) {
+      focusNode.requestFocus();
+    });
+    super.initState();
+  }
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     final theme = context.watch<AppTheme>();
 
 
     return TextFieldTags(
     return TextFieldTags(
-      textEditingController: _controller,
-      textfieldTagsController: tagController,
-      initialTags: selectedOptionMap.keys.toList(),
-      focusNode: _focusNode,
+      textEditingController: controller,
+      textfieldTagsController: widget.tagController,
+      initialTags: widget.selectedOptionMap.keys.toList(),
+      focusNode: focusNode,
       textSeparators: const [','],
       textSeparators: const [','],
-      inputfieldBuilder: (BuildContext context, editController, focusNode,
-          error, onChanged, onSubmitted) {
+      inputfieldBuilder: (
+        BuildContext context,
+        editController,
+        focusNode,
+        error,
+        onChanged,
+        onSubmitted,
+      ) {
         return ((context, sc, tags, onTagDelegate) {
         return ((context, sc, tags, onTagDelegate) {
           return TextField(
           return TextField(
-            autofocus: true,
             controller: editController,
             controller: editController,
             focusNode: focusNode,
             focusNode: focusNode,
-            onTap: onClick,
+            onTap: widget.onClick,
             onChanged: (text) {
             onChanged: (text) {
               if (onChanged != null) {
               if (onChanged != null) {
                 onChanged(text);
                 onChanged(text);
               }
               }
-              newText(text);
+              widget.newText(text);
             },
             },
             onSubmitted: (text) {
             onSubmitted: (text) {
               if (onSubmitted != null) {
               if (onSubmitted != null) {
@@ -69,7 +88,7 @@ class SelectOptionTextField extends StatelessWidget {
               }
               }
 
 
               if (text.isNotEmpty) {
               if (text.isNotEmpty) {
-                onNewTag(text);
+                widget.onNewTag(text);
                 focusNode.requestFocus();
                 focusNode.requestFocus();
               }
               }
             },
             },
@@ -83,7 +102,8 @@ class SelectOptionTextField extends StatelessWidget {
               isDense: true,
               isDense: true,
               prefixIcon: _renderTags(context, sc),
               prefixIcon: _renderTags(context, sc),
               hintText: LocaleKeys.grid_selectOption_searchOption.tr(),
               hintText: LocaleKeys.grid_selectOption_searchOption.tr(),
-              prefixIconConstraints: BoxConstraints(maxWidth: distanceToText),
+              prefixIconConstraints:
+                  BoxConstraints(maxWidth: widget.distanceToText),
               focusedBorder: OutlineInputBorder(
               focusedBorder: OutlineInputBorder(
                 borderSide: BorderSide(color: theme.main1, width: 1.0),
                 borderSide: BorderSide(color: theme.main1, width: 1.0),
                 borderRadius: Corners.s10Border,
                 borderRadius: Corners.s10Border,
@@ -96,11 +116,11 @@ class SelectOptionTextField extends StatelessWidget {
   }
   }
 
 
   Widget? _renderTags(BuildContext context, ScrollController sc) {
   Widget? _renderTags(BuildContext context, ScrollController sc) {
-    if (selectedOptionMap.isEmpty) {
+    if (widget.selectedOptionMap.isEmpty) {
       return null;
       return null;
     }
     }
 
 
-    final children = selectedOptionMap.values
+    final children = widget.selectedOptionMap.values
         .map((option) =>
         .map((option) =>
             SelectOptionTag.fromOption(context: context, option: option))
             SelectOptionTag.fromOption(context: context, option: option))
         .toList();
         .toList();

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

@@ -2,7 +2,7 @@ import 'dart:async';
 import 'package:app_flowy/generated/locale_keys.g.dart';
 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/plugins/grid/application/cell/url_cell_bloc.dart';
 import 'package:app_flowy/workspace/presentation/home/toast.dart';
 import 'package:app_flowy/workspace/presentation/home/toast.dart';
-import 'package:appflowy_popover/popover.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/theme.dart';
@@ -130,7 +130,7 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
             ),
             ),
           );
           );
 
 
-          return AppFlowyStylePopover(
+          return AppFlowyPopover(
             controller: _popoverController,
             controller: _popoverController,
             constraints: BoxConstraints.loose(const Size(300, 160)),
             constraints: BoxConstraints.loose(const Size(300, 160)),
             direction: PopoverDirection.bottomWithLeftAligned,
             direction: PopoverDirection.bottomWithLeftAligned,
@@ -216,11 +216,11 @@ class _EditURLAccessoryState extends State<_EditURLAccessory>
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     final theme = context.watch<AppTheme>();
-    return AppFlowyStylePopover(
+    return AppFlowyPopover(
       constraints: BoxConstraints.loose(const Size(300, 160)),
       constraints: BoxConstraints.loose(const Size(300, 160)),
       controller: _popoverController,
       controller: _popoverController,
       direction: PopoverDirection.bottomWithLeftAligned,
       direction: PopoverDirection.bottomWithLeftAligned,
-      triggerActions: PopoverTriggerActionFlags.click,
+      triggerActions: PopoverTriggerFlags.click,
       offset: const Offset(0, 20),
       offset: const Offset(0, 20),
       child: svgWidget("editor/edit", color: theme.iconColor),
       child: svgWidget("editor/edit", color: theme.iconColor),
       popupBuilder: (BuildContext popoverContext) {
       popupBuilder: (BuildContext popoverContext) {

+ 12 - 4
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_cell.dart

@@ -1,8 +1,9 @@
 import 'package:app_flowy/plugins/grid/application/field/field_cell_bloc.dart';
 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/field_service.dart';
-import 'package:appflowy_popover/popover.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/hover.dart';
 import 'package:flowy_infra_ui/style_widget/hover.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
@@ -29,9 +30,10 @@ class GridFieldCell extends StatelessWidget {
       },
       },
       child: BlocBuilder<FieldCellBloc, FieldCellState>(
       child: BlocBuilder<FieldCellBloc, FieldCellState>(
         builder: (context, state) {
         builder: (context, state) {
-          final button = Popover(
+          final button = AppFlowyPopover(
+            constraints: BoxConstraints.loose(const Size(240, 840)),
             direction: PopoverDirection.bottomWithLeftAligned,
             direction: PopoverDirection.bottomWithLeftAligned,
-            triggerActions: PopoverTriggerActionFlags.click,
+            triggerActions: PopoverTriggerFlags.click,
             offset: const Offset(0, 10),
             offset: const Offset(0, 10),
             popupBuilder: (BuildContext context) {
             popupBuilder: (BuildContext context) {
               return GridFieldCellActionSheet(
               return GridFieldCellActionSheet(
@@ -135,9 +137,11 @@ class _DragToExpandLine extends StatelessWidget {
 class FieldCellButton extends StatelessWidget {
 class FieldCellButton extends StatelessWidget {
   final VoidCallback onTap;
   final VoidCallback onTap;
   final FieldPB field;
   final FieldPB field;
+  final int? maxLines;
   const FieldCellButton({
   const FieldCellButton({
     required this.field,
     required this.field,
     required this.onTap,
     required this.onTap,
+    this.maxLines = 1,
     Key? key,
     Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
@@ -148,7 +152,11 @@ class FieldCellButton extends StatelessWidget {
       hoverColor: theme.shader6,
       hoverColor: theme.shader6,
       onTap: onTap,
       onTap: onTap,
       leftIcon: svgWidget(field.fieldType.iconName(), color: theme.iconColor),
       leftIcon: svgWidget(field.fieldType.iconName(), color: theme.iconColor),
-      text: FlowyText.medium(field.name, fontSize: 12),
+      text: FlowyText.medium(
+        field.name,
+        fontSize: 12,
+        maxLines: maxLines,
+      ),
       margin: GridSize.cellContentInsets,
       margin: GridSize.cellContentInsets,
     );
     );
   }
   }

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

@@ -3,9 +3,9 @@ import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_editor.
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/plugins/grid/application/prelude.dart';
 import 'package:app_flowy/plugins/grid/application/prelude.dart';
 import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
 import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/theme.dart';
-import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
@@ -32,38 +32,32 @@ class _GridFieldCellActionSheetState extends State<GridFieldCellActionSheet> {
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     if (_showFieldEditor) {
     if (_showFieldEditor) {
       final field = widget.cellContext.field;
       final field = widget.cellContext.field;
-      return OverlayContainer(
-        constraints: BoxConstraints.loose(const Size(240, 200)),
-        child: FieldEditor(
+      return FieldEditor(
+        gridId: widget.cellContext.gridId,
+        fieldName: field.name,
+        typeOptionLoader: FieldTypeOptionLoader(
           gridId: widget.cellContext.gridId,
           gridId: widget.cellContext.gridId,
-          fieldName: field.name,
-          typeOptionLoader: FieldTypeOptionLoader(
-            gridId: widget.cellContext.gridId,
-            field: field,
-          ),
+          field: field,
         ),
         ),
       );
       );
     }
     }
     return BlocProvider(
     return BlocProvider(
       create: (context) =>
       create: (context) =>
           getIt<FieldActionSheetBloc>(param1: widget.cellContext),
           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, () {}),
-            ],
-          ),
+      child: SingleChildScrollView(
+        child: Column(
+          children: [
+            _EditFieldButton(
+              cellContext: widget.cellContext,
+              onTap: () {
+                setState(() {
+                  _showFieldEditor = true;
+                });
+              },
+            ),
+            const VSpace(6),
+            _FieldOperationList(widget.cellContext, () {}),
+          ],
         ),
         ),
       ),
       ),
     );
     );
@@ -159,8 +153,11 @@ class FieldActionCell extends StatelessWidget {
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     final theme = context.watch<AppTheme>();
     return FlowyButton(
     return FlowyButton(
-      text: FlowyText.medium(action.title(),
-          fontSize: 12, color: enable ? null : theme.shader4),
+      text: FlowyText.medium(
+        action.title(),
+        fontSize: 12,
+        color: enable ? null : theme.shader4,
+      ),
       hoverColor: theme.hover,
       hoverColor: theme.hover,
       onTap: () {
       onTap: () {
         if (enable) {
         if (enable) {
@@ -168,8 +165,10 @@ class FieldActionCell extends StatelessWidget {
           onTap();
           onTap();
         }
         }
       },
       },
-      leftIcon: svgWidget(action.iconName(),
-          color: enable ? theme.iconColor : theme.disableIconColor),
+      leftIcon: svgWidget(
+        action.iconName(),
+        color: enable ? theme.iconColor : theme.disableIconColor,
+      ),
     );
     );
   }
   }
 }
 }
@@ -216,6 +215,7 @@ extension _FieldActionExtension on FieldAction {
             .add(const FieldActionSheetEvent.duplicateField());
             .add(const FieldActionSheetEvent.duplicateField());
         break;
         break;
       case FieldAction.delete:
       case FieldAction.delete:
+        PopoverContainer.of(context).close();
         NavigatorAlertDialog(
         NavigatorAlertDialog(
           title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
           title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
           confirm: () {
           confirm: () {

+ 121 - 61
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart

@@ -1,18 +1,17 @@
 import 'package:app_flowy/plugins/grid/application/field/field_editor_bloc.dart';
 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:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
-import 'package:appflowy_popover/popover.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
+import 'package:dartz/dartz.dart' show none;
 import 'package:easy_localization/easy_localization.dart';
 import 'package:easy_localization/easy_localization.dart';
-import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/theme.dart';
-import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
+import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/log.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'package:app_flowy/generated/locale_keys.g.dart';
-import 'field_name_input.dart';
 import 'field_type_option_editor.dart';
 import 'field_type_option_editor.dart';
 
 
 class FieldEditor extends StatefulWidget {
 class FieldEditor extends StatefulWidget {
@@ -44,6 +43,12 @@ class _FieldEditorState extends State<FieldEditor> {
     super.initState();
     super.initState();
   }
   }
 
 
+  @override
+  void dispose() {
+    popoverMutex.dispose();
+    super.dispose();
+  }
+
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return BlocProvider(
     return BlocProvider(
@@ -53,32 +58,42 @@ class _FieldEditorState extends State<FieldEditor> {
         isGroupField: widget.isGroupField,
         isGroupField: widget.isGroupField,
         loader: widget.typeOptionLoader,
         loader: widget.typeOptionLoader,
       )..add(const FieldEditorEvent.initial()),
       )..add(const FieldEditorEvent.initial()),
-      child: BlocBuilder<FieldEditorBloc, FieldEditorState>(
+      child: ListView(
+        shrinkWrap: true,
+        children: [
+          FlowyText.medium(
+            LocaleKeys.grid_field_editProperty.tr(),
+            fontSize: 12,
+          ),
+          const VSpace(10),
+          _FieldNameTextField(popoverMutex: popoverMutex),
+          const VSpace(10),
+          ..._addDeleteFieldButton(),
+          _FieldTypeOptionCell(popoverMutex: popoverMutex),
+        ],
+      ),
+    );
+  }
+
+  List<Widget> _addDeleteFieldButton() {
+    if (widget.onDeleted == null) {
+      return [];
+    }
+    return [
+      BlocBuilder<FieldEditorBloc, FieldEditorState>(
         builder: (context, state) {
         builder: (context, state) {
-          return ListView(
-            shrinkWrap: true,
-            children: [
-              FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(),
-                  fontSize: 12),
-              const VSpace(10),
-              const _FieldNameCell(),
-              const VSpace(10),
-              _DeleteFieldButton(
-                popoverMutex: popoverMutex,
-                onDeleted: () {
-                  state.field.fold(
-                    () => Log.error('Can not delete the field'),
-                    (field) => widget.onDeleted?.call(field.id),
-                  );
-                },
-              ),
-              const VSpace(10),
-              _FieldTypeOptionCell(popoverMutex: popoverMutex),
-            ],
+          return _DeleteFieldButton(
+            popoverMutex: popoverMutex,
+            onDeleted: () {
+              state.field.fold(
+                () => Log.error('Can not delete the field'),
+                (field) => widget.onDeleted?.call(field.id),
+              );
+            },
           );
           );
         },
         },
       ),
       ),
-    );
+    ];
   }
   }
 }
 }
 
 
@@ -111,25 +126,90 @@ class _FieldTypeOptionCell extends StatelessWidget {
   }
   }
 }
 }
 
 
-class _FieldNameCell extends StatelessWidget {
-  const _FieldNameCell({Key? key}) : super(key: key);
+class _FieldNameTextField extends StatefulWidget {
+  final PopoverMutex popoverMutex;
+  const _FieldNameTextField({
+    required this.popoverMutex,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  State<_FieldNameTextField> createState() => _FieldNameTextFieldState();
+}
+
+class _FieldNameTextFieldState extends State<_FieldNameTextField> {
+  FocusNode focusNode = FocusNode();
+  VoidCallback? _popoverCallback;
+  late TextEditingController controller;
+
+  @override
+  void initState() {
+    controller = TextEditingController();
+    focusNode.addListener(() {
+      if (focusNode.hasFocus) {
+        widget.popoverMutex.close();
+      }
+    });
+
+    super.initState();
+  }
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    return BlocBuilder<FieldEditorBloc, FieldEditorState>(
-      builder: (context, state) {
-        return FieldNameTextField(
-          name: state.name,
-          errorText: context.read<FieldEditorBloc>().state.errorText,
-          onNameChanged: (newName) {
-            context
-                .read<FieldEditorBloc>()
-                .add(FieldEditorEvent.updateName(newName));
+    final theme = context.watch<AppTheme>();
+    return MultiBlocListener(
+      listeners: [
+        BlocListener<FieldEditorBloc, FieldEditorState>(
+          listenWhen: (p, c) => p.field == none(),
+          listener: (context, state) {
+            focusNode.requestFocus();
           },
           },
-        );
-      },
+        ),
+        BlocListener<FieldEditorBloc, FieldEditorState>(
+          listenWhen: (p, c) => controller.text != c.name,
+          listener: (context, state) {
+            controller.text = state.name;
+          },
+        ),
+      ],
+      child: BlocBuilder<FieldEditorBloc, FieldEditorState>(
+        buildWhen: (previous, current) =>
+            previous.errorText != current.errorText,
+        builder: (context, state) {
+          listenOnPopoverChanged(context);
+
+          return RoundedInputField(
+            height: 36,
+            focusNode: focusNode,
+            style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
+            controller: controller,
+            normalBorderColor: theme.shader4,
+            errorBorderColor: theme.red,
+            focusBorderColor: theme.main1,
+            cursorColor: theme.main1,
+            errorText: context.read<FieldEditorBloc>().state.errorText,
+            onChanged: (newName) {
+              context
+                  .read<FieldEditorBloc>()
+                  .add(FieldEditorEvent.updateName(newName));
+            },
+          );
+        },
+      ),
     );
     );
   }
   }
+
+  void listenOnPopoverChanged(BuildContext context) {
+    if (_popoverCallback != null) {
+      widget.popoverMutex.removePopoverListener(_popoverCallback!);
+    }
+    _popoverCallback = widget.popoverMutex.listenOnPopoverChanged(() {
+      if (focusNode.hasFocus) {
+        final node = FocusScope.of(context);
+        node.unfocus();
+      }
+    });
+  }
 }
 }
 
 
 class _DeleteFieldButton extends StatelessWidget {
 class _DeleteFieldButton extends StatelessWidget {
@@ -155,31 +235,11 @@ class _DeleteFieldButton extends StatelessWidget {
             fontSize: 12,
             fontSize: 12,
             color: enable ? null : theme.shader4,
             color: enable ? null : theme.shader4,
           ),
           ),
+          onTap: () => onDeleted?.call(),
         );
         );
-        if (enable) button = _wrapPopover(button);
+        // if (enable) button = button;
         return button;
         return button;
       },
       },
     );
     );
   }
   }
-
-  Widget _wrapPopover(Widget widget) {
-    return AppFlowyStylePopover(
-      triggerActions: PopoverTriggerActionFlags.click,
-      constraints: BoxConstraints.loose(const Size(400, 240)),
-      mutex: popoverMutex,
-      direction: PopoverDirection.center,
-      popupBuilder: (popupContext) {
-        return PopoverAlertView(
-          title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
-          cancel: () => popoverMutex.state?.close(),
-          confirm: () {
-            onDeleted?.call();
-            popoverMutex.state?.close();
-          },
-          popoverMutex: popoverMutex,
-        );
-      },
-      child: widget,
-    );
-  }
 }
 }

+ 0 - 56
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_name_input.dart

@@ -1,56 +0,0 @@
-import 'package:flowy_infra/theme.dart';
-import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-
-class FieldNameTextField extends StatefulWidget {
-  final void Function(String) onNameChanged;
-  final String name;
-  final String errorText;
-  const FieldNameTextField({
-    required this.name,
-    required this.errorText,
-    required this.onNameChanged,
-    Key? key,
-  }) : super(key: key);
-
-  @override
-  State<FieldNameTextField> createState() => _FieldNameTextFieldState();
-}
-
-class _FieldNameTextFieldState extends State<FieldNameTextField> {
-  late String name;
-  TextEditingController controller = TextEditingController();
-
-  @override
-  void initState() {
-    controller.text = widget.name;
-    super.initState();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
-    return RoundedInputField(
-      height: 36,
-      autoFocus: true,
-      style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
-      controller: controller,
-      normalBorderColor: theme.shader4,
-      errorBorderColor: theme.red,
-      focusBorderColor: theme.main1,
-      cursorColor: theme.main1,
-      errorText: widget.errorText,
-      onChanged: widget.onNameChanged,
-    );
-  }
-
-  @override
-  void didUpdateWidget(covariant FieldNameTextField oldWidget) {
-    controller.text = widget.name;
-    controller.selection = TextSelection.fromPosition(
-        TextPosition(offset: controller.text.length));
-
-    super.didUpdateWidget(oldWidget);
-  }
-}

+ 1 - 5
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_type_list.dart

@@ -1,4 +1,4 @@
-import 'package:appflowy_popover/popover.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@@ -47,10 +47,6 @@ class FieldTypeList extends StatelessWidget with FlowyOverlayDelegate {
       ),
       ),
     );
     );
   }
   }
-
-  static String identifier() {
-    return (FieldTypeList).toString();
-  }
 }
 }
 
 
 class FieldTypeCell extends StatelessWidget {
 class FieldTypeCell extends StatelessWidget {

+ 5 - 4
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_type_option_editor.dart

@@ -1,6 +1,6 @@
 import 'dart:typed_data';
 import 'dart:typed_data';
 import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_data_controller.dart';
 import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_data_controller.dart';
-import 'package:appflowy_popover/popover.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:dartz/dartz.dart' show Either;
 import 'package:dartz/dartz.dart' show Either;
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/theme.dart';
@@ -64,9 +64,10 @@ class FieldTypeOptionEditor extends StatelessWidget {
     final theme = context.watch<AppTheme>();
     final theme = context.watch<AppTheme>();
     return SizedBox(
     return SizedBox(
       height: GridSize.typeOptionItemHeight,
       height: GridSize.typeOptionItemHeight,
-      child: AppFlowyStylePopover(
-        constraints: BoxConstraints.loose(const Size(460, 440)),
-        triggerActions: PopoverTriggerActionFlags.click,
+      child: AppFlowyPopover(
+        constraints: BoxConstraints.loose(const Size(460, 540)),
+        asBarrier: true,
+        triggerActions: PopoverTriggerFlags.click | PopoverTriggerFlags.hover,
         mutex: popoverMutex,
         mutex: popoverMutex,
         offset: const Offset(20, 0),
         offset: const Offset(20, 0),
         popupBuilder: (context) {
         popupBuilder: (context) {

+ 5 - 4
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/grid_header.dart

@@ -4,7 +4,7 @@ import 'package:app_flowy/plugins/grid/application/field/type_option/type_option
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/plugins/grid/application/prelude.dart';
 import 'package:app_flowy/plugins/grid/application/prelude.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:easy_localization/easy_localization.dart';
-import 'package:appflowy_popover/popover.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@@ -176,10 +176,11 @@ class CreateFieldButton extends StatelessWidget {
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     final theme = context.watch<AppTheme>();
 
 
-    return AppFlowyStylePopover(
-      triggerActions: PopoverTriggerActionFlags.click,
+    return AppFlowyPopover(
+      triggerActions: PopoverTriggerFlags.click,
       direction: PopoverDirection.bottomWithRightAligned,
       direction: PopoverDirection.bottomWithRightAligned,
-      constraints: BoxConstraints.loose(const Size(240, 200)),
+      asBarrier: true,
+      constraints: BoxConstraints.loose(const Size(240, 600)),
       child: FlowyButton(
       child: FlowyButton(
         text: FlowyText.medium(
         text: FlowyText.medium(
           LocaleKeys.grid_field_newColumn.tr(),
           LocaleKeys.grid_field_newColumn.tr(),

+ 4 - 2
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/builder.dart

@@ -3,7 +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/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_context.dart';
 import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_data_controller.dart';
 import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_data_controller.dart';
-import 'package:appflowy_popover/popover.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pb.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/date_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/multi_select_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/multi_select_type_option.pb.dart';
@@ -50,7 +50,9 @@ Widget? makeTypeOptionWidget({
   required PopoverMutex popoverMutex,
   required PopoverMutex popoverMutex,
 }) {
 }) {
   final builder = makeTypeOptionWidgetBuilder(
   final builder = makeTypeOptionWidgetBuilder(
-      dataController: dataController, popoverMutex: popoverMutex);
+    dataController: dataController,
+    popoverMutex: popoverMutex,
+  );
   return builder.build(context);
   return builder.build(context);
 }
 }
 
 

+ 17 - 29
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/date.dart

@@ -11,7 +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:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:appflowy_popover/popover.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 import '../../../layout/sizes.dart';
 import '../../../layout/sizes.dart';
 import '../field_type_option_editor.dart';
 import '../field_type_option_editor.dart';
 import 'builder.dart';
 import 'builder.dart';
@@ -62,10 +62,10 @@ class DateTypeOptionWidget extends TypeOptionWidget {
   }
   }
 
 
   Widget _renderDateFormatButton(BuildContext context, DateFormat dataFormat) {
   Widget _renderDateFormatButton(BuildContext context, DateFormat dataFormat) {
-    return AppFlowyStylePopover(
+    return AppFlowyPopover(
       mutex: popoverMutex,
       mutex: popoverMutex,
-      triggerActions:
-          PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
+      asBarrier: true,
+      triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
       offset: const Offset(20, 0),
       offset: const Offset(20, 0),
       constraints: BoxConstraints.loose(const Size(460, 440)),
       constraints: BoxConstraints.loose(const Size(460, 440)),
       popupBuilder: (popoverContext) {
       popupBuilder: (popoverContext) {
@@ -75,7 +75,7 @@ class DateTypeOptionWidget extends TypeOptionWidget {
             context
             context
                 .read<DateTypeOptionBloc>()
                 .read<DateTypeOptionBloc>()
                 .add(DateTypeOptionEvent.didSelectDateFormat(format));
                 .add(DateTypeOptionEvent.didSelectDateFormat(format));
-            PopoverContainer.of(popoverContext).closeAll();
+            PopoverContainer.of(popoverContext).close();
           },
           },
         );
         );
       },
       },
@@ -84,10 +84,10 @@ class DateTypeOptionWidget extends TypeOptionWidget {
   }
   }
 
 
   Widget _renderTimeFormatButton(BuildContext context, TimeFormat timeFormat) {
   Widget _renderTimeFormatButton(BuildContext context, TimeFormat timeFormat) {
-    return AppFlowyStylePopover(
+    return AppFlowyPopover(
       mutex: popoverMutex,
       mutex: popoverMutex,
-      triggerActions:
-          PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
+      asBarrier: true,
+      triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
       offset: const Offset(20, 0),
       offset: const Offset(20, 0),
       constraints: BoxConstraints.loose(const Size(460, 440)),
       constraints: BoxConstraints.loose(const Size(460, 440)),
       popupBuilder: (BuildContext popoverContext) {
       popupBuilder: (BuildContext popoverContext) {
@@ -97,7 +97,7 @@ class DateTypeOptionWidget extends TypeOptionWidget {
             context
             context
                 .read<DateTypeOptionBloc>()
                 .read<DateTypeOptionBloc>()
                 .add(DateTypeOptionEvent.didSelectTimeFormat(format));
                 .add(DateTypeOptionEvent.didSelectTimeFormat(format));
-            PopoverContainer.of(popoverContext).closeAll();
+            PopoverContainer.of(popoverContext).close();
           },
           },
         );
         );
       },
       },
@@ -201,12 +201,10 @@ class DateFormatList extends StatelessWidget {
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final cells = DateFormat.values.map((format) {
     final cells = DateFormat.values.map((format) {
       return DateFormatCell(
       return DateFormatCell(
-          dateFormat: format,
-          onSelected: (format) {
-            onSelected(format);
-            FlowyOverlay.of(context).remove(DateFormatList.identifier());
-          },
-          isSelected: selectedFormat == format);
+        dateFormat: format,
+        onSelected: onSelected,
+        isSelected: selectedFormat == format,
+      );
     }).toList();
     }).toList();
 
 
     return SizedBox(
     return SizedBox(
@@ -224,10 +222,6 @@ class DateFormatList extends StatelessWidget {
       ),
       ),
     );
     );
   }
   }
-
-  static String identifier() {
-    return (DateFormatList).toString();
-  }
 }
 }
 
 
 class DateFormatCell extends StatelessWidget {
 class DateFormatCell extends StatelessWidget {
@@ -291,12 +285,10 @@ class TimeFormatList extends StatelessWidget {
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final cells = TimeFormat.values.map((format) {
     final cells = TimeFormat.values.map((format) {
       return TimeFormatCell(
       return TimeFormatCell(
-          isSelected: format == selectedFormat,
-          timeFormat: format,
-          onSelected: (format) {
-            onSelected(format);
-            FlowyOverlay.of(context).remove(TimeFormatList.identifier());
-          });
+        isSelected: format == selectedFormat,
+        timeFormat: format,
+        onSelected: onSelected,
+      );
     }).toList();
     }).toList();
 
 
     return SizedBox(
     return SizedBox(
@@ -314,10 +306,6 @@ class TimeFormatList extends StatelessWidget {
       ),
       ),
     );
     );
   }
   }
-
-  static String identifier() {
-    return (TimeFormatList).toString();
-  }
 }
 }
 
 
 class TimeFormatCell extends StatelessWidget {
 class TimeFormatCell extends StatelessWidget {

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

@@ -1,7 +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/multi_select_type_option.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_context.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
-import 'package:appflowy_popover/popover.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 
 
 import '../field_type_option_editor.dart';
 import '../field_type_option_editor.dart';
 import 'builder.dart';
 import 'builder.dart';

+ 5 - 11
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/number.dart

@@ -1,7 +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_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/number_format_bloc.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_context.dart';
-import 'package:appflowy_popover/popover.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@@ -55,10 +55,10 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
           listener: (context, state) =>
           listener: (context, state) =>
               typeOptionContext.typeOption = state.typeOption,
               typeOptionContext.typeOption = state.typeOption,
           builder: (context, state) {
           builder: (context, state) {
-            return AppFlowyStylePopover(
+            return AppFlowyPopover(
               mutex: popoverMutex,
               mutex: popoverMutex,
-              triggerActions: PopoverTriggerActionFlags.hover |
-                  PopoverTriggerActionFlags.click,
+              triggerActions:
+                  PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
               offset: const Offset(20, 0),
               offset: const Offset(20, 0),
               constraints: BoxConstraints.loose(const Size(460, 440)),
               constraints: BoxConstraints.loose(const Size(460, 440)),
               child: FlowyButton(
               child: FlowyButton(
@@ -82,7 +82,7 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
                     context
                     context
                         .read<NumberTypeOptionBloc>()
                         .read<NumberTypeOptionBloc>()
                         .add(NumberTypeOptionEvent.didSelectFormat(format));
                         .add(NumberTypeOptionEvent.didSelectFormat(format));
-                    PopoverContainer.of(popoverContext).closeAll();
+                    PopoverContainer.of(popoverContext).close();
                   },
                   },
                   selectedFormat: state.typeOption.format,
                   selectedFormat: state.typeOption.format,
                 );
                 );
@@ -123,8 +123,6 @@ class NumberFormatList extends StatelessWidget {
                       format: format,
                       format: format,
                       onSelected: (format) {
                       onSelected: (format) {
                         onSelected(format);
                         onSelected(format);
-                        FlowyOverlay.of(context)
-                            .remove(NumberFormatList.identifier());
                       });
                       });
                 }).toList();
                 }).toList();
 
 
@@ -147,10 +145,6 @@ class NumberFormatList extends StatelessWidget {
       ),
       ),
     );
     );
   }
   }
-
-  static String identifier() {
-    return (NumberFormatList).toString();
-  }
 }
 }
 
 
 class NumberFormatCell extends StatelessWidget {
 class NumberFormatCell extends StatelessWidget {

+ 5 - 4
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/select_option.dart

@@ -1,5 +1,5 @@
 import 'package:app_flowy/plugins/grid/application/field/type_option/select_option_type_option_bloc.dart';
 import 'package:app_flowy/plugins/grid/application/field/type_option/select_option_type_option_bloc.dart';
-import 'package:appflowy_popover/popover.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@@ -180,10 +180,11 @@ class _OptionCellState extends State<_OptionCell> {
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     final theme = context.watch<AppTheme>();
 
 
-    return AppFlowyStylePopover(
+    return AppFlowyPopover(
       controller: _popoverController,
       controller: _popoverController,
       mutex: widget.popoverMutex,
       mutex: widget.popoverMutex,
       offset: const Offset(20, 0),
       offset: const Offset(20, 0),
+      asBarrier: true,
       constraints: BoxConstraints.loose(const Size(460, 440)),
       constraints: BoxConstraints.loose(const Size(460, 440)),
       child: SizedBox(
       child: SizedBox(
         height: GridSize.typeOptionItemHeight,
         height: GridSize.typeOptionItemHeight,
@@ -207,13 +208,13 @@ class _OptionCellState extends State<_OptionCell> {
             context
             context
                 .read<SelectOptionTypeOptionBloc>()
                 .read<SelectOptionTypeOptionBloc>()
                 .add(SelectOptionTypeOptionEvent.deleteOption(widget.option));
                 .add(SelectOptionTypeOptionEvent.deleteOption(widget.option));
-            PopoverContainer.of(popoverContext).closeAll();
+            PopoverContainer.of(popoverContext).close();
           },
           },
           onUpdated: (updatedOption) {
           onUpdated: (updatedOption) {
             context
             context
                 .read<SelectOptionTypeOptionBloc>()
                 .read<SelectOptionTypeOptionBloc>()
                 .add(SelectOptionTypeOptionEvent.updateOption(updatedOption));
                 .add(SelectOptionTypeOptionEvent.updateOption(updatedOption));
-            PopoverContainer.of(popoverContext).closeAll();
+            PopoverContainer.of(popoverContext).close();
           },
           },
           key: ValueKey(widget.option.id),
           key: ValueKey(widget.option.id),
         );
         );

+ 1 - 1
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/select_option_editor.dart

@@ -139,7 +139,7 @@ class SelectOptionColorList extends StatelessWidget {
           child: SizedBox(
           child: SizedBox(
             height: GridSize.typeOptionItemHeight,
             height: GridSize.typeOptionItemHeight,
             child: FlowyText.medium(
             child: FlowyText.medium(
-              LocaleKeys.grid_selectOption_colorPannelTitle.tr(),
+              LocaleKeys.grid_selectOption_colorPanelTitle.tr(),
               fontSize: 12,
               fontSize: 12,
               textAlign: TextAlign.left,
               textAlign: TextAlign.left,
             ),
             ),

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

@@ -2,7 +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:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import '../field_type_option_editor.dart';
 import '../field_type_option_editor.dart';
-import 'package:appflowy_popover/popover.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 import 'builder.dart';
 import 'builder.dart';
 import 'select_option.dart';
 import 'select_option.dart';
 
 

+ 68 - 30
frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/grid_row.dart

@@ -1,8 +1,10 @@
 import 'package:app_flowy/plugins/grid/application/prelude.dart';
 import 'package:app_flowy/plugins/grid/application/prelude.dart';
 import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
 import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
 import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
 import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/style_widget/icon_button.dart';
 import 'package:flowy_infra_ui/style_widget/icon_button.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
@@ -56,20 +58,21 @@ class _GridRowWidgetState extends State<GridRowWidget> {
         child: BlocBuilder<RowBloc, RowState>(
         child: BlocBuilder<RowBloc, RowState>(
           buildWhen: (p, c) => p.rowInfo.rowPB.height != c.rowInfo.rowPB.height,
           buildWhen: (p, c) => p.rowInfo.rowPB.height != c.rowInfo.rowPB.height,
           builder: (context, state) {
           builder: (context, state) {
-            final children = [
-              const _RowLeading(),
-              Expanded(
-                child: RowContent(
-                  builder: widget.cellBuilder,
-                  onExpand: () => widget.openDetailPage(
-                    context,
-                    widget.cellBuilder,
-                  ),
+            final content = Expanded(
+              child: RowContent(
+                builder: widget.cellBuilder,
+                onExpand: () => widget.openDetailPage(
+                  context,
+                  widget.cellBuilder,
                 ),
                 ),
               ),
               ),
+            );
+
+            return Row(children: [
+              const _RowLeading(),
+              content,
               const _RowTrailing(),
               const _RowTrailing(),
-            ];
-            return Row(children: children);
+            ]);
           },
           },
         ),
         ),
       ),
       ),
@@ -83,26 +86,51 @@ class _GridRowWidgetState extends State<GridRowWidget> {
   }
   }
 }
 }
 
 
-class _RowLeading extends StatelessWidget {
+class _RowLeading extends StatefulWidget {
   const _RowLeading({Key? key}) : super(key: key);
   const _RowLeading({Key? key}) : super(key: key);
 
 
+  @override
+  State<_RowLeading> createState() => _RowLeadingState();
+}
+
+class _RowLeadingState extends State<_RowLeading> {
+  late PopoverController popoverController;
+
+  @override
+  void initState() {
+    popoverController = PopoverController();
+    super.initState();
+  }
+
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    return Consumer<RegionStateNotifier>(
-      builder: (context, state, _) {
-        return SizedBox(
-            width: GridSize.leadingHeaderPadding,
-            child: state.onEnter ? _activeWidget() : null);
+    return AppFlowyPopover(
+      controller: popoverController,
+      constraints: BoxConstraints.loose(const Size(140, 200)),
+      direction: PopoverDirection.rightWithCenterAligned,
+      popupBuilder: (BuildContext popoverContext) {
+        return GridRowActionSheet(
+            rowData: context.read<RowBloc>().state.rowInfo);
       },
       },
+      child: Consumer<RegionStateNotifier>(
+        builder: (context, state, _) {
+          return SizedBox(
+            width: GridSize.leadingHeaderPadding,
+            child: state.onEnter ? _activeWidget() : null,
+          );
+        },
+      ),
     );
     );
   }
   }
 
 
   Widget _activeWidget() {
   Widget _activeWidget() {
     return Row(
     return Row(
       mainAxisAlignment: MainAxisAlignment.center,
       mainAxisAlignment: MainAxisAlignment.center,
-      children: const [
-        _InsertRowButton(),
-        _DeleteRowButton(),
+      children: [
+        const _InsertButton(),
+        _MenuButton(openMenu: () {
+          popoverController.show();
+        }),
       ],
       ],
     );
     );
   }
   }
@@ -117,8 +145,8 @@ class _RowTrailing extends StatelessWidget {
   }
   }
 }
 }
 
 
-class _InsertRowButton extends StatelessWidget {
-  const _InsertRowButton({Key? key}) : super(key: key);
+class _InsertButton extends StatelessWidget {
+  const _InsertButton({Key? key}) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
@@ -128,17 +156,29 @@ class _InsertRowButton extends StatelessWidget {
       hoverColor: theme.hover,
       hoverColor: theme.hover,
       width: 20,
       width: 20,
       height: 30,
       height: 30,
-      onPressed: () => context.read<RowBloc>().add(
-            const RowEvent.createRow(),
-          ),
+      onPressed: () => context.read<RowBloc>().add(const RowEvent.createRow()),
       iconPadding: const EdgeInsets.all(3),
       iconPadding: const EdgeInsets.all(3),
       icon: svgWidget("home/add"),
       icon: svgWidget("home/add"),
     );
     );
   }
   }
 }
 }
 
 
-class _DeleteRowButton extends StatelessWidget {
-  const _DeleteRowButton({Key? key}) : super(key: key);
+class _MenuButton extends StatefulWidget {
+  final VoidCallback openMenu;
+  const _MenuButton({
+    required this.openMenu,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  State<_MenuButton> createState() => _MenuButtonState();
+}
+
+class _MenuButtonState extends State<_MenuButton> {
+  @override
+  void initState() {
+    super.initState();
+  }
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
@@ -148,9 +188,7 @@ class _DeleteRowButton extends StatelessWidget {
       hoverColor: theme.hover,
       hoverColor: theme.hover,
       width: 20,
       width: 20,
       height: 30,
       height: 30,
-      onPressed: () => GridRowActionSheet(
-        rowData: context.read<RowBloc>().state.rowInfo,
-      ).show(context),
+      onPressed: () => widget.openMenu(),
       iconPadding: const EdgeInsets.all(3),
       iconPadding: const EdgeInsets.all(3),
       icon: svgWidget("editor/details"),
       icon: svgWidget("editor/details"),
     );
     );

+ 2 - 33
frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_action_sheet.dart

@@ -3,7 +3,6 @@ import 'package:easy_localization/easy_localization.dart';
 import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/theme.dart';
-import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
@@ -27,10 +26,7 @@ class GridRowActionSheet extends StatelessWidget {
           final cells = _RowAction.values
           final cells = _RowAction.values
               .where((value) => value.enable())
               .where((value) => value.enable())
               .map(
               .map(
-                (action) => _RowActionCell(
-                  action: action,
-                  onDismissed: () => remove(context),
-                ),
+                (action) => _RowActionCell(action: action),
               )
               )
               .toList();
               .toList();
 
 
@@ -52,37 +48,11 @@ class GridRowActionSheet extends StatelessWidget {
       ),
       ),
     );
     );
   }
   }
-
-  void show(
-    BuildContext overlayContext, {
-    AnchorDirection direction = AnchorDirection.leftWithCenterAligned,
-  }) {
-    FlowyOverlay.of(overlayContext).insertWithAnchor(
-      widget: OverlayContainer(
-        constraints: BoxConstraints.loose(const Size(140, 200)),
-        child: this,
-      ),
-      identifier: GridRowActionSheet.identifier(),
-      anchorContext: overlayContext,
-      anchorDirection: direction,
-    );
-  }
-
-  void remove(BuildContext overlayContext) {
-    FlowyOverlay.of(overlayContext).remove(GridRowActionSheet.identifier());
-  }
-
-  static String identifier() {
-    return (GridRowActionSheet).toString();
-  }
 }
 }
 
 
 class _RowActionCell extends StatelessWidget {
 class _RowActionCell extends StatelessWidget {
   final _RowAction action;
   final _RowAction action;
-  final VoidCallback onDismissed;
-  const _RowActionCell(
-      {required this.action, required this.onDismissed, Key? key})
-      : super(key: key);
+  const _RowActionCell({required this.action, Key? key}) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
@@ -101,7 +71,6 @@ class _RowActionCell extends StatelessWidget {
           if (action.enable()) {
           if (action.enable()) {
             action.performAction(context);
             action.performAction(context);
           }
           }
-          onDismissed();
         },
         },
         leftIcon: svgWidget(action.iconName(), color: theme.iconColor),
         leftIcon: svgWidget(action.iconName(), color: theme.iconColor),
       ),
       ),

+ 97 - 78
frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart

@@ -2,6 +2,7 @@ import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_servic
 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_context.dart';
 import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
 import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
 import 'package:app_flowy/plugins/grid/application/row/row_detail_bloc.dart';
 import 'package:app_flowy/plugins/grid/application/row/row_detail_bloc.dart';
+import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@@ -15,7 +16,7 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:appflowy_popover/popover.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 
 
 import '../../layout/sizes.dart';
 import '../../layout/sizes.dart';
 import '../cell/cell_accessory.dart';
 import '../cell/cell_accessory.dart';
@@ -112,67 +113,61 @@ class _PropertyList extends StatelessWidget {
       builder: (context, state) {
       builder: (context, state) {
         return Column(
         return Column(
           children: [
           children: [
-            Expanded(
-              child: ScrollbarListStack(
-                axis: Axis.vertical,
-                controller: _scrollController,
-                barSize: GridSize.scrollBarSize,
-                autoHideScrollbar: false,
-                child: ListView.separated(
-                  controller: _scrollController,
-                  itemCount: state.gridCells.length,
-                  itemBuilder: (BuildContext context, int index) {
-                    return _RowDetailCell(
-                      cellId: state.gridCells[index],
-                      cellBuilder: cellBuilder,
-                    );
-                  },
-                  separatorBuilder: (BuildContext context, int index) {
-                    return const VSpace(2);
-                  },
-                ),
-              ),
-            ),
+            Expanded(child: _wrapScrollbar(buildList(state))),
             const VSpace(10),
             const VSpace(10),
             _CreateFieldButton(
             _CreateFieldButton(
               viewId: viewId,
               viewId: viewId,
-              onClosed: () {
-                WidgetsBinding.instance.addPostFrameCallback((_) {
-                  _scrollController.animateTo(
-                    _scrollController.position.maxScrollExtent,
-                    duration: const Duration(milliseconds: 250),
-                    curve: Curves.ease,
-                  );
-                });
-              },
-              onOpened: (controller) {
-                return FieldEditor(
-                  gridId: viewId,
-                  typeOptionLoader: NewFieldTypeOptionLoader(gridId: viewId),
-                  onDeleted: (fieldId) {
-                    controller.close();
-                    context
-                        .read<RowDetailBloc>()
-                        .add(RowDetailEvent.deleteField(fieldId));
-                  },
-                );
-              },
+              onClosed: _handleDidCreateField,
             ),
             ),
           ],
           ],
         );
         );
       },
       },
     );
     );
   }
   }
+
+  Widget buildList(RowDetailState state) {
+    return ListView.separated(
+      controller: _scrollController,
+      itemCount: state.gridCells.length,
+      itemBuilder: (BuildContext context, int index) {
+        return _RowDetailCell(
+          cellId: state.gridCells[index],
+          cellBuilder: cellBuilder,
+        );
+      },
+      separatorBuilder: (BuildContext context, int index) {
+        return const VSpace(2);
+      },
+    );
+  }
+
+  Widget _wrapScrollbar(Widget child) {
+    return ScrollbarListStack(
+      axis: Axis.vertical,
+      controller: _scrollController,
+      barSize: GridSize.scrollBarSize,
+      autoHideScrollbar: false,
+      child: child,
+    );
+  }
+
+  void _handleDidCreateField() {
+    WidgetsBinding.instance.addPostFrameCallback((_) {
+      _scrollController.animateTo(
+        _scrollController.position.maxScrollExtent,
+        duration: const Duration(milliseconds: 250),
+        curve: Curves.ease,
+      );
+    });
+  }
 }
 }
 
 
 class _CreateFieldButton extends StatefulWidget {
 class _CreateFieldButton extends StatefulWidget {
   final String viewId;
   final String viewId;
-  final Widget Function(PopoverController) onOpened;
   final VoidCallback onClosed;
   final VoidCallback onClosed;
 
 
   const _CreateFieldButton({
   const _CreateFieldButton({
     required this.viewId,
     required this.viewId,
-    required this.onOpened,
     required this.onClosed,
     required this.onClosed,
     Key? key,
     Key? key,
   }) : super(key: key);
   }) : super(key: key);
@@ -194,10 +189,10 @@ class _CreateFieldButtonState extends State<_CreateFieldButton> {
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final theme = context.read<AppTheme>();
     final theme = context.read<AppTheme>();
 
 
-    return AppFlowyStylePopover(
+    return AppFlowyPopover(
       constraints: BoxConstraints.loose(const Size(240, 200)),
       constraints: BoxConstraints.loose(const Size(240, 200)),
       controller: popoverController,
       controller: popoverController,
-      triggerActions: PopoverTriggerActionFlags.click,
+      triggerActions: PopoverTriggerFlags.click,
       direction: PopoverDirection.topWithLeftAligned,
       direction: PopoverDirection.topWithLeftAligned,
       onClose: widget.onClosed,
       onClose: widget.onClosed,
       child: Container(
       child: Container(
@@ -213,8 +208,24 @@ class _CreateFieldButtonState extends State<_CreateFieldButton> {
           leftIcon: svgWidget("home/add"),
           leftIcon: svgWidget("home/add"),
         ),
         ),
       ),
       ),
-      popupBuilder: (BuildContext context) =>
-          widget.onOpened(popoverController),
+      popupBuilder: (BuildContext popOverContext) {
+        return FieldEditor(
+          gridId: widget.viewId,
+          typeOptionLoader: NewFieldTypeOptionLoader(gridId: widget.viewId),
+          onDeleted: (fieldId) {
+            popoverController.close();
+
+            NavigatorAlertDialog(
+              title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
+              confirm: () {
+                context
+                    .read<RowDetailBloc>()
+                    .add(RowDetailEvent.deleteField(fieldId));
+              },
+            ).show(context);
+          },
+        );
+      },
     );
     );
   }
   }
 
 
@@ -260,41 +271,25 @@ class _RowDetailCellState extends State<_RowDetailCell> {
       ),
       ),
     );
     );
 
 
-    return ConstrainedBox(
-      constraints: const BoxConstraints(minHeight: 40),
-      child: IntrinsicHeight(
+    return IntrinsicHeight(
+      child: ConstrainedBox(
+        constraints: const BoxConstraints(minHeight: 40),
         child: Row(
         child: Row(
           crossAxisAlignment: CrossAxisAlignment.stretch,
           crossAxisAlignment: CrossAxisAlignment.stretch,
           mainAxisAlignment: MainAxisAlignment.center,
           mainAxisAlignment: MainAxisAlignment.center,
           children: [
           children: [
-            SizedBox(
-              width: 150,
-              child: Popover(
-                controller: popover,
-                offset: const Offset(20, 0),
-                popupBuilder: (popoverContext) {
-                  return OverlayContainer(
-                    constraints: BoxConstraints.loose(const Size(240, 200)),
-                    child: FieldEditor(
-                      gridId: widget.cellId.gridId,
-                      fieldName: widget.cellId.fieldContext.field.name,
-                      isGroupField: widget.cellId.fieldContext.isGroupField,
-                      typeOptionLoader: FieldTypeOptionLoader(
-                        gridId: widget.cellId.gridId,
-                        field: widget.cellId.fieldContext.field,
-                      ),
-                      onDeleted: (fieldId) {
-                        popover.close();
-                        context
-                            .read<RowDetailBloc>()
-                            .add(RowDetailEvent.deleteField(fieldId));
-                      },
-                    ),
-                  );
-                },
+            AppFlowyPopover(
+              controller: popover,
+              constraints: BoxConstraints.loose(const Size(240, 600)),
+              popupBuilder: (popoverContext) => buildFieldEditor(),
+              child: SizedBox(
+                width: 150,
                 child: FieldCellButton(
                 child: FieldCellButton(
+                  maxLines: null,
                   field: widget.cellId.fieldContext.field,
                   field: widget.cellId.fieldContext.field,
-                  onTap: () => popover.show(),
+                  onTap: () {
+                    popover.show();
+                  },
                 ),
                 ),
               ),
               ),
             ),
             ),
@@ -305,6 +300,30 @@ class _RowDetailCellState extends State<_RowDetailCell> {
       ),
       ),
     );
     );
   }
   }
+
+  Widget buildFieldEditor() {
+    return FieldEditor(
+      gridId: widget.cellId.gridId,
+      fieldName: widget.cellId.fieldContext.field.name,
+      isGroupField: widget.cellId.fieldContext.isGroupField,
+      typeOptionLoader: FieldTypeOptionLoader(
+        gridId: widget.cellId.gridId,
+        field: widget.cellId.fieldContext.field,
+      ),
+      onDeleted: (fieldId) {
+        popover.close();
+
+        NavigatorAlertDialog(
+          title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
+          confirm: () {
+            context
+                .read<RowDetailBloc>()
+                .add(RowDetailEvent.deleteField(fieldId));
+          },
+        ).show(context);
+      },
+    );
+  }
 }
 }
 
 
 GridCellStyle? _customCellStyle(AppTheme theme, FieldType fieldType) {
 GridCellStyle? _customCellStyle(AppTheme theme, FieldType fieldType) {

+ 0 - 1
frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_group.dart

@@ -99,7 +99,6 @@ class _GridGroupCell extends StatelessWidget {
                 ),
                 ),
               );
               );
           onSelected();
           onSelected();
-          // FlowyOverlay.of(context).remove(GridGroupList.identifier());
         },
         },
       ),
       ),
     );
     );

+ 8 - 6
frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_property.dart

@@ -3,7 +3,7 @@ import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_editor.
 import 'package:app_flowy/startup/startup.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/application/setting/property_bloc.dart';
 import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_type_extension.dart';
 import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_type_extension.dart';
-import 'package:appflowy_popover/popover.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@@ -116,11 +116,11 @@ class _GridPropertyCell extends StatelessWidget {
   }
   }
 
 
   Widget _editFieldButton(AppTheme theme, BuildContext context) {
   Widget _editFieldButton(AppTheme theme, BuildContext context) {
-    return AppFlowyStylePopover(
+    return AppFlowyPopover(
       mutex: popoverMutex,
       mutex: popoverMutex,
-      triggerActions: PopoverTriggerActionFlags.click,
+      triggerActions: PopoverTriggerFlags.click,
       offset: const Offset(20, 0),
       offset: const Offset(20, 0),
-      constraints: BoxConstraints.loose(const Size(240, 200)),
+      constraints: BoxConstraints.loose(const Size(240, 400)),
       child: FlowyButton(
       child: FlowyButton(
         text: FlowyText.medium(fieldContext.name, fontSize: 12),
         text: FlowyText.medium(fieldContext.name, fontSize: 12),
         hoverColor: theme.hover,
         hoverColor: theme.hover,
@@ -131,8 +131,10 @@ class _GridPropertyCell extends StatelessWidget {
         return FieldEditor(
         return FieldEditor(
           gridId: gridId,
           gridId: gridId,
           fieldName: fieldContext.name,
           fieldName: fieldContext.name,
-          typeOptionLoader:
-              FieldTypeOptionLoader(gridId: gridId, field: fieldContext.field),
+          typeOptionLoader: FieldTypeOptionLoader(
+            gridId: gridId,
+            field: fieldContext.field,
+          ),
         );
         );
       },
       },
     );
     );

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

@@ -2,7 +2,6 @@ import 'package:app_flowy/plugins/grid/application/setting/setting_bloc.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/theme.dart';
-import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
@@ -40,7 +39,6 @@ class GridSettingList extends StatelessWidget {
             previous.selectedAction != current.selectedAction,
             previous.selectedAction != current.selectedAction,
         listener: (context, state) {
         listener: (context, state) {
           state.selectedAction.foldLeft(null, (_, action) {
           state.selectedAction.foldLeft(null, (_, action) {
-            FlowyOverlay.of(context).remove(identifier());
             onAction(action, settingContext);
             onAction(action, settingContext);
           });
           });
         },
         },

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

@@ -1,4 +1,4 @@
-import 'package:appflowy_popover/popover.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:app_flowy/plugins/grid/application/setting/setting_bloc.dart';
 import 'package:app_flowy/plugins/grid/application/setting/setting_bloc.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/theme.dart';
@@ -53,9 +53,9 @@ class _SettingButton extends StatelessWidget {
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     final theme = context.watch<AppTheme>();
-    return AppFlowyStylePopover(
+    return AppFlowyPopover(
       constraints: BoxConstraints.loose(const Size(260, 400)),
       constraints: BoxConstraints.loose(const Size(260, 400)),
-      triggerActions: PopoverTriggerActionFlags.click,
+      triggerActions: PopoverTriggerFlags.click,
       offset: const Offset(0, 10),
       offset: const Offset(0, 10),
       child: FlowyIconButton(
       child: FlowyIconButton(
         width: 22,
         width: 22,

+ 6 - 6
frontend/app_flowy/lib/plugins/trash/trash.dart

@@ -140,23 +140,23 @@ class _TrashPageState extends State<TrashPage> {
     return SizedBox(
     return SizedBox(
       height: 36,
       height: 36,
       child: Row(
       child: Row(
+        crossAxisAlignment: CrossAxisAlignment.stretch,
         children: [
         children: [
           FlowyText.semibold(LocaleKeys.trash_text.tr()),
           FlowyText.semibold(LocaleKeys.trash_text.tr()),
           const Spacer(),
           const Spacer(),
-          SizedBox.fromSize(
-            size: const Size(102, 30),
+          IntrinsicWidth(
             child: FlowyButton(
             child: FlowyButton(
               text: FlowyText.medium(LocaleKeys.trash_restoreAll.tr(),
               text: FlowyText.medium(LocaleKeys.trash_restoreAll.tr(),
                   fontSize: 12),
                   fontSize: 12),
               leftIcon: svgWidget('editor/restore', color: theme.iconColor),
               leftIcon: svgWidget('editor/restore', color: theme.iconColor),
               hoverColor: theme.hover,
               hoverColor: theme.hover,
-              onTap: () =>
-                  context.read<TrashBloc>().add(const TrashEvent.restoreAll()),
+              onTap: () => context.read<TrashBloc>().add(
+                    const TrashEvent.restoreAll(),
+                  ),
             ),
             ),
           ),
           ),
           const HSpace(6),
           const HSpace(6),
-          SizedBox.fromSize(
-            size: const Size(102, 30),
+          IntrinsicWidth(
             child: FlowyButton(
             child: FlowyButton(
               text: FlowyText.medium(LocaleKeys.trash_deleteAll.tr(),
               text: FlowyText.medium(LocaleKeys.trash_deleteAll.tr(),
                   fontSize: 12),
                   fontSize: 12),

+ 16 - 9
frontend/app_flowy/lib/workspace/application/appearance.dart

@@ -16,7 +16,8 @@ class AppearanceSettingModel extends ChangeNotifier with EquatableMixin {
 
 
   AppearanceSettingModel(this.setting)
   AppearanceSettingModel(this.setting)
       : _theme = AppTheme.fromName(name: setting.theme),
       : _theme = AppTheme.fromName(name: setting.theme),
-        _locale = Locale(setting.locale.languageCode, setting.locale.countryCode);
+        _locale =
+            Locale(setting.locale.languageCode, setting.locale.countryCode);
 
 
   AppTheme get theme => _theme;
   AppTheme get theme => _theme;
   Locale get locale => _locale;
   Locale get locale => _locale;
@@ -34,7 +35,8 @@ class AppearanceSettingModel extends ChangeNotifier with EquatableMixin {
   }
   }
 
 
   void swapTheme() {
   void swapTheme() {
-    final themeType = (_theme.ty == ThemeType.light ? ThemeType.dark : ThemeType.light);
+    final themeType =
+        (_theme.ty == ThemeType.light ? ThemeType.dark : ThemeType.light);
 
 
     if (_theme.ty != themeType) {
     if (_theme.ty != themeType) {
       _theme = AppTheme.fromType(themeType);
       _theme = AppTheme.fromType(themeType);
@@ -45,14 +47,15 @@ class AppearanceSettingModel extends ChangeNotifier with EquatableMixin {
   }
   }
 
 
   void setLocale(BuildContext context, Locale newLocale) {
   void setLocale(BuildContext context, Locale newLocale) {
-    if (_locale != newLocale) {
-      if (!context.supportedLocales.contains(newLocale)) {
-        Log.warn("Unsupported locale: $newLocale");
-        newLocale = const Locale('en');
-        Log.debug("Fallback to locale: $newLocale");
-      }
+    if (!context.supportedLocales.contains(newLocale)) {
+      Log.warn("Unsupported locale: $newLocale");
+      newLocale = const Locale('en');
+      Log.debug("Fallback to locale: $newLocale");
+    }
+
+    context.setLocale(newLocale);
 
 
-      context.setLocale(newLocale);
+    if (_locale != newLocale) {
       _locale = newLocale;
       _locale = newLocale;
       setting.locale.languageCode = _locale.languageCode;
       setting.locale.languageCode = _locale.languageCode;
       setting.locale.countryCode = _locale.countryCode ?? "";
       setting.locale.countryCode = _locale.countryCode ?? "";
@@ -67,6 +70,10 @@ class AppearanceSettingModel extends ChangeNotifier with EquatableMixin {
       save();
       save();
 
 
       setLocale(context, context.deviceLocale);
       setLocale(context, context.deviceLocale);
+      return;
     }
     }
+
+    // when opening app the first time
+    setLocale(context, _locale);
   }
   }
 }
 }

+ 0 - 39
frontend/app_flowy/lib/workspace/presentation/widgets/dialogs.dart

@@ -1,4 +1,3 @@
-import 'package:appflowy_popover/popover.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/text_style.dart';
 import 'package:flowy_infra/text_style.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/theme.dart';
@@ -87,44 +86,6 @@ class _CreateTextFieldDialog extends State<NavigatorTextFieldDialog> {
   }
   }
 }
 }
 
 
-class PopoverAlertView extends StatelessWidget {
-  final PopoverMutex popoverMutex;
-  final String title;
-  final void Function()? cancel;
-  final void Function()? confirm;
-
-  const PopoverAlertView({
-    required this.popoverMutex,
-    required this.title,
-    this.confirm,
-    this.cancel,
-    Key? key,
-  }) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
-    return StyledDialog(
-      child: Column(
-        crossAxisAlignment: CrossAxisAlignment.start,
-        mainAxisAlignment: MainAxisAlignment.center,
-        children: <Widget>[
-          ...[
-            FlowyText.medium(title, color: theme.shader4),
-          ],
-          if (confirm != null) ...[
-            const VSpace(20),
-            OkCancelButton(
-              onOkPressed: confirm,
-              onCancelPressed: cancel,
-            )
-          ]
-        ],
-      ),
-    );
-  }
-}
-
 class NavigatorAlertDialog extends StatefulWidget {
 class NavigatorAlertDialog extends StatefulWidget {
   final String title;
   final String title;
   final void Function()? cancel;
   final void Function()? cancel;

+ 0 - 63
frontend/app_flowy/lib/workspace/presentation/widgets/pop_up_window.dart

@@ -1,63 +0,0 @@
-import 'package:flowy_infra_ui/flowy_infra_ui.dart';
-import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
-import 'package:flutter/material.dart';
-import 'package:window_size/window_size.dart';
-
-class FlowyPoppuWindow extends StatelessWidget {
-  final Widget child;
-  const FlowyPoppuWindow({Key? key, required this.child}) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    return Material(
-      type: MaterialType.transparency,
-      child: child,
-    );
-  }
-
-  static Future<void> show(
-    BuildContext context, {
-    required Widget child,
-    required Size size,
-  }) async {
-    final window = await getWindowInfo();
-    // ignore: use_build_context_synchronously
-    FlowyOverlay.of(context).insertWithRect(
-      widget: FlowyPoppuWindow(child: child),
-      identifier: 'FlowyPoppuWindow',
-      anchorPosition: Offset(-size.width / 2.0, -size.height / 2.0),
-      anchorSize: window.frame.size,
-      anchorDirection: AnchorDirection.center,
-      style: FlowyOverlayStyle(blur: false),
-    );
-  }
-}
-
-class PopupTextField extends StatelessWidget {
-  final void Function(String) textDidChange;
-  const PopupTextField({
-    Key? key,
-    required this.textDidChange,
-  }) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    return RoundedInputField(
-      style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
-      hintText: '',
-      normalBorderColor: const Color(0xffbdbdbd),
-      onChanged: textDidChange,
-    );
-  }
-
-  static void show(
-      {required BuildContext context,
-      required Size size,
-      required void Function(String) textDidChange}) {
-    FlowyPoppuWindow.show(
-      context,
-      size: size,
-      child: PopupTextField(textDidChange: textDidChange),
-    );
-  }
-}

+ 0 - 3
frontend/app_flowy/macos/Runner.xcodeproj/project.pbxproj

@@ -416,7 +416,6 @@
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
 			baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
 			baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
 			buildSettings = {
 			buildSettings = {
-				ARCHS = arm64;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_MODULES = YES;
 				CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
 				CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
@@ -550,7 +549,6 @@
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
 			baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
 			baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
 			buildSettings = {
 			buildSettings = {
-				ARCHS = arm64;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_MODULES = YES;
 				CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
 				CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
@@ -575,7 +573,6 @@
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
 			baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
 			baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
 			buildSettings = {
 			buildSettings = {
-				ARCHS = arm64;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_MODULES = YES;
 				CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
 				CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;

+ 2 - 6
frontend/app_flowy/packages/appflowy_editor/README.md

@@ -27,7 +27,7 @@ and the Flutter guide for
 </p>
 </p>
 
 
 <div align="center">
 <div align="center">
-    <img src="https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/documentation/images/appflowy-editor-example.gif?raw=true" width = "700" style = "padding: 100"/>
+    <img src="https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/documentation/images/appflowy_editor_example.mp4?raw=true" width = "700" style = "padding: 100"/>
 </div>
 </div>
 
 
 ## Key Features
 ## Key Features
@@ -58,8 +58,6 @@ final editorStyle = EditorStyle.defaultStyle();
 final editorState = EditorState.empty(); // an empty state
 final editorState = EditorState.empty(); // an empty state
 final editor = AppFlowyEditor(
 final editor = AppFlowyEditor(
     editorState: editorState,
     editorState: editorState,
-    shortcutEvents: const [],
-    customBuilders: const {},
     editorStyle: editorStyle,
     editorStyle: editorStyle,
 );
 );
 ```
 ```
@@ -72,8 +70,6 @@ final editorStyle = EditorStyle.defaultStyle();
 final editorState = EditorState(StateTree.fromJson(data));
 final editorState = EditorState(StateTree.fromJson(data));
 final editor = AppFlowyEditor(
 final editor = AppFlowyEditor(
     editorState: editorState,
     editorState: editorState,
-    shortcutEvents: const [],
-    customBuilders: const {},
     editorStyle: editorStyle,
     editorStyle: editorStyle,
 );
 );
 ```
 ```
@@ -113,7 +109,7 @@ Please refer to our documentation on customizing AppFlowy for a detailed discuss
 
 
 Below are some examples of shortcut event customizations:
 Below are some examples of shortcut event customizations:
 
 
- * [BIUS](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart) demonstrates how to make text bold/italic/underline/strikethrough through shortcut keys
+ * [BIUS](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/format_style_handler.dart) demonstrates how to make text bold/italic/underline/strikethrough through shortcut keys
  * [Paste HTML](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart) gives you an idea on how to handle pasted styles through shortcut keys
  * [Paste HTML](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart) gives you an idea on how to handle pasted styles through shortcut keys
  * Need more examples? Check out [Internal key event handlers](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers)
  * Need more examples? Check out [Internal key event handlers](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers)
 
 

+ 22 - 15
frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md

@@ -27,7 +27,7 @@ Widget build(BuildContext context) {
 
 
 At this point, nothing magic will happen after typing `_xxx_`.
 At this point, nothing magic will happen after typing `_xxx_`.
 
 
-![Before](./images/customizing_a_shortcut_event_before.gif)
+![Before](./images/customize_a_shortcut_event_before.gif)
 
 
 To implement our shortcut event we will create a `ShortcutEvent` instance to handle an underscore input.
 To implement our shortcut event we will create a `ShortcutEvent` instance to handle an underscore input.
 
 
@@ -39,11 +39,10 @@ We need to define `key` and `command` in a ShortCutEvent object to customize hot
 ```dart
 ```dart
 import 'package:appflowy_editor/appflowy_editor.dart';
 import 'package:appflowy_editor/appflowy_editor.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
 
 
 ShortcutEvent underscoreToItalicEvent = ShortcutEvent(
 ShortcutEvent underscoreToItalicEvent = ShortcutEvent(
   key: 'Underscore to italic',
   key: 'Underscore to italic',
-  command: 'underscore',
+  command: 'shift+underscore',
   handler: _underscoreToItalicHandler,
   handler: _underscoreToItalicHandler,
 );
 );
 
 
@@ -82,9 +81,12 @@ ShortcutEventHandler _underscoreToItalicHandler = (editorState, event) {
 
 
   final textNode = textNodes.first;
   final textNode = textNodes.first;
   final text = textNode.toRawString();
   final text = textNode.toRawString();
-  // Determine if an 'underscore' already exists in the text node
-  final previousUnderscore = text.indexOf('_');
-  if (previousUnderscore == -1) {
+  // Determine if an 'underscore' already exists in the text node and only once.
+  final firstUnderscore = text.indexOf('_');
+  final lastUnderscore = text.lastIndexOf('_');
+  if (firstUnderscore == -1 ||
+      firstUnderscore != lastUnderscore ||
+      firstUnderscore == selection.start.offset - 1) {
     return KeyEventResult.ignored;
     return KeyEventResult.ignored;
   }
   }
 
 
@@ -92,15 +94,20 @@ ShortcutEventHandler _underscoreToItalicHandler = (editorState, event) {
   // update the style of the text surrounded by the two underscores to 'italic',
   // update the style of the text surrounded by the two underscores to 'italic',
   // and update the cursor position.
   // and update the cursor position.
   TransactionBuilder(editorState)
   TransactionBuilder(editorState)
-    ..deleteText(textNode, previousUnderscore, 1)
+    ..deleteText(textNode, firstUnderscore, 1)
     ..formatText(
     ..formatText(
       textNode,
       textNode,
-      previousUnderscore,
-      selection.end.offset - previousUnderscore - 1,
-      {'italic': true},
+      firstUnderscore,
+      selection.end.offset - firstUnderscore - 1,
+      {
+        BuiltInAttributeKey.italic: true,
+      },
     )
     )
     ..afterSelection = Selection.collapsed(
     ..afterSelection = Selection.collapsed(
-      Position(path: textNode.path, offset: selection.end.offset - 1),
+      Position(
+        path: textNode.path,
+        offset: selection.end.offset - 1,
+      ),
     )
     )
     ..commit();
     ..commit();
 
 
@@ -121,7 +128,7 @@ Widget build(BuildContext context) {
         editorStyle: EditorStyle.defaultStyle(),
         editorStyle: EditorStyle.defaultStyle(),
         customBuilders: const {},
         customBuilders: const {},
         shortcutEvents: [
         shortcutEvents: [
-            _underscoreToItalicHandler,
+          underscoreToItalic,
         ],
         ],
       ),
       ),
     ),
     ),
@@ -129,9 +136,9 @@ Widget build(BuildContext context) {
 }
 }
 ```
 ```
 
 
-![After](./images/customizing_a_shortcut_event_after.gif)
+![After](./images/customize_a_shortcut_event_after.gif)
 
 
-Check out the [complete code](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic_key_event_handler.dart) file of this example.
+Check out the [complete code](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic.dart) file of this example.
 
 
 
 
 ## Customizing a Component
 ## Customizing a Component
@@ -297,7 +304,7 @@ return AppFlowyEditor(
 );
 );
 ```
 ```
 
 
-![Whew!](./images/customizing_a_component.gif)
+![Whew!](./images/customize_a_component.gif)
 
 
 Check out the [complete code](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/network_image_node_widget.dart) file of this example.
 Check out the [complete code](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/network_image_node_widget.dart) file of this example.
 
 

BIN
frontend/app_flowy/packages/appflowy_editor/documentation/images/appflowy-editor-example.gif


BIN
frontend/app_flowy/packages/appflowy_editor/documentation/images/appflowy_editor_example.mp4


BIN
frontend/app_flowy/packages/appflowy_editor/documentation/images/customize_a_component.gif


BIN
frontend/app_flowy/packages/appflowy_editor/documentation/images/customize_a_shortcut_event_after.gif


BIN
frontend/app_flowy/packages/appflowy_editor/documentation/images/customize_a_shortcut_event_before.gif


BIN
frontend/app_flowy/packages/appflowy_editor/documentation/images/customizing_a_component.gif


BIN
frontend/app_flowy/packages/appflowy_editor/documentation/images/customizing_a_shortcut_event_after.gif


BIN
frontend/app_flowy/packages/appflowy_editor/documentation/images/customizing_a_shortcut_event_before.gif


+ 4 - 3
frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart

@@ -1,7 +1,7 @@
 import 'dart:convert';
 import 'dart:convert';
 import 'dart:io';
 import 'dart:io';
 
 
-import 'package:example/plugin/underscore_to_italic_key_event_handler.dart';
+import 'package:example/plugin/underscore_to_italic.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter_localizations/flutter_localizations.dart';
 import 'package:flutter_localizations/flutter_localizations.dart';
@@ -29,7 +29,7 @@ class MyApp extends StatelessWidget {
         GlobalWidgetsLocalizations.delegate,
         GlobalWidgetsLocalizations.delegate,
         AppFlowyEditorLocalizations.delegate,
         AppFlowyEditorLocalizations.delegate,
       ],
       ],
-      supportedLocales: AppFlowyEditorLocalizations.delegate.supportedLocales,
+      supportedLocales: const [Locale('en', 'US')],
       debugShowCheckedModeBanner: false,
       debugShowCheckedModeBanner: false,
       theme: ThemeData(
       theme: ThemeData(
         primarySwatch: Colors.blue,
         primarySwatch: Colors.blue,
@@ -113,7 +113,7 @@ class _MyHomePageState extends State<MyHomePage> {
               editorState: _editorState!,
               editorState: _editorState!,
               editorStyle: _editorStyle,
               editorStyle: _editorStyle,
               shortcutEvents: [
               shortcutEvents: [
-                underscoreToItalicEvent,
+                underscoreToItalic,
               ],
               ],
             ),
             ),
           );
           );
@@ -186,6 +186,7 @@ class _MyHomePageState extends State<MyHomePage> {
     final path = directory.path;
     final path = directory.path;
     final file = File('$path/editor.json');
     final file = File('$path/editor.json');
     setState(() {
     setState(() {
+      _editorState = null;
       _jsonString = file.readAsString();
       _jsonString = file.readAsString();
     });
     });
   }
   }

+ 53 - 0
frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic.dart

@@ -0,0 +1,53 @@
+import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:flutter/material.dart';
+
+ShortcutEvent underscoreToItalic = ShortcutEvent(
+  key: 'Underscore to italic',
+  command: 'shift+underscore',
+  handler: _underscoreToItalicHandler,
+);
+
+ShortcutEventHandler _underscoreToItalicHandler = (editorState, event) {
+  // Obtain the selection and selected nodes of the current document through the 'selectionService'
+  // to determine whether the selection is collapsed and whether the selected node is a text node.
+  final selectionService = editorState.service.selectionService;
+  final selection = selectionService.currentSelection.value;
+  final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
+  if (selection == null || !selection.isSingle || textNodes.length != 1) {
+    return KeyEventResult.ignored;
+  }
+
+  final textNode = textNodes.first;
+  final text = textNode.toRawString();
+  // Determine if an 'underscore' already exists in the text node and only once.
+  final firstUnderscore = text.indexOf('_');
+  final lastUnderscore = text.lastIndexOf('_');
+  if (firstUnderscore == -1 ||
+      firstUnderscore != lastUnderscore ||
+      firstUnderscore == selection.start.offset - 1) {
+    return KeyEventResult.ignored;
+  }
+
+  // Delete the previous 'underscore',
+  // update the style of the text surrounded by the two underscores to 'italic',
+  // and update the cursor position.
+  TransactionBuilder(editorState)
+    ..deleteText(textNode, firstUnderscore, 1)
+    ..formatText(
+      textNode,
+      firstUnderscore,
+      selection.end.offset - firstUnderscore - 1,
+      {
+        BuiltInAttributeKey.italic: true,
+      },
+    )
+    ..afterSelection = Selection.collapsed(
+      Position(
+        path: textNode.path,
+        offset: selection.end.offset - 1,
+      ),
+    )
+    ..commit();
+
+  return KeyEventResult.handled;
+};

+ 0 - 45
frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic_key_event_handler.dart

@@ -1,45 +0,0 @@
-import 'package:appflowy_editor/appflowy_editor.dart';
-import 'package:flutter/material.dart';
-
-ShortcutEvent underscoreToItalicEvent = ShortcutEvent(
-  key: 'Underscore to italic',
-  command: 'underscore',
-  handler: _underscoreToItalicHandler,
-);
-
-ShortcutEventHandler _underscoreToItalicHandler = (editorState, event) {
-  // Obtaining the selection and selected nodes of the current document through `selectionService`,
-  // and determine whether it is a single selection and whether the selected node is a text node.
-  final selectionService = editorState.service.selectionService;
-  final selection = selectionService.currentSelection.value;
-  final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
-  if (selection == null || !selection.isSingle || textNodes.length != 1) {
-    return KeyEventResult.ignored;
-  }
-
-  final textNode = textNodes.first;
-  final text = textNode.toRawString();
-  // Determine if `underscore` already exists in the text node
-  final previousUnderscore = text.indexOf('_');
-  if (previousUnderscore == -1) {
-    return KeyEventResult.ignored;
-  }
-
-  // Delete the previous `underscore`,
-  // update the style of the text surrounded by two underscores to `italic`,
-  // and update the cursor position.
-  TransactionBuilder(editorState)
-    ..deleteText(textNode, previousUnderscore, 1)
-    ..formatText(
-      textNode,
-      previousUnderscore,
-      selection.end.offset - previousUnderscore - 1,
-      {'italic': true},
-    )
-    ..afterSelection = Selection.collapsed(
-      Position(path: textNode.path, offset: selection.end.offset - 1),
-    )
-    ..commit();
-
-  return KeyEventResult.handled;
-};

+ 7 - 6
frontend/app_flowy/packages/appflowy_editor/example/macos/Runner/Base.lproj/MainMenu.xib

@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21225" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
     <dependencies>
     <dependencies>
         <deployment identifier="macosx"/>
         <deployment identifier="macosx"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21225"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
     </dependencies>
     <objects>
     <objects>
@@ -13,7 +13,7 @@
         </customObject>
         </customObject>
         <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
         <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
         <customObject id="-3" userLabel="Application" customClass="NSObject"/>
         <customObject id="-3" userLabel="Application" customClass="NSObject"/>
-        <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target">
+        <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="example" customModuleProvider="target">
             <connections>
             <connections>
                 <outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
                 <outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
                 <outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
                 <outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
@@ -330,14 +330,15 @@
             </items>
             </items>
             <point key="canvasLocation" x="142" y="-258"/>
             <point key="canvasLocation" x="142" y="-258"/>
         </menu>
         </menu>
-        <window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target">
-            <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
+        <window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" titlebarAppearsTransparent="YES" titleVisibility="hidden" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="example" customModuleProvider="target">
+            <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES" fullSizeContentView="YES"/>
             <rect key="contentRect" x="335" y="390" width="800" height="600"/>
             <rect key="contentRect" x="335" y="390" width="800" height="600"/>
-            <rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
+            <rect key="screenRect" x="0.0" y="0.0" width="1920" height="1055"/>
             <view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
             <view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
                 <rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
                 <rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
                 <autoresizingMask key="autoresizingMask"/>
                 <autoresizingMask key="autoresizingMask"/>
             </view>
             </view>
+            <point key="canvasLocation" x="126" y="-658"/>
         </window>
         </window>
     </objects>
     </objects>
 </document>
 </document>

+ 1 - 0
frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart

@@ -10,6 +10,7 @@ export 'src/document/selection.dart';
 export 'src/document/state_tree.dart';
 export 'src/document/state_tree.dart';
 export 'src/document/text_delta.dart';
 export 'src/document/text_delta.dart';
 export 'src/document/attributes.dart';
 export 'src/document/attributes.dart';
+export 'src/document/built_in_attribute_keys.dart';
 export 'src/editor_state.dart';
 export 'src/editor_state.dart';
 export 'src/operation/operation.dart';
 export 'src/operation/operation.dart';
 export 'src/operation/transaction.dart';
 export 'src/operation/transaction.dart';

+ 10 - 11
frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/whitespace_handler.dart

@@ -44,23 +44,22 @@ ShortcutEventHandler whiteSpaceHandler = (editorState, event) {
   }
   }
 
 
   final textNode = textNodes.first;
   final textNode = textNodes.first;
-  final text = textNode.toRawString();
+  final text = textNode.toRawString().substring(0, selection.end.offset);
 
 
   final numberMatch = _numberRegex.firstMatch(text);
   final numberMatch = _numberRegex.firstMatch(text);
-  if (numberMatch != null) {
-    final matchText = numberMatch.group(0);
-    final numText = numberMatch.group(1);
-    if (matchText != null && numText != null) {
-      return _toNumberList(editorState, textNode, matchText, numText);
-    }
-  }
 
 
-  if ((_checkboxListSymbols + _unCheckboxListSymbols).any(text.startsWith)) {
+  if ((_checkboxListSymbols + _unCheckboxListSymbols).contains(text)) {
     return _toCheckboxList(editorState, textNode);
     return _toCheckboxList(editorState, textNode);
-  } else if (_bulletedListSymbols.any(text.startsWith)) {
+  } else if (_bulletedListSymbols.contains(text)) {
     return _toBulletedList(editorState, textNode);
     return _toBulletedList(editorState, textNode);
   } else if (_countOfSign(text, selection) != 0) {
   } else if (_countOfSign(text, selection) != 0) {
     return _toHeadingStyle(editorState, textNode, selection);
     return _toHeadingStyle(editorState, textNode, selection);
+  } else if (numberMatch != null) {
+    final matchText = numberMatch.group(0);
+    final numText = numberMatch.group(1);
+    if (matchText != null && numText != null) {
+      return _toNumberList(editorState, textNode, matchText, numText);
+    }
   }
   }
 
 
   return KeyEventResult.ignored;
   return KeyEventResult.ignored;
@@ -196,7 +195,7 @@ KeyEventResult _toHeadingStyle(
 
 
 int _countOfSign(String text, Selection selection) {
 int _countOfSign(String text, Selection selection) {
   for (var i = 6; i >= 0; i--) {
   for (var i = 6; i >= 0; i--) {
-    if (text.substring(0, selection.end.offset).startsWith('#' * i)) {
+    if (text.substring(0, selection.end.offset).contains('#' * i)) {
       return i;
       return i;
     }
     }
   }
   }

+ 40 - 0
frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/white_space_handler_test.dart

@@ -174,5 +174,45 @@ void main() async {
         expect(textNode.subtype, BuiltInAttributeKey.bulletedList);
         expect(textNode.subtype, BuiltInAttributeKey.bulletedList);
       }
       }
     });
     });
+
+    testWidgets('Presses whitespace key in edge cases', (tester) async {
+      const text = '';
+      final editor = tester.editor..insertTextNode(text);
+      await editor.startTesting();
+
+      final textNode = editor.nodeAtPath([0]) as TextNode;
+      await editor.updateSelection(
+        Selection.single(path: [0], startOffset: 0),
+      );
+
+      await editor.insertText(textNode, '*', 0);
+      await editor.pressLogicKey(LogicalKeyboardKey.space);
+      expect(textNode.subtype, BuiltInAttributeKey.bulletedList);
+
+      await editor.insertText(textNode, '[]', 0);
+      await editor.pressLogicKey(LogicalKeyboardKey.space);
+      expect(textNode.subtype, BuiltInAttributeKey.checkbox);
+      expect(textNode.attributes.check, false);
+
+      await editor.insertText(textNode, '1.', 0);
+      await editor.pressLogicKey(LogicalKeyboardKey.space);
+      expect(textNode.subtype, BuiltInAttributeKey.numberList);
+
+      await editor.insertText(textNode, '#', 0);
+      await editor.pressLogicKey(LogicalKeyboardKey.space);
+      expect(textNode.subtype, BuiltInAttributeKey.heading);
+
+      await editor.insertText(textNode, '[x]', 0);
+      await editor.pressLogicKey(LogicalKeyboardKey.space);
+      expect(textNode.subtype, BuiltInAttributeKey.checkbox);
+      expect(textNode.attributes.check, true);
+
+      const insertedText = '[]AppFlowy';
+      await editor.insertText(textNode, insertedText, 0);
+      await editor.pressLogicKey(LogicalKeyboardKey.space);
+      expect(textNode.subtype, BuiltInAttributeKey.checkbox);
+      expect(textNode.attributes.check, true);
+      expect(textNode.toRawString(), insertedText);
+    });
   });
   });
 }
 }

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

@@ -1,5 +1,5 @@
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
-import 'package:appflowy_popover/popover.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 
 
 class PopoverMenu extends StatefulWidget {
 class PopoverMenu extends StatefulWidget {
   const PopoverMenu({Key? key}) : super(key: key);
   const PopoverMenu({Key? key}) : super(key: key);
@@ -41,8 +41,8 @@ class _PopoverMenuState extends State<PopoverMenu> {
                       decoration: null)),
                       decoration: null)),
             ),
             ),
             Popover(
             Popover(
-              triggerActions: PopoverTriggerActionFlags.hover |
-                  PopoverTriggerActionFlags.click,
+              triggerActions:
+                  PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
               mutex: popOverMutex,
               mutex: popOverMutex,
               offset: const Offset(10, 0),
               offset: const Offset(10, 0),
               popupBuilder: (BuildContext context) {
               popupBuilder: (BuildContext context) {
@@ -54,8 +54,8 @@ class _PopoverMenuState extends State<PopoverMenu> {
               ),
               ),
             ),
             ),
             Popover(
             Popover(
-              triggerActions: PopoverTriggerActionFlags.hover |
-                  PopoverTriggerActionFlags.click,
+              triggerActions:
+                  PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
               mutex: popOverMutex,
               mutex: popOverMutex,
               offset: const Offset(10, 0),
               offset: const Offset(10, 0),
               popupBuilder: (BuildContext context) {
               popupBuilder: (BuildContext context) {
@@ -86,7 +86,7 @@ class ExampleButton extends StatelessWidget {
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return Popover(
     return Popover(
-      triggerActions: PopoverTriggerActionFlags.click,
+      triggerActions: PopoverTriggerFlags.click,
       offset: offset,
       offset: offset,
       direction: direction ?? PopoverDirection.rightWithTopAligned,
       direction: direction ?? PopoverDirection.rightWithTopAligned,
       child: TextButton(child: Text(label), onPressed: () {}),
       child: TextButton(child: Text(label), onPressed: () {}),

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

@@ -1,4 +1,4 @@
-import 'package:appflowy_popover/popover.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import "./example_button.dart";
 import "./example_button.dart";
 
 

+ 5 - 0
frontend/app_flowy/packages/appflowy_popover/lib/appflowy_popover.dart

@@ -0,0 +1,5 @@
+/// AppFlowyBoard library
+library appflowy_popover;
+
+export 'src/mutex.dart';
+export 'src/popover.dart';

+ 0 - 0
frontend/app_flowy/packages/appflowy_popover/lib/follower.dart → frontend/app_flowy/packages/appflowy_popover/lib/src/follower.dart


+ 13 - 1
frontend/app_flowy/packages/appflowy_popover/lib/layout.dart → frontend/app_flowy/packages/appflowy_popover/lib/src/layout.dart

@@ -323,9 +323,21 @@ class PopoverTargetRenderBox extends RenderProxyBox {
 
 
   @override
   @override
   void detach() {
   void detach() {
+    super.detach();
     link.leaderOffset = null;
     link.leaderOffset = null;
     link.leaderSize = null;
     link.leaderSize = null;
-    super.detach();
+  }
+
+  @override
+  void attach(covariant PipelineOwner owner) {
+    super.attach(owner);
+    if (hasSize) {
+      // The leaderSize was set after [performLayout], but was
+      // set to null when [detach] get called.
+      //
+      // set the leaderSize when attach get called
+      link.leaderSize = size;
+    }
   }
   }
 
 
   @override
   @override

+ 116 - 0
frontend/app_flowy/packages/appflowy_popover/lib/src/mask.dart

@@ -0,0 +1,116 @@
+import 'dart:collection';
+
+import 'package:appflowy_popover/appflowy_popover.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+
+typedef EntryMap = LinkedHashMap<PopoverState, OverlayEntryContext>;
+
+class RootOverlayEntry {
+  final EntryMap _entries = EntryMap();
+  RootOverlayEntry();
+
+  void addEntry(
+    BuildContext context,
+    PopoverState newState,
+    OverlayEntry entry,
+    bool asBarrier,
+  ) {
+    _entries[newState] = OverlayEntryContext(entry, newState, asBarrier);
+    Overlay.of(context)?.insert(entry);
+  }
+
+  bool contains(PopoverState oldState) {
+    return _entries.containsKey(oldState);
+  }
+
+  void removeEntry(PopoverState oldState) {
+    if (_entries.isEmpty) return;
+
+    final removedEntry = _entries.remove(oldState);
+    removedEntry?.overlayEntry.remove();
+  }
+
+  bool get isEmpty => _entries.isEmpty;
+
+  bool get isNotEmpty => _entries.isNotEmpty;
+
+  bool hasEntry() {
+    return _entries.isNotEmpty;
+  }
+
+  PopoverState? popEntry() {
+    if (_entries.isEmpty) return null;
+
+    final lastEntry = _entries.values.last;
+    _entries.remove(lastEntry.popoverState);
+    lastEntry.overlayEntry.remove();
+    lastEntry.popoverState.widget.onClose?.call();
+
+    if (lastEntry.asBarrier) {
+      return lastEntry.popoverState;
+    } else {
+      return popEntry();
+    }
+  }
+}
+
+class OverlayEntryContext {
+  final bool asBarrier;
+  final PopoverState popoverState;
+  final OverlayEntry overlayEntry;
+
+  OverlayEntryContext(
+    this.overlayEntry,
+    this.popoverState,
+    this.asBarrier,
+  );
+}
+
+class PopoverMask extends StatefulWidget {
+  final void Function() onTap;
+  final void Function()? onExit;
+  final Decoration? decoration;
+
+  const PopoverMask(
+      {Key? key, required this.onTap, this.onExit, this.decoration})
+      : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() => _PopoverMaskState();
+}
+
+class _PopoverMaskState extends State<PopoverMask> {
+  @override
+  void initState() {
+    HardwareKeyboard.instance.addHandler(_handleGlobalKeyEvent);
+    super.initState();
+  }
+
+  bool _handleGlobalKeyEvent(KeyEvent event) {
+    if (event.logicalKey == LogicalKeyboardKey.escape) {
+      if (widget.onExit != null) {
+        widget.onExit!();
+      }
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  @override
+  void deactivate() {
+    HardwareKeyboard.instance.removeHandler(_handleGlobalKeyEvent);
+    super.deactivate();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return GestureDetector(
+      onTap: widget.onTap,
+      child: Container(
+        decoration: widget.decoration,
+      ),
+    );
+  }
+}

+ 51 - 0
frontend/app_flowy/packages/appflowy_popover/lib/src/mutex.dart

@@ -0,0 +1,51 @@
+import 'package:flutter/material.dart';
+
+import 'popover.dart';
+
+/// If multiple popovers are exclusive,
+/// pass the same mutex to them.
+class PopoverMutex {
+  final _PopoverStateNotifier _stateNotifier = _PopoverStateNotifier();
+  PopoverMutex();
+
+  void removePopoverListener(VoidCallback listener) {
+    _stateNotifier.removeListener(listener);
+  }
+
+  VoidCallback listenOnPopoverChanged(VoidCallback callback) {
+    listenerCallback() {
+      callback();
+    }
+
+    _stateNotifier.addListener(listenerCallback);
+    return listenerCallback;
+  }
+
+  void close() => _stateNotifier.state?.close();
+
+  PopoverState? get state => _stateNotifier.state;
+
+  set state(PopoverState? newState) => _stateNotifier.state = newState;
+
+  void removeState() {
+    _stateNotifier.state = null;
+  }
+
+  void dispose() {
+    _stateNotifier.dispose();
+  }
+}
+
+class _PopoverStateNotifier extends ChangeNotifier {
+  PopoverState? _state;
+
+  PopoverState? get state => _state;
+
+  set state(PopoverState? newState) {
+    if (_state != null && _state != newState) {
+      _state?.close();
+    }
+    _state = newState;
+    notifyListeners();
+  }
+}

+ 72 - 138
frontend/app_flowy/packages/appflowy_popover/lib/popover.dart → frontend/app_flowy/packages/appflowy_popover/lib/src/popover.dart

@@ -1,27 +1,22 @@
-import 'package:appflowy_popover/layout.dart';
-import 'package:flutter/gestures.dart';
+import 'dart:async';
+import 'package:appflowy_popover/src/layout.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-
-/// If multiple popovers are exclusive,
-/// pass the same mutex to them.
-class PopoverMutex {
-  PopoverState? state;
-}
+import 'mask.dart';
+import 'mutex.dart';
 
 
 class PopoverController {
 class PopoverController {
-  PopoverState? state;
+  PopoverState? _state;
 
 
   close() {
   close() {
-    state?.close();
+    _state?.close();
   }
   }
 
 
   show() {
   show() {
-    state?.showOverlay();
+    _state?.showOverlay();
   }
   }
 }
 }
 
 
-class PopoverTriggerActionFlags {
+class PopoverTriggerFlags {
   static int click = 0x01;
   static int click = 0x01;
   static int hover = 0x02;
   static int hover = 0x02;
 }
 }
@@ -72,6 +67,8 @@ class Popover extends StatefulWidget {
 
 
   final void Function()? onClose;
   final void Function()? onClose;
 
 
+  final bool asBarrier;
+
   /// The content area of the popover.
   /// The content area of the popover.
   final Widget child;
   final Widget child;
 
 
@@ -81,11 +78,14 @@ class Popover extends StatefulWidget {
     required this.popupBuilder,
     required this.popupBuilder,
     this.controller,
     this.controller,
     this.offset,
     this.offset,
-    this.maskDecoration,
+    this.maskDecoration = const BoxDecoration(
+      color: Color.fromARGB(0, 244, 67, 54),
+    ),
     this.triggerActions = 0,
     this.triggerActions = 0,
     this.direction = PopoverDirection.rightWithTopAligned,
     this.direction = PopoverDirection.rightWithTopAligned,
     this.mutex,
     this.mutex,
     this.onClose,
     this.onClose,
+    this.asBarrier = false,
   }) : super(key: key);
   }) : super(key: key);
 
 
   @override
   @override
@@ -93,172 +93,106 @@ class Popover extends StatefulWidget {
 }
 }
 
 
 class PopoverState extends State<Popover> {
 class PopoverState extends State<Popover> {
+  static final RootOverlayEntry _rootEntry = RootOverlayEntry();
   final PopoverLink popoverLink = PopoverLink();
   final PopoverLink popoverLink = PopoverLink();
-  OverlayEntry? _overlayEntry;
-  bool hasMask = true;
-
-  static PopoverState? _popoverWithMask;
+  Timer? _debounceEnterRegionAction;
 
 
   @override
   @override
   void initState() {
   void initState() {
-    widget.controller?.state = this;
+    widget.controller?._state = this;
     super.initState();
     super.initState();
   }
   }
 
 
-  showOverlay() {
+  void showOverlay() {
     close();
     close();
 
 
     if (widget.mutex != null) {
     if (widget.mutex != null) {
-      if (widget.mutex!.state != null && widget.mutex!.state != this) {
-        widget.mutex!.state!.close();
-      }
-
-      widget.mutex!.state = this;
-    }
-
-    if (_popoverWithMask == null) {
-      _popoverWithMask = this;
-    } else {
-      hasMask = false;
+      widget.mutex?.state = this;
     }
     }
-
+    final shouldAddMask = _rootEntry.isEmpty;
     final newEntry = OverlayEntry(builder: (context) {
     final newEntry = OverlayEntry(builder: (context) {
       final children = <Widget>[];
       final children = <Widget>[];
-
-      if (hasMask) {
-        children.add(_PopoverMask(
-          decoration: widget.maskDecoration,
-          onTap: () => close(),
-          onExit: () => close(),
-        ));
+      if (shouldAddMask) {
+        children.add(
+          PopoverMask(
+            decoration: widget.maskDecoration,
+            onTap: () => _removeRootOverlay(),
+            onExit: () => _removeRootOverlay(),
+          ),
+        );
       }
       }
 
 
-      children.add(PopoverContainer(
-        direction: widget.direction,
-        popoverLink: popoverLink,
-        offset: widget.offset ?? Offset.zero,
-        popupBuilder: widget.popupBuilder,
-        onClose: () => close(),
-        onCloseAll: () => closeAll(),
-      ));
+      children.add(
+        PopoverContainer(
+          direction: widget.direction,
+          popoverLink: popoverLink,
+          offset: widget.offset ?? Offset.zero,
+          popupBuilder: widget.popupBuilder,
+          onClose: () => close(),
+          onCloseAll: () => _removeRootOverlay(),
+        ),
+      );
 
 
       return Stack(children: children);
       return Stack(children: children);
     });
     });
 
 
-    _overlayEntry = newEntry;
-
-    Overlay.of(context)?.insert(newEntry);
+    _rootEntry.addEntry(context, this, newEntry, widget.asBarrier);
   }
   }
 
 
-  close() {
-    if (_overlayEntry != null) {
-      _overlayEntry!.remove();
-      _overlayEntry = null;
-      if (_popoverWithMask == this) {
-        _popoverWithMask = null;
-      }
-      if (widget.onClose != null) {
-        widget.onClose!();
-      }
+  void close() {
+    if (_rootEntry.contains(this)) {
+      _rootEntry.removeEntry(this);
+      widget.onClose?.call();
     }
     }
+  }
+
+  void _removeRootOverlay() {
+    _rootEntry.popEntry();
 
 
     if (widget.mutex?.state == this) {
     if (widget.mutex?.state == this) {
-      widget.mutex!.state = null;
+      widget.mutex?.removeState();
     }
     }
   }
   }
 
 
-  closeAll() {
-    _popoverWithMask?.close();
-  }
-
   @override
   @override
   void deactivate() {
   void deactivate() {
     close();
     close();
     super.deactivate();
     super.deactivate();
   }
   }
 
 
-  _handleTargetPointerDown(PointerDownEvent event) {
-    if (widget.triggerActions & PopoverTriggerActionFlags.click != 0) {
-      showOverlay();
-    }
-  }
-
-  _handleTargetPointerEnter(PointerEnterEvent event) {
-    if (widget.triggerActions & PopoverTriggerActionFlags.hover != 0) {
-      showOverlay();
-    }
-  }
-
-  _buildContent(BuildContext context) {
-    if (widget.triggerActions == 0) {
-      return widget.child;
-    }
-
-    return MouseRegion(
-      onEnter: _handleTargetPointerEnter,
-      child: Listener(
-        onPointerDown: _handleTargetPointerDown,
-        child: widget.child,
-      ),
-    );
-  }
-
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return PopoverTarget(
     return PopoverTarget(
       link: popoverLink,
       link: popoverLink,
-      child: _buildContent(context),
+      child: _buildChild(context),
     );
     );
   }
   }
-}
-
-class _PopoverMask extends StatefulWidget {
-  final void Function() onTap;
-  final void Function()? onExit;
-  final Decoration? decoration;
-
-  const _PopoverMask(
-      {Key? key, required this.onTap, this.onExit, this.decoration})
-      : super(key: key);
-
-  @override
-  State<StatefulWidget> createState() => _PopoverMaskState();
-}
-
-class _PopoverMaskState extends State<_PopoverMask> {
-  @override
-  void initState() {
-    HardwareKeyboard.instance.addHandler(_handleGlobalKeyEvent);
-    super.initState();
-  }
 
 
-  bool _handleGlobalKeyEvent(KeyEvent event) {
-    if (event.logicalKey == LogicalKeyboardKey.escape) {
-      if (widget.onExit != null) {
-        widget.onExit!();
-      }
-
-      return true;
+  Widget _buildChild(BuildContext context) {
+    if (widget.triggerActions == 0) {
+      return widget.child;
     }
     }
-    return false;
-  }
 
 
-  @override
-  void deactivate() {
-    HardwareKeyboard.instance.removeHandler(_handleGlobalKeyEvent);
-    super.deactivate();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return GestureDetector(
-      onTap: widget.onTap,
-      child: Container(
-        // decoration: widget.decoration,
-        decoration: widget.decoration ??
-            const BoxDecoration(
-              color: Color.fromARGB(0, 244, 67, 54),
-            ),
+    return MouseRegion(
+      onEnter: (event) {
+        _debounceEnterRegionAction =
+            Timer(const Duration(milliseconds: 200), () {
+          if (widget.triggerActions & PopoverTriggerFlags.hover != 0) {
+            showOverlay();
+          }
+        });
+      },
+      onExit: (event) {
+        _debounceEnterRegionAction?.cancel();
+        _debounceEnterRegionAction = null;
+      },
+      child: Listener(
+        child: widget.child,
+        onPointerDown: (PointerDownEvent event) {
+          if (widget.triggerActions & PopoverTriggerFlags.click != 0) {
+            showOverlay();
+          }
+        },
       ),
       ),
     );
     );
   }
   }

+ 6 - 3
frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/appflowy_stype_popover.dart

@@ -1,8 +1,8 @@
 import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
-import 'package:appflowy_popover/popover.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 
 
-class AppFlowyStylePopover extends StatelessWidget {
+class AppFlowyPopover extends StatelessWidget {
   final Widget child;
   final Widget child;
   final PopoverController? controller;
   final PopoverController? controller;
   final Widget Function(BuildContext context) popupBuilder;
   final Widget Function(BuildContext context) popupBuilder;
@@ -12,8 +12,9 @@ class AppFlowyStylePopover extends StatelessWidget {
   final void Function()? onClose;
   final void Function()? onClose;
   final PopoverMutex? mutex;
   final PopoverMutex? mutex;
   final Offset? offset;
   final Offset? offset;
+  final bool asBarrier;
 
 
-  const AppFlowyStylePopover({
+  const AppFlowyPopover({
     Key? key,
     Key? key,
     required this.child,
     required this.child,
     required this.popupBuilder,
     required this.popupBuilder,
@@ -24,6 +25,7 @@ class AppFlowyStylePopover extends StatelessWidget {
     this.triggerActions = 0,
     this.triggerActions = 0,
     this.offset,
     this.offset,
     this.controller,
     this.controller,
+    this.asBarrier = false,
   }) : super(key: key);
   }) : super(key: key);
 
 
   @override
   @override
@@ -33,6 +35,7 @@ class AppFlowyStylePopover extends StatelessWidget {
       onClose: onClose,
       onClose: onClose,
       direction: direction,
       direction: direction,
       mutex: mutex,
       mutex: mutex,
+      asBarrier: asBarrier,
       triggerActions: triggerActions,
       triggerActions: triggerActions,
       popupBuilder: (context) {
       popupBuilder: (context) {
         final child = popupBuilder(context);
         final child = popupBuilder(context);

+ 30 - 9
frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/text.dart

@@ -8,6 +8,7 @@ class FlowyText extends StatelessWidget {
   final double fontSize;
   final double fontSize;
   final FontWeight fontWeight;
   final FontWeight fontWeight;
   final TextAlign? textAlign;
   final TextAlign? textAlign;
+  final int? maxLines;
   final Color? color;
   final Color? color;
 
 
   const FlowyText(
   const FlowyText(
@@ -18,21 +19,40 @@ class FlowyText extends StatelessWidget {
     this.fontWeight = FontWeight.w400,
     this.fontWeight = FontWeight.w400,
     this.textAlign,
     this.textAlign,
     this.color,
     this.color,
+    this.maxLines = 1,
   }) : super(key: key);
   }) : super(key: key);
 
 
-  const FlowyText.semibold(this.title,
-      {Key? key, this.fontSize = 16, this.overflow, this.color, this.textAlign})
-      : fontWeight = FontWeight.w600,
+  const FlowyText.semibold(
+    this.title, {
+    Key? key,
+    this.fontSize = 16,
+    this.overflow,
+    this.color,
+    this.textAlign,
+    this.maxLines = 1,
+  })  : fontWeight = FontWeight.w600,
         super(key: key);
         super(key: key);
 
 
-  const FlowyText.medium(this.title,
-      {Key? key, this.fontSize = 16, this.overflow, this.color, this.textAlign})
-      : fontWeight = FontWeight.w500,
+  const FlowyText.medium(
+    this.title, {
+    Key? key,
+    this.fontSize = 16,
+    this.overflow,
+    this.color,
+    this.textAlign,
+    this.maxLines = 1,
+  })  : fontWeight = FontWeight.w500,
         super(key: key);
         super(key: key);
 
 
-  const FlowyText.regular(this.title,
-      {Key? key, this.fontSize = 16, this.overflow, this.color, this.textAlign})
-      : fontWeight = FontWeight.w400,
+  const FlowyText.regular(
+    this.title, {
+    Key? key,
+    this.fontSize = 16,
+    this.overflow,
+    this.color,
+    this.textAlign,
+    this.maxLines = 1,
+  })  : fontWeight = FontWeight.w400,
         super(key: key);
         super(key: key);
 
 
   @override
   @override
@@ -40,6 +60,7 @@ class FlowyText extends StatelessWidget {
     final theme = context.watch<AppTheme>();
     final theme = context.watch<AppTheme>();
     return Text(
     return Text(
       title,
       title,
+      maxLines: maxLines,
       textAlign: textAlign,
       textAlign: textAlign,
       overflow: overflow ?? TextOverflow.clip,
       overflow: overflow ?? TextOverflow.clip,
       style: TextStyle(
       style: TextStyle(

+ 0 - 2
frontend/app_flowy/packages/flowy_infra_ui/pubspec.yaml

@@ -29,8 +29,6 @@ dependencies:
     path: flowy_infra_ui_web
     path: flowy_infra_ui_web
   appflowy_popover:
   appflowy_popover:
     path: ../appflowy_popover
     path: ../appflowy_popover
-
-  # Flowy packages
   flowy_infra:
   flowy_infra:
     path: ../flowy_infra
     path: ../flowy_infra
 
 

+ 15 - 1
frontend/app_flowy/pubspec.lock

@@ -35,7 +35,7 @@ packages:
       path: "packages/appflowy_editor"
       path: "packages/appflowy_editor"
       relative: true
       relative: true
     source: path
     source: path
-    version: "0.0.4"
+    version: "0.0.5"
   appflowy_popover:
   appflowy_popover:
     dependency: "direct main"
     dependency: "direct main"
     description:
     description:
@@ -43,6 +43,13 @@ packages:
       relative: true
       relative: true
     source: path
     source: path
     version: "0.0.1"
     version: "0.0.1"
+  archive:
+    dependency: transitive
+    description:
+      name: archive
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.3.1"
   args:
   args:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -660,6 +667,13 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "0.17.0"
     version: "0.17.0"
+  intl_utils:
+    dependency: transitive
+    description:
+      name: intl_utils
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.7.0"
   io:
   io:
     dependency: transitive
     dependency: transitive
     description:
     description:

+ 14 - 12
frontend/rust-lib/dart-ffi/Cargo.toml

@@ -12,30 +12,32 @@ crate-type = ["staticlib"]
 
 
 
 
 [dependencies]
 [dependencies]
-allo-isolate = {version = "^0.1", features = ["catch-unwind",]}
-byteorder = {version = "1.3.4"}
-ffi-support = {version = "0.4.2"}
-protobuf = {version = "2.20.0"}
+allo-isolate = { version = "^0.1", features = ["catch-unwind"] }
+byteorder = { version = "1.3.4" }
+ffi-support = { version = "0.4.2" }
+protobuf = { version = "2.20.0" }
 tokio = { version = "1", features = ["rt", "rt-multi-thread"] }
 tokio = { version = "1", features = ["rt", "rt-multi-thread"] }
 log = "0.4.14"
 log = "0.4.14"
 serde = { version = "1.0", features = ["derive"] }
 serde = { version = "1.0", features = ["derive"] }
-serde_json = {version = "1.0"}
+serde_json = { version = "1.0" }
 bytes = { version = "1.0" }
 bytes = { version = "1.0" }
 once_cell = "1"
 once_cell = "1"
 crossbeam-utils = "0.8.7"
 crossbeam-utils = "0.8.7"
 
 
 
 
-lib-dispatch = {path = "../lib-dispatch" }
-flowy-sdk = {path = "../flowy-sdk"}
-dart-notify = {path = "../dart-notify" }
-flowy-derive = {path = "../../../shared-lib/flowy-derive" }
+lib-dispatch = { path = "../lib-dispatch" }
+flowy-sdk = { path = "../flowy-sdk" }
+dart-notify = { path = "../dart-notify" }
+flowy-derive = { path = "../../../shared-lib/flowy-derive" }
 
 
 [features]
 [features]
 default = ["flowy-sdk/dart", "dart-notify/dart", "flutter"]
 default = ["flowy-sdk/dart", "dart-notify/dart", "flutter"]
 flutter = []
 flutter = []
 http_sync = ["flowy-sdk/http_sync", "flowy-sdk/use_bunyan"]
 http_sync = ["flowy-sdk/http_sync", "flowy-sdk/use_bunyan"]
-#use_serde = ["bincode"]
-#use_protobuf= ["protobuf"]
+openssl_vendored = ["flowy-sdk/openssl_vendored"]
 
 
 [build-dependencies]
 [build-dependencies]
-lib-infra = { path = "../../../shared-lib/lib-infra", features = ["protobuf_file_gen", "dart"] }
+lib-infra = { path = "../../../shared-lib/lib-infra", features = [
+    "protobuf_file_gen",
+    "dart",
+] }

+ 17 - 14
frontend/rust-lib/flowy-database/Cargo.toml

@@ -1,14 +1,17 @@
-[package]
-name = "flowy-database"
-version = "0.1.0"
-edition = "2018"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-diesel = {version = "1.4.8", features = ["sqlite"]}
-diesel_derives = {version = "1.4.1", features = ["sqlite"]}
-diesel_migrations = {version = "1.4.0", features = ["sqlite"]}
-lib-sqlite = { path = "../lib-sqlite" }
-log = "0.4"
-lazy_static = "1.4.0"
+[package]
+name = "flowy-database"
+version = "0.1.0"
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+diesel = { version = "1.4.8", features = ["sqlite"] }
+diesel_derives = { version = "1.4.1", features = ["sqlite"] }
+diesel_migrations = { version = "1.4.0", features = ["sqlite"] }
+lib-sqlite = { path = "../lib-sqlite" }
+log = "0.4"
+lazy_static = "1.4.0"
+
+[features]
+openssl_vendored = ["lib-sqlite/openssl_vendored"]

+ 52 - 44
frontend/rust-lib/flowy-sdk/Cargo.toml

@@ -1,44 +1,52 @@
-[package]
-name = "flowy-sdk"
-version = "0.1.0"
-edition = "2018"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-lib-dispatch = { path = "../lib-dispatch" }
-lib-log = { path = "../lib-log" }
-flowy-user = { path = "../flowy-user" }
-flowy-net = { path = "../flowy-net" }
-flowy-folder = { path = "../flowy-folder", default-features = false }
-flowy-grid = { path = "../flowy-grid", default-features = false }
-flowy-grid-data-model = { path = "../../../shared-lib/flowy-grid-data-model" }
-flowy-database = { path = "../flowy-database" }
-flowy-text-block = { path = "../flowy-text-block", default-features = false }
-flowy-revision = { path = "../flowy-revision" }
-
-tracing = { version = "0.1" }
-log = "0.4.14"
-futures-core = { version = "0.3", default-features = false }
-color-eyre = { version = "0.5", default-features = false }
-bytes = "1.0"
-tokio = { version = "1", features = ["rt"] }
-parking_lot = "0.11"
-
-flowy-sync = { path = "../../../shared-lib/flowy-sync" }
-lib-ws = { path = "../../../shared-lib/lib-ws" }
-lib-infra = { path = "../../../shared-lib/lib-infra" }
-
-[dev-dependencies]
-serde = { version = "1.0", features = ["derive"] }
-bincode = { version = "1.3"}
-protobuf = {version = "2.24.1"}
-claim = "0.5.0"
-tokio = { version = "1", features = ["full"]}
-futures-util = "0.3.15"
-
-[features]
-http_sync = ["flowy-folder/cloud_sync", "flowy-text-block/cloud_sync"]
-native_sync = ["flowy-folder/cloud_sync", "flowy-text-block/cloud_sync"]
-use_bunyan = ["lib-log/use_bunyan"]
-dart = ["flowy-user/dart", "flowy-net/dart", "flowy-folder/dart", "flowy-sync/dart", "flowy-grid/dart", "flowy-text-block/dart"]
+[package]
+name = "flowy-sdk"
+version = "0.1.0"
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+lib-dispatch = { path = "../lib-dispatch" }
+lib-log = { path = "../lib-log" }
+flowy-user = { path = "../flowy-user" }
+flowy-net = { path = "../flowy-net" }
+flowy-folder = { path = "../flowy-folder", default-features = false }
+flowy-grid = { path = "../flowy-grid", default-features = false }
+flowy-grid-data-model = { path = "../../../shared-lib/flowy-grid-data-model" }
+flowy-database = { path = "../flowy-database" }
+flowy-text-block = { path = "../flowy-text-block", default-features = false }
+flowy-revision = { path = "../flowy-revision" }
+
+tracing = { version = "0.1" }
+log = "0.4.14"
+futures-core = { version = "0.3", default-features = false }
+color-eyre = { version = "0.5", default-features = false }
+bytes = "1.0"
+tokio = { version = "1", features = ["rt"] }
+parking_lot = "0.11"
+
+flowy-sync = { path = "../../../shared-lib/flowy-sync" }
+lib-ws = { path = "../../../shared-lib/lib-ws" }
+lib-infra = { path = "../../../shared-lib/lib-infra" }
+
+[dev-dependencies]
+serde = { version = "1.0", features = ["derive"] }
+bincode = { version = "1.3" }
+protobuf = { version = "2.24.1" }
+claim = "0.5.0"
+tokio = { version = "1", features = ["full"] }
+futures-util = "0.3.15"
+
+[features]
+http_sync = ["flowy-folder/cloud_sync", "flowy-text-block/cloud_sync"]
+native_sync = ["flowy-folder/cloud_sync", "flowy-text-block/cloud_sync"]
+use_bunyan = ["lib-log/use_bunyan"]
+dart = [
+    "flowy-user/dart",
+    "flowy-net/dart",
+    "flowy-folder/dart",
+    "flowy-sync/dart",
+    "flowy-grid/dart",
+    "flowy-text-block/dart",
+]
+openssl_vendored = ["flowy-database/openssl_vendored"]

+ 1 - 0
frontend/rust-lib/flowy-text-block/src/editor.rs

@@ -24,6 +24,7 @@ use tokio::sync::{mpsc, oneshot};
 
 
 pub struct TextBlockEditor {
 pub struct TextBlockEditor {
     pub doc_id: String,
     pub doc_id: String,
+    #[allow(dead_code)]
     rev_manager: Arc<RevisionManager>,
     rev_manager: Arc<RevisionManager>,
     #[cfg(feature = "sync")]
     #[cfg(feature = "sync")]
     ws_manager: Arc<flowy_revision::RevisionWebSocketManager>,
     ws_manager: Arc<flowy_revision::RevisionWebSocketManager>,

+ 8 - 7
frontend/rust-lib/lib-sqlite/Cargo.toml

@@ -7,14 +7,15 @@ edition = "2018"
 
 
 [dependencies]
 [dependencies]
 r2d2 = "0.8.9"
 r2d2 = "0.8.9"
-libsqlite3-sys = {version = ">=0.8.0, <0.24.0", features = ["bundled"]}
-diesel = {version = "1.4.8", features = ["sqlite"]}
-diesel_derives = {version = "1.4.1", features = ["sqlite"]}
-diesel_migrations = {version = "1.4.0", features = ["sqlite"]}
+libsqlite3-sys = { version = ">=0.8.0, <0.24.0", features = ["bundled"] }
+diesel = { version = "1.4.8", features = ["sqlite"] }
+diesel_derives = { version = "1.4.1", features = ["sqlite"] }
+diesel_migrations = { version = "1.4.0", features = ["sqlite"] }
 lazy_static = "1.4.0"
 lazy_static = "1.4.0"
 scheduled-thread-pool = "0.2.5"
 scheduled-thread-pool = "0.2.5"
 error-chain = "=0.12.0"
 error-chain = "=0.12.0"
 log = "0.4.11"
 log = "0.4.11"
-openssl = { version = "0.10.38", features = ["vendored"] }
-#[features]
-#windows = ["libsqlite3-sys/bundled-windows"]
+openssl = { version = "0.10.38", optional = true }
+
+[features]
+openssl_vendored = ["openssl/vendored"]