Browse Source

Merge remote-tracking branch 'origin/main' into image_selection_area

Lucas.Xu 2 years ago
parent
commit
9c40a501fe
100 changed files with 1168 additions and 1787 deletions
  1. 2 0
      .github/workflows/appflowy_editor_test.yml
  2. 5 1
      .github/workflows/ci.yaml
  3. 3 1
      .github/workflows/dart_lint.yml
  4. 6 9
      .github/workflows/dart_test.yml
  5. 78 1
      .github/workflows/release.yml
  6. 2 0
      .github/workflows/rust_coverage.yml
  7. 2 0
      .github/workflows/rust_lint.yml
  8. 3 1
      .github/workflows/rust_test.yml
  9. 28 12
      CHANGELOG.md
  10. 8 4
      README.md
  11. 2 1
      frontend/Makefile.toml
  12. 5 5
      frontend/app_flowy/android/README.md
  13. 93 0
      frontend/app_flowy/assets/google_fonts/Poppins/OFL.txt
  14. BIN
      frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Black.ttf
  15. BIN
      frontend/app_flowy/assets/google_fonts/Poppins/Poppins-BlackItalic.ttf
  16. BIN
      frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Bold.ttf
  17. BIN
      frontend/app_flowy/assets/google_fonts/Poppins/Poppins-BoldItalic.ttf
  18. BIN
      frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ExtraBold.ttf
  19. BIN
      frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ExtraBoldItalic.ttf
  20. BIN
      frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ExtraLight.ttf
  21. BIN
      frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ExtraLightItalic.ttf
  22. BIN
      frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Italic.ttf
  23. BIN
      frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Light.ttf
  24. BIN
      frontend/app_flowy/assets/google_fonts/Poppins/Poppins-LightItalic.ttf
  25. BIN
      frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Medium.ttf
  26. BIN
      frontend/app_flowy/assets/google_fonts/Poppins/Poppins-MediumItalic.ttf
  27. BIN
      frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Regular.ttf
  28. BIN
      frontend/app_flowy/assets/google_fonts/Poppins/Poppins-SemiBold.ttf
  29. BIN
      frontend/app_flowy/assets/google_fonts/Poppins/Poppins-SemiBoldItalic.ttf
  30. BIN
      frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Thin.ttf
  31. BIN
      frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ThinItalic.ttf
  32. 2 2
      frontend/app_flowy/assets/translations/en.json
  33. 2 2
      frontend/app_flowy/assets/translations/fr-FR.json
  34. 1 1
      frontend/app_flowy/assets/translations/id-ID.json
  35. 3 3
      frontend/app_flowy/assets/translations/pt-BR.json
  36. 235 0
      frontend/app_flowy/assets/translations/sv.json
  37. 14 7
      frontend/app_flowy/lib/core/grid_notification.dart
  38. 6 3
      frontend/app_flowy/lib/core/network_monitor.dart
  39. 3 0
      frontend/app_flowy/lib/plugins/blank/blank.dart
  40. 5 4
      frontend/app_flowy/lib/plugins/board/application/board_bloc.dart
  41. 14 12
      frontend/app_flowy/lib/plugins/board/application/board_data_controller.dart
  42. 3 3
      frontend/app_flowy/lib/plugins/board/application/board_listener.dart
  43. 1 1
      frontend/app_flowy/lib/plugins/board/application/card/board_checkbox_cell_bloc.dart
  44. 1 1
      frontend/app_flowy/lib/plugins/board/application/card/board_date_cell_bloc.dart
  45. 1 1
      frontend/app_flowy/lib/plugins/board/application/card/board_number_cell_bloc.dart
  46. 1 1
      frontend/app_flowy/lib/plugins/board/application/card/board_select_option_cell_bloc.dart
  47. 1 1
      frontend/app_flowy/lib/plugins/board/application/card/board_text_cell_bloc.dart
  48. 1 1
      frontend/app_flowy/lib/plugins/board/application/card/board_url_cell_bloc.dart
  49. 1 0
      frontend/app_flowy/lib/plugins/board/application/card/card_bloc.dart
  50. 1 1
      frontend/app_flowy/lib/plugins/board/application/group_controller.dart
  51. 4 4
      frontend/app_flowy/lib/plugins/board/application/group_listener.dart
  52. 4 1
      frontend/app_flowy/lib/plugins/board/board.dart
  53. 24 40
      frontend/app_flowy/lib/plugins/board/presentation/board_page.dart
  54. 1 2
      frontend/app_flowy/lib/plugins/board/presentation/card/board_date_cell.dart
  55. 3 5
      frontend/app_flowy/lib/plugins/board/presentation/card/board_text_cell.dart
  56. 8 7
      frontend/app_flowy/lib/plugins/board/presentation/card/board_url_cell.dart
  57. 5 4
      frontend/app_flowy/lib/plugins/board/presentation/card/card.dart
  58. 7 9
      frontend/app_flowy/lib/plugins/board/presentation/card/container/accessory.dart
  59. 6 6
      frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_setting.dart
  60. 5 6
      frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_toolbar.dart
  61. 0 28
      frontend/app_flowy/lib/plugins/doc/application/share_service.dart
  62. 0 74
      frontend/app_flowy/lib/plugins/doc/presentation/style_widgets.dart
  63. 0 97
      frontend/app_flowy/lib/plugins/doc/presentation/toolbar/check_button.dart
  64. 0 280
      frontend/app_flowy/lib/plugins/doc/presentation/toolbar/color_picker.dart
  65. 0 101
      frontend/app_flowy/lib/plugins/doc/presentation/toolbar/header_button.dart
  66. 0 33
      frontend/app_flowy/lib/plugins/doc/presentation/toolbar/history_button.dart
  67. 0 85
      frontend/app_flowy/lib/plugins/doc/presentation/toolbar/image_button.dart
  68. 0 98
      frontend/app_flowy/lib/plugins/doc/presentation/toolbar/link_button.dart
  69. 0 84
      frontend/app_flowy/lib/plugins/doc/presentation/toolbar/toggle_button.dart
  70. 0 308
      frontend/app_flowy/lib/plugins/doc/presentation/toolbar/tool_bar.dart
  71. 0 38
      frontend/app_flowy/lib/plugins/doc/presentation/toolbar/toolbar_icon_button.dart
  72. 0 133
      frontend/app_flowy/lib/plugins/doc/styles.dart
  73. 93 55
      frontend/app_flowy/lib/plugins/document/application/doc_bloc.dart
  74. 15 8
      frontend/app_flowy/lib/plugins/document/application/doc_service.dart
  75. 0 0
      frontend/app_flowy/lib/plugins/document/application/prelude.dart
  76. 19 31
      frontend/app_flowy/lib/plugins/document/application/share_bloc.dart
  77. 30 0
      frontend/app_flowy/lib/plugins/document/application/share_service.dart
  78. 31 30
      frontend/app_flowy/lib/plugins/document/document.dart
  79. 31 54
      frontend/app_flowy/lib/plugins/document/document_page.dart
  80. 65 0
      frontend/app_flowy/lib/plugins/document/editor_styles.dart
  81. 13 14
      frontend/app_flowy/lib/plugins/document/presentation/banner.dart
  82. 168 0
      frontend/app_flowy/lib/plugins/document/presentation/plugins/horizontal_rule_node_widget.dart
  83. 2 2
      frontend/app_flowy/lib/plugins/grid/application/block/block_cache.dart
  84. 7 5
      frontend/app_flowy/lib/plugins/grid/application/block/block_listener.dart
  85. 2 2
      frontend/app_flowy/lib/plugins/grid/application/cell/cell_listener.dart
  86. 5 5
      frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_controller.dart
  87. 1 1
      frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_data_loader.dart
  88. 8 5
      frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_data_persistence.dart
  89. 2 2
      frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_service.dart
  90. 1 1
      frontend/app_flowy/lib/plugins/grid/application/cell/checkbox_cell_bloc.dart
  91. 1 1
      frontend/app_flowy/lib/plugins/grid/application/cell/date_cal_bloc.dart
  92. 1 1
      frontend/app_flowy/lib/plugins/grid/application/cell/date_cell_bloc.dart
  93. 1 1
      frontend/app_flowy/lib/plugins/grid/application/cell/number_cell_bloc.dart
  94. 1 1
      frontend/app_flowy/lib/plugins/grid/application/cell/select_option_cell_bloc.dart
  95. 36 25
      frontend/app_flowy/lib/plugins/grid/application/cell/select_option_editor_bloc.dart
  96. 12 13
      frontend/app_flowy/lib/plugins/grid/application/cell/select_option_service.dart
  97. 1 1
      frontend/app_flowy/lib/plugins/grid/application/cell/text_cell_bloc.dart
  98. 1 1
      frontend/app_flowy/lib/plugins/grid/application/cell/url_cell_bloc.dart
  99. 1 1
      frontend/app_flowy/lib/plugins/grid/application/cell/url_cell_editor_bloc.dart
  100. 20 5
      frontend/app_flowy/lib/plugins/grid/application/field/field_action_sheet_bloc.dart

+ 2 - 0
.github/workflows/appflowy_editor_test.yml

@@ -4,10 +4,12 @@ on:
   push:
     branches:
       - "main"
+      - "release/*"
 
   pull_request:
     branches:
       - "main"
+      - "release/*"
     paths:
       - "frontend/app_flowy/packages/appflowy_editor/**"
 

+ 5 - 1
.github/workflows/ci.yaml

@@ -4,10 +4,12 @@ on:
   push:
     branches:
       - "main"
+      - "release/*"
 
   pull_request:
     branches:
       - "main"
+      - "release/*"
 
 jobs:
   build:
@@ -62,6 +64,8 @@ jobs:
             sudo apt-get update
             sudo apt-get install -y dart curl build-essential libsqlite3-dev libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev
             sudo apt-get install keybinder-3.0
+          elif [ "$RUNNER_OS" == "Windows" ]; then
+            vcpkg integrate install
           elif [ "$RUNNER_OS" == "macOS" ]; then
             echo 'do nothing'
           fi
@@ -85,7 +89,7 @@ jobs:
             flutter config --enable-linux-desktop
           elif [ "$RUNNER_OS" == "macOS" ]; then
             flutter config --enable-macos-desktop
-          elif [ "$RUNNER_OS" == "windows" ]; then
+          elif [ "$RUNNER_OS" == "Windows" ]; then
             flutter config --enable-windows-desktop
           fi
         shell: bash

+ 3 - 1
.github/workflows/dart_lint.yml

@@ -9,12 +9,14 @@ on:
   push:
     branches:
       - "main"
+      - "release/*"
     paths:
       - "frontend/app_flowy/**"
 
   pull_request:
     branches:
       - "main"
+      - "release/*"
     paths:
       - "frontend/app_flowy/**"
 
@@ -73,7 +75,7 @@ jobs:
         run: |
           cargo make --profile development-linux-x86_64 flowy-sdk-dev
 
-      - name: Code Generation
+      - name: Flutter Code Generation
         working-directory: frontend/app_flowy
         run: |
           flutter packages pub run easy_localization:generate -f keys -o locale_keys.g.dart -S assets/translations -s en.json

+ 6 - 9
.github/workflows/dart_test.yml

@@ -1,15 +1,17 @@
-name: Unit test(Flutter)
+name: Frontend test
 
 on:
   push:
     branches:
       - "main"
+      - "release/*"
     paths:
       - "frontend/app_flowy/**"
 
   pull_request:
     branches:
       - "main"
+      - "release/*"
     paths:
       - "frontend/app_flowy/**"
 
@@ -21,7 +23,6 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
-
       - uses: actions-rs/toolchain@v1
         with:
           toolchain: "stable-2022-04-07"
@@ -53,10 +54,6 @@ jobs:
         working-directory: frontend
         run: |
           cargo install cargo-make
-
-      - name: Cargo make flowy dev
-        working-directory: frontend
-        run: |
           cargo make flowy_dev
 
       - name: Flutter Deps
@@ -64,12 +61,12 @@ jobs:
         run: |
           flutter config --enable-linux-desktop
 
-      - name: Build FlowySDK
+      - name: Build Test lib
         working-directory: frontend
         run: |
-          cargo make --profile test-linux test-lib-build
+          cargo make --profile test-linux build-test-lib
 
-      - name: Code Generation
+      - name: Flutter Code Generation
         working-directory: frontend/app_flowy
         run: |
           flutter packages pub get

+ 78 - 1
.github/workflows/release.yml

@@ -28,7 +28,7 @@ jobs:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         with:
           tag_name: ${{ github.ref }}
-          release_name: Release ${{ github.ref }}
+          release_name: v${{ github.ref }}
           body_path: ${{ env.RELEASE_NOTES_PATH }}
 
   build-linux-x86:
@@ -37,6 +37,7 @@ jobs:
     env:
       LINUX_APP_RELEASE_PATH: frontend/app_flowy/product/${{ github.ref_name }}/linux/Release
       LINUX_ZIP_NAME: AppFlowy-linux-x86.tar.gz
+      LINUX_PACKAGE_NAME: AppFlowy_${{ github.ref_name }}_linux-amd64.deb
     steps:
       - name: Checkout
         uses: actions/checkout@v2
@@ -70,6 +71,46 @@ jobs:
           flutter config --enable-linux-desktop
           cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-linux-x86_64 appflowy
 
+      - name: Build Linux package
+        working-directory: ${{ env.LINUX_APP_RELEASE_PATH }}
+        run: |
+          mkdir -p package/opt && mv AppFlowy package/opt/
+          cd package && mkdir DEBIAN
+          # Create control file
+          printf 'Package: AppFlowy
+          Version: %s
+          Architecture: amd64
+          Essential: no
+          Priority: optional
+          Maintainer: AppFlowy
+          Description: An Open Source Alternative to Notion\n' "${{ github.ref_name }}" > DEBIAN/control
+
+          # postinst script for creating symlink
+          printf '#!/bin/bash
+          if [ -e /usr/local/bin/appflowy ]; then
+            echo "Symlink already exists, skipping."
+          else
+            echo "Creating Symlink in /usr/local/bin/appflowy"
+            ln -s /opt/AppFlowy/app_flowy /usr/local/bin/appflowy
+          fi' > DEBIAN/postinst
+          chmod 0755 DEBIAN/postinst
+
+          # postrm script for cleaning up residuals
+          printf '#!/bin/bash
+          if [ -e /usr/local/bin/appflowy ]; then
+            rm /usr/local/bin/appflowy
+          fi' > DEBIAN/postrm
+          chmod 0755 DEBIAN/postrm
+
+          mkdir -p usr/share/applications
+          # Update Exec & icon path in desktop entry
+          grep -rl "\[CHANGE_THIS\]" ./opt/AppFlowy/appflowy.desktop.temp | xargs sed -i "s/\[CHANGE_THIS\]/\/opt/"
+          # Add desktop entry in package
+          mv ./opt/AppFlowy/appflowy.desktop.temp ./usr/share/applications/appflowy.desktop
+
+          # Build
+          cd ../ && dpkg-deb --build --root-owner-group package ${{ env.LINUX_PACKAGE_NAME }}
+
       - name: Upload Release Asset
         id: upload-release-asset
         uses: actions/upload-release-asset@v1
@@ -81,12 +122,24 @@ jobs:
           asset_name: ${{ env.LINUX_ZIP_NAME }}
           asset_content_type: application/octet-stream
 
+      - name: Upload Release Asset Install Package
+        id: upload-release-asset-install-package
+        uses: actions/upload-release-asset@v1
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          upload_url: ${{ needs.create-release.outputs.upload_url }}
+          asset_path: ${{ env.LINUX_APP_RELEASE_PATH }}/${{ env.LINUX_PACKAGE_NAME }}
+          asset_name: ${{ env.LINUX_PACKAGE_NAME }}
+          asset_content_type: application/octet-stream
+
   build-macos-x86_64:
     runs-on: macos-latest
     needs: create-release
     env:
       MACOS_APP_RELEASE_PATH: frontend/app_flowy/product/${{ github.ref_name }}/macos/Release
       MACOS_X86_ZIP_NAME: Appflowy-macos-x86_64.zip
+      MACOS_DMG_NAME: Appflowy-macos-x86_64-installer
     steps:
       - name: Checkout
         uses: actions/checkout@v2
@@ -116,6 +169,21 @@ jobs:
           flutter config --enable-macos-desktop
           cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-mac-x86_64 appflowy
 
+      - name: Create MacOS dmg
+        working-directory: frontend
+        run: |
+            brew install create-dmg
+            create-dmg \
+            --volname ${{ env.MACOS_DMG_NAME }} \
+            --hide-extension "AppFlowy.app" \
+            --background scripts/dmg_assets/AppFlowyInstallerBackground.jpg \
+            --window-size 600 450 \
+            --icon-size 94 \
+            --icon "AppFlowy.app" 141 249 \
+            --app-drop-link 458 249 \
+            "${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_DMG_NAME }}.dmg" \
+            "${{ env.MACOS_APP_RELEASE_PATH }}/AppFlowy.app"
+
       - name: Archive macOS app
         working-directory: ${{ env.MACOS_APP_RELEASE_PATH }}
         run: zip --symlinks -qr ${{ env.MACOS_X86_ZIP_NAME }} AppFlowy.app
@@ -129,6 +197,15 @@ jobs:
           asset_path: ${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_X86_ZIP_NAME }}
           asset_name: ${{ env.MACOS_X86_ZIP_NAME }}
           asset_content_type: application/octet-stream
+      - name: Upload DMG Release Asset
+        uses: actions/upload-release-asset@v1
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+              upload_url: ${{ needs.create-release.outputs.upload_url }}
+              asset_path: ${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_DMG_NAME }}.dmg
+              asset_name: ${{ env.MACOS_DMG_NAME }}.dmg
+              asset_content_type: application/octet-stream
 
   build-windows-x86_64:
     runs-on: windows-latest

+ 2 - 0
.github/workflows/rust_coverage.yml

@@ -4,6 +4,7 @@ on:
   push:
     branches:
       - "main"
+      - "release/*"
     paths:
       - "frontend/rust-lib/**"
       - "shared-lib/**"
@@ -11,6 +12,7 @@ on:
   pull_request:
     branches:
       - "main"
+      - "release/*"
     paths:
       - "frontend/rust-lib/**"
       - "shared-lib/**"

+ 2 - 0
.github/workflows/rust_lint.yml

@@ -4,6 +4,7 @@ on:
   push:
     branches:
       - "main"
+      - "release/*"
     paths:
       - "frontend/rust-lib/**"
       - "shared-lib/**"
@@ -11,6 +12,7 @@ on:
   pull_request:
     branches:
       - "main"
+      - "release/*"
     paths:
       - "frontend/rust-lib/**"
       - "shared-lib/**"

+ 3 - 1
.github/workflows/rust_test.yml

@@ -1,9 +1,10 @@
-name: Unit test(Rust)
+name: Backend test
 
 on:
   push:
     branches:
       - "main"
+      - "release/*"
     paths:
       - "frontend/rust-lib/**"
       - "shared-lib/**"
@@ -11,6 +12,7 @@ on:
   pull_request:
     branches:
       - "main"
+      - "release/*"
     paths:
       - "frontend/rust-lib/**"
       - "shared-lib/**"

+ 28 - 12
CHANGELOG.md

@@ -1,8 +1,24 @@
 # Release Notes
 
+## Version 0.0.6.2 - 10/30/2022
+- Fix some bugs
+
+## Version 0.0.6.1 - 10/26/2022
+### New features
+- Optimzie appflowy_editor dark mode style
+
+### Bug Fixes
+- Unable to copy the text with checkbox or link style
+
+## Version 0.0.6 - 10/23/2022
+
+### New features
+- Integrate **appflowy_editor** 
+
+
 ## Version 0.0.5.3 - 09/26/2022
 
-New features
+### New features
 - Open the next page automatically after deleting the current page
 - Refresh the Kanban board after altering a property type
 
@@ -16,7 +32,7 @@ New features
 
 ## Version 0.0.5.2 - 09/16/2022
 
-New features
+### New features
 - Enable adding a new card to the "No Status" group
 - Fix some bugs
 
@@ -27,19 +43,19 @@ New features
 
 ## Version 0.0.5.1 - 09/14/2022
 
-New features
+### New features
 - Enable deleting a field in board 
 - Fix some bugs
 
 
 ## Version 0.0.5 - 09/08/2022
-New Features - Kanban Board like Notion and Trello beta
+### New features - Kanban Board like Notion and Trello beta
 Boards are the best way to manage projects & tasks. Use them to group your databases by select, multiselect, and checkbox.
 
 <p align="left"><img src="https://user-images.githubusercontent.com/12026239/190055984-6efa2d7a-ee38-4551-859e-ee56388e1859.gif" width="1000px" /></p>
 
-- Set up columns that represent a specific phase of the project cycle and use cards to represent each project / task
-- Drag and drop a card from one phase / column to another phase / column
+- Set up columns that represent a specific phase of the project cycle and use cards to represent each project/task
+- Drag and drop a card from one phase/column to another phase/column
 - Update database properties in the Board view by clicking on a property and making edits on the card
 
 ### Other Features & Improvements
@@ -49,7 +65,7 @@ Boards are the best way to manage projects & tasks. Use them to group your datab
 
 ## Version 0.0.5 - beta.2 - beta.1 - 09/01/2022
 
-New features
+### New features
 - Board-view database
   - Support start editing after creating a new card
   - Support editing the card directly by clicking the edit button
@@ -61,7 +77,7 @@ New features
 
 ## Version 0.0.5 - beta.1 - 08/25/2022
 
-New features
+### New features
 - Board-view database  
   - Group by single select
   - drag and drop cards
@@ -84,7 +100,7 @@ New features
 
 ## Version 0.0.4 - beta.3 - 05/02/2022
 - Drag to reorder app/ view/ field
-- Row record open as a page
+- Row record opens as a page
 - Auto resize the height of the row in the grid
 - Support more number formats
 - Search column options, supporting Single-select, Multi-select, and number format
@@ -108,7 +124,7 @@ New features
 ## Version 0.0.4 - beta.1 - 04/08/2022
 v0.0.4 - beta.1 is pre-release
 
-New features
+### New features
 - Table-view database
    - supported column types: Text, Checkbox, Single-select, Multi-select, Numbers
    - hide / delete columns
@@ -117,12 +133,12 @@ New features
 ## Version 0.0.3 - 02/23/2022
 v0.0.3 is production ready, available on Linux, macOS, and Windows
 
-New features
+### New features
 - Dark Mode 
 - Support new languages: French, Italian, Russian,  Simplified Chinese, Spanish
 - Add Settings: Toggle on Dark Mode; Select a language
 - Show device info 
-- Add tooltip on toolbar icons
+- Add tooltip on the toolbar icons
 
 Bug fixes and improvements
 - Increased height of action

+ 8 - 4
README.md

@@ -23,13 +23,15 @@ You are in charge of your data and customizations.
     <a href="https://twitter.com/appflowy"><b>Twitter</b></a>
 </p>
 
-<p align="center"><img src="https://user-images.githubusercontent.com/12026239/174754661-682980e4-e386-4685-bb6f-2da357390b61.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
+<p align="center"><img src="https://user-images.githubusercontent.com/12026239/200787830-96be260b-d0a0-4152-864e-6730b19095cd.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
 <p align="center"><img src="https://user-images.githubusercontent.com/12026239/174753177-98e4c899-2356-4137-bb42-374bba2b127b.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
 <p align="center"><img src="https://user-images.githubusercontent.com/12026239/190650183-a940f1e0-a2c5-4797-ab3a-56758f6f696c.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
 
 ## User Installation
 
-Please view the [documentation](https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods) for OS specific installation instructions.
+* [Windows/Mac/Linux](https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods/mac-windows-linux-packages)
+* [Docker](https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods/installing-with-docker)
+* [Source](https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods/from-source)
 
 ## Built With
 
@@ -49,7 +51,9 @@ Please view the [documentation](https://appflowy.gitbook.io/docs/essential-docum
 - [AppFlowy Roadmap ReadMe](https://appflowy.gitbook.io/docs/essential-documentation/roadmap)
 - [AppFlowy Public Roadmap](https://github.com/orgs/AppFlowy-IO/projects/5/views/12)
 
-If you'd like to propose a feature, submit an issue [here](https://github.com/AppFlowy-IO/appflowy/issues).
+
+If you'd like to propose a feature, submit a feature request [here](https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labels=&template=feature_request.yaml&title=%5BFR%5D+) <br/>
+If you'd like to report a bug, submit bug report [here](https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labels=&template=bug_report.yaml&title=%5BBug%5D+)
 
 ## **Releases**
 
@@ -91,7 +95,7 @@ To be honest, we do not claim to outperform Notion in terms of functionality and
 
 ## License
 
-Distributed under the AGPLv3 License. See `LICENSE.md` for more information.
+Distributed under the AGPLv3 License. See [`LICENSE.md`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/LICENSE) for more information.
 
 ## Acknowledgements
 

+ 2 - 1
frontend/Makefile.toml

@@ -22,7 +22,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
 CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
 CARGO_MAKE_CRATE_NAME = "dart-ffi"
 LIB_NAME = "dart_ffi"
-CURRENT_APP_VERSION = "0.0.5.2"
+CURRENT_APP_VERSION = "0.0.6.2"
 FEATURES = "flutter"
 PRODUCT_NAME = "AppFlowy"
 # CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html
@@ -47,6 +47,7 @@ PROTOBUF_DERIVE_CACHE = "../shared-lib/flowy-derive/src/derive_cache/derive_cach
 # Test default config
 TEST_CRATE_TYPE = "cdylib"
 TEST_LIB_EXT = "dylib"
+TEST_RUST_LOG = "info"
 TEST_BUILD_FLAG = "debug"
 TEST_COMPILE_TARGET = "x86_64-apple-darwin"
 

+ 5 - 5
frontend/app_flowy/android/README.md

@@ -12,8 +12,8 @@ When compiling for android we need the following pre-requisites:
 - [Download](https://developer.android.com/ndk/downloads/) Android NDK version 24.
 - When downloading Android NDK you can get the compressed version as a standalone from the site.
     Or you can download it through [Android Studio](https://developer.android.com/studio).
-- After downloading the two you need to set the environment variables. For Windows that's a seperate process.
-    On MacOs and Linux the process is similar.
+- After downloading the two you need to set the environment variables. For Windows that's a separate process.
+    On macOS and Linux the process is similar.
 - The variables needed are '$ANDROID_NDK_HOME', this will point to where the NDK is located.
 ---
 
@@ -48,9 +48,9 @@ linker = "/home/user/Android/Sdk/ndk/24.0.8215888/toolchains/llvm/prebuilt/linux
 
 **Folder path: 'Android/Sdk/ndk/24.0.8215888/toolchains/llvm/prebuilt/linux-x86_64/lib64/clang/14.0.1/lib/linux'.**
 After that you have to copy this file into three different folders namely aarch64, arm, i386 and x86_64.
-We have to do this so we Android NDK can find clang on our system, if we used NDK 22 we wouldnt have to do this process.
-Though using NDK v22 will not give us alot of features to work with.
-This github [issue](https://github.com/fzyzcjy/flutter_rust_bridge/issues/419) explains the reason why we are doing this.
+We have to do this so we Android NDK can find clang on our system, if we used NDK 22 we wouldn't have to do this process.
+Though using NDK v22 will not give us a lot of features to work with.
+This GitHub [issue](https://github.com/fzyzcjy/flutter_rust_bridge/issues/419) explains the reason why we are doing this.
 
  ---
 

+ 93 - 0
frontend/app_flowy/assets/google_fonts/Poppins/OFL.txt

@@ -0,0 +1,93 @@
+Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded, 
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.

BIN
frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Black.ttf


BIN
frontend/app_flowy/assets/google_fonts/Poppins/Poppins-BlackItalic.ttf


BIN
frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Bold.ttf


BIN
frontend/app_flowy/assets/google_fonts/Poppins/Poppins-BoldItalic.ttf


BIN
frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ExtraBold.ttf


BIN
frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ExtraBoldItalic.ttf


BIN
frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ExtraLight.ttf


BIN
frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ExtraLightItalic.ttf


BIN
frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Italic.ttf


BIN
frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Light.ttf


BIN
frontend/app_flowy/assets/google_fonts/Poppins/Poppins-LightItalic.ttf


BIN
frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Medium.ttf


BIN
frontend/app_flowy/assets/google_fonts/Poppins/Poppins-MediumItalic.ttf


BIN
frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Regular.ttf


BIN
frontend/app_flowy/assets/google_fonts/Poppins/Poppins-SemiBold.ttf


BIN
frontend/app_flowy/assets/google_fonts/Poppins/Poppins-SemiBoldItalic.ttf


BIN
frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Thin.ttf


BIN
frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ThinItalic.ttf


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

@@ -221,7 +221,7 @@
     "menuName": "Grid"
   },
   "document": {
-    "menuName": "Doc",
+    "menuName": "Document",
     "date": {
       "timeHintTextInTwelveHour": "01:00 PM",
       "timeHintTextInTwentyFourHour": "13:00"
@@ -232,4 +232,4 @@
       "create_new_card": "New"
     }
   }
-}
+}

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

@@ -221,7 +221,7 @@
     "menuName": "Grille"
   },
   "document": {
-    "menuName": "Doc",
+    "menuName": "Document",
     "date": {
       "timeHintTextInTwelveHour": "01:00 PM",
       "timeHintTextInTwentyFourHour": "13:00"
@@ -232,4 +232,4 @@
       "create_new_card": "Nouveau"
     }
   }
-}
+}

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

@@ -209,7 +209,7 @@
     "menuName": "Grid"
   },
   "document": {
-    "menuName": "Doc",
+    "menuName": "Dokter",
     "date": {
       "timeHintTextInTwelveHour": "01:00 PM",
       "timeHintTextInTwentyFourHour": "13:00"

+ 3 - 3
frontend/app_flowy/assets/translations/pt-BR.json

@@ -220,10 +220,10 @@
     "menuName": "Grade"
   },
   "document": {
-    "menuName": "Doc",
+    "menuName": "Documento",
     "date": {
-      "timeHintTextInTwelveHour": "12:00 AM",
-      "timeHintTextInTwentyFourHour": "12:00"
+      "timeHintTextInTwelveHour": "01:00 PM",
+      "timeHintTextInTwentyFourHour": "13:00"
     }
   },
   "board": {

+ 235 - 0
frontend/app_flowy/assets/translations/sv.json

@@ -0,0 +1,235 @@
+{
+  "appName": "AppFlowy",
+  "defaultUsername": "Jag",
+  "welcomeText": "Välkommen till @:appName",
+  "githubStarText": "Stärnmärk på GitHub",
+  "subscribeNewsletterText": "Prenumerera på nyhetsbrev",
+  "letsGoButtonText": "Kör igång",
+  "title": "Namn",
+  "signUp": {
+    "buttonText": "Registrera dig",
+    "title": "Registrera dig på @:appName",
+    "getStartedText": "Sätt igång",
+    "emptyPasswordError": "Lösenordet kan inte vara tomt",
+    "repeatPasswordEmptyError": "Upprepat lösenord kan inte vara tomt",
+    "unmatchedPasswordError": "Upprepat lösenord är inte samma som det första",
+    "alreadyHaveAnAccount": "Har du redan ett konto?",
+    "emailHint": "E-post",
+    "passwordHint": "Lösenord",
+    "repeatPasswordHint": "Uprepa lösenordet"
+  },
+  "signIn": {
+    "loginTitle": "Logga in till @:appName",
+    "loginButtonText": "Logga in",
+    "buttonText": "Registrering",
+    "forgotPassword": "Glömt lösenordet?",
+    "emailHint": "E-post",
+    "passwordHint": "Lösenord",
+    "dontHaveAnAccount": "Har du inget konto?",
+    "repeatPasswordEmptyError": "Upprepat lösenord kan inte vara tomt",
+    "unmatchedPasswordError": "Upprepat lösenord är inte samma som det första"
+  },
+  "workspace": {
+    "create": "Skapa arbetsyta",
+    "hint": "Arbetsyta",
+    "notFoundError": "Hittade ingen arbetsyta"
+  },
+  "shareAction": {
+    "buttonText": "Dela",
+    "workInProgress": "Kommer snart",
+    "markdown": "Markdown",
+    "copyLink": "Kopiera länk"
+  },
+  "disclosureAction": {
+    "rename": "Byt namn",
+    "delete": "Ta bort",
+    "duplicate": "Klona"
+  },
+  "blankPageTitle": "Tom sida",
+  "newPageText": "Ny sida",
+  "trash": {
+    "text": "Skräp",
+    "restoreAll": "Återställ alla",
+    "deleteAll": "Ta bort alla",
+    "pageHeader": {
+      "fileName": "Filnamn",
+      "lastModified": "Ändrad",
+      "created": "Skapad"
+    }
+  },
+  "deletePagePrompt": {
+    "text": "Denna sida är i skräpmappen",
+    "restore": "Återställ sida",
+    "deletePermanent": "Radera permanent"
+  },
+  "dialogCreatePageNameHint": "Sidnamn",
+  "questionBubble": {
+    "whatsNew": "Vad nytt?",
+    "help": "Hjälp & Support",
+    "debug": {
+      "name": "Felsökningsinfo",
+      "success": "Kopierade felsökningsinfo till urklipp!",
+      "fail": "Kunde inte kopiera felsökningsinfo till urklipp"
+    }
+  },
+  "menuAppHeader": {
+    "addPageTooltip": "Lägg snabbt till en sida inuti",
+    "defaultNewPageName": "Namnlös",
+    "renameDialog": "Byt namn"
+  },
+  "toolbar": {
+    "undo": "Ångra",
+    "redo": "Upprepa",
+    "bold": "Fet",
+    "italic": "Kursiv",
+    "underline": "Understruken",
+    "strike": "Genomstruken",
+    "numList": "Numrerad lista",
+    "bulletList": "Punktlista",
+    "checkList": "Checklista",
+    "inlineCode": "Infogad kod",
+    "quote": "Citatblock",
+    "header": "Rubrik",
+    "highlight": "Färgmarkera"
+  },
+  "tooltip": {
+    "lightMode": "Växla till ljust läge",
+    "darkMode": "Växla till mörkt läge",
+    "openAsPage": "Öppna som sida",
+    "addNewRow": "Lägg till ny rad",
+    "openMenu": "Klicka för att öppna meny"
+  },
+  "sideBar": {
+    "closeSidebar": "Stäng sidofältet",
+    "openSidebar": "Öppna sidofältet"
+  },
+  "notifications": {
+    "export": {
+      "markdown": "Exporterade anteckning till Markdown",
+      "path": "Dokument/flowy"
+    }
+  },
+  "contactsPage": {
+    "title": "Kontakter",
+    "whatsHappening": "Vad händer denna vecka?",
+    "addContact": "Lägg till kontakt",
+    "editContact": "Redigera kontakt"
+  },
+  "button": {
+    "OK": "OK",
+    "Cancel": "Avbryt",
+    "signIn": "Logga in",
+    "signOut": "Logga ut",
+    "complete": "Slutfört",
+    "save": "Spara"
+  },
+  "label": {
+    "welcome": "Välkommen!",
+    "firstName": "Förnamn",
+    "middleName": "Mellannamn",
+    "lastName": "Efternamn",
+    "stepX": "Steg {X}"
+  },
+  "oAuth": {
+    "err": {
+      "failedTitle": "Kan inte ansluta till ditt konto.",
+      "failedMsg": "Tillse att du har slutfört registreringsprocessen i din webbläsare."
+    },
+    "google": {
+      "title": "GOOGLE-inloggning",
+      "instruction1": "För att kunna importera dina Google-kontakter, måste du auktorisera detta program med hjälp av din webbläsare.",
+      "instruction2": "Kopiera den här koden till urklipp genom att klicka på ikonen eller genom att markera texten:",
+      "instruction3": "Gå till följande länk i din webbläsare, och ange ovanstående kod:",
+      "instruction4": "Tryck på nedanstående knapp när du slutfört registreringen:"
+    }
+  },
+  "settings": {
+    "title": "Inställningar",
+    "menu": {
+      "appearance": "Utseende",
+      "language": "Språk",
+      "user": "Användare",
+      "open": "Öppna inställningarna"
+    },
+    "appearance": {
+      "lightLabel": "Ljust läge",
+      "darkLabel": "Mörkt läge"
+    }
+  },
+  "grid": {
+    "settings": {
+      "filter": "Filter",
+      "sortBy": "Sortera efter",
+      "Properties": "Egenskaper",
+      "group": "Grupp"
+    },
+    "field": {
+      "hide": "Dölj",
+      "insertLeft": "Infoga till vänster",
+      "insertRight": "Infoga till höger",
+      "duplicate": "Klona",
+      "delete": "Ta bort",
+      "textFieldName": "Text",
+      "checkboxFieldName": "Checkruta",
+      "dateFieldName": "Datum",
+      "numberFieldName": "Siffror",
+      "singleSelectFieldName": "Välj",
+      "multiSelectFieldName": "Välj flera",
+      "urlFieldName": "URL",
+      "numberFormat": " Sifferformat",
+      "dateFormat": " Datumformat",
+      "includeTime": " Inkludera tid",
+      "dateFormatFriendly": "Månad Dag,År",
+      "dateFormatISO": "År-Månad-Dag",
+      "dateFormatLocal": "Månad/Dag/År",
+      "dateFormatUS": "År/Månad/Dag",
+      "timeFormat": " Tidsformat",
+      "invalidTimeFormat": "Ogiltigt format",
+      "timeFormatTwelveHour": "12-timmars",
+      "timeFormatTwentyFourHour": "24-timmars",
+      "addSelectOption": "Lägg till ett alternativ",
+      "optionTitle": "Alternativ",
+      "addOption": "Lägg till alternativ",
+      "editProperty": "Redigera egenskap",
+      "newColumn": "Ny kolumn",
+      "deleteFieldPromptMessage": "Är du säker? Denna egenskap kommer att raderas."
+    },
+    "row": {
+      "duplicate": "Klona",
+      "delete": "Ta bort",
+      "textPlaceholder": "Tom",
+      "copyProperty": "Kopierade egenskap till urklipp",
+      "count": "Antal",
+      "newRow": "Ny rad"
+    },
+    "selectOption": {
+      "create": "Skapa",
+      "purpleColor": "Purpur",
+      "pinkColor": "Rosa",
+      "lightPinkColor": "Ljusrosa",
+      "orangeColor": "Orange",
+      "yellowColor": "Gul",
+      "limeColor": "Lime",
+      "greenColor": "Grön",
+      "aquaColor": "Vatten",
+      "blueColor": "Blå",
+      "deleteTag": "Ta bort tagg",
+      "colorPanelTitle": "Färger",
+      "panelTitle": "Välj ett alternativ eller skapa ett",
+      "searchOption": "Sök efter ett alternativ"
+    },
+    "menuName": "Tabell"
+  },
+  "document": {
+    "menuName": "Dokument",
+    "date": {
+      "timeHintTextInTwelveHour": "01:00 PM",
+      "timeHintTextInTwentyFourHour": "13:00"
+    }
+  },
+  "board": {
+    "column": {
+      "create_new_card": "Nytt"
+    }
+  }
+}

+ 14 - 7
frontend/app_flowy/lib/core/grid_notification.dart

@@ -9,31 +9,38 @@ import 'package:flowy_sdk/rust_stream.dart';
 import 'notification_helper.dart';
 
 // GridPB
-typedef GridNotificationCallback = void Function(GridNotification, Either<Uint8List, FlowyError>);
+typedef GridNotificationCallback = void Function(
+    GridDartNotification, Either<Uint8List, FlowyError>);
 
-class GridNotificationParser extends NotificationParser<GridNotification, FlowyError> {
-  GridNotificationParser({String? id, required GridNotificationCallback callback})
+class GridNotificationParser
+    extends NotificationParser<GridDartNotification, FlowyError> {
+  GridNotificationParser(
+      {String? id, required GridNotificationCallback callback})
       : super(
           id: id,
           callback: callback,
-          tyParser: (ty) => GridNotification.valueOf(ty),
+          tyParser: (ty) => GridDartNotification.valueOf(ty),
           errorParser: (bytes) => FlowyError.fromBuffer(bytes),
         );
 }
 
-typedef GridNotificationHandler = Function(GridNotification ty, Either<Uint8List, FlowyError> result);
+typedef GridNotificationHandler = Function(
+    GridDartNotification ty, Either<Uint8List, FlowyError> result);
 
 class GridNotificationListener {
   StreamSubscription<SubscribeObject>? _subscription;
   GridNotificationParser? _parser;
 
-  GridNotificationListener({required String objectId, required GridNotificationHandler handler})
+  GridNotificationListener(
+      {required String objectId, required GridNotificationHandler handler})
       : _parser = GridNotificationParser(id: objectId, callback: handler) {
-    _subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
+    _subscription =
+        RustStreamReceiver.listen((observable) => _parser?.parse(observable));
   }
 
   Future<void> stop() async {
     _parser = null;
     await _subscription?.cancel();
+    _subscription = null;
   }
 }

+ 6 - 3
frontend/app_flowy/lib/core/network_monitor.dart

@@ -11,7 +11,8 @@ class NetworkListener {
   late StreamSubscription<ConnectivityResult> _connectivitySubscription;
 
   NetworkListener() {
-    _connectivitySubscription = _connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
+    _connectivitySubscription =
+        _connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
   }
 
   Future<void> start() async {
@@ -39,9 +40,11 @@ class NetworkListener {
           return NetworkType.Ethernet;
         case ConnectivityResult.mobile:
           return NetworkType.Cell;
-        case ConnectivityResult.none:
-          return NetworkType.UnknownNetworkType;
         case ConnectivityResult.bluetooth:
+          return NetworkType.Bluetooth;
+        case ConnectivityResult.vpn:
+          return NetworkType.VPN;
+        case ConnectivityResult.none:
           return NetworkType.UnknownNetworkType;
       }
     }();

+ 3 - 0
frontend/app_flowy/lib/plugins/blank/blank.dart

@@ -15,6 +15,9 @@ class BlankPluginBuilder extends PluginBuilder {
   @override
   String get menuName => "Blank";
 
+  @override
+  String get menuIcon => "";
+
   @override
   PluginType get pluginType => PluginType.blank;
 }

+ 5 - 4
frontend/app_flowy/lib/plugins/board/application/board_bloc.dart

@@ -24,7 +24,8 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
   final BoardDataController _gridDataController;
   late final AppFlowyBoardController boardController;
   final MoveRowFFIService _rowService;
-  LinkedHashMap<String, GroupController> groupControllers = LinkedHashMap();
+  final LinkedHashMap<String, GroupController> groupControllers =
+      LinkedHashMap();
 
   GridFieldController get fieldController =>
       _gridDataController.fieldController;
@@ -69,7 +70,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
         await event.when(
           initial: () async {
             _startListening();
-            await _loadGrid(emit);
+            await _openGrid(emit);
           },
           createBottomRow: (groupId) async {
             final startRowId = groupControllers[groupId]?.lastRow()?.id;
@@ -284,8 +285,8 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
     return <AppFlowyGroupItem>[...items];
   }
 
-  Future<void> _loadGrid(Emitter<BoardState> emit) async {
-    final result = await _gridDataController.loadData();
+  Future<void> _openGrid(Emitter<BoardState> emit) async {
+    final result = await _gridDataController.openGrid();
     result.fold(
       (grid) => emit(
         state.copyWith(loadingState: GridLoadingState.finish(left(unit))),

+ 14 - 12
frontend/app_flowy/lib/plugins/board/application/board_data_controller.dart

@@ -34,7 +34,8 @@ class BoardDataController {
 
   // key: the block id
   final LinkedHashMap<String, GridBlockCache> _blocks;
-  LinkedHashMap<String, GridBlockCache> get blocks => _blocks;
+  UnmodifiableMapView<String, GridBlockCache> get blocks =>
+      UnmodifiableMapView(_blocks);
 
   OnFieldsChanged? _onFieldsChanged;
   OnGridChanged? _onGridChanged;
@@ -107,21 +108,22 @@ class BoardDataController {
     );
   }
 
-  Future<Either<Unit, FlowyError>> loadData() async {
-    final result = await _gridFFIService.loadGrid();
+  Future<Either<Unit, FlowyError>> openGrid() async {
+    final result = await _gridFFIService.openGrid();
     return Future(
       () => result.fold(
         (grid) async {
           _onGridChanged?.call(grid);
-          return await fieldController.loadFields(fieldIds: grid.fields).then(
-                (result) => result.fold(
-                  (l) {
-                    _loadGroups(grid.blocks);
-                    return left(l);
-                  },
-                  (err) => right(err),
-                ),
-              );
+          final result = await fieldController.loadFields(
+            fieldIds: grid.fields,
+          );
+          return result.fold(
+            (l) {
+              _loadGroups(grid.blocks);
+              return left(l);
+            },
+            (err) => right(err),
+          );
         },
         (err) => right(err),
       ),

+ 3 - 3
frontend/app_flowy/lib/plugins/board/application/board_listener.dart

@@ -32,18 +32,18 @@ class BoardListener {
   }
 
   void _handler(
-    GridNotification ty,
+    GridDartNotification ty,
     Either<Uint8List, FlowyError> result,
   ) {
     switch (ty) {
-      case GridNotification.DidUpdateGroupView:
+      case GridDartNotification.DidUpdateGroupView:
         result.fold(
           (payload) => _groupUpdateNotifier?.value =
               left(GroupViewChangesetPB.fromBuffer(payload)),
           (error) => _groupUpdateNotifier?.value = right(error),
         );
         break;
-      case GridNotification.DidGroupByNewField:
+      case GridDartNotification.DidGroupByNewField:
         result.fold(
           (payload) => _groupByNewFieldNotifier?.value =
               left(GroupViewChangesetPB.fromBuffer(payload).newGroups),

+ 1 - 1
frontend/app_flowy/lib/plugins/board/application/card/board_checkbox_cell_bloc.dart

@@ -35,7 +35,7 @@ class BoardCheckboxCellBloc
       cellController.removeListener(_onCellChangedFn!);
       _onCellChangedFn = null;
     }
-    cellController.dispose();
+    await cellController.dispose();
     return super.close();
   }
 

+ 1 - 1
frontend/app_flowy/lib/plugins/board/application/card/board_date_cell_bloc.dart

@@ -31,7 +31,7 @@ class BoardDateCellBloc extends Bloc<BoardDateCellEvent, BoardDateCellState> {
       cellController.removeListener(_onCellChangedFn!);
       _onCellChangedFn = null;
     }
-    cellController.dispose();
+    await cellController.dispose();
     return super.close();
   }
 

+ 1 - 1
frontend/app_flowy/lib/plugins/board/application/card/board_number_cell_bloc.dart

@@ -32,7 +32,7 @@ class BoardNumberCellBloc
       cellController.removeListener(_onCellChangedFn!);
       _onCellChangedFn = null;
     }
-    cellController.dispose();
+    await cellController.dispose();
     return super.close();
   }
 

+ 1 - 1
frontend/app_flowy/lib/plugins/board/application/card/board_select_option_cell_bloc.dart

@@ -34,7 +34,7 @@ class BoardSelectOptionCellBloc
       cellController.removeListener(_onCellChangedFn!);
       _onCellChangedFn = null;
     }
-    cellController.dispose();
+    await cellController.dispose();
     return super.close();
   }
 

+ 1 - 1
frontend/app_flowy/lib/plugins/board/application/card/board_text_cell_bloc.dart

@@ -41,7 +41,7 @@ class BoardTextCellBloc extends Bloc<BoardTextCellEvent, BoardTextCellState> {
       cellController.removeListener(_onCellChangedFn!);
       _onCellChangedFn = null;
     }
-    cellController.dispose();
+    await cellController.dispose();
     return super.close();
   }
 

+ 1 - 1
frontend/app_flowy/lib/plugins/board/application/card/board_url_cell_bloc.dart

@@ -38,7 +38,7 @@ class BoardURLCellBloc extends Bloc<BoardURLCellEvent, BoardURLCellState> {
       cellController.removeListener(_onCellChangedFn!);
       _onCellChangedFn = null;
     }
-    cellController.dispose();
+    await cellController.dispose();
     return super.close();
   }
 

+ 1 - 0
frontend/app_flowy/lib/plugins/board/application/card/card_bloc.dart

@@ -66,6 +66,7 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
         state.cells.map((cell) => cell.identifier.fieldContext).toList(),
       ),
       rowPB: state.rowPB,
+      visible: true,
     );
   }
 

+ 1 - 1
frontend/app_flowy/lib/plugins/board/application/group_controller.dart

@@ -39,7 +39,7 @@ class GroupController {
   void startListening() {
     _listener.start(onGroupChanged: (result) {
       result.fold(
-        (GroupChangesetPB changeset) {
+        (GroupRowsNotificationPB changeset) {
           for (final deletedRow in changeset.deletedRows) {
             group.rows.removeWhere((rowPB) => rowPB.id == deletedRow);
             delegate.removeRow(group, deletedRow);

+ 4 - 4
frontend/app_flowy/lib/plugins/board/application/group_listener.dart

@@ -8,7 +8,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/group_changeset.pb.dart';
 
-typedef UpdateGroupNotifiedValue = Either<GroupChangesetPB, FlowyError>;
+typedef UpdateGroupNotifiedValue = Either<GroupRowsNotificationPB, FlowyError>;
 
 class GroupListener {
   final GroupPB group;
@@ -27,14 +27,14 @@ class GroupListener {
   }
 
   void _handler(
-    GridNotification ty,
+    GridDartNotification ty,
     Either<Uint8List, FlowyError> result,
   ) {
     switch (ty) {
-      case GridNotification.DidUpdateGroup:
+      case GridDartNotification.DidUpdateGroup:
         result.fold(
           (payload) => _groupNotifier?.value =
-              left(GroupChangesetPB.fromBuffer(payload)),
+              left(GroupRowsNotificationPB.fromBuffer(payload)),
           (error) => _groupNotifier?.value = right(error),
         );
         break;

+ 4 - 1
frontend/app_flowy/lib/plugins/board/board.dart

@@ -20,11 +20,14 @@ class BoardPluginBuilder implements PluginBuilder {
   @override
   String get menuName => "Board";
 
+  @override
+  String get menuIcon => "editor/board";
+
   @override
   PluginType get pluginType => PluginType.board;
 
   @override
-  ViewDataTypePB get dataType => ViewDataTypePB.Database;
+  ViewDataFormatPB get dataFormatType => ViewDataFormatPB.DatabaseFormat;
 
   @override
   ViewLayoutTypePB? get layoutType => ViewLayoutTypePB.Board;

+ 24 - 40
frontend/app_flowy/lib/plugins/board/presentation/board_page.dart

@@ -9,11 +9,9 @@ import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
 import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
 import 'package:app_flowy/plugins/grid/presentation/widgets/cell/cell_builder.dart';
 import 'package:app_flowy/plugins/grid/presentation/widgets/row/row_detail.dart';
-import 'package:app_flowy/workspace/application/appearance.dart';
 import 'package:appflowy_board/appflowy_board.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/image.dart';
-import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
 import 'package:flowy_infra_ui/widget/error_page.dart';
@@ -22,7 +20,6 @@ import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:provider/provider.dart';
 import '../../grid/application/row/row_cache.dart';
 import '../application/board_bloc.dart';
 import 'card/card.dart';
@@ -102,27 +99,21 @@ class _BoardContentState extends State<BoardContent> {
   }
 
   Widget _buildBoard(BuildContext context) {
-    return ChangeNotifierProvider.value(
-      value: Provider.of<AppearanceSetting>(context, listen: true),
-      child: Selector<AppearanceSetting, AppTheme>(
-        selector: (ctx, notifier) => notifier.theme,
-        builder: (ctx, theme, child) => Expanded(
-          child: AppFlowyBoard(
-            boardScrollController: scrollManager,
-            scrollController: ScrollController(),
-            controller: context.read<BoardBloc>().boardController,
-            headerBuilder: _buildHeader,
-            footerBuilder: _buildFooter,
-            cardBuilder: (_, column, columnItem) => _buildCard(
-              context,
-              column,
-              columnItem,
-            ),
-            groupConstraints: const BoxConstraints.tightFor(width: 300),
-            config: AppFlowyBoardConfig(
-              groupBackgroundColor: theme.bg1,
-            ),
-          ),
+    return Expanded(
+      child: AppFlowyBoard(
+        boardScrollController: scrollManager,
+        scrollController: ScrollController(),
+        controller: context.read<BoardBloc>().boardController,
+        headerBuilder: _buildHeader,
+        footerBuilder: _buildFooter,
+        cardBuilder: (_, column, columnItem) => _buildCard(
+          context,
+          column,
+          columnItem,
+        ),
+        groupConstraints: const BoxConstraints.tightFor(width: 300),
+        config: AppFlowyBoardConfig(
+          groupBackgroundColor: Theme.of(context).colorScheme.surfaceVariant,
         ),
       ),
     );
@@ -159,7 +150,6 @@ class _BoardContentState extends State<BoardContent> {
           groupData.headerData.groupName,
           fontSize: 14,
           overflow: TextOverflow.clip,
-          color: context.read<AppTheme>().textColor,
         ),
       ),
       icon: _buildHeaderIcon(boardCustomData),
@@ -168,7 +158,7 @@ class _BoardContentState extends State<BoardContent> {
         width: 20,
         child: svgWidget(
           "home/add",
-          color: context.read<AppTheme>().iconColor,
+          color: Theme.of(context).colorScheme.onSurface,
         ),
       ),
       onAddButtonClick: () {
@@ -191,13 +181,12 @@ class _BoardContentState extends State<BoardContent> {
         width: 20,
         child: svgWidget(
           "home/add",
-          color: context.read<AppTheme>().iconColor,
+          color: Theme.of(context).colorScheme.onSurface,
         ),
       ),
       title: FlowyText.medium(
         LocaleKeys.board_column_create_new_card.tr(),
         fontSize: 14,
-        color: context.read<AppTheme>().textColor,
       ),
       height: 50,
       margin: config.footerPadding,
@@ -276,10 +265,12 @@ class _BoardContentState extends State<BoardContent> {
   }
 
   BoxDecoration _makeBoxDecoration(BuildContext context) {
-    final theme = context.read<AppTheme>();
-    final borderSide = BorderSide(color: theme.shader6, width: 1.0);
+    final borderSide = BorderSide(
+      color: Theme.of(context).dividerColor,
+      width: 1.0,
+    );
     return BoxDecoration(
-      color: theme.surface,
+      color: Theme.of(context).colorScheme.surface,
       border: Border.fromBorderSide(borderSide),
       borderRadius: const BorderRadius.all(Radius.circular(6)),
     );
@@ -296,6 +287,7 @@ class _BoardContentState extends State<BoardContent> {
       gridId: gridId,
       fields: UnmodifiableListView(fieldController.fieldContexts),
       rowPB: rowPB,
+      visible: true,
     );
 
     final dataController = GridRowDataController(
@@ -329,15 +321,7 @@ class _ToolbarBlocAdaptor extends StatelessWidget {
           fieldController: bloc.fieldController,
         );
 
-        return ChangeNotifierProvider.value(
-          value: Provider.of<AppearanceSetting>(context, listen: true),
-          child: Selector<AppearanceSetting, AppTheme>(
-            selector: (ctx, notifier) => notifier.theme,
-            builder: (ctx, theme, child) {
-              return BoardToolbar(toolbarContext: toolbarContext);
-            },
-          ),
-        );
+        return BoardToolbar(toolbarContext: toolbarContext);
       },
     );
   }

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

@@ -1,6 +1,5 @@
 import 'package:app_flowy/plugins/board/application/card/board_date_cell_bloc.dart';
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
-import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
@@ -53,7 +52,7 @@ class _BoardDateCellState extends State<BoardDateCell> {
                 child: FlowyText.regular(
                   state.dateStr,
                   fontSize: 13,
-                  color: context.read<AppTheme>().shader3,
+                  color: Theme.of(context).hintColor,
                 ),
               ),
             );

+ 3 - 5
frontend/app_flowy/lib/plugins/board/presentation/card/board_text_cell.dart

@@ -1,9 +1,11 @@
 import 'package:app_flowy/plugins/board/application/card/board_text_cell_bloc.dart';
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
 import 'package:app_flowy/plugins/grid/presentation/widgets/cell/cell_builder.dart';
+import 'package:flowy_infra/size.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:textstyle_extensions/textstyle_extensions.dart';
 import 'board_cell.dart';
 import 'define.dart';
 
@@ -150,11 +152,7 @@ class _BoardTextCellState extends State<BoardTextCell> {
         onChanged: (value) => focusChanged(),
         onEditingComplete: () => focusNode.unfocus(),
         maxLines: null,
-        style: const TextStyle(
-          fontSize: 14,
-          fontWeight: FontWeight.w500,
-          fontFamily: 'Mulish',
-        ),
+        style: Theme.of(context).textTheme.bodyMedium!.size(FontSizes.s14),
         decoration: InputDecoration(
           // Magic number 4 makes the textField take up the same space as FlowyText
           contentPadding: EdgeInsets.symmetric(

+ 8 - 7
frontend/app_flowy/lib/plugins/board/presentation/card/board_url_cell.dart

@@ -1,8 +1,9 @@
 import 'package:app_flowy/plugins/board/application/card/board_url_cell_bloc.dart';
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
-import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra/size.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:textstyle_extensions/textstyle_extensions.dart';
 
 import 'define.dart';
 
@@ -34,7 +35,6 @@ class _BoardUrlCellState extends State<BoardUrlCell> {
 
   @override
   Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
     return BlocProvider.value(
       value: _cellBloc,
       child: BlocBuilder<BoardURLCellBloc, BoardURLCellState>(
@@ -53,11 +53,12 @@ class _BoardUrlCellState extends State<BoardUrlCell> {
                   textAlign: TextAlign.left,
                   text: TextSpan(
                     text: state.content,
-                    style: TextStyle(
-                      color: theme.main2,
-                      fontSize: 14,
-                      decoration: TextDecoration.underline,
-                    ),
+                    style: Theme.of(context)
+                        .textTheme
+                        .bodyMedium!
+                        .size(FontSizes.s14)
+                        .textColor(Theme.of(context).colorScheme.primary)
+                        .underline,
                   ),
                 ),
               ),

+ 5 - 4
frontend/app_flowy/lib/plugins/board/presentation/card/card.dart

@@ -3,7 +3,6 @@ import 'package:app_flowy/plugins/board/application/card/card_data_controller.da
 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/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
@@ -221,8 +220,10 @@ class _CardMoreOption extends StatelessWidget with CardAccessory {
   Widget build(BuildContext context) {
     return Padding(
       padding: const EdgeInsets.all(3.0),
-      child:
-          svgWidget('grid/details', color: context.read<AppTheme>().iconColor),
+      child: svgWidget(
+        'grid/details',
+        color: Theme.of(context).colorScheme.onSurface,
+      ),
     );
   }
 
@@ -243,7 +244,7 @@ class _CardEditOption extends StatelessWidget with CardAccessory {
       padding: const EdgeInsets.all(3.0),
       child: svgWidget(
         'editor/edit',
-        color: context.read<AppTheme>().iconColor,
+        color: Theme.of(context).colorScheme.onSurface,
       ),
     );
   }

+ 7 - 9
frontend/app_flowy/lib/plugins/board/presentation/card/container/accessory.dart

@@ -1,7 +1,5 @@
-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,
@@ -28,7 +26,6 @@ class CardAccessoryContainer extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    final theme = context.read<AppTheme>();
     final children = accessories.map((accessory) {
       return GestureDetector(
         behavior: HitTestBehavior.opaque,
@@ -36,17 +33,16 @@ class CardAccessoryContainer extends StatelessWidget {
           accessory.onTap(context);
           onTapAccessory(accessory.type);
         },
-        child: _wrapHover(theme, accessory),
+        child: _wrapHover(context, accessory),
       );
     }).toList();
     return _wrapDecoration(context, Row(children: children));
   }
 
-  FlowyHover _wrapHover(AppTheme theme, CardAccessory accessory) {
+  FlowyHover _wrapHover(BuildContext context, CardAccessory accessory) {
     return FlowyHover(
       style: HoverStyle(
-        hoverColor: theme.hover,
-        backgroundColor: theme.surface,
+        backgroundColor: Theme.of(context).colorScheme.surface,
         borderRadius: BorderRadius.zero,
       ),
       builder: (_, onHover) => SizedBox(
@@ -58,8 +54,10 @@ class CardAccessoryContainer extends StatelessWidget {
   }
 
   Widget _wrapDecoration(BuildContext context, Widget child) {
-    final theme = context.read<AppTheme>();
-    final borderSide = BorderSide(color: theme.shader6, width: 1.0);
+    final borderSide = BorderSide(
+      color: Theme.of(context).dividerColor,
+      width: 1.0,
+    );
     final decoration = BoxDecoration(
       color: Colors.transparent,
       border: Border.fromBorderSide(borderSide),

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

@@ -7,7 +7,7 @@ import 'package:app_flowy/plugins/grid/presentation/widgets/toolbar/grid_propert
 import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/image.dart';
-import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra/size.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/text.dart';
@@ -95,7 +95,6 @@ class _SettingItem extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    final theme = context.read<AppTheme>();
     final isSelected = context
         .read<BoardSettingBloc>()
         .state
@@ -108,16 +107,17 @@ class _SettingItem extends StatelessWidget {
         isSelected: isSelected,
         text: FlowyText.medium(
           action.title(),
-          fontSize: 12,
-          color: theme.textColor,
+          fontSize: FontSizes.s12,
         ),
-        hoverColor: theme.hover,
         onTap: () {
           context
               .read<BoardSettingBloc>()
               .add(BoardSettingEvent.performAction(action));
         },
-        leftIcon: svgWidget(action.iconName(), color: theme.iconColor),
+        leftIcon: svgWidget(
+          action.iconName(),
+          color: Theme.of(context).colorScheme.onSurface,
+        ),
       ),
     );
   }

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

@@ -1,11 +1,9 @@
 import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
 import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:flowy_infra/image.dart';
-import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/style_widget/icon_button.dart';
-import 'package:flutter/widgets.dart';
-import 'package:provider/provider.dart';
+import 'package:flutter/material.dart';
 
 import 'board_setting.dart';
 
@@ -61,16 +59,17 @@ class _SettingButtonState extends State<_SettingButton> {
 
   @override
   Widget build(BuildContext context) {
-    final theme = context.read<AppTheme>();
     return AppFlowyPopover(
       controller: popoverController,
       constraints: BoxConstraints.loose(const Size(260, 400)),
       child: FlowyIconButton(
-        hoverColor: theme.hover,
         width: 22,
         icon: Padding(
           padding: const EdgeInsets.symmetric(vertical: 3.0, horizontal: 3.0),
-          child: svgWidget("grid/setting/setting", color: theme.iconColor),
+          child: svgWidget(
+            "grid/setting/setting",
+            color: Theme.of(context).colorScheme.onSurface,
+          ),
         ),
       ),
       popupBuilder: (BuildContext popoverContext) {

+ 0 - 28
frontend/app_flowy/lib/plugins/doc/application/share_service.dart

@@ -1,28 +0,0 @@
-import 'dart:async';
-import 'package:dartz/dartz.dart';
-import 'package:flowy_sdk/dispatch/dispatch.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-document/protobuf.dart';
-
-class ShareService {
-  Future<Either<ExportDataPB, FlowyError>> export(
-      String docId, ExportType type) {
-    final request = ExportPayloadPB.create()
-      ..viewId = docId
-      ..exportType = type;
-
-    return DocumentEventExportDocument(request).send();
-  }
-
-  Future<Either<ExportDataPB, FlowyError>> exportText(String docId) {
-    return export(docId, ExportType.Text);
-  }
-
-  Future<Either<ExportDataPB, FlowyError>> exportMarkdown(String docId) {
-    return export(docId, ExportType.Markdown);
-  }
-
-  Future<Either<ExportDataPB, FlowyError>> exportURL(String docId) {
-    return export(docId, ExportType.Link);
-  }
-}

+ 0 - 74
frontend/app_flowy/lib/plugins/doc/presentation/style_widgets.dart

@@ -1,74 +0,0 @@
-import 'package:flowy_infra/image.dart';
-import 'package:flowy_infra/theme.dart';
-import 'package:flowy_infra_ui/style_widget/icon_button.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_quill/flutter_quill.dart';
-
-class StyleWidgetBuilder {
-  static QuillCheckboxBuilder checkbox(AppTheme theme) {
-    return EditorCheckboxBuilder(theme);
-  }
-}
-
-class EditorCheckboxBuilder extends QuillCheckboxBuilder {
-  final AppTheme theme;
-
-  EditorCheckboxBuilder(this.theme);
-
-  @override
-  Widget build(
-      {required BuildContext context,
-      required bool isChecked,
-      required ValueChanged<bool> onChanged}) {
-    return FlowyEditorCheckbox(
-      theme: theme,
-      isChecked: isChecked,
-      onChanged: onChanged,
-    );
-  }
-}
-
-class FlowyEditorCheckbox extends StatefulWidget {
-  final bool isChecked;
-  final ValueChanged<bool> onChanged;
-  final AppTheme theme;
-  const FlowyEditorCheckbox({
-    required this.theme,
-    required this.isChecked,
-    required this.onChanged,
-    Key? key,
-  }) : super(key: key);
-
-  @override
-  FlowyEditorCheckboxState createState() => FlowyEditorCheckboxState();
-}
-
-class FlowyEditorCheckboxState extends State<FlowyEditorCheckbox> {
-  late bool isChecked;
-
-  @override
-  void initState() {
-    isChecked = widget.isChecked;
-    super.initState();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    final icon = isChecked
-        ? svgWidget('editor/editor_check')
-        : svgWidget('editor/editor_uncheck');
-    return Align(
-      alignment: Alignment.centerLeft,
-      child: FlowyIconButton(
-        onPressed: () {
-          isChecked = !isChecked;
-          widget.onChanged(isChecked);
-          setState(() {});
-        },
-        iconPadding: EdgeInsets.zero,
-        icon: icon,
-        width: 23,
-      ),
-    );
-  }
-}

+ 0 - 97
frontend/app_flowy/lib/plugins/doc/presentation/toolbar/check_button.dart

@@ -1,97 +0,0 @@
-import 'package:flutter_quill/flutter_quill.dart';
-import 'package:flutter_quill/models/documents/style.dart';
-import 'package:flutter/material.dart';
-
-import 'toolbar_icon_button.dart';
-
-class FlowyCheckListButton extends StatefulWidget {
-  const FlowyCheckListButton({
-    required this.controller,
-    required this.attribute,
-    required this.tooltipText,
-    this.iconSize = defaultIconSize,
-    this.fillColor,
-    this.childBuilder = defaultToggleStyleButtonBuilder,
-    Key? key,
-  }) : super(key: key);
-
-  final double iconSize;
-
-  final Color? fillColor;
-
-  final QuillController controller;
-
-  final ToggleStyleButtonBuilder childBuilder;
-
-  final Attribute attribute;
-
-  final String tooltipText;
-
-  @override
-  FlowyCheckListButtonState createState() => FlowyCheckListButtonState();
-}
-
-class FlowyCheckListButtonState extends State<FlowyCheckListButton> {
-  bool? _isToggled;
-
-  Style get _selectionStyle => widget.controller.getSelectionStyle();
-
-  void _didChangeEditingValue() {
-    setState(() {
-      _isToggled =
-          _getIsToggled(widget.controller.getSelectionStyle().attributes);
-    });
-  }
-
-  @override
-  void initState() {
-    super.initState();
-    _isToggled = _getIsToggled(_selectionStyle.attributes);
-    widget.controller.addListener(_didChangeEditingValue);
-  }
-
-  bool _getIsToggled(Map<String, Attribute> attrs) {
-    if (widget.attribute.key == Attribute.list.key) {
-      final attribute = attrs[widget.attribute.key];
-      if (attribute == null) {
-        return false;
-      }
-      return attribute.value == widget.attribute.value ||
-          attribute.value == Attribute.checked.value;
-    }
-    return attrs.containsKey(widget.attribute.key);
-  }
-
-  @override
-  void didUpdateWidget(covariant FlowyCheckListButton oldWidget) {
-    super.didUpdateWidget(oldWidget);
-    if (oldWidget.controller != widget.controller) {
-      oldWidget.controller.removeListener(_didChangeEditingValue);
-      widget.controller.addListener(_didChangeEditingValue);
-      _isToggled = _getIsToggled(_selectionStyle.attributes);
-    }
-  }
-
-  @override
-  void dispose() {
-    widget.controller.removeListener(_didChangeEditingValue);
-    super.dispose();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return ToolbarIconButton(
-      onPressed: _toggleAttribute,
-      width: widget.iconSize * kIconButtonFactor,
-      iconName: 'editor/checkbox',
-      isToggled: _isToggled ?? false,
-      tooltipText: widget.tooltipText,
-    );
-  }
-
-  void _toggleAttribute() {
-    widget.controller.formatSelection(_isToggled!
-        ? Attribute.clone(Attribute.unchecked, null)
-        : Attribute.unchecked);
-  }
-}

+ 0 - 280
frontend/app_flowy/lib/plugins/doc/presentation/toolbar/color_picker.dart

@@ -1,280 +0,0 @@
-import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_quill/flutter_quill.dart';
-import 'package:flutter_quill/models/documents/style.dart';
-import 'package:flutter_quill/utils/color.dart';
-import 'package:app_flowy/generated/locale_keys.g.dart';
-import 'package:easy_localization/easy_localization.dart';
-import 'toolbar_icon_button.dart';
-
-class FlowyColorButton extends StatefulWidget {
-  const FlowyColorButton({
-    required this.icon,
-    required this.controller,
-    required this.background,
-    this.iconSize = defaultIconSize,
-    this.iconTheme,
-    Key? key,
-  }) : super(key: key);
-
-  final IconData icon;
-  final double iconSize;
-  final bool background;
-  final QuillController controller;
-  final QuillIconTheme? iconTheme;
-
-  @override
-  FlowyColorButtonState createState() => FlowyColorButtonState();
-}
-
-class FlowyColorButtonState extends State<FlowyColorButton> {
-  late bool _isToggledColor;
-  late bool _isToggledBackground;
-  late bool _isWhite;
-  late bool _isWhitebackground;
-
-  Style get _selectionStyle => widget.controller.getSelectionStyle();
-
-  void _didChangeEditingValue() {
-    setState(() {
-      _isToggledColor =
-          _getIsToggledColor(widget.controller.getSelectionStyle().attributes);
-      _isToggledBackground = _getIsToggledBackground(
-          widget.controller.getSelectionStyle().attributes);
-      _isWhite = _isToggledColor &&
-          _selectionStyle.attributes['color']!.value == '#ffffff';
-      _isWhitebackground = _isToggledBackground &&
-          _selectionStyle.attributes['background']!.value == '#ffffff';
-    });
-  }
-
-  @override
-  void initState() {
-    super.initState();
-    _isToggledColor = _getIsToggledColor(_selectionStyle.attributes);
-    _isToggledBackground = _getIsToggledBackground(_selectionStyle.attributes);
-    _isWhite = _isToggledColor &&
-        _selectionStyle.attributes['color']!.value == '#ffffff';
-    _isWhitebackground = _isToggledBackground &&
-        _selectionStyle.attributes['background']!.value == '#ffffff';
-    widget.controller.addListener(_didChangeEditingValue);
-  }
-
-  bool _getIsToggledColor(Map<String, Attribute> attrs) {
-    return attrs.containsKey(Attribute.color.key);
-  }
-
-  bool _getIsToggledBackground(Map<String, Attribute> attrs) {
-    return attrs.containsKey(Attribute.background.key);
-  }
-
-  @override
-  void didUpdateWidget(covariant FlowyColorButton oldWidget) {
-    super.didUpdateWidget(oldWidget);
-    if (oldWidget.controller != widget.controller) {
-      oldWidget.controller.removeListener(_didChangeEditingValue);
-      widget.controller.addListener(_didChangeEditingValue);
-      _isToggledColor = _getIsToggledColor(_selectionStyle.attributes);
-      _isToggledBackground =
-          _getIsToggledBackground(_selectionStyle.attributes);
-      _isWhite = _isToggledColor &&
-          _selectionStyle.attributes['color']!.value == '#ffffff';
-      _isWhitebackground = _isToggledBackground &&
-          _selectionStyle.attributes['background']!.value == '#ffffff';
-    }
-  }
-
-  @override
-  void dispose() {
-    widget.controller.removeListener(_didChangeEditingValue);
-    super.dispose();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    final theme = Theme.of(context);
-
-    final fillColor = _isToggledColor && !widget.background && _isWhite
-        ? stringToColor('#ffffff')
-        : (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor);
-    final fillColorBackground =
-        _isToggledBackground && widget.background && _isWhitebackground
-            ? stringToColor('#ffffff')
-            : (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor);
-
-    return Tooltip(
-      message: LocaleKeys.toolbar_highlight.tr(),
-      showDuration: Duration.zero,
-      child: QuillIconButton(
-        highlightElevation: 0,
-        hoverElevation: 0,
-        size: widget.iconSize * kIconButtonFactor,
-        icon: Icon(widget.icon,
-            size: widget.iconSize, color: theme.iconTheme.color),
-        fillColor: widget.background ? fillColorBackground : fillColor,
-        onPressed: _showColorPicker,
-      ),
-    );
-  }
-
-  void _changeColor(BuildContext context, Color color) {
-    var hex = color.value.toRadixString(16);
-    if (hex.startsWith('ff')) {
-      hex = hex.substring(2);
-    }
-    hex = '#$hex';
-    widget.controller.formatSelection(
-        widget.background ? BackgroundAttribute(hex) : ColorAttribute(hex));
-    Navigator.of(context).pop();
-  }
-
-  void _showColorPicker() {
-    final style = widget.controller.getSelectionStyle();
-    final values = style.values
-        .where((v) => v.key == Attribute.background.key)
-        .map((v) => v.value);
-    int initialColor = 0;
-    if (values.isNotEmpty) {
-      assert(values.length == 1);
-      initialColor = stringToHex(values.first);
-    }
-
-    StyledDialog(
-      child: SingleChildScrollView(
-        child: FlowyColorPicker(
-          onColorChanged: (color) {
-            if (color == null) {
-              widget.controller.formatSelection(BackgroundAttribute(null));
-              Navigator.of(context).pop();
-            } else {
-              _changeColor(context, color);
-            }
-          },
-          initialColor: initialColor,
-        ),
-      ),
-    ).show(context);
-  }
-}
-
-int stringToHex(String code) {
-  return int.parse(code.substring(1, 7), radix: 16) + 0xFF000000;
-}
-
-class FlowyColorPicker extends StatefulWidget {
-  final List<int> colors = [
-    0xffe8e0ff,
-    0xffffe7fd,
-    0xffffe7ee,
-    0xffffefe3,
-    0xfffff2cd,
-    0xfff5ffdc,
-    0xffddffd6,
-    0xffdefff1,
-  ];
-  final Function(Color?) onColorChanged;
-  final int initialColor;
-  FlowyColorPicker(
-      {Key? key, required this.onColorChanged, this.initialColor = 0})
-      : super(key: key);
-
-  @override
-  State<FlowyColorPicker> createState() => _FlowyColorPickerState();
-}
-
-// if (shrinkWrap) {
-//       innerContent = IntrinsicWidth(child: IntrinsicHeight(child: innerContent));
-//     }
-class _FlowyColorPickerState extends State<FlowyColorPicker> {
-  @override
-  Widget build(BuildContext context) {
-    const double width = 480;
-    const int crossAxisCount = 6;
-    const double mainAxisSpacing = 10;
-    const double crossAxisSpacing = 10;
-    final numberOfRows = (widget.colors.length / crossAxisCount).ceil();
-
-    const perRowHeight =
-        ((width - ((crossAxisCount - 1) * mainAxisSpacing)) / crossAxisCount);
-    final totalHeight =
-        numberOfRows * perRowHeight + numberOfRows * crossAxisSpacing;
-
-    return Container(
-      constraints: BoxConstraints.tightFor(width: width, height: totalHeight),
-      child: CustomScrollView(
-        scrollDirection: Axis.vertical,
-        controller: ScrollController(),
-        physics: const ClampingScrollPhysics(),
-        slivers: [
-          SliverGrid(
-            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
-              crossAxisCount: crossAxisCount,
-              mainAxisSpacing: mainAxisSpacing,
-              crossAxisSpacing: crossAxisSpacing,
-              childAspectRatio: 1.0,
-            ),
-            delegate: SliverChildBuilderDelegate(
-              (BuildContext context, int index) {
-                if (widget.colors.length > index) {
-                  final isSelected =
-                      widget.colors[index] == widget.initialColor;
-                  return ColorItem(
-                    color: Color(widget.colors[index]),
-                    onPressed: widget.onColorChanged,
-                    isSelected: isSelected,
-                  );
-                } else {
-                  return null;
-                }
-              },
-              childCount: widget.colors.length,
-            ),
-          ),
-        ],
-      ),
-    );
-  }
-}
-
-class ColorItem extends StatelessWidget {
-  final Function(Color?) onPressed;
-  final bool isSelected;
-  final Color color;
-  const ColorItem({
-    Key? key,
-    required this.color,
-    required this.onPressed,
-    this.isSelected = false,
-  }) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    if (!isSelected) {
-      return RawMaterialButton(
-        onPressed: () {
-          onPressed(color);
-        },
-        elevation: 0,
-        hoverElevation: 0.6,
-        fillColor: color,
-        shape: const CircleBorder(),
-      );
-    } else {
-      return RawMaterialButton(
-        shape: const CircleBorder(
-                side: BorderSide(color: Colors.white, width: 8)) +
-            CircleBorder(side: BorderSide(color: color, width: 4)),
-        onPressed: () {
-          if (isSelected) {
-            onPressed(null);
-          } else {
-            onPressed(color);
-          }
-        },
-        elevation: 1.0,
-        hoverElevation: 0.6,
-        fillColor: color,
-      );
-    }
-  }
-}

+ 0 - 101
frontend/app_flowy/lib/plugins/doc/presentation/toolbar/header_button.dart

@@ -1,101 +0,0 @@
-import 'package:flutter_quill/flutter_quill.dart';
-import 'package:flutter_quill/models/documents/style.dart';
-import 'package:flutter/material.dart';
-import 'package:app_flowy/generated/locale_keys.g.dart';
-import 'package:easy_localization/easy_localization.dart';
-import 'toolbar_icon_button.dart';
-
-class FlowyHeaderStyleButton extends StatefulWidget {
-  const FlowyHeaderStyleButton({
-    required this.controller,
-    this.iconSize = defaultIconSize,
-    Key? key,
-  }) : super(key: key);
-
-  final QuillController controller;
-  final double iconSize;
-
-  @override
-  FlowyHeaderStyleButtonState createState() => FlowyHeaderStyleButtonState();
-}
-
-class FlowyHeaderStyleButtonState extends State<FlowyHeaderStyleButton> {
-  Attribute? _value;
-
-  Style get _selectionStyle => widget.controller.getSelectionStyle();
-
-  @override
-  void initState() {
-    super.initState();
-    setState(() {
-      _value =
-          _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
-    });
-    widget.controller.addListener(_didChangeEditingValue);
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    final valueToText = <Attribute, String>{
-      Attribute.h1: 'H1',
-      Attribute.h2: 'H2',
-      Attribute.h3: 'H3',
-    };
-
-    final valueAttribute = <Attribute>[
-      Attribute.h1,
-      Attribute.h2,
-      Attribute.h3
-    ];
-    final valueString = <String>['H1', 'H2', 'H3'];
-    final attributeImageName = <String>['editor/H1', 'editor/H2', 'editor/H3'];
-
-    return Row(
-      mainAxisSize: MainAxisSize.min,
-      children: List.generate(3, (index) {
-        // final child =
-        //     _valueToText[_value] == _valueString[index] ? svg('editor/H1', color: Colors.white) : svg('editor/H1');
-
-        final headerTitle = "${LocaleKeys.toolbar_header.tr()} ${index + 1}";
-        final isToggled = valueToText[_value] == valueString[index];
-        return ToolbarIconButton(
-          onPressed: () {
-            if (isToggled) {
-              widget.controller.formatSelection(Attribute.header);
-            } else {
-              widget.controller.formatSelection(valueAttribute[index]);
-            }
-          },
-          width: widget.iconSize * kIconButtonFactor,
-          iconName: attributeImageName[index],
-          isToggled: isToggled,
-          tooltipText: headerTitle,
-        );
-      }),
-    );
-  }
-
-  void _didChangeEditingValue() {
-    setState(() {
-      _value =
-          _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
-    });
-  }
-
-  @override
-  void didUpdateWidget(covariant FlowyHeaderStyleButton oldWidget) {
-    super.didUpdateWidget(oldWidget);
-    if (oldWidget.controller != widget.controller) {
-      oldWidget.controller.removeListener(_didChangeEditingValue);
-      widget.controller.addListener(_didChangeEditingValue);
-      _value =
-          _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
-    }
-  }
-
-  @override
-  void dispose() {
-    widget.controller.removeListener(_didChangeEditingValue);
-    super.dispose();
-  }
-}

+ 0 - 33
frontend/app_flowy/lib/plugins/doc/presentation/toolbar/history_button.dart

@@ -1,33 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_quill/flutter_quill.dart';
-
-class FlowyHistoryButton extends StatelessWidget {
-  final IconData icon;
-  final double iconSize;
-  final bool undo;
-  final QuillController controller;
-  final String tooltipText;
-
-  const FlowyHistoryButton({
-    required this.icon,
-    required this.controller,
-    required this.undo,
-    required this.tooltipText,
-    required this.iconSize,
-    Key? key,
-  }) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    return Tooltip(
-      message: tooltipText,
-      showDuration: Duration.zero,
-      child: HistoryButton(
-        icon: icon,
-        iconSize: iconSize,
-        controller: controller,
-        undo: undo,
-      ),
-    );
-  }
-}

+ 0 - 85
frontend/app_flowy/lib/plugins/doc/presentation/toolbar/image_button.dart

@@ -1,85 +0,0 @@
-import 'package:flutter_quill/flutter_quill.dart';
-import 'package:flutter/material.dart';
-import 'toolbar_icon_button.dart';
-
-class FlowyImageButton extends StatelessWidget {
-  const FlowyImageButton({
-    required this.controller,
-    required this.tooltipText,
-    this.iconSize = defaultIconSize,
-    this.onImagePickCallback,
-    this.fillColor,
-    this.filePickImpl,
-    this.webImagePickImpl,
-    this.mediaPickSettingSelector,
-    Key? key,
-  }) : super(key: key);
-
-  final double iconSize;
-
-  final Color? fillColor;
-
-  final QuillController controller;
-
-  final OnImagePickCallback? onImagePickCallback;
-
-  final WebImagePickImpl? webImagePickImpl;
-
-  final FilePickImpl? filePickImpl;
-
-  final MediaPickSettingSelector? mediaPickSettingSelector;
-
-  final String tooltipText;
-
-  @override
-  Widget build(BuildContext context) {
-    return ToolbarIconButton(
-      iconName: 'editor/image',
-      width: iconSize * 1.77,
-      onPressed: () => _onPressedHandler(context),
-      isToggled: false,
-      tooltipText: tooltipText,
-    );
-  }
-
-  Future<void> _onPressedHandler(BuildContext context) async {
-    // if (onImagePickCallback != null) {
-    //   final selector = mediaPickSettingSelector ?? ImageVideoUtils.selectMediaPickSetting;
-    //   final source = await selector(context);
-    //   if (source != null) {
-    //     if (source == MediaPickSetting.Gallery) {
-    //       _pickImage(context);
-    //     } else {
-    //       _typeLink(context);
-    //     }
-    //   }
-    // } else {
-    //   _typeLink(context);
-    // }
-  }
-
-  // void _pickImage(BuildContext context) => ImageVideoUtils.handleImageButtonTap(
-  //       context,
-  //       controller,
-  //       ImageSource.gallery,
-  //       onImagePickCallback!,
-  //       filePickImpl: filePickImpl,
-  //       webImagePickImpl: webImagePickImpl,
-  //     );
-
-  // void _typeLink(BuildContext context) {
-  //   TextFieldDialog(
-  //     title: 'URL',
-  //     value: "",
-  //     confirm: (newValue) {
-  //       if (newValue.isEmpty) {
-  //         return;
-  //       }
-  //       final index = controller.selection.baseOffset;
-  //       final length = controller.selection.extentOffset - index;
-
-  //       controller.replaceText(index, length, BlockEmbed.image(newValue), null);
-  //     },
-  //   ).show(context);
-  // }
-}

+ 0 - 98
frontend/app_flowy/lib/plugins/doc/presentation/toolbar/link_button.dart

@@ -1,98 +0,0 @@
-import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
-import 'package:flutter_quill/flutter_quill.dart';
-import 'package:flowy_infra/image.dart';
-import 'package:flowy_infra/theme.dart';
-import 'package:flowy_infra_ui/style_widget/icon_button.dart';
-import 'package:flutter/material.dart';
-import 'package:provider/provider.dart';
-
-import 'toolbar_icon_button.dart';
-
-class FlowyLinkStyleButton extends StatefulWidget {
-  const FlowyLinkStyleButton({
-    required this.controller,
-    this.iconSize = defaultIconSize,
-    Key? key,
-  }) : super(key: key);
-
-  final QuillController controller;
-  final double iconSize;
-
-  @override
-  FlowyLinkStyleButtonState createState() => FlowyLinkStyleButtonState();
-}
-
-class FlowyLinkStyleButtonState extends State<FlowyLinkStyleButton> {
-  void _didChangeSelection() {
-    setState(() {});
-  }
-
-  @override
-  void initState() {
-    super.initState();
-    widget.controller.addListener(_didChangeSelection);
-  }
-
-  @override
-  void didUpdateWidget(covariant FlowyLinkStyleButton oldWidget) {
-    super.didUpdateWidget(oldWidget);
-    if (oldWidget.controller != widget.controller) {
-      oldWidget.controller.removeListener(_didChangeSelection);
-      widget.controller.addListener(_didChangeSelection);
-    }
-  }
-
-  @override
-  void dispose() {
-    super.dispose();
-    widget.controller.removeListener(_didChangeSelection);
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
-    final isEnabled = !widget.controller.selection.isCollapsed;
-    final pressedHandler = isEnabled ? () => _openLinkDialog(context) : null;
-    final icon = isEnabled
-        ? svgWidget(
-            'editor/share',
-            color: theme.iconColor,
-          )
-        : svgWidget(
-            'editor/share',
-            color: theme.disableIconColor,
-          );
-
-    return FlowyIconButton(
-      onPressed: pressedHandler,
-      iconPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
-      icon: icon,
-      fillColor: theme.shader6,
-      hoverColor: theme.shader5,
-      width: widget.iconSize * kIconButtonFactor,
-    );
-  }
-
-  void _openLinkDialog(BuildContext context) {
-    final style = widget.controller.getSelectionStyle();
-    final values = style.values
-        .where((v) => v.key == Attribute.link.key)
-        .map((v) => v.value);
-    String value = "";
-    if (values.isNotEmpty) {
-      assert(values.length == 1);
-      value = values.first;
-    }
-
-    NavigatorTextFieldDialog(
-      title: 'URL',
-      value: value,
-      confirm: (newValue) {
-        if (newValue.isEmpty) {
-          return;
-        }
-        widget.controller.formatSelection(LinkAttribute(newValue));
-      },
-    ).show(context);
-  }
-}

+ 0 - 84
frontend/app_flowy/lib/plugins/doc/presentation/toolbar/toggle_button.dart

@@ -1,84 +0,0 @@
-import 'package:flutter_quill/flutter_quill.dart';
-import 'package:flutter_quill/models/documents/style.dart';
-import 'package:flutter/material.dart';
-
-import 'toolbar_icon_button.dart';
-
-class FlowyToggleStyleButton extends StatefulWidget {
-  final Attribute attribute;
-  final String normalIcon;
-  final double iconSize;
-  final QuillController controller;
-  final String tooltipText;
-
-  const FlowyToggleStyleButton({
-    required this.attribute,
-    required this.normalIcon,
-    required this.controller,
-    required this.tooltipText,
-    this.iconSize = defaultIconSize,
-    Key? key,
-  }) : super(key: key);
-
-  @override
-  ToggleStyleButtonState createState() => ToggleStyleButtonState();
-}
-
-class ToggleStyleButtonState extends State<FlowyToggleStyleButton> {
-  bool? _isToggled;
-  Style get _selectionStyle => widget.controller.getSelectionStyle();
-  @override
-  void initState() {
-    super.initState();
-    _isToggled = _getIsToggled(_selectionStyle.attributes);
-    widget.controller.addListener(_didChangeEditingValue);
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return ToolbarIconButton(
-      onPressed: _toggleAttribute,
-      width: widget.iconSize * kIconButtonFactor,
-      isToggled: _isToggled ?? false,
-      iconName: widget.normalIcon,
-      tooltipText: widget.tooltipText,
-    );
-  }
-
-  @override
-  void didUpdateWidget(covariant FlowyToggleStyleButton oldWidget) {
-    super.didUpdateWidget(oldWidget);
-    if (oldWidget.controller != widget.controller) {
-      oldWidget.controller.removeListener(_didChangeEditingValue);
-      widget.controller.addListener(_didChangeEditingValue);
-      _isToggled = _getIsToggled(_selectionStyle.attributes);
-    }
-  }
-
-  @override
-  void dispose() {
-    widget.controller.removeListener(_didChangeEditingValue);
-    super.dispose();
-  }
-
-  void _didChangeEditingValue() {
-    setState(() => _isToggled = _getIsToggled(_selectionStyle.attributes));
-  }
-
-  bool _getIsToggled(Map<String, Attribute> attrs) {
-    if (widget.attribute.key == Attribute.list.key) {
-      final attribute = attrs[widget.attribute.key];
-      if (attribute == null) {
-        return false;
-      }
-      return attribute.value == widget.attribute.value;
-    }
-    return attrs.containsKey(widget.attribute.key);
-  }
-
-  void _toggleAttribute() {
-    widget.controller.formatSelection(_isToggled!
-        ? Attribute.clone(widget.attribute, null)
-        : widget.attribute);
-  }
-}

+ 0 - 308
frontend/app_flowy/lib/plugins/doc/presentation/toolbar/tool_bar.dart

@@ -1,308 +0,0 @@
-import 'dart:async';
-import 'dart:math';
-
-import 'package:app_flowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart';
-import 'package:easy_localization/easy_localization.dart';
-import 'package:flutter_quill/flutter_quill.dart';
-import 'package:flutter/material.dart';
-import 'package:styled_widget/styled_widget.dart';
-import 'check_button.dart';
-import 'color_picker.dart';
-import 'header_button.dart';
-import 'history_button.dart';
-import 'link_button.dart';
-import 'toggle_button.dart';
-import 'toolbar_icon_button.dart';
-import 'package:app_flowy/generated/locale_keys.g.dart';
-
-class EditorToolbar extends StatelessWidget implements PreferredSizeWidget {
-  final List<Widget> children;
-  final double toolBarHeight;
-  final Color? color;
-
-  const EditorToolbar({
-    required this.children,
-    this.toolBarHeight = 46,
-    this.color,
-    Key? key,
-  }) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    return Container(
-      color: Theme.of(context).canvasColor,
-      constraints: BoxConstraints.tightFor(height: preferredSize.height),
-      child: ToolbarButtonList(buttons: children)
-          .padding(horizontal: 4, vertical: 4),
-    );
-  }
-
-  @override
-  Size get preferredSize => Size.fromHeight(toolBarHeight);
-
-  factory EditorToolbar.basic({
-    required QuillController controller,
-    double toolbarIconSize = defaultIconSize,
-    OnImagePickCallback? onImagePickCallback,
-    OnVideoPickCallback? onVideoPickCallback,
-    MediaPickSettingSelector? mediaPickSettingSelector,
-    FilePickImpl? filePickImpl,
-    WebImagePickImpl? webImagePickImpl,
-    WebVideoPickImpl? webVideoPickImpl,
-    Key? key,
-  }) {
-    return EditorToolbar(
-      key: key,
-      toolBarHeight: toolbarIconSize * 2,
-      children: [
-        FlowyHistoryButton(
-          icon: Icons.undo_outlined,
-          iconSize: toolbarIconSize,
-          controller: controller,
-          undo: true,
-          tooltipText: LocaleKeys.toolbar_undo.tr(),
-        ),
-        FlowyHistoryButton(
-          icon: Icons.redo_outlined,
-          iconSize: toolbarIconSize,
-          controller: controller,
-          undo: false,
-          tooltipText: LocaleKeys.toolbar_redo.tr(),
-        ),
-        FlowyToggleStyleButton(
-          attribute: Attribute.bold,
-          normalIcon: 'editor/bold',
-          iconSize: toolbarIconSize,
-          controller: controller,
-          tooltipText: LocaleKeys.toolbar_bold.tr(),
-        ),
-        FlowyToggleStyleButton(
-          attribute: Attribute.italic,
-          normalIcon: 'editor/italic',
-          iconSize: toolbarIconSize,
-          controller: controller,
-          tooltipText: LocaleKeys.toolbar_italic.tr(),
-        ),
-        FlowyToggleStyleButton(
-          attribute: Attribute.underline,
-          normalIcon: 'editor/underline',
-          iconSize: toolbarIconSize,
-          controller: controller,
-          tooltipText: LocaleKeys.toolbar_underline.tr(),
-        ),
-        FlowyToggleStyleButton(
-          attribute: Attribute.strikeThrough,
-          normalIcon: 'editor/strikethrough',
-          iconSize: toolbarIconSize,
-          controller: controller,
-          tooltipText: LocaleKeys.toolbar_strike.tr(),
-        ),
-        FlowyColorButton(
-          icon: Icons.format_color_fill,
-          iconSize: toolbarIconSize,
-          controller: controller,
-          background: true,
-        ),
-        // FlowyImageButton(
-        //   iconSize: toolbarIconSize,
-        //   controller: controller,
-        //   onImagePickCallback: onImagePickCallback,
-        //   filePickImpl: filePickImpl,
-        //   webImagePickImpl: webImagePickImpl,
-        //   mediaPickSettingSelector: mediaPickSettingSelector,
-        // ),
-        FlowyHeaderStyleButton(
-          controller: controller,
-          iconSize: toolbarIconSize,
-        ),
-        FlowyToggleStyleButton(
-          attribute: Attribute.ol,
-          controller: controller,
-          normalIcon: 'editor/numbers',
-          iconSize: toolbarIconSize,
-          tooltipText: LocaleKeys.toolbar_numList.tr(),
-        ),
-        FlowyToggleStyleButton(
-          attribute: Attribute.ul,
-          controller: controller,
-          normalIcon: 'editor/bullet_list',
-          iconSize: toolbarIconSize,
-          tooltipText: LocaleKeys.toolbar_bulletList.tr(),
-        ),
-        FlowyCheckListButton(
-          attribute: Attribute.unchecked,
-          controller: controller,
-          iconSize: toolbarIconSize,
-          tooltipText: LocaleKeys.toolbar_checkList.tr(),
-        ),
-        FlowyToggleStyleButton(
-          attribute: Attribute.inlineCode,
-          controller: controller,
-          normalIcon: 'editor/inline_block',
-          iconSize: toolbarIconSize,
-          tooltipText: LocaleKeys.toolbar_inlineCode.tr(),
-        ),
-        FlowyToggleStyleButton(
-          attribute: Attribute.blockQuote,
-          controller: controller,
-          normalIcon: 'editor/quote',
-          iconSize: toolbarIconSize,
-          tooltipText: LocaleKeys.toolbar_quote.tr(),
-        ),
-        FlowyLinkStyleButton(
-          controller: controller,
-          iconSize: toolbarIconSize,
-        ),
-        FlowyEmojiStyleButton(
-          normalIcon: 'editor/insert_emoticon',
-          controller: controller,
-          tooltipText: "Emoji Picker",
-        ),
-      ],
-    );
-  }
-}
-
-class ToolbarButtonList extends StatefulWidget {
-  const ToolbarButtonList({required this.buttons, Key? key}) : super(key: key);
-
-  final List<Widget> buttons;
-
-  @override
-  ToolbarButtonListState createState() => ToolbarButtonListState();
-}
-
-class ToolbarButtonListState extends State<ToolbarButtonList>
-    with WidgetsBindingObserver {
-  final ScrollController _controller = ScrollController();
-  bool _showLeftArrow = false;
-  bool _showRightArrow = false;
-
-  @override
-  void initState() {
-    super.initState();
-    _controller.addListener(_handleScroll);
-
-    // Listening to the WidgetsBinding instance is necessary so that we can
-    // hide the arrows when the window gets a new size and thus the toolbar
-    // becomes scrollable/unscrollable.
-    WidgetsBinding.instance.addObserver(this);
-
-    // Workaround to allow the scroll controller attach to our ListView so that
-    // we can detect if overflow arrows need to be shown on init.
-    Timer.run(_handleScroll);
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return LayoutBuilder(
-      builder: (BuildContext context, BoxConstraints constraints) {
-        List<Widget> children = [];
-        double width =
-            (widget.buttons.length + 2) * defaultIconSize * kIconButtonFactor;
-        final isFit = constraints.maxWidth > width;
-        if (!isFit) {
-          children.add(_buildLeftArrow());
-          width = width + 18;
-        }
-
-        children.add(_buildScrollableList(constraints, isFit));
-
-        if (!isFit) {
-          children.add(_buildRightArrow());
-          width = width + 18;
-        }
-
-        return SizedBox(
-          width: min(constraints.maxWidth, width),
-          child: Row(
-            children: children,
-          ),
-        );
-      },
-    );
-  }
-
-  @override
-  void didChangeMetrics() => _handleScroll();
-
-  @override
-  void dispose() {
-    _controller.dispose();
-    WidgetsBinding.instance.removeObserver(this);
-    super.dispose();
-  }
-
-  void _handleScroll() {
-    if (!mounted) return;
-    setState(() {
-      _showLeftArrow =
-          _controller.position.minScrollExtent != _controller.position.pixels;
-      _showRightArrow =
-          _controller.position.maxScrollExtent != _controller.position.pixels;
-    });
-  }
-
-  Widget _buildLeftArrow() {
-    return SizedBox(
-      width: 8,
-      child: Transform.translate(
-        // Move the icon a few pixels to center it
-        offset: const Offset(-5, 0),
-        child: _showLeftArrow ? const Icon(Icons.arrow_left, size: 18) : null,
-      ),
-    );
-  }
-
-  // [[sliver: https://medium.com/flutter/slivers-demystified-6ff68ab0296f]]
-  Widget _buildScrollableList(BoxConstraints constraints, bool isFit) {
-    Widget child = Expanded(
-      child: CustomScrollView(
-        scrollDirection: Axis.horizontal,
-        controller: _controller,
-        physics: const ClampingScrollPhysics(),
-        slivers: [
-          SliverList(
-            delegate: SliverChildBuilderDelegate(
-              (BuildContext context, int index) {
-                return widget.buttons[index];
-              },
-              childCount: widget.buttons.length,
-              addAutomaticKeepAlives: false,
-            ),
-          )
-        ],
-      ),
-    );
-
-    if (!isFit) {
-      child = ScrollConfiguration(
-        // Remove the glowing effect, as we already have the arrow indicators
-        behavior: _NoGlowBehavior(),
-        // The CustomScrollView is necessary so that the children are not
-        // stretched to the height of the toolbar, https://bit.ly/3uC3bjI
-        child: child,
-      );
-    }
-
-    return child;
-  }
-
-  Widget _buildRightArrow() {
-    return SizedBox(
-      width: 8,
-      child: Transform.translate(
-        // Move the icon a few pixels to center it
-        offset: const Offset(-5, 0),
-        child: _showRightArrow ? const Icon(Icons.arrow_right, size: 18) : null,
-      ),
-    );
-  }
-}
-
-class _NoGlowBehavior extends ScrollBehavior {
-  @override
-  Widget buildViewportChrome(BuildContext _, Widget child, AxisDirection __) {
-    return child;
-  }
-}

+ 0 - 38
frontend/app_flowy/lib/plugins/doc/presentation/toolbar/toolbar_icon_button.dart

@@ -1,38 +0,0 @@
-import 'package:flowy_infra/image.dart';
-import 'package:flowy_infra_ui/style_widget/icon_button.dart';
-import 'package:flutter/material.dart';
-import 'package:flowy_infra/theme.dart';
-import 'package:provider/provider.dart';
-
-const double defaultIconSize = 18;
-
-class ToolbarIconButton extends StatelessWidget {
-  final double width;
-  final VoidCallback? onPressed;
-  final bool isToggled;
-  final String iconName;
-  final String tooltipText;
-
-  const ToolbarIconButton({
-    Key? key,
-    required this.onPressed,
-    required this.isToggled,
-    required this.width,
-    required this.iconName,
-    required this.tooltipText,
-  }) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
-    return FlowyIconButton(
-      iconPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
-      onPressed: onPressed,
-      width: width,
-      icon: isToggled == true ? svgWidget(iconName, color: Colors.white) : svgWidget(iconName, color: theme.iconColor),
-      fillColor: isToggled == true ? theme.main1 : theme.shader6,
-      hoverColor: isToggled == true ? theme.main1 : theme.hover,
-      tooltipText: tooltipText,
-    );
-  }
-}

+ 0 - 133
frontend/app_flowy/lib/plugins/doc/styles.dart

@@ -1,133 +0,0 @@
-import 'package:app_flowy/plugins/doc/presentation/style_widgets.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_quill/flutter_quill.dart';
-import 'package:provider/provider.dart';
-import 'package:tuple/tuple.dart';
-import 'package:flowy_infra/theme.dart';
-
-DefaultStyles customStyles(BuildContext context) {
-  const baseSpacing = Tuple2<double, double>(6, 0);
-
-  final theme = context.watch<AppTheme>();
-  final themeData = theme.themeData;
-  final fontFamily = makeFontFamily(themeData);
-
-  final defaultTextStyle = DefaultTextStyle.of(context);
-  final baseStyle = defaultTextStyle.style.copyWith(
-    fontSize: 18,
-    height: 1.3,
-    fontWeight: FontWeight.w300,
-    letterSpacing: 0.6,
-    fontFamily: fontFamily,
-  );
-
-  return DefaultStyles(
-      h1: DefaultTextBlockStyle(
-          defaultTextStyle.style.copyWith(
-            fontSize: 34,
-            color: defaultTextStyle.style.color!.withOpacity(0.70),
-            height: 1.15,
-            fontWeight: FontWeight.w300,
-          ),
-          const Tuple2(16, 0),
-          const Tuple2(0, 0),
-          null),
-      h2: DefaultTextBlockStyle(
-          defaultTextStyle.style.copyWith(
-            fontSize: 24,
-            color: defaultTextStyle.style.color!.withOpacity(0.70),
-            height: 1.15,
-            fontWeight: FontWeight.normal,
-          ),
-          const Tuple2(8, 0),
-          const Tuple2(0, 0),
-          null),
-      h3: DefaultTextBlockStyle(
-          defaultTextStyle.style.copyWith(
-            fontSize: 20,
-            color: defaultTextStyle.style.color!.withOpacity(0.70),
-            height: 1.25,
-            fontWeight: FontWeight.w500,
-          ),
-          const Tuple2(8, 0),
-          const Tuple2(0, 0),
-          null),
-      paragraph: DefaultTextBlockStyle(
-          baseStyle, const Tuple2(10, 0), const Tuple2(0, 0), null),
-      bold: const TextStyle(fontWeight: FontWeight.bold),
-      italic: const TextStyle(fontStyle: FontStyle.italic),
-      small: const TextStyle(fontSize: 12, color: Colors.black45),
-      underline: const TextStyle(decoration: TextDecoration.underline),
-      strikeThrough: const TextStyle(decoration: TextDecoration.lineThrough),
-      inlineCode: TextStyle(
-        color: Colors.blue.shade900.withOpacity(0.9),
-        fontFamily: fontFamily,
-        fontSize: 13,
-      ),
-      link: TextStyle(
-        color: themeData.colorScheme.secondary,
-        decoration: TextDecoration.underline,
-      ),
-      color: theme.textColor,
-      placeHolder: DefaultTextBlockStyle(
-          defaultTextStyle.style.copyWith(
-            fontSize: 20,
-            height: 1.5,
-            color: Colors.grey.withOpacity(0.6),
-          ),
-          const Tuple2(0, 0),
-          const Tuple2(0, 0),
-          null),
-      lists: DefaultListBlockStyle(baseStyle, baseSpacing, const Tuple2(0, 6),
-          null, StyleWidgetBuilder.checkbox(theme)),
-      quote: DefaultTextBlockStyle(
-          TextStyle(color: baseStyle.color!.withOpacity(0.6)),
-          baseSpacing,
-          const Tuple2(6, 2),
-          BoxDecoration(
-            border: Border(
-              left: BorderSide(width: 4, color: theme.shader5),
-            ),
-          )),
-      code: DefaultTextBlockStyle(
-          TextStyle(
-            color: Colors.blue.shade900.withOpacity(0.9),
-            fontFamily: fontFamily,
-            fontSize: 13,
-            height: 1.15,
-          ),
-          baseSpacing,
-          const Tuple2(0, 0),
-          BoxDecoration(
-            color: Colors.grey.shade50,
-            borderRadius: BorderRadius.circular(2),
-          )),
-      indent: DefaultTextBlockStyle(
-          baseStyle, baseSpacing, const Tuple2(0, 6), null),
-      align: DefaultTextBlockStyle(
-          baseStyle, const Tuple2(0, 0), const Tuple2(0, 0), null),
-      leading: DefaultTextBlockStyle(
-          baseStyle, const Tuple2(0, 0), const Tuple2(0, 0), null),
-      sizeSmall: const TextStyle(fontSize: 10),
-      sizeLarge: const TextStyle(fontSize: 18),
-      sizeHuge: const TextStyle(fontSize: 22));
-}
-
-String makeFontFamily(ThemeData themeData) {
-  String fontFamily;
-  switch (themeData.platform) {
-    case TargetPlatform.iOS:
-    case TargetPlatform.macOS:
-      fontFamily = 'Mulish';
-      break;
-    case TargetPlatform.android:
-    case TargetPlatform.fuchsia:
-    case TargetPlatform.windows:
-    case TargetPlatform.linux:
-      fontFamily = 'Roboto Mono';
-      break;
-    default:
-      throw UnimplementedError();
-  }
-  return fontFamily;
-}

+ 93 - 55
frontend/app_flowy/lib/plugins/doc/application/doc_bloc.dart → frontend/app_flowy/lib/plugins/document/application/doc_bloc.dart

@@ -1,11 +1,12 @@
 import 'dart:convert';
 import 'package:app_flowy/plugins/trash/application/trash_service.dart';
 import 'package:app_flowy/workspace/application/view/view_listener.dart';
-import 'package:app_flowy/plugins/doc/application/doc_service.dart';
+import 'package:app_flowy/plugins/document/application/doc_service.dart';
+import 'package:appflowy_editor/appflowy_editor.dart'
+    show EditorState, Document, Transaction;
 import 'package:flowy_sdk/protobuf/flowy-folder/trash.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
-import 'package:flutter_quill/flutter_quill.dart' show Document, Delta;
 import 'package:flowy_sdk/log.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
@@ -14,27 +15,26 @@ import 'dart:async';
 
 part 'doc_bloc.freezed.dart';
 
-typedef FlutterQuillDocument = Document;
-
 class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
   final ViewPB view;
-  final DocumentService service;
+  final DocumentService _documentService;
 
-  final ViewListener listener;
-  final TrashService trashService;
-  late FlutterQuillDocument document;
+  final ViewListener _listener;
+  final TrashService _trashService;
+  late EditorState editorState;
   StreamSubscription? _subscription;
 
   DocumentBloc({
     required this.view,
-    required this.service,
-    required this.listener,
-    required this.trashService,
-  }) : super(DocumentState.initial()) {
+  })  : _documentService = DocumentService(),
+        _listener = ViewListener(view: view),
+        _trashService = TrashService(),
+        super(DocumentState.initial()) {
     on<DocumentEvent>((event, emit) async {
       await event.map(
         initial: (Initial value) async {
           await _initial(value, emit);
+          _listenOnViewChange();
         },
         deleted: (Deleted value) async {
           emit(state.copyWith(isDeleted: true));
@@ -43,7 +43,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
           emit(state.copyWith(isDeleted: false));
         },
         deletePermanently: (DeletePermanently value) async {
-          final result = await trashService
+          final result = await _trashService
               .deleteViews([Tuple2(view.id, TrashType.TrashView)]);
 
           final newState = result.fold(
@@ -51,7 +51,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
           emit(newState);
         },
         restorePage: (RestorePage value) async {
-          final result = await trashService.putback(view.id);
+          final result = await _trashService.putback(view.id);
           final newState = result.fold(
               (l) => state.copyWith(isDeleted: false), (r) => state);
           emit(newState);
@@ -62,18 +62,41 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
 
   @override
   Future<void> close() async {
-    await listener.stop();
+    await _listener.stop();
 
     if (_subscription != null) {
       await _subscription?.cancel();
     }
 
-    await service.closeDocument(docId: view.id);
+    await _documentService.closeDocument(docId: view.id);
     return super.close();
   }
 
   Future<void> _initial(Initial value, Emitter<DocumentState> emit) async {
-    listener.start(
+    final result = await _documentService.openDocument(view: view);
+    result.fold(
+      (block) {
+        final document = Document.fromJson(jsonDecode(block.snapshot));
+        editorState = EditorState(document: document);
+        _listenOnDocumentChange();
+        emit(
+          state.copyWith(
+            loadingState: DocumentLoadingState.finish(left(unit)),
+          ),
+        );
+      },
+      (err) {
+        emit(
+          state.copyWith(
+            loadingState: DocumentLoadingState.finish(right(err)),
+          ),
+        );
+      },
+    );
+  }
+
+  void _listenOnViewChange() {
+    _listener.start(
       onViewDeleted: (result) {
         result.fold(
           (view) => add(const DocumentEvent.deleted()),
@@ -87,46 +110,20 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
         );
       },
     );
-    final result = await service.openDocument(docId: view.id);
-    result.fold(
-      (block) {
-        document = _decodeJsonToDocument(block.snapshot);
-        _subscription = document.changes.listen((event) {
-          final delta = event.item2;
-          final documentDelta = document.toDelta();
-          _composeDelta(delta, documentDelta);
-        });
-        emit(state.copyWith(
-            loadingState: DocumentLoadingState.finish(left(unit))));
-      },
-      (err) {
-        emit(state.copyWith(
-            loadingState: DocumentLoadingState.finish(right(err))));
-      },
-    );
-  }
-
-  // Document _decodeListToDocument(Uint8List data) {
-  //   final json = jsonDecode(utf8.decode(data));
-  //   final document = Document.fromJson(json);
-  //   return document;
-  // }
-
-  void _composeDelta(Delta composedDelta, Delta documentDelta) async {
-    final json = jsonEncode(composedDelta.toJson());
-    Log.debug("doc_id: $view.id - Send json: $json");
-    final result = await service.applyEdit(docId: view.id, data: json);
-
-    result.fold(
-      (_) {},
-      (r) => Log.error(r),
-    );
   }
 
-  Document _decodeJsonToDocument(String data) {
-    final json = jsonDecode(data);
-    final document = Document.fromJson(json);
-    return document;
+  void _listenOnDocumentChange() {
+    _subscription = editorState.transactionStream.listen((transaction) {
+      final json = jsonEncode(TransactionAdaptor(transaction).toJson());
+      _documentService
+          .applyEdit(docId: view.id, operations: json)
+          .then((result) {
+        result.fold(
+          (l) => null,
+          (err) => Log.error(err),
+        );
+      });
+    });
   }
 }
 
@@ -160,3 +157,44 @@ class DocumentLoadingState with _$DocumentLoadingState {
   const factory DocumentLoadingState.finish(
       Either<Unit, FlowyError> successOrFail) = _Finish;
 }
+
+/// Uses to erase the different between appflowy editor and the backend
+class TransactionAdaptor {
+  final Transaction transaction;
+  TransactionAdaptor(this.transaction);
+
+  Map<String, dynamic> toJson() {
+    final json = <String, dynamic>{};
+    if (transaction.operations.isNotEmpty) {
+      // The backend uses [0,0] as the beginning path, but the editor uses [0].
+      // So it needs to extend the path by inserting `0` at the head for all
+      // operations before passing to the backend.
+      json['operations'] = transaction.operations
+          .map((e) => e.copyWith(path: [0, ...e.path]).toJson())
+          .toList();
+    }
+    if (transaction.afterSelection != null) {
+      final selection = transaction.afterSelection!;
+      final start = selection.start;
+      final end = selection.end;
+      json['after_selection'] = selection
+          .copyWith(
+            start: start.copyWith(path: [0, ...start.path]),
+            end: end.copyWith(path: [0, ...end.path]),
+          )
+          .toJson();
+    }
+    if (transaction.beforeSelection != null) {
+      final selection = transaction.beforeSelection!;
+      final start = selection.start;
+      final end = selection.end;
+      json['before_selection'] = selection
+          .copyWith(
+            start: start.copyWith(path: [0, ...start.path]),
+            end: end.copyWith(path: [0, ...end.path]),
+          )
+          .toJson();
+    }
+    return json;
+  }
+}

+ 15 - 8
frontend/app_flowy/lib/plugins/doc/application/doc_service.dart → frontend/app_flowy/lib/plugins/document/application/doc_service.dart

@@ -3,28 +3,35 @@ import 'package:flowy_sdk/dispatch/dispatch.dart';
 
 import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-sync/document.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-document/entities.pb.dart';
 
 class DocumentService {
   Future<Either<DocumentSnapshotPB, FlowyError>> openDocument({
-    required String docId,
+    required ViewPB view,
   }) async {
-    await FolderEventSetLatestView(ViewIdPB(value: docId)).send();
+    await FolderEventSetLatestView(ViewIdPB(value: view.id)).send();
+
+    final payload = OpenDocumentContextPB()
+      ..documentId = view.id
+      ..documentVersion = DocumentVersionPB.V1;
+    // switch (view.dataFormat) {
+    //   case ViewDataFormatPB.DeltaFormat:
+    //     payload.documentVersion = DocumentVersionPB.V0;
+    //     break;
+    //   default:
+    //     break;
+    // }
 
-    final payload = DocumentIdPB(value: docId);
     return DocumentEventGetDocument(payload).send();
   }
 
   Future<Either<Unit, FlowyError>> applyEdit({
     required String docId,
-    required String data,
-    String operations = "",
+    required String operations,
   }) {
     final payload = EditPayloadPB.create()
       ..docId = docId
-      ..operations = operations
-      ..operationsStr = data;
+      ..operations = operations;
     return DocumentEventApplyEdit(payload).send();
   }
 

+ 0 - 0
frontend/app_flowy/lib/plugins/doc/application/prelude.dart → frontend/app_flowy/lib/plugins/document/application/prelude.dart


+ 19 - 31
frontend/app_flowy/lib/plugins/doc/application/share_bloc.dart → frontend/app_flowy/lib/plugins/document/application/share_bloc.dart

@@ -1,14 +1,14 @@
-import 'dart:async';
+import 'dart:convert';
 import 'dart:io';
-import 'package:app_flowy/startup/tasks/rust_sdk.dart';
-import 'package:app_flowy/workspace/application/markdown/delta_markdown.dart';
-import 'package:app_flowy/plugins/doc/application/share_service.dart';
+import 'package:app_flowy/plugins/document/application/share_service.dart';
 import 'package:flowy_sdk/protobuf/flowy-document/entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:dartz/dartz.dart';
+import 'package:appflowy_editor/appflowy_editor.dart'
+    show Document, documentToMarkdown;
 part 'share_bloc.freezed.dart';
 
 class DocShareBloc extends Bloc<DocShareEvent, DocShareState> {
@@ -18,11 +18,14 @@ class DocShareBloc extends Bloc<DocShareEvent, DocShareState> {
       : super(const DocShareState.initial()) {
     on<DocShareEvent>((event, emit) async {
       await event.map(
-        shareMarkdown: (ShareMarkdown value) async {
-          await service.exportMarkdown(view.id).then((result) {
+        shareMarkdown: (ShareMarkdown shareMarkdown) async {
+          await service.exportMarkdown(view).then((result) {
             result.fold(
               (value) => emit(
-                  DocShareState.finish(left(_convertDeltaToMarkdown(value)))),
+                DocShareState.finish(
+                  left(_saveMarkdown(value, shareMarkdown.path)),
+                ),
+              ),
               (error) => emit(DocShareState.finish(right(error))),
             );
           });
@@ -35,38 +38,23 @@ class DocShareBloc extends Bloc<DocShareEvent, DocShareState> {
     });
   }
 
-  ExportDataPB _convertDeltaToMarkdown(ExportDataPB value) {
-    final result = deltaToMarkdown(value.data);
-    value.data = result;
-    writeFile(result);
+  ExportDataPB _saveMarkdown(ExportDataPB value, String path) {
+    final markdown = _convertDocumentToMarkdown(value);
+    value.data = markdown;
+    File(path).writeAsStringSync(markdown);
     return value;
   }
 
-  Future<Directory> get _exportDir async {
-    Directory documentsDir = await appFlowyDocumentDirectory();
-
-    return documentsDir;
-  }
-
-  Future<String> get _localPath async {
-    final dir = await _exportDir;
-    return dir.path;
-  }
-
-  Future<File> get _localFile async {
-    final path = await _localPath;
-    return File('$path/${view.name}.md');
-  }
-
-  Future<File> writeFile(String md) async {
-    final file = await _localFile;
-    return file.writeAsString(md);
+  String _convertDocumentToMarkdown(ExportDataPB value) {
+    final json = jsonDecode(value.data);
+    final document = Document.fromJson(json);
+    return documentToMarkdown(document);
   }
 }
 
 @freezed
 class DocShareEvent with _$DocShareEvent {
-  const factory DocShareEvent.shareMarkdown() = ShareMarkdown;
+  const factory DocShareEvent.shareMarkdown(String path) = ShareMarkdown;
   const factory DocShareEvent.shareText() = ShareText;
   const factory DocShareEvent.shareLink() = ShareLink;
 }

+ 30 - 0
frontend/app_flowy/lib/plugins/document/application/share_service.dart

@@ -0,0 +1,30 @@
+import 'dart:async';
+import 'package:dartz/dartz.dart';
+import 'package:flowy_sdk/dispatch/dispatch.dart';
+import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-document/protobuf.dart';
+import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
+
+class ShareService {
+  Future<Either<ExportDataPB, FlowyError>> export(
+      ViewPB view, ExportType type) {
+    var payload = ExportPayloadPB.create()
+      ..viewId = view.id
+      ..exportType = type
+      ..documentVersion = DocumentVersionPB.V1;
+
+    return DocumentEventExportDocument(payload).send();
+  }
+
+  Future<Either<ExportDataPB, FlowyError>> exportText(ViewPB view) {
+    return export(view, ExportType.Text);
+  }
+
+  Future<Either<ExportDataPB, FlowyError>> exportMarkdown(ViewPB view) {
+    return export(view, ExportType.Markdown);
+  }
+
+  Future<Either<ExportDataPB, FlowyError>> exportURL(ViewPB view) {
+    return export(view, ExportType.Link);
+  }
+}

+ 31 - 30
frontend/app_flowy/lib/plugins/doc/document.dart → frontend/app_flowy/lib/plugins/document/document.dart

@@ -4,8 +4,7 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'package:app_flowy/plugins/util.dart';
 import 'package:app_flowy/startup/plugin/plugin.dart';
 import 'package:app_flowy/startup/startup.dart';
-import 'package:app_flowy/workspace/application/appearance.dart';
-import 'package:app_flowy/plugins/doc/application/share_bloc.dart';
+import 'package:app_flowy/plugins/document/application/share_bloc.dart';
 import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
 import 'package:app_flowy/workspace/presentation/home/toast.dart';
 import 'package:app_flowy/workspace/presentation/widgets/left_bar_item.dart';
@@ -14,8 +13,8 @@ import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
 import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:clipboard/clipboard.dart';
 import 'package:easy_localization/easy_localization.dart';
+import 'package:file_picker/file_picker.dart';
 import 'package:flowy_infra/size.dart';
-import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/widget/rounded_button.dart';
 import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
@@ -23,7 +22,6 @@ import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-document/entities.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:provider/provider.dart';
 
 import 'document_page.dart';
 
@@ -40,11 +38,14 @@ class DocumentPluginBuilder extends PluginBuilder {
   @override
   String get menuName => LocaleKeys.document_menuName.tr();
 
+  @override
+  String get menuIcon => "editor/documents";
+
   @override
   PluginType get pluginType => PluginType.editor;
 
   @override
-  ViewDataTypePB get dataType => ViewDataTypePB.Text;
+  ViewDataFormatPB get dataFormatType => ViewDataFormatPB.TreeFormat;
 }
 
 class DocumentPlugin extends Plugin<int> {
@@ -128,21 +129,13 @@ class DocumentShareButton extends StatelessWidget {
           );
         },
         child: BlocBuilder<DocShareBloc, DocShareState>(
-          builder: (context, state) {
-            return ChangeNotifierProvider.value(
-              value: Provider.of<AppearanceSetting>(context, listen: true),
-              child: Selector<AppearanceSetting, Locale>(
-                selector: (ctx, notifier) => notifier.locale,
-                builder: (ctx, _, child) => ConstrainedBox(
-                  constraints: const BoxConstraints.expand(
-                    height: 30,
-                    width: 100,
-                  ),
-                  child: const ShareActionList(),
-                ),
-              ),
-            );
-          },
+          builder: (context, state) => ConstrainedBox(
+            constraints: const BoxConstraints.expand(
+              height: 30,
+              width: 100,
+            ),
+            child: ShareActionList(view: view),
+          ),
         ),
       ),
     );
@@ -165,11 +158,16 @@ class DocumentShareButton extends StatelessWidget {
 }
 
 class ShareActionList extends StatelessWidget {
-  const ShareActionList({Key? key}) : super(key: key);
+  const ShareActionList({
+    Key? key,
+    required this.view,
+  }) : super(key: key);
+
+  final ViewPB view;
 
   @override
   Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
+    final docShareBloc = context.read<DocShareBloc>();
     return PopoverActionList<ShareActionWrapper>(
       direction: PopoverDirection.bottomWithCenterAligned,
       actions: ShareAction.values
@@ -178,20 +176,23 @@ class ShareActionList extends StatelessWidget {
       buildChild: (controller) {
         return RoundedTextButton(
           title: LocaleKeys.shareAction_buttonText.tr(),
-          fontSize: 12,
+          fontSize: FontSizes.s12,
           borderRadius: Corners.s6Border,
-          color: theme.main1,
+          color: Theme.of(context).colorScheme.primary,
           onPressed: () => controller.show(),
         );
       },
-      onSelected: (action, controller) {
+      onSelected: (action, controller) async {
         switch (action.inner) {
           case ShareAction.markdown:
-            context
-                .read<DocShareBloc>()
-                .add(const DocShareEvent.shareMarkdown());
-            showMessageToast(
-                'Exported to: ${LocaleKeys.notifications_export_path.tr()}');
+            final exportPath = await FilePicker.platform.saveFile(
+              dialogTitle: '',
+              fileName: '${view.name}.md',
+            );
+            if (exportPath != null) {
+              docShareBloc.add(DocShareEvent.shareMarkdown(exportPath));
+              showMessageToast('Exported to: $exportPath');
+            }
             break;
           case ShareAction.copyLink:
             NavigatorAlertDialog(

+ 31 - 54
frontend/app_flowy/lib/plugins/doc/document_page.dart → frontend/app_flowy/lib/plugins/document/document_page.dart

@@ -1,17 +1,14 @@
+import 'package:app_flowy/plugins/document/editor_styles.dart';
+import 'package:app_flowy/plugins/document/presentation/plugins/horizontal_rule_node_widget.dart';
 import 'package:app_flowy/startup/startup.dart';
-import 'package:app_flowy/workspace/application/appearance.dart';
-import 'package:app_flowy/plugins/doc/presentation/banner.dart';
-import 'package:app_flowy/plugins/doc/presentation/toolbar/tool_bar.dart';
-import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
-import 'package:flowy_infra_ui/widget/spacing.dart';
-import 'package:flutter_quill/flutter_quill.dart' as quill;
+import 'package:app_flowy/plugins/document/presentation/banner.dart';
+import 'package:appflowy_editor/appflowy_editor.dart';
 import 'package:flowy_infra_ui/widget/error_page.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:provider/provider.dart';
+import 'package:intl/intl.dart';
 import 'application/doc_bloc.dart';
-import 'styles.dart';
 
 class DocumentPage extends StatefulWidget {
   final VoidCallback onDeleted;
@@ -29,11 +26,12 @@ class DocumentPage extends StatefulWidget {
 
 class _DocumentPageState extends State<DocumentPage> {
   late DocumentBloc documentBloc;
-  final scrollController = ScrollController();
   final FocusNode _focusNode = FocusNode();
 
   @override
   void initState() {
+    // The appflowy editor use Intl as localization, set the default language as fallback.
+    Intl.defaultLocale = 'en_US';
     documentBloc = getIt<DocumentBloc>(param1: super.widget.view)
       ..add(const DocumentEvent.initial());
     super.initState();
@@ -48,9 +46,9 @@ class _DocumentPageState extends State<DocumentPage> {
       child:
           BlocBuilder<DocumentBloc, DocumentState>(builder: (context, state) {
         return state.loadingState.map(
-          // loading: (_) => const FlowyProgressIndicator(),
-          loading: (_) =>
-              SizedBox.expand(child: Container(color: Colors.transparent)),
+          loading: (_) => SizedBox.expand(
+            child: Container(color: Colors.transparent),
+          ),
           finish: (result) => result.successOrFail.fold(
             (_) {
               if (state.forceClose) {
@@ -75,23 +73,12 @@ class _DocumentPageState extends State<DocumentPage> {
   }
 
   Widget _renderDocument(BuildContext context, DocumentState state) {
-    quill.QuillController controller = quill.QuillController(
-      document: context.read<DocumentBloc>().document,
-      selection: const TextSelection.collapsed(offset: 0),
-    );
     return Column(
       children: [
         if (state.isDeleted) _renderBanner(context),
-        Expanded(
-          child: Column(
-            mainAxisAlignment: MainAxisAlignment.spaceBetween,
-            children: [
-              _renderEditor(controller),
-              const VSpace(10),
-              _renderToolbar(controller),
-              const VSpace(10),
-            ],
-          ),
+        // AppFlowy Editor
+        _renderAppFlowyEditor(
+          context.read<DocumentBloc>().editorState,
         ),
       ],
     );
@@ -107,36 +94,26 @@ class _DocumentPageState extends State<DocumentPage> {
     );
   }
 
-  Widget _renderEditor(quill.QuillController controller) {
-    final editor = quill.QuillEditor(
-      controller: controller,
-      focusNode: _focusNode,
-      scrollable: true,
-      paintCursorAboveText: true,
-      autoFocus: controller.document.isEmpty(),
-      expands: false,
-      padding: const EdgeInsets.symmetric(horizontal: 8.0),
-      readOnly: false,
-      scrollBottomInset: 0,
-      scrollController: scrollController,
-      customStyles: customStyles(context),
+  Widget _renderAppFlowyEditor(EditorState editorState) {
+    final theme = Theme.of(context);
+    final editor = AppFlowyEditor(
+      editorState: editorState,
+      autoFocus: editorState.document.isEmpty,
+      customBuilders: {
+        'horizontal_rule': HorizontalRuleWidgetBuilder(),
+      },
+      shortcutEvents: [
+        insertHorizontalRule,
+      ],
+      themeData: theme.copyWith(extensions: [
+        ...theme.extensions.values,
+        customEditorTheme(context),
+        ...customPluginTheme(context),
+      ]),
     );
-
     return Expanded(
-      child: ScrollbarListStack(
-        axis: Axis.vertical,
-        controller: scrollController,
-        barSize: 6.0,
-        child: SizedBox.expand(child: editor),
-      ),
-    );
-  }
-
-  Widget _renderToolbar(quill.QuillController controller) {
-    return ChangeNotifierProvider.value(
-      value: Provider.of<AppearanceSetting>(context, listen: true),
-      child: EditorToolbar.basic(
-        controller: controller,
+      child: SizedBox.expand(
+        child: editor,
       ),
     );
   }

+ 65 - 0
frontend/app_flowy/lib/plugins/document/editor_styles.dart

@@ -0,0 +1,65 @@
+import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:flutter/material.dart';
+
+const _baseFontSize = 14.0;
+
+EditorStyle customEditorTheme(BuildContext context) {
+  var editorStyle = Theme.of(context).brightness == Brightness.dark
+      ? EditorStyle.dark
+      : EditorStyle.light;
+  editorStyle = editorStyle.copyWith(
+    textStyle: editorStyle.textStyle?.copyWith(
+      fontFamily: 'poppins',
+      fontSize: _baseFontSize,
+    ),
+    placeholderTextStyle: editorStyle.placeholderTextStyle?.copyWith(
+      fontFamily: 'poppins',
+      fontSize: _baseFontSize,
+    ),
+    bold: editorStyle.bold?.copyWith(
+      fontWeight: FontWeight.w500,
+    ),
+    backgroundColor: Theme.of(context).colorScheme.surface,
+  );
+  return editorStyle;
+}
+
+Iterable<ThemeExtension<dynamic>> customPluginTheme(BuildContext context) {
+  const basePadding = 12.0;
+  var headingPluginStyle = Theme.of(context).brightness == Brightness.dark
+      ? HeadingPluginStyle.dark
+      : HeadingPluginStyle.light;
+  headingPluginStyle = headingPluginStyle.copyWith(
+    textStyle: (EditorState editorState, Node node) {
+      final headingToFontSize = {
+        'h1': _baseFontSize + 12,
+        'h2': _baseFontSize + 8,
+        'h3': _baseFontSize + 4,
+        'h4': _baseFontSize,
+        'h5': _baseFontSize,
+        'h6': _baseFontSize,
+      };
+      final fontSize =
+          headingToFontSize[node.attributes.heading] ?? _baseFontSize;
+      return TextStyle(fontSize: fontSize, fontWeight: FontWeight.w600);
+    },
+    padding: (EditorState editorState, Node node) {
+      final headingToPadding = {
+        'h1': basePadding + 6,
+        'h2': basePadding + 4,
+        'h3': basePadding + 2,
+        'h4': basePadding,
+        'h5': basePadding,
+        'h6': basePadding,
+      };
+      final padding = headingToPadding[node.attributes.heading] ?? basePadding;
+      return EdgeInsets.only(bottom: padding);
+    },
+  );
+  final pluginTheme = Theme.of(context).brightness == Brightness.dark
+      ? darkPlguinStyleExtension
+      : lightPlguinStyleExtension;
+  return pluginTheme.toList()
+    ..removeWhere((element) => element is HeadingPluginStyle)
+    ..add(headingPluginStyle);
+}

+ 13 - 14
frontend/app_flowy/lib/plugins/doc/presentation/banner.dart → frontend/app_flowy/lib/plugins/document/presentation/banner.dart

@@ -1,11 +1,9 @@
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/size.dart';
-import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/widget/buttons/base_styled_button.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flutter/material.dart';
-import 'package:provider/provider.dart';
 import 'package:app_flowy/generated/locale_keys.g.dart';
 
 class DocumentBanner extends StatelessWidget {
@@ -17,12 +15,11 @@ class DocumentBanner extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
     return ConstrainedBox(
       constraints: const BoxConstraints(minHeight: 60),
       child: Container(
         width: double.infinity,
-        color: theme.main1,
+        color: Theme.of(context).colorScheme.primary,
         child: FittedBox(
           alignment: Alignment.center,
           fit: BoxFit.scaleDown,
@@ -36,30 +33,32 @@ class DocumentBanner extends StatelessWidget {
                   minHeight: 40,
                   contentPadding: EdgeInsets.zero,
                   bgColor: Colors.transparent,
-                  hoverColor: theme.main2,
-                  downColor: theme.main1,
+                  hoverColor: Theme.of(context).colorScheme.primary,
+                  downColor: Theme.of(context).colorScheme.primaryContainer,
                   outlineColor: Colors.white,
                   borderRadius: Corners.s8Border,
                   onPressed: onRestore,
                   child: FlowyText.medium(
-                      LocaleKeys.deletePagePrompt_restore.tr(),
-                      color: Colors.white,
-                      fontSize: 14)),
+                    LocaleKeys.deletePagePrompt_restore.tr(),
+                    color: Theme.of(context).colorScheme.onPrimary,
+                    fontSize: 14,
+                  )),
               const HSpace(20),
               BaseStyledButton(
                   minWidth: 220,
                   minHeight: 40,
                   contentPadding: EdgeInsets.zero,
                   bgColor: Colors.transparent,
-                  hoverColor: theme.main2,
-                  downColor: theme.main1,
+                  hoverColor: Theme.of(context).colorScheme.primaryContainer,
+                  downColor: Theme.of(context).colorScheme.primary,
                   outlineColor: Colors.white,
                   borderRadius: Corners.s8Border,
                   onPressed: onDelete,
                   child: FlowyText.medium(
-                      LocaleKeys.deletePagePrompt_deletePermanent.tr(),
-                      color: Colors.white,
-                      fontSize: 14)),
+                    LocaleKeys.deletePagePrompt_deletePermanent.tr(),
+                    color: Theme.of(context).colorScheme.onPrimary,
+                    fontSize: 14,
+                  )),
             ],
           ),
         ),

+ 168 - 0
frontend/app_flowy/lib/plugins/document/presentation/plugins/horizontal_rule_node_widget.dart

@@ -0,0 +1,168 @@
+import 'dart:collection';
+
+import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:flutter/material.dart';
+
+ShortcutEvent insertHorizontalRule = ShortcutEvent(
+  key: 'Horizontal rule',
+  command: 'Minus',
+  handler: _insertHorzaontalRule,
+);
+
+ShortcutEventHandler _insertHorzaontalRule = (editorState, event) {
+  final selection = editorState.service.selectionService.currentSelection.value;
+  final textNodes = editorState.service.selectionService.currentSelectedNodes
+      .whereType<TextNode>();
+  if (textNodes.length != 1 || selection == null) {
+    return KeyEventResult.ignored;
+  }
+  final textNode = textNodes.first;
+  if (textNode.toPlainText() == '--') {
+    final transaction = editorState.transaction
+      ..deleteText(textNode, 0, 2)
+      ..insertNode(
+        textNode.path,
+        Node(
+          type: 'horizontal_rule',
+          children: LinkedList(),
+          attributes: {},
+        ),
+      )
+      ..afterSelection =
+          Selection.single(path: textNode.path.next, startOffset: 0);
+    editorState.apply(transaction);
+    return KeyEventResult.handled;
+  }
+  return KeyEventResult.ignored;
+};
+
+SelectionMenuItem horizontalRuleMenuItem = SelectionMenuItem(
+  name: () => 'Horizontal rule',
+  icon: (editorState, onSelected) => Icon(
+    Icons.horizontal_rule,
+    color: onSelected
+        ? editorState.editorStyle.selectionMenuItemSelectedIconColor
+        : editorState.editorStyle.selectionMenuItemIconColor,
+    size: 18.0,
+  ),
+  keywords: ['horizontal rule'],
+  handler: (editorState, _, __) {
+    final selection =
+        editorState.service.selectionService.currentSelection.value;
+    final textNodes = editorState.service.selectionService.currentSelectedNodes
+        .whereType<TextNode>();
+    if (selection == null || textNodes.isEmpty) {
+      return;
+    }
+    final textNode = textNodes.first;
+    if (textNode.toPlainText().isEmpty) {
+      final transaction = editorState.transaction
+        ..insertNode(
+          textNode.path,
+          Node(
+            type: 'horizontal_rule',
+            children: LinkedList(),
+            attributes: {},
+          ),
+        )
+        ..afterSelection =
+            Selection.single(path: textNode.path.next, startOffset: 0);
+      editorState.apply(transaction);
+    } else {
+      final transaction = editorState.transaction
+        ..insertNode(
+          selection.end.path.next,
+          TextNode(
+            children: LinkedList(),
+            attributes: {
+              'subtype': 'horizontal_rule',
+            },
+            delta: Delta()..insert('---'),
+          ),
+        )
+        ..afterSelection = selection;
+      editorState.apply(transaction);
+    }
+  },
+);
+
+class HorizontalRuleWidgetBuilder extends NodeWidgetBuilder<Node> {
+  @override
+  Widget build(NodeWidgetContext<Node> context) {
+    return _HorizontalRuleWidget(
+      key: context.node.key,
+      node: context.node,
+      editorState: context.editorState,
+    );
+  }
+
+  @override
+  NodeValidator<Node> get nodeValidator => (node) {
+        return true;
+      };
+}
+
+class _HorizontalRuleWidget extends StatefulWidget {
+  const _HorizontalRuleWidget({
+    Key? key,
+    required this.node,
+    required this.editorState,
+  }) : super(key: key);
+
+  final Node node;
+  final EditorState editorState;
+
+  @override
+  State<_HorizontalRuleWidget> createState() => __HorizontalRuleWidgetState();
+}
+
+class __HorizontalRuleWidgetState extends State<_HorizontalRuleWidget>
+    with SelectableMixin {
+  RenderBox get _renderBox => context.findRenderObject() as RenderBox;
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      padding: const EdgeInsets.symmetric(vertical: 10),
+      child: Container(
+        height: 1,
+        color: Colors.grey,
+      ),
+    );
+  }
+
+  @override
+  Position start() => Position(path: widget.node.path, offset: 0);
+
+  @override
+  Position end() => Position(path: widget.node.path, offset: 1);
+
+  @override
+  Position getPositionInOffset(Offset start) => end();
+
+  @override
+  bool get shouldCursorBlink => false;
+
+  @override
+  CursorStyle get cursorStyle => CursorStyle.borderLine;
+
+  @override
+  Rect? getCursorRectInPosition(Position position) {
+    final size = _renderBox.size;
+    return Rect.fromLTWH(-size.width / 2.0, 0, size.width, size.height);
+  }
+
+  @override
+  List<Rect> getRectsInSelection(Selection selection) =>
+      [Offset.zero & _renderBox.size];
+
+  @override
+  Selection getSelectionInRange(Offset start, Offset end) => Selection.single(
+        path: widget.node.path,
+        startOffset: 0,
+        endOffset: 1,
+      );
+
+  @override
+  Offset localToGlobal(Offset offset) => _renderBox.localToGlobal(offset);
+}

+ 2 - 2
frontend/app_flowy/lib/plugins/grid/application/block/block_cache.dart

@@ -13,7 +13,7 @@ class GridBlockCache {
   late GridRowCache _rowCache;
   late GridBlockListener _listener;
 
-  List<RowInfo> get rows => _rowCache.rows;
+  List<RowInfo> get rows => _rowCache.visibleRows;
   GridRowCache get rowCache => _rowCache;
 
   GridBlockCache({
@@ -30,7 +30,7 @@ class GridBlockCache {
     _listener = GridBlockListener(blockId: block.id);
     _listener.start((result) {
       result.fold(
-        (changesets) => _rowCache.applyChangesets(changesets),
+        (changeset) => _rowCache.applyChangesets(changeset),
         (err) => Log.error(err),
       );
     });

+ 7 - 5
frontend/app_flowy/lib/plugins/grid/application/block/block_listener.dart

@@ -7,11 +7,12 @@ import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
 
-typedef GridBlockUpdateNotifierValue = Either<List<GridBlockChangesetPB>, FlowyError>;
+typedef GridBlockUpdateNotifierValue = Either<GridBlockChangesetPB, FlowyError>;
 
 class GridBlockListener {
   final String blockId;
-  PublishNotifier<GridBlockUpdateNotifierValue>? _rowsUpdateNotifier = PublishNotifier();
+  PublishNotifier<GridBlockUpdateNotifierValue>? _rowsUpdateNotifier =
+      PublishNotifier();
   GridNotificationListener? _listener;
 
   GridBlockListener({required this.blockId});
@@ -29,11 +30,12 @@ class GridBlockListener {
     _rowsUpdateNotifier?.addPublishListener(onBlockChanged);
   }
 
-  void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) {
+  void _handler(GridDartNotification ty, Either<Uint8List, FlowyError> result) {
     switch (ty) {
-      case GridNotification.DidUpdateGridBlock:
+      case GridDartNotification.DidUpdateGridBlock:
         result.fold(
-          (payload) => _rowsUpdateNotifier?.value = left([GridBlockChangesetPB.fromBuffer(payload)]),
+          (payload) => _rowsUpdateNotifier?.value =
+              left(GridBlockChangesetPB.fromBuffer(payload)),
           (error) => _rowsUpdateNotifier?.value = right(error),
         );
         break;

+ 2 - 2
frontend/app_flowy/lib/plugins/grid/application/cell/cell_listener.dart

@@ -22,9 +22,9 @@ class CellListener {
         objectId: "$rowId:$fieldId", handler: _handler);
   }
 
-  void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) {
+  void _handler(GridDartNotification ty, Either<Uint8List, FlowyError> result) {
     switch (ty) {
-      case GridNotification.DidUpdateCell:
+      case GridDartNotification.DidUpdateCell:
         result.fold(
           (payload) => _updateCellNotifier?.value = left(unit),
           (error) => _updateCellNotifier?.value = right(error),

+ 5 - 5
frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_controller.dart

@@ -234,7 +234,7 @@ class IGridCellController<T, D> extends Equatable {
     return data;
   }
 
-  /// Return the FieldTypeOptionDataPB that can be parsed into corresponding class using the [parser].
+  /// Return the TypeOptionPB that can be parsed into corresponding class using the [parser].
   /// [PD] is the type that the parser return.
   Future<Either<PD, FlowyError>>
       getFieldTypeOption<PD, P extends TypeOptionDataParser>(P parser) {
@@ -290,20 +290,20 @@ class IGridCellController<T, D> extends Equatable {
     });
   }
 
-  void dispose() {
+  Future<void> dispose() async {
     if (_isDispose) {
       Log.error("$this should only dispose once");
       return;
     }
     _isDispose = true;
-    _cellListener?.stop();
+    await _cellListener?.stop();
     _loadDataOperation?.cancel();
     _saveDataOperation?.cancel();
     _cellDataNotifier = null;
 
     if (_onFieldChangedFn != null) {
       _fieldNotifier.unregister(_cacheKey, _onFieldChangedFn!);
-      _fieldNotifier.dispose();
+      await _fieldNotifier.dispose();
       _onFieldChangedFn = null;
     }
   }
@@ -329,7 +329,7 @@ class GridCellFieldNotifierImpl extends IGridCellFieldNotifier {
 
   @override
   void onCellFieldChanged(void Function(FieldPB p1) callback) {
-    _onChangesetFn = (FieldChangesetPB changeset) {
+    _onChangesetFn = (GridFieldChangesetPB changeset) {
       for (final updatedField in changeset.updatedFields) {
         callback(updatedField);
       }

+ 1 - 1
frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_data_loader.dart

@@ -25,7 +25,7 @@ class GridCellDataLoader<T> {
     final fut = service.getCell(cellId: cellId);
     return fut.then(
       (result) => result.fold(
-        (GridCellPB cell) {
+        (CellPB cell) {
           try {
             return parser.parserData(cell.data);
           } catch (e, s) {

+ 8 - 5
frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_data_persistence.dart

@@ -29,10 +29,12 @@ class CellDataPersistence implements IGridCellDataPersistence<String> {
 
 @freezed
 class CalendarData with _$CalendarData {
-  const factory CalendarData({required DateTime date, String? time}) = _CalendarData;
+  const factory CalendarData({required DateTime date, String? time}) =
+      _CalendarData;
 }
 
-class DateCellDataPersistence implements IGridCellDataPersistence<CalendarData> {
+class DateCellDataPersistence
+    implements IGridCellDataPersistence<CalendarData> {
   final GridCellIdentifier cellId;
   DateCellDataPersistence({
     required this.cellId,
@@ -40,10 +42,11 @@ class DateCellDataPersistence implements IGridCellDataPersistence<CalendarData>
 
   @override
   Future<Option<FlowyError>> save(CalendarData data) {
-    var payload = DateChangesetPayloadPB.create()..cellIdentifier = _makeCellIdPayload(cellId);
+    var payload = DateChangesetPB.create()..cellPath = _makeCellPath(cellId);
 
     final date = (data.date.millisecondsSinceEpoch ~/ 1000).toString();
     payload.date = date;
+    payload.isUtc = data.date.isUtc;
 
     if (data.time != null) {
       payload.time = data.time!;
@@ -58,8 +61,8 @@ class DateCellDataPersistence implements IGridCellDataPersistence<CalendarData>
   }
 }
 
-GridCellIdPB _makeCellIdPayload(GridCellIdentifier cellId) {
-  return GridCellIdPB.create()
+CellPathPB _makeCellPath(GridCellIdentifier cellId) {
+  return CellPathPB.create()
     ..gridId = cellId.gridId
     ..fieldId = cellId.fieldId
     ..rowId = cellId.rowId;

+ 2 - 2
frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_service.dart

@@ -42,10 +42,10 @@ class CellService {
     return GridEventUpdateCell(payload).send();
   }
 
-  Future<Either<GridCellPB, FlowyError>> getCell({
+  Future<Either<CellPB, FlowyError>> getCell({
     required GridCellIdentifier cellId,
   }) {
-    final payload = GridCellIdPB.create()
+    final payload = CellPathPB.create()
       ..gridId = cellId.gridId
       ..fieldId = cellId.fieldId
       ..rowId = cellId.rowId;

+ 1 - 1
frontend/app_flowy/lib/plugins/grid/application/cell/checkbox_cell_bloc.dart

@@ -37,7 +37,7 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
       _onCellChangedFn = null;
     }
 
-    cellController.dispose();
+    await cellController.dispose();
     return super.close();
   }
 

+ 1 - 1
frontend/app_flowy/lib/plugins/grid/application/cell/date_cal_bloc.dart

@@ -139,7 +139,7 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
       cellController.removeListener(_onCellChangedFn!);
       _onCellChangedFn = null;
     }
-    cellController.dispose();
+    await cellController.dispose();
     return super.close();
   }
 

+ 1 - 1
frontend/app_flowy/lib/plugins/grid/application/cell/date_cell_bloc.dart

@@ -31,7 +31,7 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
       cellController.removeListener(_onCellChangedFn!);
       _onCellChangedFn = null;
     }
-    cellController.dispose();
+    await cellController.dispose();
     return super.close();
   }
 

+ 1 - 1
frontend/app_flowy/lib/plugins/grid/application/cell/number_cell_bloc.dart

@@ -46,7 +46,7 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
       cellController.removeListener(_onCellChangedFn!);
       _onCellChangedFn = null;
     }
-    cellController.dispose();
+    await cellController.dispose();
     return super.close();
   }
 

+ 1 - 1
frontend/app_flowy/lib/plugins/grid/application/cell/select_option_cell_bloc.dart

@@ -36,7 +36,7 @@ class SelectOptionCellBloc
       cellController.removeListener(_onCellChangedFn!);
       _onCellChangedFn = null;
     }
-    cellController.dispose();
+    await cellController.dispose();
     return super.close();
   }
 

+ 36 - 25
frontend/app_flowy/lib/plugins/grid/application/cell/select_option_editor_bloc.dart

@@ -1,7 +1,6 @@
 import 'dart:async';
 
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
-import 'package:collection/collection.dart';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
@@ -46,19 +45,29 @@ class SelectOptionCellEditorBloc
             ));
           },
           deleteOption: (_DeleteOption value) {
-            _deleteOption(value.option);
+            _deleteOption([value.option]);
+          },
+          deleteAllOptions: (_DeleteAllOptions value) {
+            if (state.allOptions.isNotEmpty) {
+              _deleteOption(state.allOptions);
+            }
           },
           updateOption: (_UpdateOption value) {
             _updateOption(value.option);
           },
           selectOption: (_SelectOption value) {
-            _onSelectOption(value.optionId);
+            _selectOptionService.select(optionIds: [value.optionId]);
+          },
+          unSelectOption: (_UnSelectOption value) {
+            _selectOptionService.unSelect(optionIds: [value.optionId]);
           },
           trySelectOption: (_TrySelectOption value) {
             _trySelectOption(value.optionName, emit);
           },
           selectMultipleOptions: (_SelectMultipleOptions value) {
-            _selectMultipleOptions(value.optionNames);
+            if (value.optionNames.isNotEmpty) {
+              _selectMultipleOptions(value.optionNames);
+            }
             _filterOption(value.remainder, emit);
           },
           filterOption: (_SelectOptionFilter value) {
@@ -72,7 +81,7 @@ class SelectOptionCellEditorBloc
   @override
   Future<void> close() async {
     _delayOperation?.cancel();
-    cellController.dispose();
+    await cellController.dispose();
     return super.close();
   }
 
@@ -81,11 +90,8 @@ class SelectOptionCellEditorBloc
     result.fold((l) => {}, (err) => Log.error(err));
   }
 
-  void _deleteOption(SelectOptionPB option) async {
-    final result = await _selectOptionService.delete(
-      option: option,
-    );
-
+  void _deleteOption(List<SelectOptionPB> options) async {
+    final result = await _selectOptionService.delete(options: options);
     result.fold((l) => null, (err) => Log.error(err));
   }
 
@@ -97,16 +103,6 @@ class SelectOptionCellEditorBloc
     result.fold((l) => null, (err) => Log.error(err));
   }
 
-  void _onSelectOption(String optionId) {
-    final hasSelected = state.selectedOptions
-        .firstWhereOrNull((option) => option.id == optionId);
-    if (hasSelected != null) {
-      _selectOptionService.unSelect(optionIds: [optionId]);
-    } else {
-      _selectOptionService.select(optionIds: [optionId]);
-    }
-  }
-
   void _trySelectOption(
       String optionName, Emitter<SelectOptionEditorState> emit) {
     SelectOptionPB? matchingOption;
@@ -138,9 +134,19 @@ class SelectOptionCellEditorBloc
   }
 
   void _selectMultipleOptions(List<String> optionNames) {
-    final optionIds = state.options
-        .where((e) => optionNames.contains(e.name))
-        .map((e) => e.id);
+    // The options are unordered. So in order to keep the inserted [optionNames]
+    // order, it needs to get the option id in the [optionNames] order.
+    final lowerCaseNames = optionNames.map((e) => e.toLowerCase());
+    final Map<String, String> optionIdsMap = {};
+    for (final option in state.options) {
+      optionIdsMap[option.name.toLowerCase()] = option.id;
+    }
+
+    final optionIds = lowerCaseNames
+        .where((name) => optionIdsMap[name] != null)
+        .map((name) => optionIdsMap[name]!)
+        .toList();
+
     _selectOptionService.select(optionIds: optionIds);
   }
 
@@ -162,8 +168,10 @@ class SelectOptionCellEditorBloc
           return;
         }
         return result.fold(
-          (data) => add(SelectOptionEditorEvent.didReceiveOptions(
-              data.options, data.selectOptions)),
+          (data) => add(
+            SelectOptionEditorEvent.didReceiveOptions(
+                data.options, data.selectOptions),
+          ),
           (err) {
             Log.error(err);
             return null;
@@ -225,10 +233,13 @@ class SelectOptionEditorEvent with _$SelectOptionEditorEvent {
       _NewOption;
   const factory SelectOptionEditorEvent.selectOption(String optionId) =
       _SelectOption;
+  const factory SelectOptionEditorEvent.unSelectOption(String optionId) =
+      _UnSelectOption;
   const factory SelectOptionEditorEvent.updateOption(SelectOptionPB option) =
       _UpdateOption;
   const factory SelectOptionEditorEvent.deleteOption(SelectOptionPB option) =
       _DeleteOption;
+  const factory SelectOptionEditorEvent.deleteAllOptions() = _DeleteAllOptions;
   const factory SelectOptionEditorEvent.filterOption(String optionName) =
       _SelectOptionFilter;
   const factory SelectOptionEditorEvent.trySelectOption(String optionName) =

+ 12 - 13
frontend/app_flowy/lib/plugins/grid/application/cell/select_option_service.dart

@@ -21,11 +21,11 @@ class SelectOptionService {
       (result) {
         return result.fold(
           (option) {
-            final cellIdentifier = GridCellIdPB.create()
+            final cellIdentifier = CellPathPB.create()
               ..gridId = gridId
               ..fieldId = fieldId
               ..rowId = rowId;
-            final payload = SelectOptionChangesetPayloadPB.create()
+            final payload = SelectOptionChangesetPB.create()
               ..insertOptions.add(option)
               ..cellIdentifier = cellIdentifier;
             return GridEventUpdateSelectOption(payload).send();
@@ -39,24 +39,23 @@ class SelectOptionService {
   Future<Either<Unit, FlowyError>> update({
     required SelectOptionPB option,
   }) {
-    final payload = SelectOptionChangesetPayloadPB.create()
+    final payload = SelectOptionChangesetPB.create()
       ..updateOptions.add(option)
       ..cellIdentifier = _cellIdentifier();
     return GridEventUpdateSelectOption(payload).send();
   }
 
-  Future<Either<Unit, FlowyError>> delete({
-    required SelectOptionPB option,
-  }) {
-    final payload = SelectOptionChangesetPayloadPB.create()
-      ..deleteOptions.add(option)
+  Future<Either<Unit, FlowyError>> delete(
+      {required Iterable<SelectOptionPB> options}) {
+    final payload = SelectOptionChangesetPB.create()
+      ..deleteOptions.addAll(options)
       ..cellIdentifier = _cellIdentifier();
 
     return GridEventUpdateSelectOption(payload).send();
   }
 
   Future<Either<SelectOptionCellDataPB, FlowyError>> getOptionContext() {
-    final payload = GridCellIdPB.create()
+    final payload = CellPathPB.create()
       ..gridId = gridId
       ..fieldId = fieldId
       ..rowId = rowId;
@@ -66,7 +65,7 @@ class SelectOptionService {
 
   Future<Either<void, FlowyError>> select(
       {required Iterable<String> optionIds}) {
-    final payload = SelectOptionCellChangesetPayloadPB.create()
+    final payload = SelectOptionCellChangesetPB.create()
       ..cellIdentifier = _cellIdentifier()
       ..insertOptionIds.addAll(optionIds);
     return GridEventUpdateSelectOptionCell(payload).send();
@@ -74,14 +73,14 @@ class SelectOptionService {
 
   Future<Either<void, FlowyError>> unSelect(
       {required Iterable<String> optionIds}) {
-    final payload = SelectOptionCellChangesetPayloadPB.create()
+    final payload = SelectOptionCellChangesetPB.create()
       ..cellIdentifier = _cellIdentifier()
       ..deleteOptionIds.addAll(optionIds);
     return GridEventUpdateSelectOptionCell(payload).send();
   }
 
-  GridCellIdPB _cellIdentifier() {
-    return GridCellIdPB.create()
+  CellPathPB _cellIdentifier() {
+    return CellPathPB.create()
       ..gridId = gridId
       ..fieldId = fieldId
       ..rowId = rowId;

+ 1 - 1
frontend/app_flowy/lib/plugins/grid/application/cell/text_cell_bloc.dart

@@ -35,7 +35,7 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
       cellController.removeListener(_onCellChangedFn!);
       _onCellChangedFn = null;
     }
-    cellController.dispose();
+    await cellController.dispose();
     return super.close();
   }
 

+ 1 - 1
frontend/app_flowy/lib/plugins/grid/application/cell/url_cell_bloc.dart

@@ -38,7 +38,7 @@ class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
       cellController.removeListener(_onCellChangedFn!);
       _onCellChangedFn = null;
     }
-    cellController.dispose();
+    await cellController.dispose();
     return super.close();
   }
 

+ 1 - 1
frontend/app_flowy/lib/plugins/grid/application/cell/url_cell_editor_bloc.dart

@@ -36,7 +36,7 @@ class URLCellEditorBloc extends Bloc<URLCellEditorEvent, URLCellEditorState> {
       cellController.removeListener(_onCellChangedFn!);
       _onCellChangedFn = null;
     }
-    cellController.dispose();
+    await cellController.dispose();
     return super.close();
   }
 

+ 20 - 5
frontend/app_flowy/lib/plugins/grid/application/field/field_action_sheet_bloc.dart

@@ -11,9 +11,16 @@ class FieldActionSheetBloc
     extends Bloc<FieldActionSheetEvent, FieldActionSheetState> {
   final FieldService fieldService;
 
-  FieldActionSheetBloc({required FieldPB field, required this.fieldService})
-      : super(FieldActionSheetState.initial(
-            FieldTypeOptionDataPB.create()..field_2 = field)) {
+  FieldActionSheetBloc({required GridFieldCellContext fieldCellContext})
+      : fieldService = FieldService(
+          gridId: fieldCellContext.gridId,
+          fieldId: fieldCellContext.field.id,
+        ),
+        super(
+          FieldActionSheetState.initial(
+            TypeOptionPB.create()..field_2 = fieldCellContext.field,
+          ),
+        ) {
     on<FieldActionSheetEvent>(
       (event, emit) async {
         await event.map(
@@ -31,6 +38,13 @@ class FieldActionSheetBloc
               (err) => Log.error(err),
             );
           },
+          showField: (_ShowField value) async {
+            final result = await fieldService.updateField(visibility: true);
+            result.fold(
+              (l) => null,
+              (err) => Log.error(err),
+            );
+          },
           deleteField: (_DeleteField value) async {
             final result = await fieldService.deleteField();
             result.fold(
@@ -62,6 +76,7 @@ class FieldActionSheetEvent with _$FieldActionSheetEvent {
   const factory FieldActionSheetEvent.updateFieldName(String name) =
       _UpdateFieldName;
   const factory FieldActionSheetEvent.hideField() = _HideField;
+  const factory FieldActionSheetEvent.showField() = _ShowField;
   const factory FieldActionSheetEvent.duplicateField() = _DuplicateField;
   const factory FieldActionSheetEvent.deleteField() = _DeleteField;
   const factory FieldActionSheetEvent.saveField() = _SaveField;
@@ -70,12 +85,12 @@ class FieldActionSheetEvent with _$FieldActionSheetEvent {
 @freezed
 class FieldActionSheetState with _$FieldActionSheetState {
   const factory FieldActionSheetState({
-    required FieldTypeOptionDataPB fieldTypeOptionData,
+    required TypeOptionPB fieldTypeOptionData,
     required String errorText,
     required String fieldName,
   }) = _FieldActionSheetState;
 
-  factory FieldActionSheetState.initial(FieldTypeOptionDataPB data) =>
+  factory FieldActionSheetState.initial(TypeOptionPB data) =>
       FieldActionSheetState(
         fieldTypeOptionData: data,
         errorText: '',

Some files were not shown because too many files changed in this diff