Browse Source

chore: Merge branch 'main' into release/0.0.9.1

nathan 2 years ago
parent
commit
1b295c0ab6
100 changed files with 1186 additions and 961 deletions
  1. 2 1
      .github/workflows/appflowy_editor_test.yml
  2. 0 86
      .github/workflows/dart_lint.yml
  3. 0 80
      .github/workflows/dart_test.yml
  4. 43 36
      .github/workflows/flutter_ci.yaml
  5. 1 1
      .github/workflows/integration_test.yml
  6. 195 192
      .github/workflows/release.yml
  7. 0 35
      .github/workflows/release_docker.yml
  8. 86 0
      .github/workflows/rust_ci.yaml
  9. 20 24
      .github/workflows/rust_coverage.yml
  10. 0 61
      .github/workflows/rust_lint.yml
  11. 0 60
      .github/workflows/rust_test.yml
  12. 15 17
      frontend/Makefile.toml
  13. 10 0
      frontend/app_flowy/assets/translations/en.json
  14. 5 5
      frontend/app_flowy/ios/Podfile.lock
  15. 16 10
      frontend/app_flowy/lib/core/folder_notification.dart
  16. 4 4
      frontend/app_flowy/lib/core/grid_notification.dart
  17. 3 3
      frontend/app_flowy/lib/core/network_monitor.dart
  18. 6 2
      frontend/app_flowy/lib/core/notification_helper.dart
  19. 16 10
      frontend/app_flowy/lib/core/user_notification.dart
  20. 4 4
      frontend/app_flowy/lib/plugins/board/application/board_bloc.dart
  21. 14 13
      frontend/app_flowy/lib/plugins/board/application/board_data_controller.dart
  22. 4 4
      frontend/app_flowy/lib/plugins/board/application/board_listener.dart
  23. 1 1
      frontend/app_flowy/lib/plugins/board/application/card/board_checkbox_cell_bloc.dart
  24. 1 1
      frontend/app_flowy/lib/plugins/board/application/card/board_date_cell_bloc.dart
  25. 1 1
      frontend/app_flowy/lib/plugins/board/application/card/board_number_cell_bloc.dart
  26. 1 1
      frontend/app_flowy/lib/plugins/board/application/card/board_select_option_cell_bloc.dart
  27. 2 2
      frontend/app_flowy/lib/plugins/board/application/card/board_text_cell_bloc.dart
  28. 1 1
      frontend/app_flowy/lib/plugins/board/application/card/board_url_cell_bloc.dart
  29. 1 1
      frontend/app_flowy/lib/plugins/board/application/card/card_bloc.dart
  30. 1 1
      frontend/app_flowy/lib/plugins/board/application/card/card_data_controller.dart
  31. 1 1
      frontend/app_flowy/lib/plugins/board/application/group.dart
  32. 3 3
      frontend/app_flowy/lib/plugins/board/application/group_controller.dart
  33. 4 4
      frontend/app_flowy/lib/plugins/board/application/group_listener.dart
  34. 1 1
      frontend/app_flowy/lib/plugins/board/board.dart
  35. 4 5
      frontend/app_flowy/lib/plugins/board/presentation/board_page.dart
  36. 1 1
      frontend/app_flowy/lib/plugins/board/presentation/card/board_text_cell.dart
  37. 1 1
      frontend/app_flowy/lib/plugins/board/presentation/card/card_cell_builder.dart
  38. 2 1
      frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_setting.dart
  39. 3 2
      frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_toolbar.dart
  40. 111 0
      frontend/app_flowy/lib/plugins/calendar/calendar.dart
  41. 4 4
      frontend/app_flowy/lib/plugins/document/application/doc_bloc.dart
  42. 4 4
      frontend/app_flowy/lib/plugins/document/application/doc_service.dart
  43. 3 3
      frontend/app_flowy/lib/plugins/document/application/share_bloc.dart
  44. 4 4
      frontend/app_flowy/lib/plugins/document/application/share_service.dart
  45. 1 1
      frontend/app_flowy/lib/plugins/document/document.dart
  46. 1 1
      frontend/app_flowy/lib/plugins/document/document_page.dart
  47. 4 4
      frontend/app_flowy/lib/plugins/document/presentation/share/share_button.dart
  48. 2 2
      frontend/app_flowy/lib/plugins/grid/application/cell/cell_listener.dart
  49. 19 19
      frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_controller.dart
  50. 6 6
      frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_data_loader.dart
  51. 4 5
      frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_data_persistence.dart
  52. 8 8
      frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_service.dart
  53. 1 1
      frontend/app_flowy/lib/plugins/grid/application/cell/checkbox_cell_bloc.dart
  54. 2 2
      frontend/app_flowy/lib/plugins/grid/application/cell/checklist_cell_bloc.dart
  55. 2 2
      frontend/app_flowy/lib/plugins/grid/application/cell/checklist_cell_editor_bloc.dart
  56. 9 8
      frontend/app_flowy/lib/plugins/grid/application/cell/date_cal_bloc.dart
  57. 1 1
      frontend/app_flowy/lib/plugins/grid/application/cell/date_cell_bloc.dart
  58. 2 2
      frontend/app_flowy/lib/plugins/grid/application/cell/number_cell_bloc.dart
  59. 1 1
      frontend/app_flowy/lib/plugins/grid/application/cell/select_option_cell_bloc.dart
  60. 2 2
      frontend/app_flowy/lib/plugins/grid/application/cell/select_option_editor_bloc.dart
  61. 4 4
      frontend/app_flowy/lib/plugins/grid/application/cell/select_option_service.dart
  62. 3 2
      frontend/app_flowy/lib/plugins/grid/application/cell/text_cell_bloc.dart
  63. 1 1
      frontend/app_flowy/lib/plugins/grid/application/cell/url_cell_bloc.dart
  64. 1 1
      frontend/app_flowy/lib/plugins/grid/application/cell/url_cell_editor_bloc.dart
  65. 2 2
      frontend/app_flowy/lib/plugins/grid/application/field/field_action_sheet_bloc.dart
  66. 2 2
      frontend/app_flowy/lib/plugins/grid/application/field/field_cell_bloc.dart
  67. 321 76
      frontend/app_flowy/lib/plugins/grid/application/field/field_controller.dart
  68. 1 1
      frontend/app_flowy/lib/plugins/grid/application/field/field_editor_bloc.dart
  69. 3 3
      frontend/app_flowy/lib/plugins/grid/application/field/field_listener.dart
  70. 4 4
      frontend/app_flowy/lib/plugins/grid/application/field/field_service.dart
  71. 1 1
      frontend/app_flowy/lib/plugins/grid/application/field/field_type_option_edit_bloc.dart
  72. 3 3
      frontend/app_flowy/lib/plugins/grid/application/field/grid_listener.dart
  73. 2 2
      frontend/app_flowy/lib/plugins/grid/application/field/type_option/date_bloc.dart
  74. 1 1
      frontend/app_flowy/lib/plugins/grid/application/field/type_option/edit_select_option_bloc.dart
  75. 3 3
      frontend/app_flowy/lib/plugins/grid/application/field/type_option/multi_select_type_option.dart
  76. 2 2
      frontend/app_flowy/lib/plugins/grid/application/field/type_option/number_bloc.dart
  77. 1 1
      frontend/app_flowy/lib/plugins/grid/application/field/type_option/number_format_bloc.dart
  78. 1 1
      frontend/app_flowy/lib/plugins/grid/application/field/type_option/select_option_type_option_bloc.dart
  79. 3 3
      frontend/app_flowy/lib/plugins/grid/application/field/type_option/single_select_type_option.dart
  80. 11 11
      frontend/app_flowy/lib/plugins/grid/application/field/type_option/type_option_context.dart
  81. 3 3
      frontend/app_flowy/lib/plugins/grid/application/field/type_option/type_option_data_controller.dart
  82. 4 4
      frontend/app_flowy/lib/plugins/grid/application/field/type_option/type_option_service.dart
  83. 2 2
      frontend/app_flowy/lib/plugins/grid/application/filter/checkbox_filter_editor_bloc.dart
  84. 2 2
      frontend/app_flowy/lib/plugins/grid/application/filter/checklist_filter_bloc.dart
  85. 8 8
      frontend/app_flowy/lib/plugins/grid/application/filter/filter_create_bloc.dart
  86. 4 4
      frontend/app_flowy/lib/plugins/grid/application/filter/filter_listener.dart
  87. 13 15
      frontend/app_flowy/lib/plugins/grid/application/filter/filter_service.dart
  88. 2 2
      frontend/app_flowy/lib/plugins/grid/application/filter/select_option_filter_bloc.dart
  89. 2 2
      frontend/app_flowy/lib/plugins/grid/application/filter/select_option_filter_list_bloc.dart
  90. 2 2
      frontend/app_flowy/lib/plugins/grid/application/filter/text_filter_editor_bloc.dart
  91. 47 0
      frontend/app_flowy/lib/plugins/grid/application/grid_accessory_bloc.dart
  92. 3 3
      frontend/app_flowy/lib/plugins/grid/application/grid_bloc.dart
  93. 3 3
      frontend/app_flowy/lib/plugins/grid/application/grid_data_controller.dart
  94. 2 2
      frontend/app_flowy/lib/plugins/grid/application/grid_header_bloc.dart
  95. 7 7
      frontend/app_flowy/lib/plugins/grid/application/grid_service.dart
  96. 2 2
      frontend/app_flowy/lib/plugins/grid/application/row/row_action_sheet_bloc.dart
  97. 2 2
      frontend/app_flowy/lib/plugins/grid/application/row/row_bloc.dart
  98. 41 15
      frontend/app_flowy/lib/plugins/grid/application/row/row_cache.dart
  99. 2 2
      frontend/app_flowy/lib/plugins/grid/application/row/row_data_controller.dart
  100. 1 1
      frontend/app_flowy/lib/plugins/grid/application/row/row_detail_bloc.dart

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

@@ -15,6 +15,7 @@ on:
 
 env:
   CARGO_TERM_COLOR: always
+  FLUTTER_VERSION: "3.3.9"
 
 jobs:
   tests:
@@ -30,7 +31,7 @@ jobs:
       - uses: subosito/flutter-action@v2
         with:
           channel: "stable"
-          flutter-version: "3.0.5"
+          flutter-version: ${{ env.FLUTTER_VERSION }}
           cache: true
 
       - name: Run FlowyEditor tests

+ 0 - 86
.github/workflows/dart_lint.yml

@@ -1,86 +0,0 @@
-# This workflow uses actions that are not certified by GitHub.
-# They are provided by a third-party and are governed by
-# separate terms of service, privacy policy, and support
-# documentation.
-
-name: Flutter lint
-
-on:
-  push:
-    branches:
-      - "main"
-      - "release/*"
-    paths:
-      - "frontend/app_flowy/**"
-
-  pull_request:
-    branches:
-      - "main"
-      - "release/*"
-    paths:
-      - "frontend/app_flowy/**"
-
-env:
-  CARGO_TERM_COLOR: always
-
-jobs:
-  flutter-analyze:
-    name: flutter analyze
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v2
-      - uses: subosito/flutter-action@v1
-        with:
-          flutter-version: "3.0.5"
-          channel: "stable"
-      - uses: actions-rs/toolchain@v1
-        with:
-          toolchain: "stable-2022-04-07"
-
-      - name: Cache Cargo
-        id: cache-cargo
-        uses: actions/cache@v2
-        with:
-          path: |
-            ~/.cargo
-          key: ${{ runner.os }}-cargo-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}
-
-      - name: Cache Rust
-        id: cache-rust-target
-        uses: actions/cache@v2
-        with:
-          path: |
-            frontend/rust-lib/target
-            shared-lib/target
-          key: ${{ runner.os }}-rust-rust-lib-share-lib-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}
-
-      - if: steps.cache-cargo.outputs.cache-hit != 'true'
-        name: Rust Deps
-        working-directory: frontend
-        run: |
-          cargo install cargo-make
-
-      - name: Cargo make flowy dev
-        working-directory: frontend
-        run: |
-          cargo make appflowy-deps-tools
-
-      - name: Flutter Deps
-        run: flutter packages pub get
-        working-directory: frontend/app_flowy
-
-      - name: Build FlowySDK
-        working-directory: frontend
-        run: |
-          cargo make --profile development-linux-x86_64 appflowy-sdk-dev
-
-      - 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
-          flutter packages pub run build_runner build --delete-conflicting-outputs
-
-      - name: Run Flutter Analyzer
-        working-directory: frontend/app_flowy
-        run: flutter analyze

+ 0 - 80
.github/workflows/dart_test.yml

@@ -1,80 +0,0 @@
-name: Frontend test
-
-on:
-  push:
-    branches:
-      - "main"
-      - "release/*"
-    paths:
-      - "frontend/app_flowy/**"
-
-  pull_request:
-    branches:
-      - "main"
-      - "release/*"
-    paths:
-      - "frontend/app_flowy/**"
-
-env:
-  CARGO_TERM_COLOR: always
-
-jobs:
-  tests:
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-      - uses: actions-rs/toolchain@v1
-        with:
-          toolchain: "stable-2022-04-07"
-
-      - uses: subosito/flutter-action@v2
-        with:
-          channel: "stable"
-          flutter-version: "3.0.5"
-          cache: true
-
-      - name: Cache Cargo
-        uses: actions/cache@v2
-        with:
-          path: |
-            ~/.cargo
-          key: ${{ runner.os }}-cargo-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}
-
-      - name: Cache Rust
-        id: cache-rust-target
-        uses: actions/cache@v2
-        with:
-          path: |
-            frontend/rust-lib/target
-            shared-lib/target
-          key: ${{ runner.os }}-rust-rust-lib-share-lib-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}
-
-      - if: steps.cache-cargo.outputs.cache-hit != 'true'
-        name: Rust Deps
-        working-directory: frontend
-        run: |
-          cargo install cargo-make
-          cargo make appflowy-deps-tools
-
-      - name: Flutter Deps
-        working-directory: frontend/app_flowy
-        run: |
-          flutter config --enable-linux-desktop
-
-      - name: Build Test lib
-        working-directory: frontend
-        run: |
-          cargo make --profile test-linux build-test-lib
-
-      - name: Flutter Code Generation
-        working-directory: frontend/app_flowy
-        run: |
-          flutter packages pub get
-          flutter packages pub run easy_localization:generate -f keys -o locale_keys.g.dart -S assets/translations -s en.json
-          flutter packages pub run build_runner build --delete-conflicting-outputs
-
-      - name: Run bloc tests
-        working-directory: frontend/app_flowy
-        run: |
-          flutter pub get
-          flutter test

+ 43 - 36
.github/workflows/ci.yaml → .github/workflows/flutter_ci.yaml

@@ -1,89 +1,87 @@
-name: CI
+name: Flutter-CI
 
 on:
   push:
     branches:
       - "main"
       - "release/*"
+    paths:
+      - "frontend/**"
 
   pull_request:
     branches:
       - "main"
       - "release/*"
+    paths:
+      - "frontend/**"
+
+env:
+  FLUTTER_VERSION: "3.3.9"
+  RUST_TOOLCHAIN: "1.65"
 
 jobs:
   build:
     strategy:
+      fail-fast: false
       matrix:
         os: [ubuntu-latest, macos-latest, windows-latest]
         include:
           - os: ubuntu-latest
             flutter_profile: development-linux-x86_64
+            target: x86_64-unknown-linux-gnu
           - os: macos-latest
             flutter_profile: development-mac-x86_64
+            target: x86_64-apple-darwin
           - os: windows-latest
             flutter_profile: development-windows-x86
+            target: x86_64-pc-windows-msvc
     runs-on: ${{ matrix.os }}
 
     steps:
-      - uses: actions/checkout@v2
+      - name: Checkout source code
+        uses: actions/checkout@v2
 
-      - id: rust_toolchain
+      - name: Install Rust toolchain
+        id: rust_toolchain
         uses: actions-rs/toolchain@v1
         with:
-          toolchain: "stable-2022-04-07"
+          toolchain: ${{ env.RUST_TOOLCHAIN }}
+          target: ${{ matrix.target }}
+          override: true
+          profile: minimal
 
-      - id: flutter
+      - name: Install flutter
+        id: flutter
         uses: subosito/flutter-action@v2
         with:
           channel: "stable"
+          flutter-version: ${{ env.FLUTTER_VERSION }}
           cache: true
-          flutter-version: "3.0.5"
-
-      - name: Cache Cargo
-        id: cache-cargo
-        uses: actions/cache@v2
-        with:
-          path: |
-            ~/.cargo
-          key: ${{ runner.os }}-cargo-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}
 
-      - name: Cache Rust
-        uses: actions/cache@v2
+      - uses: Swatinem/rust-cache@v2
         with:
-          path: |
-            frontend/rust-lib/target
-            shared-lib/target
-          key: ${{ runner.os }}-rust-rust-lib-share-lib-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}
+          prefix-key: ${{ matrix.job.os }}
 
-      - name: Setup Environment
+      - name: Install prerequisites
+        working-directory: frontend
         run: |
+          cargo install --force cargo-make
+          cargo install --force duckscript_cli
           if [ "$RUNNER_OS" == "Linux" ]; then
             sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
             sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
             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 -y dart curl build-essential 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
-        shell: bash
-
-      - if: steps.cache-cargo.outputs.cache-hit != 'true'
-        name: Deps
-        working-directory: frontend
-        run: |
-          cargo install cargo-make
-          cargo install duckscript_cli
-
-      - name: Cargo make appflowy-deps-tools
-        working-directory: frontend
-        run: |
           cargo make appflowy-deps-tools
+        shell: bash
 
-      - name: Config Flutter
+      - name: Enable Flutter Desktop
         run: |
           if [ "$RUNNER_OS" == "Linux" ]; then
             flutter config --enable-linux-desktop
@@ -94,7 +92,16 @@ jobs:
           fi
         shell: bash
 
-      - name: Build
+      - name: Build AppFlowy
         working-directory: frontend
         run: |
           cargo make --profile ${{ matrix.flutter_profile }} appflowy-dev
+
+      - name: Flutter Analyzer
+        working-directory: frontend/app_flowy
+        run: flutter analyze
+
+      - name: Run Flutter unit tests
+        working-directory: frontend
+        run: |
+          cargo make dart_unit_test

+ 1 - 1
.github/workflows/integration_test.yml

@@ -35,7 +35,7 @@ jobs:
       - uses: subosito/flutter-action@v2
         with:
           channel: "stable"
-          flutter-version: "3.0.5"
+          flutter-version: "3.3.9"
           cache: true
 
       - name: Cache Cargo

+ 195 - 192
.github/workflows/release.yml

@@ -5,6 +5,10 @@ on:
     tags:
       - '*'
 
+env:
+  FLUTTER_VERSION: "3.3.9"
+  RUST_TOOLCHAIN: "1.65"
+
 jobs:
   create-release:
     runs-on: ubuntu-latest
@@ -31,221 +35,161 @@ jobs:
           release_name: v${{ github.ref }}
           body_path: ${{ env.RELEASE_NOTES_PATH }}
 
-  build-linux-x86:
-    runs-on: ubuntu-latest
+  build-for-windows:
+    name: ${{ matrix.job.target }} (${{ matrix.job.os }})
     needs: create-release
     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
+      WINDOWS_APP_RELEASE_PATH: frontend\app_flowy\product\${{ github.ref_name }}\windows
+      WINDOWS_ZIP_NAME: AppFlowy_${{ github.ref_name }}_windows-x86_64.zip
+      WINDOWS_INSTALLER_NAME: AppFlowy_${{ github.ref_name }}_windows-x86_64
+    runs-on: ${{ matrix.job.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        job:
+          - { target: x86_64-pc-windows-msvc, os: windows-2019 }
     steps:
-      - name: Checkout
-        uses: actions/checkout@v2
+      - name: Checkout source code
+        uses: actions/checkout@v3
 
-      - name: Setup environment - Rust and Cargo
+      - name: Install flutter
+        uses: subosito/flutter-action@v2
+        with:
+          channel: "stable"
+          flutter-version: ${{ env.FLUTTER_VERSION }}
+          cache: true
+
+      - name: Install Rust toolchain
         uses: actions-rs/toolchain@v1
         with:
-          toolchain: 'stable-2022-04-07'
+          toolchain: ${{ env.RUST_TOOLCHAIN }}
+          target: ${{ matrix.job.target }}
+          override: true
+          components: rustfmt
+          profile: minimal
 
-      - name: Setup environment - Flutter
-        uses: subosito/flutter-action@v2
+      - uses: Swatinem/rust-cache@v2
         with:
-          channel: 'stable'
-          flutter-version: '3.0.5'
+          prefix-key: ${{ matrix.job.os }}
 
-      - name: Pre build
+      - name: Install prerequisites
         working-directory: frontend
         run: |
-          sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
-          sudo apt-get update
-          sudo apt-get install -y build-essential libsqlite3-dev libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev
-          sudo apt-get install keybinder-3.0
-          source $HOME/.cargo/env
           cargo install --force cargo-make
           cargo install --force duckscript_cli
-          cargo make appflowy-deps-tools
 
-      - name: Build Linux app
+      - name: Build Windows app
         working-directory: frontend
         run: |
-          flutter config --enable-linux-desktop
-          cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-linux-x86_64 appflowy
+          flutter config --enable-windows-desktop
+          cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-windows-x86 appflowy
 
-      - name: Configuring Linux Package
+      - name: Archive Asset
+        uses: vimtor/action-zip@v1
+        with:
+          files: ${{ env.WINDOWS_APP_RELEASE_PATH }}\
+          dest: ${{ env.WINDOWS_APP_RELEASE_PATH }}\${{ env.WINDOWS_ZIP_NAME }}
+
+      - name: Copy installer config & icon file
         working-directory: frontend
         run: |
-          mkdir -p ../${{ env.LINUX_APP_RELEASE_PATH }}/package/opt
-          mkdir -p ../${{ env.LINUX_APP_RELEASE_PATH }}/package/usr/share/applications
-          cp -r ./scripts/linux_installer ../${{ env.LINUX_APP_RELEASE_PATH }}/package/DEBIAN
-          cd ../${{ env.LINUX_APP_RELEASE_PATH }}/package/DEBIAN
-          grep -rl "\[CHANGE_THIS\]" ./control | xargs sed -i "s/\[CHANGE_THIS\]/${{ github.ref_name }}/"
-          chmod 0755 {postinst,postrm}
+          cp scripts/windows_installer/* ../${{ env.WINDOWS_APP_RELEASE_PATH }}
 
-      - name: Build Linux package
-        working-directory: ${{ env.LINUX_APP_RELEASE_PATH }}
+      - name: Build installer executable
+        working-directory: ${{ env.WINDOWS_APP_RELEASE_PATH }}
         run: |
-          mv AppFlowy package/opt/
-          cd package
-
-          # 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 -Z xz package ${{ env.LINUX_PACKAGE_NAME }}
+          iscc /F${{ env.WINDOWS_INSTALLER_NAME }} inno_setup_config.iss /DAppVersion=${{ github.ref_name }}
 
-      - name: Upload Release Asset
+      - name: Upload Asset
         id: upload-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.LINUX_APP_RELEASE_PATH }}/${{ env.LINUX_ZIP_NAME }}
-          asset_name: ${{ env.LINUX_ZIP_NAME }}
+          asset_path: ${{ env.WINDOWS_APP_RELEASE_PATH }}\${{ env.WINDOWS_ZIP_NAME }}
+          asset_name: ${{ env.WINDOWS_ZIP_NAME }}
           asset_content_type: application/octet-stream
 
-      - name: Upload Release Asset Install Package
-        id: upload-release-asset-install-package
+      - name: Upload Installer Asset
+        id: upload-installer-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.LINUX_APP_RELEASE_PATH }}/${{ env.LINUX_PACKAGE_NAME }}
-          asset_name: ${{ env.LINUX_PACKAGE_NAME }}
+          asset_path: ${{ env.WINDOWS_APP_RELEASE_PATH }}\Output\${{ env.WINDOWS_INSTALLER_NAME }}.exe
+          asset_name: ${{ env.WINDOWS_INSTALLER_NAME }}.exe
           asset_content_type: application/octet-stream
 
-  build-linux-x86-alt:
-    runs-on: ubuntu-18.04
+  build-for-macOS:
+    name: ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-args }}]
+    runs-on: ${{ matrix.job.os }}
     needs: create-release
     env:
-      LINUX_APP_RELEASE_PATH: frontend/app_flowy/product/${{ github.ref_name }}/linux/Release
-      LINUX_PACKAGE_NAME: AppFlowy_${{ github.ref_name }}_linux-amd64-alt.deb
+      MACOS_APP_RELEASE_PATH: frontend/app_flowy/product/${{ github.ref_name }}/macos/Release
+      MACOS_X86_ZIP_NAME: AppFlowy_${{ github.ref_name }}_macos-x86_64.zip
+      MACOS_DMG_NAME: AppFlowy_${{ github.ref_name }}_macos-x86_64
+    strategy:
+      fail-fast: false
+      matrix:
+        job:
+          - { target: x86_64-apple-darwin, os: macos-10.15, extra-build-args: "" }
     steps:
-      - name: Checkout
-        uses: actions/checkout@v2
-
-      - name: Setup environment - Rust and Cargo
-        uses: actions-rs/toolchain@v1
-        with:
-          toolchain: 'stable-2022-04-07'
+      - name: Checkout source code
+        uses: actions/checkout@v3
 
-      - name: Setup environment - Flutter
+      - name: Install flutter
         uses: subosito/flutter-action@v2
         with:
-          channel: 'stable'
-          flutter-version: '3.0.5'
-
-      - name: Pre build
-        working-directory: frontend
-        run: |
-          sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
-          sudo apt-get update
-          sudo apt-get install -y build-essential libsqlite3-dev libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev
-          sudo apt-get install keybinder-3.0
-          source $HOME/.cargo/env
-          cargo install --force cargo-make
-          cargo install --force duckscript_cli
-          cargo make appflowy-deps-tools
-
-      - name: Build Linux app
-        working-directory: frontend
-        run: |
-          flutter config --enable-linux-desktop
-          cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-linux-x86_64 appflowy
-
-      - name: Configuring Linux Package
-        working-directory: frontend
-        run: |
-          mkdir -p ../${{ env.LINUX_APP_RELEASE_PATH }}/package/opt
-          mkdir -p ../${{ env.LINUX_APP_RELEASE_PATH }}/package/usr/share/applications
-          cp -r ./scripts/linux_installer ../${{ env.LINUX_APP_RELEASE_PATH }}/package/DEBIAN
-          cd ../${{ env.LINUX_APP_RELEASE_PATH }}/package/DEBIAN
-          grep -rl "\[CHANGE_THIS\]" ./control | xargs sed -i "s/\[CHANGE_THIS\]/${{ github.ref_name }}/"
-          chmod 0755 {postinst,postrm}
-
-      - name: Build Linux package
-        working-directory: ${{ env.LINUX_APP_RELEASE_PATH }}
-        run: |
-          mv AppFlowy package/opt/
-          cd package
-
-          # 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 -Z xz package ${{ env.LINUX_PACKAGE_NAME }}
-
-      - name: Upload Alternative 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
+          channel: "stable"
+          flutter-version: ${{ env.FLUTTER_VERSION }}
+          cache: true
 
-  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
-
-      - name: Setup environment - Rust and Cargo
+      - name: Install Rust toolchain
         uses: actions-rs/toolchain@v1
         with:
-          toolchain: 'stable-2022-04-07'
+          toolchain: ${{ env.RUST_TOOLCHAIN }}
+          target: ${{ matrix.job.target }}
+          override: true
+          components: rustfmt
+          profile: minimal
 
-      - name: Setup environment - Flutter
-        uses: subosito/flutter-action@v2
+      - uses: Swatinem/rust-cache@v2
         with:
-          channel: 'stable'
-          flutter-version: '3.0.5'
+          prefix-key: ${{ matrix.job.os }}
 
-      - name: Pre build
+      - name: Install prerequisites
         working-directory: frontend
         run: |
-          source $HOME/.cargo/env
           cargo install --force cargo-make
           cargo install --force duckscript_cli
-          cargo make appflowy-deps-tools
 
-      - name: Build macOS app for x86_64
+      - name: Build AppFlowy
         working-directory: frontend
         run: |
           flutter config --enable-macos-desktop
           cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-mac-x86_64 appflowy
 
-      - name: Create MacOS dmg
+      - name: Create macOS dmg
         run: |
-            brew install create-dmg
-            create-dmg \
-            --volname ${{ env.MACOS_DMG_NAME }} \
-            --hide-extension "AppFlowy.app" \
-            --background frontend/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
+          brew install create-dmg
+          create-dmg \
+          --volname ${{ env.MACOS_DMG_NAME }} \
+          --hide-extension "AppFlowy.app" \
+          --background frontend/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 Asset
         working-directory: ${{ env.MACOS_APP_RELEASE_PATH }}
         run: zip --symlinks -qr ${{ env.MACOS_X86_ZIP_NAME }} AppFlowy.app
 
-      - name: Upload Release Asset
+      - name: Upload Asset
         uses: actions/upload-release-asset@v1
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -254,85 +198,144 @@ 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
+
+      - name: Upload DMG 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
+          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
+  build-for-linux:
+    name: ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-args }}]
+    runs-on: ${{ matrix.job.os }}
     needs: create-release
     env:
-      WINDOWS_APP_RELEASE_PATH: frontend\app_flowy\product\${{ github.ref_name }}\windows
-      WINDOWS_ZIP_NAME: AppFlowy-windows-x86_64.zip
-      WINDOWS_INSTALLER_NAME: AppFlowy-${{ github.ref_name }}-windows-x86_64
+      LINUX_APP_RELEASE_PATH: frontend/app_flowy/product/${{ github.ref_name }}/linux/Release
+      LINUX_ZIP_NAME: AppFlowy_${{ matrix.job.target }}_${{ matrix.job.os }}.tar.gz
+      LINUX_PACKAGE_NAME: AppFlowy_${{ github.ref_name }}_${{ matrix.job.os }}.deb
+    strategy:
+      fail-fast: false
+      matrix:
+        job:
+          - { arch: x86_64, target: x86_64-unknown-linux-gnu, os: ubuntu-20.04, extra-build-args: "", }
+          - { arch: x86_64, target: x86_64-unknown-linux-gnu, os: ubuntu-18.04, extra-build-args: "", }
     steps:
-      - name: Checkout
+      - name: Checkout source code
         uses: actions/checkout@v3
 
-      - name: Setup environment - Rust and Cargo
+      - name: Install flutter
+        uses: subosito/flutter-action@v2
+        with:
+          channel: "stable"
+          flutter-version: ${{ env.FLUTTER_VERSION }}
+          cache: true
+
+      - name: Install Rust toolchain
         uses: actions-rs/toolchain@v1
         with:
-          toolchain: 'stable-2022-04-07'
+          toolchain: ${{ env.RUST_TOOLCHAIN }}
+          target: ${{ matrix.job.target }}
+          override: true
+          components: rustfmt
+          profile: minimal
 
-      - name: Setup environment - Flutter
-        uses: subosito/flutter-action@v2
+      - uses: Swatinem/rust-cache@v2
         with:
-          channel: 'stable'
-          flutter-version: '3.0.5'
+          prefix-key: ${{ matrix.job.os }}
 
-      - name: Pre build
+      - name: Install prerequisites
         working-directory: frontend
         run: |
-          vcpkg integrate install
+          sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
+          sudo apt-get update
+          sudo apt-get install -y build-essential libsqlite3-dev libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev
+          sudo apt-get install keybinder-3.0
+          source $HOME/.cargo/env
           cargo install --force cargo-make
           cargo install --force duckscript_cli
-          cargo make appflowy-deps-tools
 
-      - name: Build Windows app
+      - name: Build AppFlowy
         working-directory: frontend
         run: |
-          flutter config --enable-windows-desktop
-          cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-windows-x86 appflowy
+          flutter config --enable-linux-desktop
+          cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-linux-x86_64 appflowy
 
-      - uses: vimtor/action-zip@v1
-        with:
-          files: ${{ env.WINDOWS_APP_RELEASE_PATH }}\
-          dest: ${{ env.WINDOWS_APP_RELEASE_PATH }}\${{ env.WINDOWS_ZIP_NAME }}
-      
-      - name: Copy installer config & icon file
+      - name: Archive Assert
+        working-directory: ${{ env.LINUX_APP_RELEASE_PATH }}
+        run: tar -czf ${{ env.LINUX_ZIP_NAME }} *
+
+      - name: Configuring Linux Package
         working-directory: frontend
         run: |
-          cp scripts/windows_installer/* ../${{ env.WINDOWS_APP_RELEASE_PATH }}
+          mkdir -p ../${{ env.LINUX_APP_RELEASE_PATH }}/package/opt
+          mkdir -p ../${{ env.LINUX_APP_RELEASE_PATH }}/package/usr/share/applications
+          cp -r ./scripts/linux_installer ../${{ env.LINUX_APP_RELEASE_PATH }}/package/DEBIAN
+          cd ../${{ env.LINUX_APP_RELEASE_PATH }}/package/DEBIAN
+          grep -rl "\[CHANGE_THIS\]" ./control | xargs sed -i "s/\[CHANGE_THIS\]/${{ github.ref_name }}/"
+          chmod 0755 {postinst,postrm}
 
-      - name: Build installer executable
-        working-directory: ${{ env.WINDOWS_APP_RELEASE_PATH }}
+      - name: Build Linux package
+        working-directory: ${{ env.LINUX_APP_RELEASE_PATH }}
         run: |
-          iscc /F${{ env.WINDOWS_INSTALLER_NAME }} inno_setup_config.iss /DAppVersion=${{ github.ref_name }}
+          mv AppFlowy package/opt/
+          cd package
+
+          # 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
 
-      - name: Upload Release Asset
+          # Build
+          cd ../ && dpkg-deb --build --root-owner-group -Z xz package ${{ env.LINUX_PACKAGE_NAME }}
+
+      - name: Upload Asset
         id: upload-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.WINDOWS_APP_RELEASE_PATH }}\${{ env.WINDOWS_ZIP_NAME }}
-          asset_name: ${{ env.WINDOWS_ZIP_NAME }}
+          asset_path: ${{ env.LINUX_APP_RELEASE_PATH }}/${{ env.LINUX_ZIP_NAME }}
+          asset_name: ${{ env.LINUX_ZIP_NAME }}
           asset_content_type: application/octet-stream
 
-      - name: Upload Installer Asset
-        id: upload-installer-asset
+      - name: Upload 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.WINDOWS_APP_RELEASE_PATH }}\Output\${{ env.WINDOWS_INSTALLER_NAME }}.exe
-          asset_name: ${{ env.WINDOWS_INSTALLER_NAME }}.exe
+          asset_path: ${{ env.LINUX_APP_RELEASE_PATH }}/${{ env.LINUX_PACKAGE_NAME }}
+          asset_name: ${{ env.LINUX_PACKAGE_NAME }}
           asset_content_type: application/octet-stream
+
+  build-for-docker:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+
+      - name: Login to Docker Hub
+        uses: docker/login-action@v1
+        with:
+          username: ${{ secrets.DOCKER_HUB_USERNAME }}
+          password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v1
+
+      - name: Build and push
+        uses: docker/build-push-action@v2
+        with:
+          context: .
+          file: ./frontend/scripts/docker-buildfiles/Dockerfile
+          builder: ${{ steps.buildx.outputs.name }}
+          push: true
+          tags: ${{ secrets.DOCKER_HUB_USERNAME }}/appflowy_client:${{ github.ref_name }}
+          cache-from: type=registry,ref=${{ secrets.DOCKER_HUB_USERNAME }}/af_build_cache:buildcache
+          cache-to: type=registry,ref=${{ secrets.DOCKER_HUB_USERNAME }}/af_build_cache:buildcache,mode=max

+ 0 - 35
.github/workflows/release_docker.yml

@@ -1,35 +0,0 @@
-name: release_docker
-
-on:
-  push:
-    branches:
-      - 'main'
-    tags:
-      - '*'
-
-jobs:
-  build-docker-image:
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v2
-
-      - name: Login to Docker Hub
-        uses: docker/login-action@v1
-        with:
-          username: ${{ secrets.DOCKER_HUB_USERNAME }}
-          password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
-
-      - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@v1
-
-      - name: Build and push
-        uses: docker/build-push-action@v2
-        with:
-          context: .
-          file: ./frontend/scripts/docker-buildfiles/Dockerfile
-          builder: ${{ steps.buildx.outputs.name }}
-          push: true
-          tags: ${{ secrets.DOCKER_HUB_USERNAME }}/appflowy_client:${{ github.ref_name }}
-          cache-from: type=registry,ref=${{ secrets.DOCKER_HUB_USERNAME }}/af_build_cache:buildcache
-          cache-to: type=registry,ref=${{ secrets.DOCKER_HUB_USERNAME }}/af_build_cache:buildcache,mode=max

+ 86 - 0
.github/workflows/rust_ci.yaml

@@ -0,0 +1,86 @@
+name: Rust-CI
+
+on:
+  push:
+    branches:
+      - "main"
+      - "release/*"
+    paths:
+      - "frontend/rust-lib/**"
+      - "shared-lib/**"
+
+  pull_request:
+    branches:
+      - "main"
+      - "release/*"
+    paths:
+      - "frontend/rust-lib/**"
+      - "shared-lib/**"
+
+env:
+  CARGO_TERM_COLOR: always
+  RUST_TOOLCHAIN: "1.65"
+  FLUTTER_VERSION: "3.3.9"
+
+jobs:
+  test-on-ubuntu:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout source code
+        uses: actions/checkout@v2
+
+      - name: Install Rust toolchain
+        id: rust_toolchain
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: ${{ env.RUST_TOOLCHAIN }}
+          override: true
+          components: rustfmt, clippy
+          profile: minimal
+
+      - name: Install flutter
+        id: flutter
+        uses: subosito/flutter-action@v2
+        with:
+          channel: "stable"
+          flutter-version: ${{ env.FLUTTER_VERSION }}
+          cache: true
+
+      - name: Install prerequisites
+        working-directory: frontend
+        run: |
+          cargo install --force cargo-make
+          cargo install --force duckscript_cli
+
+      - uses: Swatinem/rust-cache@v2
+        with:
+          prefix-key: ${{ matrix.job.os }}
+
+      - name: Build FlowySDK
+        working-directory: frontend
+        run: |
+          cargo make --profile development-linux-x86_64 appflowy-sdk-dev
+
+      - name: rustfmt rust-lib
+        run: cargo fmt --all -- --check
+        working-directory: frontend/rust-lib/
+
+      - name: clippy rust-lib
+        run: cargo clippy --features="rev-sqlite"
+        working-directory: frontend/rust-lib
+
+      - name: Run rust-lib tests
+        working-directory: frontend/rust-lib
+        run: RUST_LOG=info cargo test --no-default-features --features="sync,rev-sqlite"
+
+      - name: rustfmt shared-lib
+        run: cargo fmt --all -- --check
+        working-directory: shared-lib
+
+      - name: clippy shared-lib
+        run:  cargo clippy -- -D warnings
+        working-directory: shared-lib
+
+      - name: Run shared-lib tests
+        working-directory: shared-lib
+        run: RUST_LOG=info cargo test --no-default-features

+ 20 - 24
.github/workflows/rust_coverage.yml

@@ -11,52 +11,48 @@ on:
 
 env:
   CARGO_TERM_COLOR: always
+  FLUTTER_VERSION: "3.3.9"
+  RUST_TOOLCHAIN: "1.65"
 
 jobs:
   tests:
     runs-on: ubuntu-latest
     steps:
-      - name: Checkout
+      - name: Checkout source code
         uses: actions/checkout@v2
 
-      - id: rust_toolchain
+      - name: Install Rust toolchain
+        id: rust_toolchain
         uses: actions-rs/toolchain@v1
         with:
-          toolchain: 'stable-2022-04-07'
+          toolchain: ${{ env.RUST_TOOLCHAIN }}
+          target: ${{ matrix.job.target }}
+          override: true
+          profile: minimal
 
-      - name: Cache Cargo
-        uses: actions/cache@v2
-        with:
-          path: |
-            ~/.cargo
-          key: ${{ runner.os }}-cargo-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}
-
-      - name: Cache Rust
-        uses: actions/cache@v2
+      - name: Install flutter
+        id: flutter
+        uses: subosito/flutter-action@v2
         with:
-          path: |
-            frontend/rust-lib/target
-            shared-lib/target
-          key: ${{ runner.os }}-rust-rust-lib-share-lib-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}
+          channel: "stable"
+          flutter-version: ${{ env.FLUTTER_VERSION }}
+          cache: true
 
-
-      - name: Install cargo-make
+      - name: Install prerequisites
         working-directory: frontend
         run: |
-          cargo install cargo-make
+          cargo install --force cargo-make
+          cargo install --force duckscript_cli
 
-      - name: Setup environment - Flutter
-        uses: subosito/flutter-action@v2
+      - uses: Swatinem/rust-cache@v2
         with:
-          channel: 'stable'
-          flutter-version: '3.0.5'
+          prefix-key: ${{ matrix.job.os }}
 
       - name: Install code-coverage tools
         working-directory: frontend
         run: |
           sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
           sudo apt-get update
-          sudo apt-get install -y build-essential libsqlite3-dev libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev
           sudo apt-get install keybinder-3.0
           cargo install grcov
           rustup component add llvm-tools-preview

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

@@ -1,61 +0,0 @@
-name: Rust lint
-
-on:
-  push:
-    branches:
-      - "main"
-      - "release/*"
-    paths:
-      - "frontend/rust-lib/**"
-      - "shared-lib/**"
-
-  pull_request:
-    branches:
-      - "main"
-      - "release/*"
-    paths:
-      - "frontend/rust-lib/**"
-      - "shared-lib/**"
-
-env:
-  CARGO_TERM_COLOR: always
-
-jobs:
-  rust-fmt:
-    name: Rustfmt
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-      - uses: actions-rs/toolchain@v1
-        with:
-          toolchain: 'stable-2022-04-07'
-          override: true
-      - uses: subosito/flutter-action@v1
-        with:
-          flutter-version: '3.0.5'
-          channel: "stable"
-
-      - name: Rust Deps
-        working-directory: frontend
-        run: |
-          cargo install cargo-make
-          cargo make appflowy-deps-tools
-
-      - name: Build FlowySDK
-        working-directory: frontend
-        run: |
-          cargo make --profile development-linux-x86_64 appflowy-sdk-dev
-
-      - run: rustup component add rustfmt
-        working-directory: frontend/rust-lib
-      - name: rustfmt
-        run: cargo fmt --all -- --check
-        working-directory: frontend/rust-lib/
-
-
-      - run: rustup component add clippy
-        working-directory: frontend/rust-lib
-      - name: clippy
-        run: cargo clippy --no-default-features
-        working-directory: frontend/rust-lib
-

+ 0 - 60
.github/workflows/rust_test.yml

@@ -1,60 +0,0 @@
-name: Backend test
-
-on:
-  push:
-    branches:
-      - "main"
-      - "release/*"
-    paths:
-      - "frontend/rust-lib/**"
-      - "shared-lib/**"
-
-  pull_request:
-    branches:
-      - "main"
-      - "release/*"
-    paths:
-      - "frontend/rust-lib/**"
-      - "shared-lib/**"
-
-env:
-  CARGO_TERM_COLOR: always
-
-jobs:
-  tests:
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v2
-      
-      - id: rust_toolchain
-        uses: actions-rs/toolchain@v1
-        with:
-          toolchain: 'stable-2022-04-07'
-
-      - name: Cache Cargo
-        uses: actions/cache@v2
-        with: 
-          path: |
-            ~/.cargo
-          key: ${{ runner.os }}-cargo-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}
-
-      - name: Cache Rust
-        uses: actions/cache@v2
-        with: 
-          path: |
-            frontend/rust-lib/target
-            shared-lib/target
-          key: ${{ runner.os }}-rust-rust-lib-share-lib-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}    
-
-      - name: Install cargo-make
-        working-directory: frontend
-        run: cargo install cargo-make
-        
-      - name: Run rust-lib tests
-        working-directory: frontend/rust-lib
-        run: RUST_LOG=info cargo test --no-default-features --features="sync"
-        
-      - name: Run shared-lib tests
-        working-directory: shared-lib
-        run: RUST_LOG=info cargo test --no-default-features

+ 15 - 17
frontend/Makefile.toml

@@ -1,13 +1,13 @@
 #https://github.com/sagiegurari/cargo-make
 
 extend = [
-    { path = "scripts/makefile/desktop.toml" },
-    { path = "scripts/makefile/protobuf.toml" },
-    { path = "scripts/makefile/tests.toml" },
-    { path = "scripts/makefile/docker.toml" },
-    { path = "scripts/makefile/env.toml" },
-    { path = "scripts/makefile/flutter.toml" },
-    { path = "scripts/makefile/tool.toml" },
+  { path = "scripts/makefile/desktop.toml" },
+  { path = "scripts/makefile/protobuf.toml" },
+  { path = "scripts/makefile/tests.toml" },
+  { path = "scripts/makefile/docker.toml" },
+  { path = "scripts/makefile/env.toml" },
+  { path = "scripts/makefile/flutter.toml" },
+  { path = "scripts/makefile/tool.toml" },
 ]
 
 [config]
@@ -23,11 +23,11 @@ CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
 CARGO_MAKE_CRATE_NAME = "dart-ffi"
 LIB_NAME = "dart_ffi"
 CURRENT_APP_VERSION = "0.0.9.1"
-FEATURES = "flutter"
+FLUTTER_DESKTOP_FEATURES = "flutter,rev-sqlite"
 PRODUCT_NAME = "AppFlowy"
 # CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html
 # If you update the macOS's CRATE_TYPE, don't forget to update the
-# flowy_sdk.podspec
+# appflowy_backend.podspec
 #   for staticlib:
 #        s.static_framework = true
 #        s.vendored_libraries = "libdart_ffi.a"
@@ -42,7 +42,7 @@ PRODUCT_NAME = "AppFlowy"
 CRATE_TYPE = "staticlib"
 LIB_EXT = "a"
 APP_ENVIRONMENT = "local"
-FLUTTER_FLOWY_SDK_PATH = "app_flowy/packages/flowy_sdk"
+FLUTTER_FLOWY_SDK_PATH = "app_flowy/packages/appflowy_backend"
 # Test default config
 TEST_CRATE_TYPE = "cdylib"
 TEST_LIB_EXT = "dylib"
@@ -150,7 +150,7 @@ script = ['''
     echo BUILD_FLAG: ${BUILD_FLAG}
     echo TARGET_OS: ${TARGET_OS}
     echo RUST_COMPILE_TARGET: ${RUST_COMPILE_TARGET}
-    echo FEATURES: ${FEATURES}
+    echo FEATURES: ${FLUTTER_DESKTOP_FEATURES}
     echo PRODUCT_EXT: ${PRODUCT_EXT}
     echo APP_ENVIRONMENT: ${APP_ENVIRONMENT}
     echo ${platforms}
@@ -181,7 +181,7 @@ FEATURES = "flutter,openssl_vendored"
 [tasks.setup-crate-type]
 private = true
 script = [
-    """
+  """
       toml = readfile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml
       val = replace ${toml} "staticlib" ${CRATE_TYPE}
       result = writefile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml ${val}
@@ -193,7 +193,7 @@ script_runner = "@duckscript"
 [tasks.restore-crate-type]
 private = true
 script = [
-    """
+  """
       toml = readfile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml
       val = replace ${toml} ${CRATE_TYPE} "staticlib"
       result = writefile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml ${val}
@@ -221,7 +221,7 @@ TEST_COMPILE_TARGET = "x86_64-pc-windows-msvc"
 [tasks.setup-test-crate-type]
 private = true
 script = [
-    """
+  """
       toml = readfile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml
       val = replace ${toml} "staticlib" ${TEST_CRATE_TYPE}
       result = writefile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml ${val}
@@ -233,7 +233,7 @@ script_runner = "@duckscript"
 [tasks.restore-test-crate-type]
 private = true
 script = [
-    """
+  """
       toml = readfile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml
       val = replace ${toml} ${TEST_CRATE_TYPE} "staticlib"
       result = writefile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml ${val}
@@ -249,5 +249,3 @@ script = ["""
       cargo build -vv --features=dart
       """]
 script_runner = "@shell"
-
-

+ 10 - 0
frontend/app_flowy/assets/translations/en.json

@@ -193,6 +193,7 @@
   "grid": {
     "settings": {
       "filter": "Filter",
+      "sort": "Sort",
       "sortBy": "Sort by",
       "Properties": "Properties",
       "group": "Group",
@@ -273,6 +274,12 @@
       "newColumn": "New column",
       "deleteFieldPromptMessage": "Are you sure? This property will be deleted"
     },
+    "sort": {
+      "ascending": "Ascending",
+      "descending": "Descending",
+      "deleteSort": "Delete sort",
+      "addSort": "Add sort"
+    },
     "row": {
       "duplicate": "Duplicate",
       "delete": "Delete",
@@ -313,5 +320,8 @@
     "column": {
       "create_new_card": "New"
     }
+  },
+  "calendar": {
+    "menuName": "Calendar"
   }
 }

+ 5 - 5
frontend/app_flowy/ios/Podfile.lock

@@ -3,7 +3,7 @@ PODS:
     - Flutter
   - flowy_infra_ui (0.0.1):
     - Flutter
-  - flowy_sdk (0.0.1):
+  - appflowy_backend (0.0.1):
     - Flutter
   - Flutter (1.0.0)
   - flutter_inappwebview (0.0.1):
@@ -28,7 +28,7 @@ PODS:
 DEPENDENCIES:
   - flowy_editor (from `.symlinks/plugins/flowy_editor/ios`)
   - flowy_infra_ui (from `.symlinks/plugins/flowy_infra_ui/ios`)
-  - flowy_sdk (from `.symlinks/plugins/flowy_sdk/ios`)
+  - appflowy_backend (from `.symlinks/plugins/appflowy_backend/ios`)
   - Flutter (from `Flutter`)
   - flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`)
   - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
@@ -46,8 +46,8 @@ EXTERNAL SOURCES:
     :path: ".symlinks/plugins/flowy_editor/ios"
   flowy_infra_ui:
     :path: ".symlinks/plugins/flowy_infra_ui/ios"
-  flowy_sdk:
-    :path: ".symlinks/plugins/flowy_sdk/ios"
+  appflowy_backend:
+    :path: ".symlinks/plugins/appflowy_backend/ios"
   Flutter:
     :path: Flutter
   flutter_inappwebview:
@@ -66,7 +66,7 @@ EXTERNAL SOURCES:
 SPEC CHECKSUMS:
   flowy_editor: bf8d58894ddb03453bd4d8521c57267ad638b837
   flowy_infra_ui: 146c88346fd55d2ee6a41ae35059a5bf095cfbb3
-  flowy_sdk: c416222c639e678828776789bf0c1a1d0d59df3c
+  appflowy_backend: c416222c639e678828776789bf0c1a1d0d59df3c
   Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
   flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721
   flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069

+ 16 - 10
frontend/app_flowy/lib/core/folder_notification.dart

@@ -1,18 +1,21 @@
 import 'dart:async';
 import 'dart:typed_data';
-import 'package:flowy_sdk/protobuf/dart-notify/protobuf.dart';
+import 'package:appflowy_backend/protobuf/dart-notify/protobuf.dart';
 import 'package:dartz/dartz.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-folder/dart_notification.pb.dart';
-import 'package:flowy_sdk/rust_stream.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder/dart_notification.pb.dart';
+import 'package:appflowy_backend/rust_stream.dart';
 
 import 'notification_helper.dart';
 
 // Folder
-typedef FolderNotificationCallback = void Function(FolderNotification, Either<Uint8List, FlowyError>);
+typedef FolderNotificationCallback = void Function(
+    FolderNotification, Either<Uint8List, FlowyError>);
 
-class FolderNotificationParser extends NotificationParser<FolderNotification, FlowyError> {
-  FolderNotificationParser({String? id, required FolderNotificationCallback callback})
+class FolderNotificationParser
+    extends NotificationParser<FolderNotification, FlowyError> {
+  FolderNotificationParser(
+      {String? id, required FolderNotificationCallback callback})
       : super(
           id: id,
           callback: callback,
@@ -21,15 +24,18 @@ class FolderNotificationParser extends NotificationParser<FolderNotification, Fl
         );
 }
 
-typedef FolderNotificationHandler = Function(FolderNotification ty, Either<Uint8List, FlowyError> result);
+typedef FolderNotificationHandler = Function(
+    FolderNotification ty, Either<Uint8List, FlowyError> result);
 
 class FolderNotificationListener {
   StreamSubscription<SubscribeObject>? _subscription;
   FolderNotificationParser? _parser;
 
-  FolderNotificationListener({required String objectId, required FolderNotificationHandler handler})
+  FolderNotificationListener(
+      {required String objectId, required FolderNotificationHandler handler})
       : _parser = FolderNotificationParser(id: objectId, callback: handler) {
-    _subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
+    _subscription =
+        RustStreamReceiver.listen((observable) => _parser?.parse(observable));
   }
 
   Future<void> stop() async {

+ 4 - 4
frontend/app_flowy/lib/core/grid_notification.dart

@@ -1,10 +1,10 @@
 import 'dart:async';
 import 'dart:typed_data';
-import 'package:flowy_sdk/protobuf/dart-notify/protobuf.dart';
+import 'package:appflowy_backend/protobuf/dart-notify/protobuf.dart';
 import 'package:dartz/dartz.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
-import 'package:flowy_sdk/rust_stream.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/dart_notification.pb.dart';
+import 'package:appflowy_backend/rust_stream.dart';
 
 import 'notification_helper.dart';
 

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

@@ -1,9 +1,9 @@
 import 'dart:async';
 
 import 'package:connectivity_plus/connectivity_plus.dart';
-import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/dispatch/dispatch.dart';
-import 'package:flowy_sdk/protobuf/flowy-net/network_state.pb.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/dispatch/dispatch.dart';
+import 'package:appflowy_backend/protobuf/flowy-net/network_state.pb.dart';
 import 'package:flutter/services.dart';
 
 class NetworkListener {

+ 6 - 2
frontend/app_flowy/lib/core/notification_helper.dart

@@ -1,5 +1,5 @@
 import 'dart:typed_data';
-import 'package:flowy_sdk/protobuf/dart-notify/protobuf.dart';
+import 'package:appflowy_backend/protobuf/dart-notify/protobuf.dart';
 import 'package:dartz/dartz.dart';
 
 class NotificationParser<T, E> {
@@ -9,7 +9,11 @@ class NotificationParser<T, E> {
   T? Function(int) tyParser;
   E Function(Uint8List) errorParser;
 
-  NotificationParser({this.id, required this.callback, required this.errorParser, required this.tyParser});
+  NotificationParser(
+      {this.id,
+      required this.callback,
+      required this.errorParser,
+      required this.tyParser});
   void parse(SubscribeObject subject) {
     if (id != null) {
       if (subject.id != id) {

+ 16 - 10
frontend/app_flowy/lib/core/user_notification.dart

@@ -1,18 +1,21 @@
 import 'dart:async';
 import 'dart:typed_data';
-import 'package:flowy_sdk/protobuf/dart-notify/protobuf.dart';
-import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart';
+import 'package:appflowy_backend/protobuf/dart-notify/protobuf.dart';
+import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
 import 'package:dartz/dartz.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:flowy_sdk/rust_stream.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/rust_stream.dart';
 
 import 'notification_helper.dart';
 
 // User
-typedef UserNotificationCallback = void Function(UserNotification, Either<Uint8List, FlowyError>);
+typedef UserNotificationCallback = void Function(
+    UserNotification, Either<Uint8List, FlowyError>);
 
-class UserNotificationParser extends NotificationParser<UserNotification, FlowyError> {
-  UserNotificationParser({required String id, required UserNotificationCallback callback})
+class UserNotificationParser
+    extends NotificationParser<UserNotification, FlowyError> {
+  UserNotificationParser(
+      {required String id, required UserNotificationCallback callback})
       : super(
           id: id,
           callback: callback,
@@ -21,15 +24,18 @@ class UserNotificationParser extends NotificationParser<UserNotification, FlowyE
         );
 }
 
-typedef UserNotificationHandler = Function(UserNotification ty, Either<Uint8List, FlowyError> result);
+typedef UserNotificationHandler = Function(
+    UserNotification ty, Either<Uint8List, FlowyError> result);
 
 class UserNotificationListener {
   StreamSubscription<SubscribeObject>? _subscription;
   UserNotificationParser? _parser;
 
-  UserNotificationListener({required String objectId, required UserNotificationHandler handler})
+  UserNotificationListener(
+      {required String objectId, required UserNotificationHandler handler})
       : _parser = UserNotificationParser(id: objectId, callback: handler) {
-    _subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
+    _subscription =
+        RustStreamReceiver.listen((observable) => _parser?.parse(observable));
   }
 
   Future<void> stop() async {

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

@@ -7,10 +7,10 @@ import 'package:app_flowy/plugins/grid/application/row/row_service.dart';
 import 'package:appflowy_board/appflowy_board.dart';
 import 'package:dartz/dartz.dart';
 import 'package:equatable/equatable.dart';
-import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/protobuf.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 

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

@@ -4,11 +4,11 @@ import 'package:app_flowy/plugins/grid/application/view/grid_view_cache.dart';
 import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
 import 'package:app_flowy/plugins/grid/application/grid_service.dart';
 import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
 import 'dart:async';
 import 'package:dartz/dartz.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/protobuf.dart';
 
 import 'board_listener.dart';
 
@@ -108,19 +108,19 @@ class BoardDataController {
 
   Future<Either<Unit, FlowyError>> openGrid() async {
     final result = await _gridFFIService.openGrid();
-
     return result.fold(
       (grid) async {
         _onGridChanged?.call(grid);
-        final result = await fieldController.loadFields(fieldIds: grid.fields);
-        return result.fold(
-          (l) {
-            _loadGroups();
-            _viewCache.rowCache.initializeRows(grid.rows);
-            return left(l);
-          },
-          (err) => right(err),
-        );
+        return fieldController.loadFields(fieldIds: grid.fields).then((result) {
+          return result.fold(
+            (l) => Future(() async {
+              await _loadGroups();
+              _viewCache.rowCache.initializeRows(grid.rows);
+              return left(l);
+            }),
+            (err) => right(err),
+          );
+        });
       },
       (err) => right(err),
     );
@@ -132,6 +132,7 @@ class BoardDataController {
   }
 
   Future<void> dispose() async {
+    await _viewCache.dispose();
     await _gridFFIService.closeGrid();
     await fieldController.dispose();
   }

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

@@ -2,11 +2,11 @@ import 'dart:typed_data';
 
 import 'package:app_flowy/core/grid_notification.dart';
 import 'package:flowy_infra/notifier.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/dart_notification.pb.dart';
 import 'package:dartz/dartz.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/group_changeset.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/group.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/group_changeset.pb.dart';
 
 typedef GroupUpdateValue = Either<GroupViewChangesetPB, FlowyError>;
 typedef GroupByNewFieldValue = Either<List<GroupPB>, FlowyError>;

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

@@ -64,7 +64,7 @@ class BoardCheckboxCellState with _$BoardCheckboxCellState {
     required bool isSelected,
   }) = _CheckboxCellState;
 
-  factory BoardCheckboxCellState.initial(GridCellController context) {
+  factory BoardCheckboxCellState.initial(GridTextCellController context) {
     return BoardCheckboxCellState(
         isSelected: _isSelected(context.getCellData()));
   }

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

@@ -1,6 +1,6 @@
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
 import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/date_type_option_entities.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

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

@@ -60,7 +60,7 @@ class BoardNumberCellState with _$BoardNumberCellState {
     required String content,
   }) = _BoardNumberCellState;
 
-  factory BoardNumberCellState.initial(GridCellController context) =>
+  factory BoardNumberCellState.initial(GridTextCellController context) =>
       BoardNumberCellState(
         content: context.getCellData() ?? "",
       );

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

@@ -1,5 +1,5 @@
 import 'dart:async';
-import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/select_type_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';

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

@@ -6,7 +6,7 @@ import 'dart:async';
 part 'board_text_cell_bloc.freezed.dart';
 
 class BoardTextCellBloc extends Bloc<BoardTextCellEvent, BoardTextCellState> {
-  final GridCellController cellController;
+  final GridTextCellController cellController;
   void Function()? _onCellChangedFn;
   BoardTextCellBloc({
     required this.cellController,
@@ -71,7 +71,7 @@ class BoardTextCellState with _$BoardTextCellState {
     required bool enableEdit,
   }) = _BoardTextCellState;
 
-  factory BoardTextCellState.initial(GridCellController context) =>
+  factory BoardTextCellState.initial(GridTextCellController context) =>
       BoardTextCellState(
         content: context.getCellData() ?? "",
         enableEdit: false,

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

@@ -1,5 +1,5 @@
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/url_type_option_entities.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

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

@@ -3,7 +3,7 @@ import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_servic
 import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
 import 'package:app_flowy/plugins/grid/application/row/row_service.dart';
 import 'package:equatable/equatable.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/row_entities.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

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

@@ -3,7 +3,7 @@ import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_servic
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_field_notifier.dart';
 import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
 import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/row_entities.pb.dart';
 import 'package:flutter/foundation.dart';
 
 typedef OnCardChanged = void Function(GridCellMap, RowsChangedReason);

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

@@ -1,4 +1,4 @@
-import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/field_entities.pb.dart';
 
 class BoardGroupService {
   final String gridId;

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

@@ -1,6 +1,6 @@
-import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/protobuf.dart';
 import 'group_listener.dart';
 
 typedef OnGroupError = void Function(FlowyError);

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

@@ -2,11 +2,11 @@ import 'dart:typed_data';
 
 import 'package:app_flowy/core/grid_notification.dart';
 import 'package:flowy_infra/notifier.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/dart_notification.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/group.pb.dart';
 import 'package:dartz/dartz.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/group_changeset.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/group_changeset.pb.dart';
 
 typedef UpdateGroupNotifiedValue = Either<GroupRowsNotificationPB, FlowyError>;
 

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

@@ -1,7 +1,7 @@
 import 'package:app_flowy/plugins/util.dart';
 import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
 import 'package:app_flowy/workspace/presentation/widgets/left_bar_item.dart';
-import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
 import 'package:app_flowy/startup/plugin/plugin.dart';
 import 'package:flutter/material.dart';
 

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

@@ -15,12 +15,11 @@ import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
 import 'package:flowy_infra_ui/widget/error_page.dart';
-import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/row_entities.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
-import '../../grid/application/row/row_cache.dart';
 import '../application/board_bloc.dart';
 import 'card/card.dart';
 import 'card/card_cell_builder.dart';
@@ -289,7 +288,7 @@ class _BoardContentState extends State<BoardContent> {
       rowPB: rowPB,
     );
 
-    final dataController = GridRowDataController(
+    final dataController = RowDataController(
       rowInfo: rowInfo,
       fieldController: fieldController,
       rowCache: rowCache,

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

@@ -35,7 +35,7 @@ class _BoardTextCellState extends State<BoardTextCell> {
   @override
   void initState() {
     final cellController =
-        widget.cellControllerBuilder.build() as GridCellController;
+        widget.cellControllerBuilder.build() as GridTextCellController;
     _cellBloc = BoardTextCellBloc(cellController: cellController)
       ..add(const BoardTextCellEvent.initial());
     _controller = TextEditingController(text: _cellBloc.state.content);

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

@@ -1,5 +1,5 @@
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flutter/material.dart';
 
 import 'board_cell.dart';

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

@@ -13,6 +13,7 @@ import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:styled_widget/styled_widget.dart';
 
 import 'board_toolbar.dart';
 
@@ -181,6 +182,6 @@ class _BoardSettingListPopoverState extends State<BoardSettingListPopover> {
       onAction: (action, settingContext) {
         setState(() => _action = action);
       },
-    );
+    ).padding(all: 6.0);
   }
 }

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

@@ -1,4 +1,5 @@
 import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
+import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart';
 import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/theme_extension.dart';
@@ -67,12 +68,12 @@ class _SettingButtonState extends State<_SettingButton> {
       direction: PopoverDirection.leftWithTopAligned,
       triggerActions: PopoverTriggerFlags.none,
       constraints: BoxConstraints.loose(const Size(260, 400)),
+      margin: EdgeInsets.zero,
       child: FlowyTextButton(
         LocaleKeys.settings_title.tr(),
-        fontSize: 14,
         fillColor: Colors.transparent,
         hoverColor: AFThemeExtension.of(context).lightGreyHover,
-        padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 6),
+        padding: GridSize.typeOptionContentInsets,
         onPressed: () {
           popoverController.show();
         },

+ 111 - 0
frontend/app_flowy/lib/plugins/calendar/calendar.dart

@@ -0,0 +1,111 @@
+import 'package:app_flowy/generated/locale_keys.g.dart';
+import 'package:app_flowy/startup/plugin/plugin.dart';
+import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
+import 'package:app_flowy/workspace/presentation/widgets/left_bar_item.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
+import 'package:flutter/material.dart';
+
+import '../util.dart';
+
+class CalendarPluginBuilder extends PluginBuilder {
+  @override
+  Plugin build(dynamic data) {
+    if (data is ViewPB) {
+      return CalendarPlugin(pluginType: pluginType, view: data);
+    } else {
+      throw FlowyPluginException.invalidData;
+    }
+  }
+
+  @override
+  String get menuName => LocaleKeys.calendar_menuName.tr();
+
+  @override
+  String get menuIcon => "editor/date";
+
+  @override
+  PluginType get pluginType => PluginType.calendar;
+
+  @override
+  ViewDataFormatPB get dataFormatType => ViewDataFormatPB.DatabaseFormat;
+
+  @override
+  ViewLayoutTypePB? get layoutType => ViewLayoutTypePB.Calendar;
+}
+
+class CalendarPluginConfig implements PluginConfig {
+  @override
+  bool get creatable => false;
+}
+
+class CalendarPlugin extends Plugin {
+  @override
+  final ViewPluginNotifier notifier;
+  final PluginType _pluginType;
+
+  CalendarPlugin({
+    required ViewPB view,
+    required PluginType pluginType,
+  })  : _pluginType = pluginType,
+        notifier = ViewPluginNotifier(view: view);
+
+  @override
+  PluginDisplay get display => CalendarPluginDisplay(notifier: notifier);
+
+  @override
+  PluginId get id => notifier.view.id;
+
+  @override
+  PluginType get ty => _pluginType;
+}
+
+class CalendarPluginDisplay extends PluginDisplay {
+  final ViewPluginNotifier notifier;
+  CalendarPluginDisplay({required this.notifier, Key? key});
+
+  ViewPB get view => notifier.view;
+
+  @override
+  Widget get leftBarItem => ViewLeftBarItem(view: view);
+
+  @override
+  Widget buildWidget(PluginContext context) {
+    notifier.isDeleted.addListener(() {
+      notifier.isDeleted.value.fold(() => null, (deletedView) {
+        if (deletedView.hasIndex()) {
+          context.onDeleted(view, deletedView.index);
+        }
+      });
+    });
+
+    return BlankPage(key: ValueKey(view.id));
+    // return BoardPage(key: ValueKey(view.id), view: view);
+  }
+
+  @override
+  List<NavigationItem> get navigationItems => [this];
+}
+
+// mark for removal
+class BlankPage extends StatefulWidget {
+  const BlankPage({Key? key}) : super(key: key);
+
+  @override
+  State<BlankPage> createState() => _BlankPageState();
+}
+
+class _BlankPageState extends State<BlankPage> {
+  @override
+  Widget build(BuildContext context) {
+    return SizedBox.expand(
+      child: Container(
+        color: Theme.of(context).colorScheme.surface,
+        child: Padding(
+          padding: const EdgeInsets.all(10),
+          child: Container(),
+        ),
+      ),
+    );
+  }
+}

+ 4 - 4
frontend/app_flowy/lib/plugins/document/application/doc_bloc.dart

@@ -4,10 +4,10 @@ import 'package:app_flowy/workspace/application/view/view_listener.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:flowy_sdk/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder/trash.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
+import 'package:appflowy_backend/log.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:dartz/dartz.dart';

+ 4 - 4
frontend/app_flowy/lib/plugins/document/application/doc_service.dart

@@ -1,9 +1,9 @@
 import 'package:dartz/dartz.dart';
-import 'package:flowy_sdk/dispatch/dispatch.dart';
+import 'package:appflowy_backend/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-document/entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-document/entities.pb.dart';
 
 class DocumentService {
   Future<Either<DocumentSnapshotPB, FlowyError>> openDocument({

+ 3 - 3
frontend/app_flowy/lib/plugins/document/application/share_bloc.dart

@@ -1,9 +1,9 @@
 import 'dart:convert';
 import 'dart:io';
 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:appflowy_backend/protobuf/flowy-document/entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
+import 'package:appflowy_backend/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';

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

@@ -1,9 +1,9 @@
 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';
+import 'package:appflowy_backend/dispatch/dispatch.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-document/protobuf.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
 
 class ShareService {
   Future<Either<ExportDataPB, FlowyError>> export(

+ 1 - 1
frontend/app_flowy/lib/plugins/document/document.dart

@@ -10,7 +10,7 @@ import 'package:app_flowy/startup/plugin/plugin.dart';
 import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
 import 'package:app_flowy/workspace/presentation/widgets/left_bar_item.dart';
 import 'package:easy_localization/easy_localization.dart';
-import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 

+ 1 - 1
frontend/app_flowy/lib/plugins/document/document_page.dart

@@ -1,7 +1,7 @@
 import 'package:appflowy_editor/appflowy_editor.dart';
 import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
 import 'package:flowy_infra_ui/widget/error_page.dart';
-import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:intl/intl.dart';

+ 4 - 4
frontend/app_flowy/lib/plugins/document/presentation/share/share_button.dart

@@ -11,10 +11,10 @@ import 'package:clipboard/clipboard.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:file_picker/file_picker.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';
-import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-document/entities.pb.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-document/entities.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 

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

@@ -1,7 +1,7 @@
 import 'package:app_flowy/core/grid_notification.dart';
 import 'package:dartz/dartz.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/dart_notification.pb.dart';
 import 'package:flowy_infra/notifier.dart';
 import 'dart:async';
 import 'dart:typed_data';

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

@@ -1,15 +1,15 @@
 part of 'cell_service.dart';
 
-typedef GridCellController = IGridCellController<String, String>;
-typedef GridCheckboxCellController = IGridCellController<String, String>;
-typedef GridNumberCellController = IGridCellController<String, String>;
+typedef GridTextCellController = GridCellController<String, String>;
+typedef GridCheckboxCellController = GridCellController<String, String>;
+typedef GridNumberCellController = GridCellController<String, String>;
 typedef GridSelectOptionCellController
-    = IGridCellController<SelectOptionCellDataPB, String>;
+    = GridCellController<SelectOptionCellDataPB, String>;
 typedef GridChecklistCellController
-    = IGridCellController<SelectOptionCellDataPB, String>;
+    = GridCellController<SelectOptionCellDataPB, String>;
 typedef GridDateCellController
-    = IGridCellController<DateCellDataPB, CalendarData>;
-typedef GridURLCellController = IGridCellController<URLCellDataPB, String>;
+    = GridCellController<DateCellDataPB, CalendarData>;
+typedef GridURLCellController = GridCellController<URLCellDataPB, String>;
 
 abstract class GridCellControllerBuilderDelegate {
   GridCellFieldNotifier buildFieldNotifier();
@@ -27,7 +27,7 @@ class GridCellControllerBuilder {
   })  : _cellCache = cellCache,
         _cellId = cellId;
 
-  IGridCellController build() {
+  GridCellController build() {
     final cellFieldNotifier = delegate.buildFieldNotifier();
     switch (_cellId.fieldType) {
       case FieldType.Checkbox:
@@ -35,12 +35,12 @@ class GridCellControllerBuilder {
           cellId: _cellId,
           parser: StringCellDataParser(),
         );
-        return GridCellController(
+        return GridTextCellController(
           cellId: _cellId,
           cellCache: _cellCache,
           cellDataLoader: cellDataLoader,
           fieldNotifier: cellFieldNotifier,
-          cellDataPersistence: CellDataPersistence(cellId: _cellId),
+          cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
         );
       case FieldType.DateTime:
         final cellDataLoader = GridCellDataLoader(
@@ -67,19 +67,19 @@ class GridCellControllerBuilder {
           cellCache: _cellCache,
           cellDataLoader: cellDataLoader,
           fieldNotifier: cellFieldNotifier,
-          cellDataPersistence: CellDataPersistence(cellId: _cellId),
+          cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
         );
       case FieldType.RichText:
         final cellDataLoader = GridCellDataLoader(
           cellId: _cellId,
           parser: StringCellDataParser(),
         );
-        return GridCellController(
+        return GridTextCellController(
           cellId: _cellId,
           cellCache: _cellCache,
           cellDataLoader: cellDataLoader,
           fieldNotifier: cellFieldNotifier,
-          cellDataPersistence: CellDataPersistence(cellId: _cellId),
+          cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
         );
       case FieldType.MultiSelect:
       case FieldType.SingleSelect:
@@ -95,7 +95,7 @@ class GridCellControllerBuilder {
           cellCache: _cellCache,
           cellDataLoader: cellDataLoader,
           fieldNotifier: cellFieldNotifier,
-          cellDataPersistence: CellDataPersistence(cellId: _cellId),
+          cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
         );
 
       case FieldType.URL:
@@ -108,7 +108,7 @@ class GridCellControllerBuilder {
           cellCache: _cellCache,
           cellDataLoader: cellDataLoader,
           fieldNotifier: cellFieldNotifier,
-          cellDataPersistence: CellDataPersistence(cellId: _cellId),
+          cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
         );
     }
     throw UnimplementedError;
@@ -123,14 +123,14 @@ class GridCellControllerBuilder {
 /// Generic D represents the type of data that will be saved to the disk
 ///
 // ignore: must_be_immutable
-class IGridCellController<T, D> extends Equatable {
+class GridCellController<T, D> extends Equatable {
   final GridCellIdentifier cellId;
   final GridCellCache _cellsCache;
   final GridCellCacheKey _cacheKey;
   final FieldService _fieldService;
   final GridCellFieldNotifier _fieldNotifier;
   final GridCellDataLoader<T> _cellDataLoader;
-  final IGridCellDataPersistence<D> _cellDataPersistence;
+  final GridCellDataPersistence<D> _cellDataPersistence;
 
   CellListener? _cellListener;
   CellDataNotifier<T?>? _cellDataNotifier;
@@ -141,12 +141,12 @@ class IGridCellController<T, D> extends Equatable {
   Timer? _saveDataOperation;
   bool _isDispose = false;
 
-  IGridCellController({
+  GridCellController({
     required this.cellId,
     required GridCellCache cellCache,
     required GridCellFieldNotifier fieldNotifier,
     required GridCellDataLoader<T> cellDataLoader,
-    required IGridCellDataPersistence<D> cellDataPersistence,
+    required GridCellDataPersistence<D> cellDataPersistence,
   })  : _cellsCache = cellCache,
         _cellDataLoader = cellDataLoader,
         _cellDataPersistence = cellDataPersistence,

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

@@ -5,14 +5,14 @@ abstract class IGridCellDataConfig {
   bool get reloadOnFieldChanged;
 }
 
-abstract class IGridCellDataParser<T> {
+abstract class GridCellDataParser<T> {
   T? parserData(List<int> data);
 }
 
 class GridCellDataLoader<T> {
   final CellService service = CellService();
   final GridCellIdentifier cellId;
-  final IGridCellDataParser<T> parser;
+  final GridCellDataParser<T> parser;
   final bool reloadOnFieldChanged;
 
   GridCellDataLoader({
@@ -43,7 +43,7 @@ class GridCellDataLoader<T> {
   }
 }
 
-class StringCellDataParser implements IGridCellDataParser<String> {
+class StringCellDataParser implements GridCellDataParser<String> {
   @override
   String? parserData(List<int> data) {
     final s = utf8.decode(data);
@@ -51,7 +51,7 @@ class StringCellDataParser implements IGridCellDataParser<String> {
   }
 }
 
-class DateCellDataParser implements IGridCellDataParser<DateCellDataPB> {
+class DateCellDataParser implements GridCellDataParser<DateCellDataPB> {
   @override
   DateCellDataPB? parserData(List<int> data) {
     if (data.isEmpty) {
@@ -62,7 +62,7 @@ class DateCellDataParser implements IGridCellDataParser<DateCellDataPB> {
 }
 
 class SelectOptionCellDataParser
-    implements IGridCellDataParser<SelectOptionCellDataPB> {
+    implements GridCellDataParser<SelectOptionCellDataPB> {
   @override
   SelectOptionCellDataPB? parserData(List<int> data) {
     if (data.isEmpty) {
@@ -72,7 +72,7 @@ class SelectOptionCellDataParser
   }
 }
 
-class URLCellDataParser implements IGridCellDataParser<URLCellDataPB> {
+class URLCellDataParser implements GridCellDataParser<URLCellDataPB> {
   @override
   URLCellDataPB? parserData(List<int> data) {
     if (data.isEmpty) {

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

@@ -2,14 +2,14 @@ part of 'cell_service.dart';
 
 /// Save the cell data to disk
 /// You can extend this class to do custom operations. For example, the DateCellDataPersistence.
-abstract class IGridCellDataPersistence<D> {
+abstract class GridCellDataPersistence<D> {
   Future<Option<FlowyError>> save(D data);
 }
 
-class CellDataPersistence implements IGridCellDataPersistence<String> {
+class TextCellDataPersistence implements GridCellDataPersistence<String> {
   final GridCellIdentifier cellId;
 
-  CellDataPersistence({
+  TextCellDataPersistence({
     required this.cellId,
   });
   final CellService _cellService = CellService();
@@ -33,8 +33,7 @@ class CalendarData with _$CalendarData {
       _CalendarData;
 }
 
-class DateCellDataPersistence
-    implements IGridCellDataPersistence<CalendarData> {
+class DateCellDataPersistence implements GridCellDataPersistence<CalendarData> {
   final GridCellIdentifier cellId;
   DateCellDataPersistence({
     required this.cellId,

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

@@ -2,14 +2,14 @@ import 'dart:async';
 import 'dart:collection';
 import 'package:dartz/dartz.dart';
 import 'package:equatable/equatable.dart';
-import 'package:flowy_sdk/dispatch/dispatch.dart';
-import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option_entities.pb.dart';
+import 'package:appflowy_backend/dispatch/dispatch.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/cell_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/date_type_option_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/select_type_option.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/url_type_option_entities.pb.dart';
 import 'package:flutter/foundation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:app_flowy/plugins/grid/application/cell/cell_listener.dart';

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

@@ -65,7 +65,7 @@ class CheckboxCellState with _$CheckboxCellState {
     required bool isSelected,
   }) = _CheckboxCellState;
 
-  factory CheckboxCellState.initial(GridCellController context) {
+  factory CheckboxCellState.initial(GridTextCellController context) {
     return CheckboxCellState(isSelected: _isSelected(context.getCellData()));
   }
 }

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

@@ -1,5 +1,5 @@
-import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/select_type_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

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

@@ -2,8 +2,8 @@ import 'dart:async';
 
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
 import 'package:dartz/dartz.dart';
-import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/select_type_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 

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

@@ -2,11 +2,11 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
 import 'package:easy_localization/easy_localization.dart'
     show StringTranslateExtension;
-import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/code.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/code.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/date_type_option.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/date_type_option_entities.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:table_calendar/table_calendar.dart';
@@ -41,8 +41,8 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
           },
           didReceiveCellUpdate: (DateCellDataPB? cellData) {
             final calData = calDataFromCellData(cellData);
-            final time =
-                calData.foldRight("", (dateData, previous) => dateData.time);
+            final time = calData.foldRight(
+                "", (dateData, previous) => dateData.time ?? '');
             emit(state.copyWith(calData: calData, time: time));
           },
           setIncludeTime: (includeTime) async {
@@ -224,7 +224,8 @@ class DateCalState with _$DateCalState {
     DateCellDataPB? cellData,
   ) {
     Option<CalendarData> calData = calDataFromCellData(cellData);
-    final time = calData.foldRight("", (dateData, previous) => dateData.time);
+    final time =
+        calData.foldRight("", (dateData, previous) => dateData.time ?? '');
     return DateCalState(
       dateTypeOptionPB: dateTypeOptionPB,
       format: CalendarFormat.month,

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

@@ -1,5 +1,5 @@
 import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/date_type_option_entities.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

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

@@ -1,4 +1,4 @@
-import 'package:flowy_sdk/log.dart';
+import 'package:appflowy_backend/log.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
@@ -82,7 +82,7 @@ class NumberCellState with _$NumberCellState {
     required String cellContent,
   }) = _NumberCellState;
 
-  factory NumberCellState.initial(GridCellController context) {
+  factory NumberCellState.initial(GridTextCellController context) {
     return NumberCellState(
       cellContent: context.getCellData() ?? "",
     );

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

@@ -1,5 +1,5 @@
 import 'dart:async';
-import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/select_type_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';

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

@@ -1,8 +1,8 @@
 import 'dart:async';
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
 import 'package:dartz/dartz.dart';
-import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/select_type_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'select_option_service.dart';

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

@@ -1,9 +1,9 @@
 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-grid/cell_entities.pb.dart';
+import 'package:appflowy_backend/dispatch/dispatch.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/cell_entities.pb.dart';
 import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_service.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/select_type_option.pb.dart';
 import 'cell_service/cell_service.dart';
 
 class SelectOptionFFIService {

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

@@ -6,7 +6,7 @@ import 'cell_service/cell_service.dart';
 part 'text_cell_bloc.freezed.dart';
 
 class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
-  final GridCellController cellController;
+  final GridTextCellController cellController;
   void Function()? _onCellChangedFn;
   TextCellBloc({
     required this.cellController,
@@ -66,7 +66,8 @@ class TextCellState with _$TextCellState {
     required String content,
   }) = _TextCellState;
 
-  factory TextCellState.initial(GridCellController context) => TextCellState(
+  factory TextCellState.initial(GridTextCellController context) =>
+      TextCellState(
         content: context.getCellData() ?? "",
       );
 }

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

@@ -1,4 +1,4 @@
-import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/url_type_option_entities.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

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

@@ -1,4 +1,4 @@
-import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/url_type_option_entities.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

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

@@ -1,5 +1,5 @@
-import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

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

@@ -1,7 +1,7 @@
 import 'package:app_flowy/plugins/grid/application/field/field_listener.dart';
 import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
-import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

+ 321 - 76
frontend/app_flowy/lib/plugins/grid/application/field/field_controller.dart

@@ -5,14 +5,19 @@ import 'package:app_flowy/plugins/grid/application/filter/filter_service.dart';
 import 'package:app_flowy/plugins/grid/application/grid_service.dart';
 import 'package:app_flowy/plugins/grid/application/setting/setting_listener.dart';
 import 'package:app_flowy/plugins/grid/application/setting/setting_service.dart';
+import 'package:app_flowy/plugins/grid/application/sort/sort_listener.dart';
+import 'package:app_flowy/plugins/grid/application/sort/sort_service.dart';
 import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
+import 'package:app_flowy/plugins/grid/presentation/widgets/sort/sort_info.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/filter_changeset.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/sort_entities.pb.dart';
 import 'package:dartz/dartz.dart';
-import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/setting_entities.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/group.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/setting_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/util.pb.dart';
 import 'package:flutter/foundation.dart';
 import '../row/row_cache.dart';
 
@@ -46,21 +51,39 @@ class _GridFilterNotifier extends ChangeNotifier {
   List<FilterInfo> get filters => _filters;
 }
 
+class _GridSortNotifier extends ChangeNotifier {
+  List<SortInfo> _sorts = [];
+
+  set sorts(List<SortInfo> sorts) {
+    _sorts = sorts;
+    notifyListeners();
+  }
+
+  void notify() {
+    notifyListeners();
+  }
+
+  List<SortInfo> get sorts => _sorts;
+}
+
 typedef OnReceiveUpdateFields = void Function(List<FieldInfo>);
 typedef OnReceiveFields = void Function(List<FieldInfo>);
 typedef OnReceiveFilters = void Function(List<FilterInfo>);
+typedef OnReceiveSorts = void Function(List<SortInfo>);
 
 class GridFieldController {
   final String gridId;
   // Listeners
   final GridFieldsListener _fieldListener;
   final SettingListener _settingListener;
-  final FiltersListener _filterListener;
+  final FiltersListener _filtersListener;
+  final SortsListener _sortsListener;
 
   // FFI services
   final GridFFIService _gridFFIService;
   final SettingFFIService _settingFFIService;
   final FilterFFIService _filterFFIService;
+  final SortFFIService _sortFFIService;
 
   // Field callbacks
   final Map<OnReceiveFields, VoidCallback> _fieldCallbacks = {};
@@ -78,9 +101,16 @@ class GridFieldController {
   _GridFilterNotifier? _filterNotifier = _GridFilterNotifier();
   final Map<String, FilterPB> _filterPBByFieldId = {};
 
+  // Sort callbacks
+  final Map<OnReceiveSorts, VoidCallback> _sortCallbacks = {};
+  _GridSortNotifier? _sortNotifier = _GridSortNotifier();
+  final Map<String, SortPB> _sortPBByFieldId = {};
+
   // Getters
   List<FieldInfo> get fieldInfos => [..._fieldNotifier?.fieldInfos ?? []];
   List<FilterInfo> get filterInfos => [..._filterNotifier?.filters ?? []];
+  List<SortInfo> get sortInfos => [..._sortNotifier?.sorts ?? []];
+
   FieldInfo? getField(String fieldId) {
     final fields = _fieldNotifier?.fieldInfos
             .where((element) => element.id == fieldId)
@@ -105,12 +135,26 @@ class GridFieldController {
     return filters.first;
   }
 
+  SortInfo? getSort(String sortId) {
+    final sorts = _sortNotifier?.sorts
+            .where((element) => element.sortId == sortId)
+            .toList() ??
+        [];
+    if (sorts.isEmpty) {
+      return null;
+    }
+    assert(sorts.length == 1);
+    return sorts.first;
+  }
+
   GridFieldController({required this.gridId})
       : _fieldListener = GridFieldsListener(gridId: gridId),
         _settingListener = SettingListener(gridId: gridId),
-        _filterListener = FiltersListener(viewId: gridId),
-        _gridFFIService = GridFFIService(gridId: gridId),
         _filterFFIService = FilterFFIService(viewId: gridId),
+        _filtersListener = FiltersListener(viewId: gridId),
+        _gridFFIService = GridFFIService(gridId: gridId),
+        _sortFFIService = SortFFIService(viewId: gridId),
+        _sortsListener = SortsListener(viewId: gridId),
         _settingFFIService = SettingFFIService(viewId: gridId) {
     //Listen on field's changes
     _listenOnFieldChanges();
@@ -121,9 +165,12 @@ class GridFieldController {
     //Listen on the fitler changes
     _listenOnFilterChanges();
 
+    //Listen on the sort changes
+    _listenOnSortChanged();
+
     _settingFFIService.getSetting().then((result) {
       result.fold(
-        (setting) => _updateSettingConfiguration(setting),
+        (setting) => _updateSetting(setting),
         (err) => Log.error(err),
       );
     });
@@ -131,70 +178,185 @@ class GridFieldController {
 
   void _listenOnFilterChanges() {
     //Listen on the fitler changes
-    _filterListener.start(onFilterChanged: (result) {
+
+    deleteFilterFromChangeset(
+      List<FilterInfo> filters,
+      FilterChangesetNotificationPB changeset,
+    ) {
+      final deleteFilterIds = changeset.deleteFilters.map((e) => e.id).toList();
+      if (deleteFilterIds.isNotEmpty) {
+        filters.retainWhere(
+          (element) => !deleteFilterIds.contains(element.filter.id),
+        );
+
+        _filterPBByFieldId
+            .removeWhere((key, value) => deleteFilterIds.contains(value.id));
+      }
+    }
+
+    insertFilterFromChangeset(
+      List<FilterInfo> filters,
+      FilterChangesetNotificationPB changeset,
+    ) {
+      for (final newFilter in changeset.insertFilters) {
+        final filterIndex =
+            filters.indexWhere((element) => element.filter.id == newFilter.id);
+        if (filterIndex == -1) {
+          final fieldInfo = _findFieldInfo(
+            fieldInfos: fieldInfos,
+            fieldId: newFilter.fieldId,
+            fieldType: newFilter.fieldType,
+          );
+          if (fieldInfo != null) {
+            _filterPBByFieldId[fieldInfo.id] = newFilter;
+            filters.add(FilterInfo(gridId, newFilter, fieldInfo));
+          }
+        }
+      }
+    }
+
+    updateFilterFromChangeset(
+      List<FilterInfo> filters,
+      FilterChangesetNotificationPB changeset,
+    ) {
+      for (final updatedFilter in changeset.updateFilters) {
+        final filterIndex = filters.indexWhere(
+          (element) => element.filter.id == updatedFilter.filterId,
+        );
+        // Remove the old filter
+        if (filterIndex != -1) {
+          filters.removeAt(filterIndex);
+          _filterPBByFieldId
+              .removeWhere((key, value) => value.id == updatedFilter.filterId);
+        }
+
+        // Insert the filter if there is a fitler and its field info is
+        // not null
+        if (updatedFilter.hasFilter()) {
+          final fieldInfo = _findFieldInfo(
+            fieldInfos: fieldInfos,
+            fieldId: updatedFilter.filter.fieldId,
+            fieldType: updatedFilter.filter.fieldType,
+          );
+
+          if (fieldInfo != null) {
+            // Insert the filter with the position: filterIndex, otherwise,
+            // append it to the end of the list.
+            final filterInfo =
+                FilterInfo(gridId, updatedFilter.filter, fieldInfo);
+            if (filterIndex != -1) {
+              filters.insert(filterIndex, filterInfo);
+            } else {
+              filters.add(filterInfo);
+            }
+            _filterPBByFieldId[fieldInfo.id] = updatedFilter.filter;
+          }
+        }
+      }
+    }
+
+    _filtersListener.start(onFilterChanged: (result) {
       result.fold(
-        (changeset) {
+        (FilterChangesetNotificationPB changeset) {
           final List<FilterInfo> filters = filterInfos;
           // Deletes the filters
-          final deleteFilterIds =
-              changeset.deleteFilters.map((e) => e.id).toList();
-          if (deleteFilterIds.isNotEmpty) {
-            filters.retainWhere(
-              (element) => !deleteFilterIds.contains(element.filter.id),
-            );
-
-            _filterPBByFieldId.removeWhere(
-                (key, value) => deleteFilterIds.contains(value.id));
-          }
+          deleteFilterFromChangeset(filters, changeset);
 
           // Inserts the new filter if it's not exist
-          for (final newFilter in changeset.insertFilters) {
-            final filterIndex = filters
-                .indexWhere((element) => element.filter.id == newFilter.id);
-            if (filterIndex == -1) {
-              final fieldInfo = _findFieldInfoForFilter(fieldInfos, newFilter);
-              if (fieldInfo != null) {
-                _filterPBByFieldId[fieldInfo.id] = newFilter;
-                filters.add(FilterInfo(gridId, newFilter, fieldInfo));
-              }
-            }
+          insertFilterFromChangeset(filters, changeset);
+
+          updateFilterFromChangeset(filters, changeset);
+
+          _updateFieldInfos();
+          _filterNotifier?.filters = filters;
+        },
+        (err) => Log.error(err),
+      );
+    });
+  }
+
+  void _listenOnSortChanged() {
+    deleteSortFromChangeset(
+      List<SortInfo> newSortInfos,
+      SortChangesetNotificationPB changeset,
+    ) {
+      final deleteSortIds = changeset.deleteSorts.map((e) => e.id).toList();
+      if (deleteSortIds.isNotEmpty) {
+        newSortInfos.retainWhere(
+          (element) => !deleteSortIds.contains(element.sortId),
+        );
+
+        _sortPBByFieldId
+            .removeWhere((key, value) => deleteSortIds.contains(value.id));
+      }
+    }
+
+    insertSortFromChangeset(
+      List<SortInfo> newSortInfos,
+      SortChangesetNotificationPB changeset,
+    ) {
+      for (final newSortPB in changeset.insertSorts) {
+        final sortIndex = newSortInfos
+            .indexWhere((element) => element.sortId == newSortPB.id);
+        if (sortIndex == -1) {
+          final fieldInfo = _findFieldInfo(
+            fieldInfos: fieldInfos,
+            fieldId: newSortPB.fieldId,
+            fieldType: newSortPB.fieldType,
+          );
+
+          if (fieldInfo != null) {
+            _sortPBByFieldId[newSortPB.fieldId] = newSortPB;
+            newSortInfos.add(SortInfo(sortPB: newSortPB, fieldInfo: fieldInfo));
           }
+        }
+      }
+    }
 
-          for (final updatedFilter in changeset.updateFilters) {
-            final filterIndex = filters.indexWhere(
-              (element) => element.filter.id == updatedFilter.filterId,
-            );
-            // Remove the old filter
-            if (filterIndex != -1) {
-              filters.removeAt(filterIndex);
-              _filterPBByFieldId.removeWhere(
-                  (key, value) => value.id == updatedFilter.filterId);
-            }
+    updateSortFromChangeset(
+      List<SortInfo> newSortInfos,
+      SortChangesetNotificationPB changeset,
+    ) {
+      for (final updatedSort in changeset.updateSorts) {
+        final sortIndex = newSortInfos.indexWhere(
+          (element) => element.sortId == updatedSort.id,
+        );
+        // Remove the old filter
+        if (sortIndex != -1) {
+          newSortInfos.removeAt(sortIndex);
+        }
 
-            // Insert the filter if there is a fitler and its field info is
-            // not null
-            if (updatedFilter.hasFilter()) {
-              final fieldInfo = _findFieldInfoForFilter(
-                fieldInfos,
-                updatedFilter.filter,
-              );
-
-              if (fieldInfo != null) {
-                // Insert the filter with the position: filterIndex, otherwise,
-                // append it to the end of the list.
-                final filterInfo =
-                    FilterInfo(gridId, updatedFilter.filter, fieldInfo);
-                if (filterIndex != -1) {
-                  filters.insert(filterIndex, filterInfo);
-                } else {
-                  filters.add(filterInfo);
-                }
-                _filterPBByFieldId[fieldInfo.id] = updatedFilter.filter;
-              }
-            }
+        final fieldInfo = _findFieldInfo(
+          fieldInfos: fieldInfos,
+          fieldId: updatedSort.fieldId,
+          fieldType: updatedSort.fieldType,
+        );
+
+        if (fieldInfo != null) {
+          final newSortInfo = SortInfo(
+            sortPB: updatedSort,
+            fieldInfo: fieldInfo,
+          );
+          if (sortIndex != -1) {
+            newSortInfos.insert(sortIndex, newSortInfo);
+          } else {
+            newSortInfos.add(newSortInfo);
           }
+          _sortPBByFieldId[updatedSort.fieldId] = updatedSort;
+        }
+      }
+    }
+
+    _sortsListener.start(onSortChanged: (result) {
+      result.fold(
+        (SortChangesetNotificationPB changeset) {
+          final List<SortInfo> newSortInfos = sortInfos;
+          deleteSortFromChangeset(newSortInfos, changeset);
+          insertSortFromChangeset(newSortInfos, changeset);
+          updateSortFromChangeset(newSortInfos, changeset);
+
           _updateFieldInfos();
-          _filterNotifier?.filters = filters;
+          _sortNotifier?.sorts = newSortInfos;
         },
         (err) => Log.error(err),
       );
@@ -205,7 +367,7 @@ class GridFieldController {
     //Listen on setting changes
     _settingListener.start(onSettingUpdated: (result) {
       result.fold(
-        (setting) => _updateSettingConfiguration(setting),
+        (setting) => _updateSetting(setting),
         (r) => Log.error(r),
       );
     });
@@ -229,14 +391,18 @@ class GridFieldController {
     });
   }
 
-  void _updateSettingConfiguration(GridSettingPB setting) {
+  void _updateSetting(GridSettingPB setting) {
     _groupConfigurationByFieldId.clear();
     for (final configuration in setting.groupConfigurations.items) {
       _groupConfigurationByFieldId[configuration.fieldId] = configuration;
     }
 
-    for (final configuration in setting.filters.items) {
-      _filterPBByFieldId[configuration.fieldId] = configuration;
+    for (final filter in setting.filters.items) {
+      _filterPBByFieldId[filter.fieldId] = filter;
+    }
+
+    for (final sort in setting.sorts.items) {
+      _sortPBByFieldId[sort.fieldId] = sort;
     }
 
     _updateFieldInfos();
@@ -247,6 +413,7 @@ class GridFieldController {
       for (var field in _fieldNotifier!.fieldInfos) {
         field._isGroupField = _groupConfigurationByFieldId[field.id] != null;
         field._hasFilter = _filterPBByFieldId[field.id] != null;
+        field._hasSort = _sortPBByFieldId[field.id] != null;
       }
       _fieldNotifier?.notify();
     }
@@ -254,8 +421,9 @@ class GridFieldController {
 
   Future<void> dispose() async {
     await _fieldListener.stop();
-    await _filterListener.stop();
+    await _filtersListener.stop();
     await _settingListener.stop();
+    await _sortsListener.stop();
 
     for (final callback in _fieldCallbacks.values) {
       _fieldNotifier?.removeListener(callback);
@@ -266,8 +434,15 @@ class GridFieldController {
     for (final callback in _filterCallbacks.values) {
       _filterNotifier?.removeListener(callback);
     }
+    for (final callback in _sortCallbacks.values) {
+      _sortNotifier?.removeListener(callback);
+    }
+
     _filterNotifier?.dispose();
     _filterNotifier = null;
+
+    _sortNotifier?.dispose();
+    _sortNotifier = null;
   }
 
   Future<Either<Unit, FlowyError>> loadFields({
@@ -280,6 +455,7 @@ class GridFieldController {
           _fieldNotifier?.fieldInfos =
               newFields.map((field) => FieldInfo(field: field)).toList();
           _loadFilters();
+          _loadSorts();
           _updateFieldInfos();
           return left(unit);
         },
@@ -294,14 +470,17 @@ class GridFieldController {
         (filterPBs) {
           final List<FilterInfo> filters = [];
           for (final filterPB in filterPBs) {
-            final fieldInfo = _findFieldInfoForFilter(fieldInfos, filterPB);
+            final fieldInfo = _findFieldInfo(
+              fieldInfos: fieldInfos,
+              fieldId: filterPB.fieldId,
+              fieldType: filterPB.fieldType,
+            );
             if (fieldInfo != null) {
               final filterInfo = FilterInfo(gridId, filterPB, fieldInfo);
               filters.add(filterInfo);
             }
           }
 
-          _updateFieldInfos();
           _filterNotifier?.filters = filters;
           return left(unit);
         },
@@ -310,10 +489,38 @@ class GridFieldController {
     });
   }
 
+  Future<Either<Unit, FlowyError>> _loadSorts() async {
+    return _sortFFIService.getAllSorts().then((result) {
+      return result.fold(
+        (sortPBs) {
+          final List<SortInfo> sortInfos = [];
+          for (final sortPB in sortPBs) {
+            final fieldInfo = _findFieldInfo(
+              fieldInfos: fieldInfos,
+              fieldId: sortPB.fieldId,
+              fieldType: sortPB.fieldType,
+            );
+
+            if (fieldInfo != null) {
+              final sortInfo = SortInfo(sortPB: sortPB, fieldInfo: fieldInfo);
+              sortInfos.add(sortInfo);
+            }
+          }
+
+          _updateFieldInfos();
+          _sortNotifier?.sorts = sortInfos;
+          return left(unit);
+        },
+        (err) => right(err),
+      );
+    });
+  }
+
   void addListener({
     OnReceiveFields? onFields,
     OnReceiveUpdateFields? onFieldsUpdated,
     OnReceiveFilters? onFilters,
+    OnReceiveSorts? onSorts,
     bool Function()? listenWhen,
   }) {
     if (onFieldsUpdated != null) {
@@ -350,10 +557,23 @@ class GridFieldController {
       _filterCallbacks[onFilters] = callback;
       _filterNotifier?.addListener(callback);
     }
+
+    if (onSorts != null) {
+      callback() {
+        if (listenWhen != null && listenWhen() == false) {
+          return;
+        }
+        onSorts(sortInfos);
+      }
+
+      _sortCallbacks[onSorts] = callback;
+      _sortNotifier?.addListener(callback);
+    }
   }
 
   void removeListener({
     OnReceiveFields? onFieldsListener,
+    OnReceiveSorts? onSortsListener,
     OnReceiveFilters? onFiltersListener,
     OnReceiveUpdateFields? onChangesetListener,
   }) {
@@ -369,6 +589,13 @@ class GridFieldController {
         _filterNotifier?.removeListener(callback);
       }
     }
+
+    if (onSortsListener != null) {
+      final callback = _sortCallbacks.remove(onSortsListener);
+      if (callback != null) {
+        _sortNotifier?.removeListener(callback);
+      }
+    }
   }
 
   void _deleteFields(List<FieldIdPB> deletedFields) {
@@ -425,7 +652,8 @@ class GridFieldController {
   }
 }
 
-class GridRowFieldNotifierImpl extends IGridRowFieldNotifier {
+class GridRowFieldNotifierImpl extends RowChangesetNotifierForward
+    with RowCacheDelegate {
   final GridFieldController _cache;
   OnReceiveUpdateFields? _onChangesetFn;
   OnReceiveFields? _onFieldFn;
@@ -466,11 +694,13 @@ class GridRowFieldNotifierImpl extends IGridRowFieldNotifier {
   }
 }
 
-FieldInfo? _findFieldInfoForFilter(
-    List<FieldInfo> fieldInfos, FilterPB filter) {
+FieldInfo? _findFieldInfo({
+  required List<FieldInfo> fieldInfos,
+  required String fieldId,
+  required FieldType fieldType,
+}) {
   final fieldIndex = fieldInfos.indexWhere((element) {
-    return element.id == filter.fieldId &&
-        element.fieldType == filter.fieldType;
+    return element.id == fieldId && element.fieldType == fieldType;
   });
   if (fieldIndex != -1) {
     return fieldInfos[fieldIndex];
@@ -485,6 +715,8 @@ class FieldInfo {
 
   bool _hasFilter = false;
 
+  bool _hasSort = false;
+
   String get id => _field.id;
 
   FieldType get fieldType => _field.fieldType;
@@ -529,5 +761,18 @@ class FieldInfo {
     }
   }
 
+  bool get canCreateSort {
+    if (_hasSort) return false;
+
+    switch (_field.fieldType) {
+      case FieldType.RichText:
+      case FieldType.Checkbox:
+      case FieldType.Number:
+        return true;
+      default:
+        return false;
+    }
+  }
+
   FieldInfo({required FieldPB field}) : _field = field;
 }

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

@@ -1,4 +1,4 @@
-import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'dart:async';
 import 'package:dartz/dartz.dart';

+ 3 - 3
frontend/app_flowy/lib/plugins/grid/application/field/field_listener.dart

@@ -1,11 +1,11 @@
 import 'package:app_flowy/core/grid_notification.dart';
 import 'package:dartz/dartz.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/dart_notification.pb.dart';
 import 'package:flowy_infra/notifier.dart';
 import 'dart:async';
 import 'dart:typed_data';
-import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/field_entities.pb.dart';
 
 typedef UpdateFieldNotifiedValue = Either<FieldPB, FlowyError>;
 

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

@@ -1,8 +1,8 @@
 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-grid/field_entities.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
+import 'package:appflowy_backend/dispatch/dispatch.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/grid_entities.pb.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 
 part 'field_service.freezed.dart';

+ 1 - 1
frontend/app_flowy/lib/plugins/grid/application/field/field_type_option_edit_bloc.dart

@@ -1,4 +1,4 @@
-import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

+ 3 - 3
frontend/app_flowy/lib/plugins/grid/application/field/grid_listener.dart

@@ -1,11 +1,11 @@
 import 'package:app_flowy/core/grid_notification.dart';
 import 'package:dartz/dartz.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/dart_notification.pb.dart';
 import 'package:flowy_infra/notifier.dart';
 import 'dart:async';
 import 'dart:typed_data';
-import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/field_entities.pb.dart';
 
 typedef UpdateFieldNotifiedValue = Either<GridFieldChangesetPB, FlowyError>;
 

+ 2 - 2
frontend/app_flowy/lib/plugins/grid/application/field/type_option/date_bloc.dart

@@ -1,5 +1,5 @@
-import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/date_type_option.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/date_type_option_entities.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

+ 1 - 1
frontend/app_flowy/lib/plugins/grid/application/field/type_option/edit_select_option_bloc.dart

@@ -1,4 +1,4 @@
-import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/select_type_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

+ 3 - 3
frontend/app_flowy/lib/plugins/grid/application/field/type_option/multi_select_type_option.dart

@@ -1,6 +1,6 @@
-import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/multi_select_type_option.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/multi_select_type_option.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/select_type_option.pb.dart';
 import 'dart:async';
 import 'select_option_type_option_bloc.dart';
 import 'type_option_context.dart';

+ 2 - 2
frontend/app_flowy/lib/plugins/grid/application/field/type_option/number_bloc.dart

@@ -1,5 +1,5 @@
-import 'package:flowy_sdk/protobuf/flowy-grid/format.pbenum.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/format.pbenum.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/number_type_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

+ 1 - 1
frontend/app_flowy/lib/plugins/grid/application/field/type_option/number_format_bloc.dart

@@ -1,4 +1,4 @@
-import 'package:flowy_sdk/protobuf/flowy-grid/format.pbenum.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/format.pbenum.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

+ 1 - 1
frontend/app_flowy/lib/plugins/grid/application/field/type_option/select_option_type_option_bloc.dart

@@ -1,4 +1,4 @@
-import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/select_type_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

+ 3 - 3
frontend/app_flowy/lib/plugins/grid/application/field/type_option/single_select_type_option.dart

@@ -1,6 +1,6 @@
-import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/single_select_type_option.pb.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/select_type_option.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/single_select_type_option.pb.dart';
 import 'dart:async';
 import 'package:protobuf/protobuf.dart';
 import 'select_option_type_option_bloc.dart';

+ 11 - 11
frontend/app_flowy/lib/plugins/grid/application/field/type_option/type_option_context.dart

@@ -1,15 +1,15 @@
-import 'package:flowy_sdk/dispatch/dispatch.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/checklist_type_option.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:appflowy_backend/dispatch/dispatch.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/checkbox_type_option.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/checklist_type_option.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/date_type_option.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:dartz/dartz.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/multi_select_type_option.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/single_select_type_option.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/text_type_option.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/multi_select_type_option.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/number_type_option.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/single_select_type_option.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/text_type_option.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/url_type_option.pb.dart';
 import 'package:protobuf/protobuf.dart';
 
 import 'type_option_data_controller.dart';

+ 3 - 3
frontend/app_flowy/lib/plugins/grid/application/field/type_option/type_option_data_controller.dart

@@ -1,11 +1,11 @@
 import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
 import 'package:flowy_infra/notifier.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
 import 'package:dartz/dartz.dart';
 import 'package:protobuf/protobuf.dart' hide FieldInfo;
-import 'package:flowy_sdk/log.dart';
+import 'package:appflowy_backend/log.dart';
 
 import 'type_option_context.dart';
 

+ 4 - 4
frontend/app_flowy/lib/plugins/grid/application/field/type_option/type_option_service.dart

@@ -1,8 +1,8 @@
 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-grid/cell_entities.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
+import 'package:appflowy_backend/dispatch/dispatch.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/cell_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/select_type_option.pb.dart';
 
 class TypeOptionFFIService {
   final String gridId;

+ 2 - 2
frontend/app_flowy/lib/plugins/grid/application/filter/checkbox_filter_editor_bloc.dart

@@ -1,6 +1,6 @@
 import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/checkbox_filter.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/util.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

+ 2 - 2
frontend/app_flowy/lib/plugins/grid/application/filter/checklist_filter_bloc.dart

@@ -1,6 +1,6 @@
 import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/checklist_filter.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/checklist_filter.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/util.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

+ 8 - 8
frontend/app_flowy/lib/plugins/grid/application/filter/filter_create_bloc.dart

@@ -1,13 +1,13 @@
 import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
 import 'package:dartz/dartz.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pbserver.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pbenum.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/checklist_filter.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/date_filter.pbenum.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/number_filter.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/select_option_filter.pbenum.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/text_filter.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/checkbox_filter.pbenum.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/checklist_filter.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/date_filter.pbenum.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/number_filter.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/select_option_filter.pbenum.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/text_filter.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

+ 4 - 4
frontend/app_flowy/lib/plugins/grid/application/filter/filter_listener.dart

@@ -2,11 +2,11 @@ import 'dart:typed_data';
 
 import 'package:app_flowy/core/grid_notification.dart';
 import 'package:flowy_infra/notifier.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/filter_changeset.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/dart_notification.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/filter_changeset.pb.dart';
 import 'package:dartz/dartz.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/util.pb.dart';
 
 typedef UpdateFilterNotifiedValue
     = Either<FilterChangesetNotificationPB, FlowyError>;

+ 13 - 15
frontend/app_flowy/lib/plugins/grid/application/filter/filter_service.dart

@@ -1,17 +1,17 @@
 import 'package:dartz/dartz.dart';
-import 'package:flowy_sdk/dispatch/dispatch.dart';
-import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pbserver.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/checklist_filter.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/date_filter.pbserver.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/number_filter.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/select_option_filter.pbserver.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/setting_entities.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/text_filter.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
+import 'package:appflowy_backend/dispatch/dispatch.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/checkbox_filter.pbserver.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/checklist_filter.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/date_filter.pbserver.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/grid_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/number_filter.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/select_option_filter.pbserver.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/setting_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/text_filter.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/util.pb.dart';
 import 'package:fixnum/fixnum.dart' as $fixnum;
 
 class FilterFFIService {
@@ -197,8 +197,6 @@ class FilterFFIService {
     required String filterId,
     required FieldType fieldType,
   }) {
-    TextFilterConditionPB.DoesNotContain.value;
-
     final deleteFilterPayload = DeleteFilterPayloadPB.create()
       ..fieldId = fieldId
       ..filterId = filterId

+ 2 - 2
frontend/app_flowy/lib/plugins/grid/application/filter/select_option_filter_bloc.dart

@@ -1,7 +1,7 @@
 import 'package:app_flowy/plugins/grid/presentation/widgets/filter/choicechip/select_option/select_option_loader.dart';
 import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/select_option_filter.pbserver.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/select_option_filter.pbserver.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/util.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

+ 2 - 2
frontend/app_flowy/lib/plugins/grid/application/filter/select_option_filter_list_bloc.dart

@@ -1,8 +1,8 @@
 import 'dart:async';
 
 import 'package:app_flowy/plugins/grid/presentation/widgets/filter/choicechip/select_option/select_option_loader.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/select_type_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 

+ 2 - 2
frontend/app_flowy/lib/plugins/grid/application/filter/text_filter_editor_bloc.dart

@@ -1,6 +1,6 @@
 import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/text_filter.pbserver.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/text_filter.pbserver.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/util.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

+ 47 - 0
frontend/app_flowy/lib/plugins/grid/application/grid_accessory_bloc.dart

@@ -0,0 +1,47 @@
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part 'grid_accessory_bloc.freezed.dart';
+
+class GridAccessoryMenuBloc
+    extends Bloc<GridAccessoryMenuEvent, GridAccessoryMenuState> {
+  final String viewId;
+
+  GridAccessoryMenuBloc({required this.viewId})
+      : super(GridAccessoryMenuState.initial(
+          viewId,
+        )) {
+    on<GridAccessoryMenuEvent>(
+      (event, emit) async {
+        event.when(
+          initial: () {},
+          toggleMenu: () {
+            emit(state.copyWith(isVisible: !state.isVisible));
+          },
+        );
+      },
+    );
+  }
+}
+
+@freezed
+class GridAccessoryMenuEvent with _$GridAccessoryMenuEvent {
+  const factory GridAccessoryMenuEvent.initial() = _Initial;
+  const factory GridAccessoryMenuEvent.toggleMenu() = _MenuVisibleChange;
+}
+
+@freezed
+class GridAccessoryMenuState with _$GridAccessoryMenuState {
+  const factory GridAccessoryMenuState({
+    required String viewId,
+    required bool isVisible,
+  }) = _GridAccessoryMenuState;
+
+  factory GridAccessoryMenuState.initial(
+    String viewId,
+  ) =>
+      GridAccessoryMenuState(
+        viewId: viewId,
+        isVisible: false,
+      );
+}

+ 3 - 3
frontend/app_flowy/lib/plugins/grid/application/grid_bloc.dart

@@ -1,9 +1,9 @@
 import 'dart:async';
 import 'package:dartz/dartz.dart';
 import 'package:equatable/equatable.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/protobuf.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'field/field_controller.dart';

+ 3 - 3
frontend/app_flowy/lib/plugins/grid/application/grid_data_controller.dart

@@ -1,7 +1,7 @@
 import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/grid_entities.pb.dart';
 import 'dart:async';
 import 'package:dartz/dartz.dart';
 import 'view/grid_view_cache.dart';

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

@@ -1,6 +1,6 @@
 import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
-import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

+ 7 - 7
frontend/app_flowy/lib/plugins/grid/application/grid_service.dart

@@ -1,11 +1,11 @@
 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-folder/view.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
+import 'package:appflowy_backend/dispatch/dispatch.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/grid_entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/group.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/row_entities.pb.dart';
 
 class GridFFIService {
   final String gridId;

+ 2 - 2
frontend/app_flowy/lib/plugins/grid/application/row/row_action_sheet_bloc.dart

@@ -1,6 +1,6 @@
 import 'package:app_flowy/plugins/grid/application/row/row_service.dart';
-import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

+ 2 - 2
frontend/app_flowy/lib/plugins/grid/application/row/row_bloc.dart

@@ -13,11 +13,11 @@ part 'row_bloc.freezed.dart';
 
 class RowBloc extends Bloc<RowEvent, RowState> {
   final RowFFIService _rowService;
-  final GridRowDataController _dataController;
+  final RowDataController _dataController;
 
   RowBloc({
     required RowInfo rowInfo,
-    required GridRowDataController dataController,
+    required RowDataController dataController,
   })  : _rowService = RowFFIService(gridId: rowInfo.gridId),
         _dataController = dataController,
         super(RowState.initial(rowInfo, dataController.loadData())) {

+ 41 - 15
frontend/app_flowy/lib/plugins/grid/application/row/row_cache.dart

@@ -1,9 +1,9 @@
 import 'dart:collection';
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
 import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
-import 'package:flowy_sdk/dispatch/dispatch.dart';
-import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
+import 'package:appflowy_backend/dispatch/dispatch.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-grid/protobuf.dart';
 import 'package:flutter/foundation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 
@@ -12,10 +12,13 @@ part 'row_cache.freezed.dart';
 
 typedef RowUpdateCallback = void Function();
 
-abstract class IGridRowFieldNotifier {
-  UnmodifiableListView<FieldInfo> get fields;
+abstract class RowChangesetNotifierForward {
   void onRowFieldsChanged(VoidCallback callback);
   void onRowFieldChanged(void Function(FieldInfo) callback);
+}
+
+abstract class RowCacheDelegate {
+  UnmodifiableListView<FieldInfo> get fields;
   void onRowDispose();
 }
 
@@ -33,8 +36,8 @@ class GridRowCache {
   final RowList _rowList = RowList();
 
   final GridCellCache _cellCache;
-  final IGridRowFieldNotifier _fieldNotifier;
-  final _RowChangesetNotifier _rowChangeReasonNotifier;
+  final RowCacheDelegate _delegate;
+  final RowChangesetNotifier _rowChangeReasonNotifier;
 
   UnmodifiableListView<RowInfo> get visibleRows {
     var visibleRows = [..._rowList.rows];
@@ -46,10 +49,11 @@ class GridRowCache {
   GridRowCache({
     required this.gridId,
     required this.rows,
-    required IGridRowFieldNotifier notifier,
+    required RowChangesetNotifierForward notifier,
+    required RowCacheDelegate delegate,
   })  : _cellCache = GridCellCache(gridId: gridId),
-        _rowChangeReasonNotifier = _RowChangesetNotifier(),
-        _fieldNotifier = notifier {
+        _rowChangeReasonNotifier = RowChangesetNotifier(),
+        _delegate = delegate {
     //
     notifier.onRowFieldsChanged(() => _rowChangeReasonNotifier
         .receive(const RowsChangedReason.fieldDidChange()));
@@ -65,7 +69,7 @@ class GridRowCache {
   }
 
   Future<void> dispose() async {
-    _fieldNotifier.onRowDispose();
+    _delegate.onRowDispose();
     _rowChangeReasonNotifier.dispose();
     await _cellCache.dispose();
   }
@@ -81,6 +85,23 @@ class GridRowCache {
     _showRows(changeset.visibleRows);
   }
 
+  void reorderAllRows(List<String> rowIds) {
+    _rowList.reorderWithRowIds(rowIds);
+    _rowChangeReasonNotifier.receive(const RowsChangedReason.reorderRows());
+  }
+
+  void reorderSingleRow(ReorderSingleRowPB reorderRow) {
+    final rowInfo = _rowList.get(reorderRow.rowId);
+    if (rowInfo != null) {
+      _rowList.moveRow(
+          reorderRow.rowId, reorderRow.oldIndex, reorderRow.newIndex);
+      _rowChangeReasonNotifier.receive(RowsChangedReason.reorderSingleRow(
+        reorderRow,
+        rowInfo,
+      ));
+    }
+  }
+
   void _deleteRows(List<String> deletedRowIds) {
     for (final rowId in deletedRowIds) {
       final deletedRow = _rowList.remove(rowId);
@@ -208,7 +229,7 @@ class GridRowCache {
   GridCellMap _makeGridCells(String rowId, RowPB? row) {
     // ignore: prefer_collection_literals
     var cellDataMap = GridCellMap();
-    for (final field in _fieldNotifier.fields) {
+    for (final field in _delegate.fields) {
       if (field.visibility) {
         cellDataMap[field.id] = GridCellIdentifier(
           rowId: rowId,
@@ -247,16 +268,16 @@ class GridRowCache {
   RowInfo buildGridRow(RowPB rowPB) {
     return RowInfo(
       gridId: gridId,
-      fields: _fieldNotifier.fields,
+      fields: _delegate.fields,
       rowPB: rowPB,
     );
   }
 }
 
-class _RowChangesetNotifier extends ChangeNotifier {
+class RowChangesetNotifier extends ChangeNotifier {
   RowsChangedReason reason = const InitialListState();
 
-  _RowChangesetNotifier();
+  RowChangesetNotifier();
 
   void receive(RowsChangedReason newReason) {
     reason = newReason;
@@ -266,6 +287,8 @@ class _RowChangesetNotifier extends ChangeNotifier {
       update: (_) => notifyListeners(),
       fieldDidChange: (_) => notifyListeners(),
       initial: (_) {},
+      reorderRows: (_) => notifyListeners(),
+      reorderSingleRow: (_) => notifyListeners(),
     );
   }
 }
@@ -292,6 +315,9 @@ class RowsChangedReason with _$RowsChangedReason {
   const factory RowsChangedReason.update(UpdatedIndexMap indexs) = _Update;
   const factory RowsChangedReason.fieldDidChange() = _FieldDidChange;
   const factory RowsChangedReason.initial() = InitialListState;
+  const factory RowsChangedReason.reorderRows() = _ReorderRows;
+  const factory RowsChangedReason.reorderSingleRow(
+      ReorderSingleRowPB reorderRow, RowInfo rowInfo) = _ReorderSingleRow;
 }
 
 class InsertedIndex {

+ 2 - 2
frontend/app_flowy/lib/plugins/grid/application/row/row_data_controller.dart

@@ -7,13 +7,13 @@ import 'row_cache.dart';
 
 typedef OnRowChanged = void Function(GridCellMap, RowsChangedReason);
 
-class GridRowDataController extends GridCellBuilderDelegate {
+class RowDataController extends GridCellBuilderDelegate {
   final RowInfo rowInfo;
   final List<VoidCallback> _onRowChangedListeners = [];
   final GridFieldController _fieldController;
   final GridRowCache _rowCache;
 
-  GridRowDataController({
+  RowDataController({
     required this.rowInfo,
     required GridFieldController fieldController,
     required GridRowCache rowCache,

+ 1 - 1
frontend/app_flowy/lib/plugins/grid/application/row/row_detail_bloc.dart

@@ -7,7 +7,7 @@ import 'row_data_controller.dart';
 part 'row_detail_bloc.freezed.dart';
 
 class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
-  final GridRowDataController dataController;
+  final RowDataController dataController;
 
   RowDetailBloc({
     required this.dataController,

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