Merge branch 'main' into feat/history

This commit is contained in:
appflowy 2022-07-20 14:07:54 +08:00
commit 1e3c69f15b
820 changed files with 15966 additions and 60868 deletions

26
.githooks/commit-msg Executable file
View File

@ -0,0 +1,26 @@
#!/bin/sh
#
# An example hook script to check the commit log message.
# Called by "git commit" with one argument, the name of the file
# that has the commit message. The hook should exit with non-zero
# status after issuing an appropriate message if it wants to stop the
# commit. The hook is allowed to edit the commit message file.
echo "Running the AppFlowy commit-msg hook."
# This example catches duplicate Signed-off-by lines.
test "" = "$(grep '^Signed-off-by: ' "$1" |
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
echo >&2 Duplicate Signed-off-by lines.
exit 1
}
npx --no -- commitlint --edit $1
if [ $? -ne 0 ]
then
echo "Please fix your commit message to match AppFlowy coding standards"
exit 1
fi

4
.githooks/pre-commit Normal file → Executable file
View File

@ -1,5 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
echo "Running local AppFlowy pre-commit hook."
#flutter format . #flutter format .
##https://gist.github.com/benmccallum/28e4f216d9d72f5965133e6c43aaff6e ##https://gist.github.com/benmccallum/28e4f216d9d72f5965133e6c43aaff6e
limit=$(( 1 * 2**20 )) # 1MB limit=$(( 1 * 2**20 )) # 1MB
@ -31,4 +33,4 @@ for file in $( git diff-index --cached --name-only $against ); do
file_too_large $filename $file_size file_too_large $filename $file_size
exit 1; exit 1;
fi fi
done done

5
.githooks/pre-push Normal file → Executable file
View File

@ -1,15 +1,20 @@
#!/usr/bin/env bash #!/usr/bin/env bash
echo "Running local AppFlowy pre-push hook."
if [[ `git status --porcelain` ]]; then if [[ `git status --porcelain` ]]; then
printf "\e[31;1m%s\e[0m\n" 'This script needs to run against committed code only. Please commit or stash you changes.' printf "\e[31;1m%s\e[0m\n" 'This script needs to run against committed code only. Please commit or stash you changes.'
exit 1 exit 1
fi fi
printf "\e[33;1m%s\e[0m\n" 'Running the Flutter analyzer' printf "\e[33;1m%s\e[0m\n" 'Running the Flutter analyzer'
flutter analyze flutter analyze
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
printf "\e[31;1m%s\e[0m\n" 'Flutter analyzer error' printf "\e[31;1m%s\e[0m\n" 'Flutter analyzer error'
exit 1 exit 1
fi fi
printf "\e[33;1m%s\e[0m\n" 'Finished running the Flutter analyzer' printf "\e[33;1m%s\e[0m\n" 'Finished running the Flutter analyzer'
printf "\e[33;1m%s\e[0m\n" 'Running unit tests' printf "\e[33;1m%s\e[0m\n" 'Running unit tests'

View File

@ -16,7 +16,7 @@ jobs:
os: [ubuntu-latest, macos-latest] os: [ubuntu-latest, macos-latest]
include: include:
- os: ubuntu-latest - os: ubuntu-latest
flutter_profile: development-linux-x86 flutter_profile: development-linux-x86_64
- os: macos-latest - os: macos-latest
flutter_profile: development-mac-x86_64 flutter_profile: development-mac-x86_64
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@ -82,4 +82,4 @@ jobs:
- name: Build - name: Build
working-directory: frontend working-directory: frontend
run: | run: |
cargo make --profile ${{ matrix.flutter_profile }} appflowy-dev cargo make --profile ${{ matrix.flutter_profile }} appflowy-dev

View File

@ -25,14 +25,31 @@ jobs:
with: with:
flutter-version: '3.0.0' flutter-version: '3.0.0'
channel: "stable" channel: "stable"
- name: Deps Flutter - uses: actions-rs/toolchain@v1
with:
toolchain: 'stable-2022-01-20'
- name: Rust Deps
working-directory: frontend
run: |
cargo install cargo-make
cargo make flowy_dev
- name: Flutter Deps
run: flutter packages pub get run: flutter packages pub get
working-directory: frontend/app_flowy working-directory: frontend/app_flowy
- name: Build FlowySDK
working-directory: frontend
run: |
cargo make --profile development-linux-x86_64 flowy-sdk-dev
- name: Code Generation - name: Code Generation
working-directory: frontend/app_flowy working-directory: frontend/app_flowy
run: | 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 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 flutter packages pub run build_runner build --delete-conflicting-outputs
- name: Run Flutter Analyzer - name: Run Flutter Analyzer
working-directory: frontend/app_flowy working-directory: frontend/app_flowy
run: flutter analyze run: flutter analyze

View File

@ -43,18 +43,21 @@ jobs:
shared-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') }} key: ${{ runner.os }}-rust-rust-lib-share-lib-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}
- name: Deps Flutter - name: Flutter Deps
working-directory: frontend/app_flowy working-directory: frontend/app_flowy
run: | run: |
flutter config --enable-linux-desktop flutter config --enable-linux-desktop
- name: Deps Rust - name: Rust Deps
working-directory: frontend working-directory: frontend
run: | run: |
cargo install cargo-make cargo install cargo-make
cargo install duckscript_cli
cargo make flowy_dev cargo make flowy_dev
- name: Build FlowySDK
working-directory: frontend
run: |
cargo make --profile development-linux-x86_64 flowy-sdk-dev
- name: Code Generation - name: Code Generation
working-directory: frontend/app_flowy working-directory: frontend/app_flowy
run: | run: |
@ -62,11 +65,6 @@ jobs:
flutter packages pub run easy_localization:generate -f keys -o locale_keys.g.dart -S assets/translations -s en.json 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 flutter packages pub run build_runner build --delete-conflicting-outputs
- name: Build FlowySDK
working-directory: frontend
run: |
cargo make --profile development-linux-x86 flowy-sdk-dev
- name: Run bloc tests - name: Run bloc tests
working-directory: frontend/app_flowy working-directory: frontend/app_flowy
run: | run: |

View File

@ -67,7 +67,7 @@ jobs:
working-directory: frontend working-directory: frontend
run: | run: |
flutter config --enable-linux-desktop flutter config --enable-linux-desktop
cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-linux-x86 appflowy cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-linux-x86_64 appflowy
- name: Upload Release Asset - name: Upload Release Asset
id: upload-release-asset id: upload-release-asset

View File

@ -17,25 +17,31 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
with: with:
toolchain: 'stable-2022-01-20' toolchain: 'stable-2022-01-20'
override: true override: true
- name: Rust Deps
working-directory: frontend
run: |
cargo install cargo-make
cargo make flowy_dev
- name: Build FlowySDK
working-directory: frontend
run: |
cargo make --profile development-linux-x86_64 flowy-sdk-dev
- run: rustup component add rustfmt - run: rustup component add rustfmt
working-directory: frontend/rust-lib working-directory: frontend/rust-lib
- run: cargo fmt --all -- --check - name: rustfmt
run: cargo fmt --all -- --check
working-directory: frontend/rust-lib/ working-directory: frontend/rust-lib/
rust-clippy:
runs-on: ubuntu-latest
name: Clippy
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: 'stable-2022-01-20'
override: true
- run: rustup component add clippy - run: rustup component add clippy
working-directory: frontend/rust-lib
- name: clippy
run: cargo clippy --no-default-features
working-directory: frontend/rust-lib working-directory: frontend/rust-lib
- run: cargo clippy --no-default-features
working-directory: frontend/rust-lib

16
.gitignore vendored
View File

@ -17,4 +17,18 @@ yarn.lock
node_modules node_modules
**/.proto_cache **/.proto_cache
**/.cache **/.cache
**/.DS_Store **/.DS_Store
**/src/protobuf
**/resources/proto
frontend/.vscode/*
!frontend/.vscode/settings.json
!frontend/.vscode/tasks.json
!frontend/.vscode/launch.json
!frontend/.vscode/extensions.json
!frontend/.vscode/*.code-snippets
# Commit the highest level pubspec.lock, but ignore the others
pubspec.lock
!frontend/app_flowy/pubspec.lock

View File

@ -1,4 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no -- commitlint --edit

View File

@ -1,35 +0,0 @@
[tasks.install-commitlint.mac]
script = [
"""
brew install npm
yarn install
yarn husky install
""",
]
script_runner = "@shell"
[tasks.install-commitlint.windows]
script = [
"""
echo "WIP"
""",
]
script_runner = "@duckscript"
[tasks.install-commitlint.linux]
script = [
"""
if command -v apt &> /dev/null
then
echo "Installing node.js and yarn (sudo apt install nodejs yarn)"
sudo apt install nodejs yarn
else
echo "Installing node.js and yarn (sudo pacman -S nodejs yarn)"
sudo pacman -S nodejs yarn
fi
yarn install
yarn husky install
""",
]
script_runner = "@shell"

View File

@ -23,7 +23,8 @@ You are in charge of your data and customizations.
<a href="https://twitter.com/appflowy"><b>Twitter</b></a> <a href="https://twitter.com/appflowy"><b>Twitter</b></a>
</p> </p>
<p align="center"><img src="https://github.com/AppFlowy-IO/appflowy/blob/main/doc/imgs/welcome.png" alt="The Open Source Alternative To Notion." width="1000px" /></p> <p align="center"><img src="https://user-images.githubusercontent.com/12026239/174754661-682980e4-e386-4685-bb6f-2da357390b61.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/174753177-98e4c899-2356-4137-bb42-374bba2b127b.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
## User Installation ## User Installation

View File

@ -5,40 +5,67 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "app_flowy", // This task builds the Rust and Dart code of AppFlowy.
"name": "AF: Build All",
"request": "launch", "request": "launch",
"program": "./lib/main.dart", "program": "./lib/main.dart",
"type": "dart", "type": "dart",
"preLaunchTask": "build_flowy_sdk", "preLaunchTask": "AF: build_flowy_sdk",
"env":{ "env": {
"RUST_LOG":"info" "RUST_LOG": "info"
}, },
"cwd": "${workspaceRoot}/app_flowy" "cwd": "${workspaceRoot}/app_flowy"
}, },
{ {
"name": "app_flowy(trace)", "name": "AF: Debug Rust",
"request": "attach",
"type": "lldb",
"pid": "${command:pickMyProcess}"
},
{
// This task only builds the Dart code of AppFlowy.
"name": "AF: Build Dart Only",
"request": "launch", "request": "launch",
"program": "./lib/main.dart", "program": "./lib/main.dart",
"type": "dart", "type": "dart",
"preLaunchTask": "build_flowy_sdk", "env": {
"env":{ "RUST_LOG": "debug"
"RUST_LOG":"trace"
}, },
"cwd": "${workspaceRoot}/app_flowy" "cwd": "${workspaceRoot}/app_flowy"
}, },
{ {
"name": "app_flowy (profile mode)", // This task builds will:
"request": "launch", // - call the clean task,
"type": "dart", // - rebuild all the generated Files (including freeze and language files)
"flutterMode": "profile" // - rebuild the the Rust and Dart code of AppFlowy.
}, "name": "AF: Clean + Rebuild All",
{
"name": "Generate Language Files",
"request": "launch", "request": "launch",
"program": "./lib/main.dart", "program": "./lib/main.dart",
"type": "dart", "type": "dart",
"preLaunchTask": "Generate Language Files", "preLaunchTask": "AF: Clean + Rebuild All",
"cwd": "${workspaceRoot}/app_flowy/" "env": {
"RUST_LOG": "info"
},
"cwd": "${workspaceRoot}/app_flowy"
},
{
"name": "AF: Build All (rustlog: trace)",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"preLaunchTask": "AF: build_flowy_sdk",
"env": {
"RUST_LOG": "trace"
},
"cwd": "${workspaceRoot}/app_flowy"
},
{
"name": "AF: app_flowy (profile mode)",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"flutterMode": "profile",
"cwd": "${workspaceRoot}/app_flowy"
}, },
] ]
} }

View File

@ -23,4 +23,5 @@
"*.log.*": "log" "*.log.*": "log"
}, },
"editor.formatOnSave": true, "editor.formatOnSave": true,
"files.eol": "\n",
} }

View File

@ -10,13 +10,31 @@
// ${cwd}: the current working directory of the spawned process // ${cwd}: the current working directory of the spawned process
"tasks": [ "tasks": [
{ {
"label": "build_flowy_sdk", "label": "AF: Clean + Rebuild All",
"type": "shell",
"dependsOrder": "sequence",
"dependsOn": [
"AF: Rust Clean",
"AF: Flutter Clean",
"AF: build_flowy_sdk",
"AF: Flutter Pub Get",
"AF: Flutter Package Get",
"AF: Generate Language Files",
"AF: Generate Freezed Files",
],
"presentation": {
"reveal": "always",
"panel": "new"
}
},
{
"label": "AF: build_flowy_sdk",
"type": "shell", "type": "shell",
"command": "sh ./scripts/build_sdk.sh", "command": "sh ./scripts/build_sdk.sh",
"windows": { "windows": {
"options": { "options": {
"env": { "env": {
"FLOWY_DEV_ENV": "Windows", "FLOWY_DEV_ENV": "Windows"
}, },
"shell": { "shell": {
"executable": "cmd.exe", "executable": "cmd.exe",
@ -31,27 +49,76 @@
"linux": { "linux": {
"options": { "options": {
"env": { "env": {
"FLOWY_DEV_ENV": "Linux-x86", "FLOWY_DEV_ENV": "Linux"
} }
}, }
}, },
"osx": { "osx": {
"options": { "options": {
"env": { "env": {
"FLOWY_DEV_ENV": "macOS", "FLOWY_DEV_ENV": "macOS"
} }
}, }
}, },
"group": "build", "group": "build",
"options": { "options": {
"cwd": "${workspaceFolder}" "cwd": "${workspaceFolder}"
}, }
// "problemMatcher": [
// "$rustc"
// ],
}, },
{ {
"label": "Generate Language Files", "label": "AF: Code Gen",
"type": "shell",
"dependsOrder": "sequence",
"dependsOn": [
"AF: Flutter Clean",
"AF: Flutter Pub Get",
"AF: Flutter Package Get",
"AF: Generate Language Files",
"AF: Generate Freezed Files"
],
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "new"
}
},
{
"label": "AF: Flutter Clean",
"type": "shell",
"command": "flutter clean",
"options": {
"cwd": "${workspaceFolder}/app_flowy"
}
},
{
"label": "AF: Flutter Pub Get",
"type": "shell",
"command": "flutter pub get",
"options": {
"cwd": "${workspaceFolder}/app_flowy"
}
},
{
"label": "AF: Flutter Package Get",
"type": "shell",
"command": "flutter packages pub get",
"options": {
"cwd": "${workspaceFolder}/app_flowy"
}
},
{
"label": "AF: Generate Freezed Files",
"type": "shell",
"command": "flutter pub run build_runner build --delete-conflicting-outputs",
"options": {
"cwd": "${workspaceFolder}/app_flowy"
}
},
{
"label": "AF: Generate Language Files",
"type": "shell", "type": "shell",
"command": "sh ./scripts/generate_language_files.sh", "command": "sh ./scripts/generate_language_files.sh",
"windows": { "windows": {
@ -69,28 +136,28 @@
"group": "build", "group": "build",
"options": { "options": {
"cwd": "${workspaceFolder}" "cwd": "${workspaceFolder}"
}, }
}, },
{ {
"label": "Clean", "label": "AF: Rust Clean",
"type": "shell", "type": "shell",
"command": "sh ./scripts/clean.sh", "command": "cargo make flowy_clean",
"windows": {
"options": {
"shell": {
"executable": "cmd.exe",
"args": [
"/d",
"/c",
".\\scripts\\clean.cmd"
]
}
}
},
"group": "build", "group": "build",
"options": { "options": {
"cwd": "${workspaceFolder}" "cwd": "${workspaceFolder}"
}, }
},
{
"label": "AF: flutter build aar",
"type": "flutter",
"command": "flutter",
"args": [
"build",
"aar"
],
"group": "build",
"problemMatcher": [],
"detail": "app_flowy"
} }
] ]
} }

View File

@ -1,2 +0,0 @@
brew 'sqlite3'
brew 'rustup-init'

View File

@ -1,14 +0,0 @@
.PHONY: flowy_dev_install flowy_clean
flowy_dev_install:
brew bundle
rustup-init -y --default-toolchain=stable
cargo install --force cargo-make
cargo install --force duckscript_cli
cargo make flowy_dev
flowy_clean:
sh ./scripts/clean.sh

View File

@ -8,6 +8,7 @@ extend = [
{ path = "scripts/makefile/env.toml" }, { path = "scripts/makefile/env.toml" },
{ path = "scripts/makefile/flutter.toml" }, { path = "scripts/makefile/flutter.toml" },
{ path = "scripts/makefile/tool.toml" }, { path = "scripts/makefile/tool.toml" },
{ path = "scripts/makefile/githooks.toml" },
] ]
[config] [config]
@ -52,6 +53,7 @@ RUST_COMPILE_TARGET = "aarch64-apple-darwin"
BUILD_FLAG = "debug" BUILD_FLAG = "debug"
FLUTTER_OUTPUT_DIR = "Debug" FLUTTER_OUTPUT_DIR = "Debug"
PRODUCT_EXT = "app" PRODUCT_EXT = "app"
BUILD_ARCHS = "arm64"
[env.development-mac-x86_64] [env.development-mac-x86_64]
RUST_LOG = "info" RUST_LOG = "info"
@ -60,6 +62,7 @@ RUST_COMPILE_TARGET = "x86_64-apple-darwin"
BUILD_FLAG = "debug" BUILD_FLAG = "debug"
FLUTTER_OUTPUT_DIR = "Debug" FLUTTER_OUTPUT_DIR = "Debug"
PRODUCT_EXT = "app" PRODUCT_EXT = "app"
BUILD_ARCHS = "x86_64"
[env.production-mac-arm64] [env.production-mac-arm64]
BUILD_FLAG = "release" BUILD_FLAG = "release"
@ -68,6 +71,7 @@ RUST_COMPILE_TARGET = "aarch64-apple-darwin"
FLUTTER_OUTPUT_DIR = "Release" FLUTTER_OUTPUT_DIR = "Release"
PRODUCT_EXT = "app" PRODUCT_EXT = "app"
APP_ENVIRONMENT = "production" APP_ENVIRONMENT = "production"
BUILD_ARCHS = "arm64"
[env.production-mac-x86_64] [env.production-mac-x86_64]
BUILD_FLAG = "release" BUILD_FLAG = "release"
@ -76,6 +80,7 @@ RUST_COMPILE_TARGET = "x86_64-apple-darwin"
FLUTTER_OUTPUT_DIR = "Release" FLUTTER_OUTPUT_DIR = "Release"
PRODUCT_EXT = "app" PRODUCT_EXT = "app"
APP_ENVIRONMENT = "production" APP_ENVIRONMENT = "production"
BUILD_ARCHS = "x86_64"
[env.development-windows-x86] [env.development-windows-x86]
TARGET_OS = "windows" TARGET_OS = "windows"
@ -96,7 +101,7 @@ CRATE_TYPE = "cdylib"
SDK_EXT = "dll" SDK_EXT = "dll"
APP_ENVIRONMENT = "production" APP_ENVIRONMENT = "production"
[env.development-linux-x86] [env.development-linux-x86_64]
TARGET_OS = "linux" TARGET_OS = "linux"
RUST_COMPILE_TARGET = "x86_64-unknown-linux-gnu" RUST_COMPILE_TARGET = "x86_64-unknown-linux-gnu"
BUILD_FLAG = "debug" BUILD_FLAG = "debug"
@ -105,7 +110,7 @@ FLUTTER_OUTPUT_DIR = "Debug"
SDK_EXT = "so" SDK_EXT = "so"
LINUX_ARCH = "x64" LINUX_ARCH = "x64"
[env.production-linux-x86] [env.production-linux-x86_64]
BUILD_FLAG = "release" BUILD_FLAG = "release"
TARGET_OS = "linux" TARGET_OS = "linux"
RUST_COMPILE_TARGET = "x86_64-unknown-linux-gnu" RUST_COMPILE_TARGET = "x86_64-unknown-linux-gnu"
@ -146,6 +151,7 @@ script = [
echo PRODUCT_EXT: ${PRODUCT_EXT} echo PRODUCT_EXT: ${PRODUCT_EXT}
echo APP_ENVIRONMENT: ${APP_ENVIRONMENT} echo APP_ENVIRONMENT: ${APP_ENVIRONMENT}
echo ${platforms} echo ${platforms}
echo ${BUILD_ARCHS}
''' '''
] ]
script_runner = "@shell" script_runner = "@shell"

View File

@ -1,48 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
// This task builds the Rust and Dart code of AppFlowy.
"name": "Build",
"request": "launch",
"program": "${workspaceRoot}/lib/main.dart",
"preLaunchTask": "build_flowy_sdk",
"type": "dart",
"env": {
"RUST_LOG": "debug"
},
"cwd": "${workspaceRoot}"
},
{
// This task only build the Dart code of AppFlowy.
"name": "Build (Dart)",
"request": "launch",
"program": "${workspaceRoot}/lib/main.dart",
"type": "dart",
"env": {
"RUST_LOG": "debug"
},
"cwd": "${workspaceRoot}"
},
{
"name": "Build (trace log)",
"request": "launch",
"program": "${workspaceRoot}/lib/main.dart",
"type": "dart",
"preLaunchTask": "build_flowy_sdk",
"env": {
"RUST_LOG": "trace"
},
"cwd": "${workspaceRoot}"
},
{
"name": "Build (profile mode)",
"request": "launch",
"type": "dart",
"flutterMode": "profile"
},
]
}

View File

@ -1,26 +0,0 @@
{
"[dart]": {
"editor.formatOnSave": true,
"editor.formatOnType": true,
"editor.rulers": [
120
],
"editor.selectionHighlight": false,
"editor.suggest.snippetsPreventQuickSuggestions": false,
"editor.suggestSelection": "first",
"editor.tabCompletion": "onlySnippets",
"editor.wordBasedSuggestions": false
},
"svgviewer.enableautopreview": true,
"svgviewer.previewcolumn": "Active",
"svgviewer.showzoominout": true,
"editor.wordWrapColumn": 120,
"editor.minimap.maxColumn": 140,
"prettier.printWidth": 140,
"editor.wordWrap": "wordWrapColumn",
"dart.lineLength": 120,
"files.associations": {
"*.log.*": "log"
},
"editor.formatOnSave": true,
}

View File

@ -1,129 +0,0 @@
{
"version": "2.0.0",
// https://code.visualstudio.com/docs/editor/tasks
// https://gist.github.com/deadalusai/9e13e36d61ec7fb72148
// ${workspaceRoot}: the root folder of the team
// ${file}: the current opened file
// ${fileBasename}: the current opened file's basename
// ${fileDirname}: the current opened file's dirname
// ${fileExtname}: the current opened file's extension
// ${cwd}: the current working directory of the spawned process
"tasks": [
{
"label": "build_flowy_sdk",
"type": "shell",
"command": "sh ./scripts/build_sdk.sh",
"windows": {
"options": {
"env": {
"FLOWY_DEV_ENV": "Windows",
},
"shell": {
"executable": "cmd.exe",
"args": [
"/d",
"/c",
".\\scripts\\build_sdk.cmd"
]
}
}
},
"linux": {
"options": {
"env": {
"FLOWY_DEV_ENV": "Linux-x86",
}
},
},
"osx": {
"options": {
"env": {
"FLOWY_DEV_ENV": "macOS",
}
},
},
"group": "build",
"options": {
"cwd": "${workspaceFolder}/../"
},
// "problemMatcher": [
// "$rustc"
// ],
},
{
"label": "Code Gen",
"type": "shell",
"dependsOn": [
"Flutter Pub",
"Flutter Package Get",
"Generate Language Files",
"Generate Freezed Files"
],
"group": {
"kind": "build",
"isDefault": true,
},
"dependsOrder": "sequence",
"presentation": {
"reveal": "always",
"panel": "new"
},
},
{
"label": "Flutter Pub",
"type": "shell",
"command": "flutter pub get",
},
{
"label": "Flutter Package Get",
"type": "shell",
"command": "flutter packages pub get",
},
{
"label": "Generate Freezed Files",
"type": "shell",
"command": "flutter pub run build_runner build --delete-conflicting-outputs",
},
{
"label": "Generate Language Files",
"type": "shell",
"command": "sh ./scripts/generate_language_files.sh",
"windows": {
"options": {
"shell": {
"executable": "cmd.exe",
"args": [
"/d",
"/c",
".\\scripts\\generate_language_files.cmd"
]
}
}
},
"group": "build",
"options": {
"cwd": "${workspaceFolder}/../"
},
},
{
"label": "Clean",
"type": "shell",
"command": "sh ./scripts/clean.sh",
"windows": {
"options": {
"shell": {
"executable": "cmd.exe",
"args": [
"/d",
"/c",
".\\scripts\\clean.cmd"
]
}
}
},
"options": {
"cwd": "${workspaceFolder}/../"
},
}
]
}

View File

@ -17,7 +17,6 @@ analyzer:
- "packages/flowy_editor/**" - "packages/flowy_editor/**"
- "packages/editor/**" - "packages/editor/**"
# - "packages/flowy_infra_ui/**" # - "packages/flowy_infra_ui/**"
linter: linter:
# The lint rules applied to this project can be customized in the # The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml` # section below to disable rules from the `package:flutter_lints/flutter.yaml`
@ -38,4 +37,4 @@ linter:
# https://dart.dev/guides/language/analysis-options # https://dart.dev/guides/language/analysis-options
errors: errors:
invalid_annotation_target: ignore invalid_annotation_target: ignore

View File

@ -96,6 +96,12 @@
"lightMode": "Switch to Light mode", "lightMode": "Switch to Light mode",
"darkMode": "Switch to Dark mode" "darkMode": "Switch to Dark mode"
}, },
"notifications": {
"export": {
"markdown": "Exported Note To Markdown",
"path": "Documents/flowy"
}
},
"contactsPage": { "contactsPage": {
"title": "Contacts", "title": "Contacts",
"whatsHappening": "What's happening this week?", "whatsHappening": "What's happening this week?",
@ -199,6 +205,10 @@
"pannelTitle": "Select an option or create one", "pannelTitle": "Select an option or create one",
"searchOption": "Search for an option" "searchOption": "Search for an option"
}, },
"menuName": "Grid"
},
"document": {
"menuName": "Doc",
"date": { "date": {
"timeHintTextInTwelveHour": "12:00 AM", "timeHintTextInTwelveHour": "12:00 AM",
"timeHintTextInTwentyFourHour": "12:00" "timeHintTextInTwentyFourHour": "12:00"

View File

@ -141,5 +141,11 @@
"lightLabel": "Modalità Chiara", "lightLabel": "Modalità Chiara",
"darkLabel": "Modalità Scura" "darkLabel": "Modalità Scura"
} }
},
"grid": {
"menuName":"Griglia"
},
"document":{
"menuName":"Documento"
} }
} }

View File

@ -0,0 +1,199 @@
{
"appName": "AppFlowy",
"defaultUsername": "ユーザー",
"welcomeText": "Welcome to @:appName",
"githubStarText": "Star on GitHub",
"subscribeNewsletterText": "新着情報を受け取る",
"letsGoButtonText": "Let's Go",
"title": "タイトル",
"signUp": {
"buttonText": "新規登録",
"title": "@:appNameに新規登録",
"getStartedText": "はじめる",
"emptyPasswordError": "パスワードを空にはできません",
"repeatPasswordEmptyError": "パスワード(確認用)を空にはできません",
"unmatchedPasswordError": "パスワード(確認用)が一致しません",
"alreadyHaveAnAccount": "すでにアカウントを登録済ですか?",
"emailHint": "メールアドレス",
"passwordHint": "パスワード",
"repeatPasswordHint": "パスワード(確認用)"
},
"signIn": {
"loginTitle": "@:appName にログイン",
"loginButtonText": "ログイン",
"buttonText": "サインイン",
"forgotPassword": "パスワードをお忘れですか?",
"emailHint": "メールアドレス",
"passwordHint": "パスワード",
"dontHaveAnAccount": "まだアカウントをお持ちではないですか?",
"repeatPasswordEmptyError": "パスワード(確認用)を空にはできません",
"unmatchedPasswordError": "パスワード(確認用)が一致しません"
},
"workspace": {
"create": "ワークスペースを作成する",
"hint": "ワークスペース",
"notFoundError": "ワークスペースがみつかりません"
},
"shareAction": {
"buttonText": "共有する",
"workInProgress": "Coming soon",
"markdown": "Markdown",
"copyLink": "Copy Link"
},
"disclosureAction": {
"rename": "名前を変更",
"delete": "削除",
"duplicate": "コピーを作成"
},
"blankPageTitle": "空のページ",
"newPageText": "新しいページ",
"trash": {
"text": "ごみ箱",
"restoreAll": "全て復元",
"deleteAll": "全て削除",
"pageHeader": {
"fileName": "ファイル名",
"lastModified": "最終更新日時",
"created": "作成日時"
}
},
"deletePagePrompt": {
"text": "このページはごみ箱にあります",
"restore": "ページを元に戻す",
"deletePermanent": "削除する"
},
"dialogCreatePageNameHint": "ページ名",
"questionBubble": {
"whatsNew": "What's new?",
"help": "ヘルプとサポート",
"debug": {
"name": "デバッグ情報",
"success": "デバッグ情報をクリップボードにコピーしました!",
"fail": "デバッグ情報をクリップボードにコピーできませんでした"
}
},
"menuAppHeader": {
"addPageTooltip": "内部ページを追加",
"defaultNewPageName": "Untitled",
"renameDialog": "名前を変更"
},
"toolbar": {
"undo": "元に戻す",
"redo": "やり直し",
"bold": "太字",
"italic": "斜体",
"underline": "下線",
"strike": "取り消し線",
"numList": "番号付きリスト",
"bulletList": "箇条書き",
"checkList": "チェックボックス",
"inlineCode": "インラインコード",
"quote": "引用文",
"header": "見出し",
"highlight": "文字の背景色"
},
"tooltip": {
"lightMode": "ライトモードに切り替える",
"darkMode": "ダークモードに切り替える"
},
"contactsPage": {
"title": "連絡先",
"whatsHappening": "今週はどんなことがありましたか?",
"addContact": "連絡先を追加する",
"editContact": "連絡先を編集する"
},
"button": {
"OK": "OK",
"Cancel": "キャンセル",
"signIn": "サインイン",
"signOut": "サインアウト",
"complete": "完了",
"save": "保存"
},
"label": {
"welcome": "ようこそ!",
"firstName": "名",
"middleName": "ミドルネーム",
"lastName": "姓",
"stepX": "Step {X}"
},
"oAuth": {
"err": {
"failedTitle": "アカウントに接続できません",
"failedMsg": "サインインが完了したことをブラウザーで確認してください"
},
"google": {
"title": "GOOGLEでサインイン",
"instruction1": "GOOGLEでのサインインを有効にするためには、Webブラウザーを使ってこのアプリケーションを認証する必要があります。",
"instruction2": "アイコンをクリックするか、以下のテキストを選択して、このコードをクリップボードにコピーします。",
"instruction3": "以下のリンク先をブラウザーで開いて、次のコードを入力します。",
"instruction4": "登録が完了したら以下のボタンを押してください。"
}
},
"settings": {
"title": "設定",
"menu": {
"appearance": "外観",
"language": "言語",
"open": "設定"
},
"appearance": {
"lightLabel": "ライトモード",
"darkLabel": "ダークモード"
}
},
"grid": {
"settings": {
"filter": "絞り込み",
"sortBy": "並び替え",
"Properties": "プロパティ"
},
"field": {
"hide": "隠す",
"insertLeft": "左に挿入",
"insertRight": "右に挿入",
"duplicate": "コピーを作成",
"delete": "削除",
"textFieldName": "テキスト",
"checkboxFieldName": "チェックボックス",
"dateFieldName": "日付",
"numberFieldName": "数値",
"singleSelectFieldName": "単一選択",
"multiSelectFieldName": "複数選択",
"numberFormat": " 数値書式",
"dateFormat": " 日付書式",
"includeTime": " 時刻を含める",
"dateFormatFriendly": "月 日,年",
"dateFormatISO": "年-月-日",
"dateFormatLocal": "年/月/日",
"dateFormatUS": "年/月/日",
"timeFormat": " 時刻書式",
"timeFormatTwelveHour": "12 時間表記",
"timeFormatTwentyFourHour": "24 時間表記",
"addSelectOption": "選択候補追加",
"optionTitle": "選択候補",
"addOption": "選択候補追加",
"editProperty": "プロパティの編集"
},
"row": {
"duplicate": "コピーを作成",
"delete": "削除",
"textPlaceholder": "空白"
},
"selectOption": {
"purpleColor": "紫",
"pinkColor": "ピンク",
"lightPinkColor": "ライトピンク",
"orangeColor": "オレンジ",
"yellowColor": "黄色",
"limeColor": "ライム",
"greenColor": "緑",
"aquaColor": "水色",
"blueColor": "青",
"deleteTag": "選択候補を削除",
"colorPannelTitle": "色",
"pannelTitle": "選択候補を検索 または 作成する",
"searchOption": "選択候補を検索"
}
}
}

View File

@ -7,11 +7,11 @@
"letsGoButtonText": "Vamos lá", "letsGoButtonText": "Vamos lá",
"title": "Título", "title": "Título",
"signUp": { "signUp": {
"buttonText": "Inscreve-se", "buttonText": "Se inscreva",
"title": "Inscrever-se @:appName", "title": "Se inscreva no @:appName",
"getStartedText": "Começar", "getStartedText": "Começar",
"emptyPasswordError": "Senha não pode ser em branco.", "emptyPasswordError": "Senha não pode estar em branco.",
"repeatPasswordEmptyError": "Confirmar a senha não pode ser em branco.", "repeatPasswordEmptyError": "Confirmar a senha não pode estar em branco.",
"unmatchedPasswordError": "As senhas não conferem.", "unmatchedPasswordError": "As senhas não conferem.",
"alreadyHaveAnAccount": "Já possui uma conta?", "alreadyHaveAnAccount": "Já possui uma conta?",
"emailHint": "Email", "emailHint": "Email",
@ -19,14 +19,14 @@
"repeatPasswordHint": "Confirme a senha" "repeatPasswordHint": "Confirme a senha"
}, },
"signIn": { "signIn": {
"loginTitle": "Login to @:appName", "loginTitle": "Entre no @:appName",
"loginButtonText": "Login", "loginButtonText": "Login",
"buttonText": "Entre", "buttonText": "Entre",
"forgotPassword": "Esqueceu a senha?", "forgotPassword": "Esqueceu a senha?",
"emailHint": "Email", "emailHint": "Email",
"passwordHint": "Senha", "passwordHint": "Senha",
"dontHaveAnAccount": "Não possui uma conta?", "dontHaveAnAccount": "Não possui uma conta?",
"repeatPasswordEmptyError": "Confirmar a senha não pode ser em branco.", "repeatPasswordEmptyError": "Confirmar a senha não pode estar em branco.",
"unmatchedPasswordError": "As senhas não conferem." "unmatchedPasswordError": "As senhas não conferem."
}, },
"workspace": { "workspace": {
@ -67,7 +67,7 @@
"whatsNew": "O que há de novo?", "whatsNew": "O que há de novo?",
"help": "Ajuda & Suporte", "help": "Ajuda & Suporte",
"debug": { "debug": {
"name": "Informação de debug", "name": "Informação de depuração",
"success": "Copiar informação de debug para o clipboard!", "success": "Copiar informação de debug para o clipboard!",
"fail": "Falha em copiar a informação de debug para o clipboard" "fail": "Falha em copiar a informação de debug para o clipboard"
} }
@ -104,7 +104,7 @@
}, },
"button": { "button": {
"OK": "OK", "OK": "OK",
"Cancel": "Canelar", "Cancel": "Cancelar",
"signIn": "Entrar", "signIn": "Entrar",
"signOut": "Sair", "signOut": "Sair",
"complete": "Completar", "complete": "Completar",
@ -143,4 +143,5 @@
} }
} }
} }

View File

@ -0,0 +1,146 @@
{
"appName": "AppFlowy",
"defaultUsername": "Me",
"welcomeText": "Bem vindo ao @:appName",
"githubStarText": "Star on GitHub",
"subscribeNewsletterText": "Inscreve-te ao Newsletter",
"letsGoButtonText": "Bora",
"title": "Título",
"signUp": {
"buttonText": "Inscreve-te",
"title": "Inscreve-te ao @:appName",
"getStartedText": "Começar",
"emptyPasswordError": "A palavra-passe não pode estar em branco.",
"repeatPasswordEmptyError": "Confirmar a palavra-passe não pode estar em branco.",
"unmatchedPasswordError": "As palavras-passes não coincidem.",
"alreadyHaveAnAccount": "Já possuis uma conta?",
"emailHint": "Email",
"passwordHint": "Password",
"repeatPasswordHint": "Confirma a tua password"
},
"signIn": {
"loginTitle": "Entre no @:appName",
"loginButtonText": "Login",
"buttonText": "Entre",
"forgotPassword": "Esqueceste-te da tua palavra-passe?",
"emailHint": "Email",
"passwordHint": "Palavra-passe",
"dontHaveAnAccount": "Não possuis uma conta?",
"repeatPasswordEmptyError": "Confirmar a palavra-passe não pode estar em branco.",
"unmatchedPasswordError": "As palavras-passes não conferem."
},
"workspace": {
"create": "Cria um ambiente de trabalho",
"hint": "ambiente de trabalho",
"notFoundError": "Ambiente de trabalho não encontrada"
},
"shareAction": {
"buttonText": "Partilhar",
"workInProgress": "Em breve",
"markdown": "Markdown",
"copyLink": "Copiar o link"
},
"disclosureAction": {
"rename": "Renomear",
"delete": "Apagar",
"duplicate": "Duplicar"
},
"blankPageTitle": "Página em branco",
"newPageText": "Nova página",
"trash": {
"text": "Lixo",
"restoreAll": "Restaurar todos",
"deleteAll": "Apagar todos",
"pageHeader": {
"fileName": "Nome do ficheiro",
"lastModified": "Última modificação",
"created": "Criado"
}
},
"deletePagePrompt": {
"text": "Esta página está no lixo",
"restore": "Restaurar a página",
"deletePermanent": "Apagar permanentemente"
},
"dialogCreatePageNameHint": "Nome da página",
"questionBubble": {
"whatsNew": "O que há de novo?",
"help": "Ajuda & Suporte",
"debug": {
"name": "Informação de depuração",
"success": "Copiar informação de depuração para o clipboard!",
"fail": "Falha em copiar a informação de depuração para o clipboard"
}
},
"menuAppHeader": {
"addPageTooltip": "Adiciona uma nova página.",
"defaultNewPageName": "Sem título",
"renameDialog": "Renomear"
},
"toolbar": {
"undo": "Desfazer",
"redo": "Refazer",
"bold": "Negrito",
"italic": "Itálico",
"underline": "Sublinhado",
"strike": "Riscado",
"numList": "Lista numerada",
"bulletList": "Lista com marcadores",
"checkList": "Lista de verificação",
"inlineCode": "Embutir código",
"quote": "Citação em bloco",
"header": "Cabeçalho",
"highlight": "Realçar"
},
"tooltip": {
"lightMode": "Mudar para o modo Claro.",
"darkMode": "Mudar para o modo Escuro."
},
"contactsPage": {
"title": "Conctatos",
"whatsHappening": "O que está a acontecer nesta semana?",
"addContact": "Adicionar um conctato",
"editContact": "Editar um conctato"
},
"button": {
"OK": "OK",
"Cancel": "Cancelar",
"signIn": "Entrar",
"signOut": "Sair",
"complete": "Completar",
"save": "Guardar"
},
"label": {
"welcome": "Bem vindo!",
"firstName": "Nome",
"middleName": "Nome do Meio",
"lastName": "Apelido",
"stepX": "Passo {X}"
},
"oAuth": {
"err": {
"failedTitle": "Erro ao conectar à sua conta.",
"failedMsg": "Verifica se concluiste o processo de login no teu navegador."
},
"google": {
"title": "GOOGLE SIGN-IN",
"instruction1": "Para importar os teus Conctatos do Google, tens de autorizar esta aplicação usando o teu navegador web.",
"instruction2": "Copia este código para a tua área de transferências clicando no ícone ou selecionando o texto:",
"instruction3": "Navega até o link a seguir no seu navegador e digite o código acima:",
"instruction4": "Clica no botão abaixo ao concluir a inscrição:"
}
},
"settings": {
"title": "Definições",
"menu": {
"appearance": "Aparência",
"language": "Idioma",
"open": "Abrir as Definições"
},
"appearance": {
"lightLabel": "Modo Claro",
"darkLabel": "Modo Escuro"
}
}
}

View File

@ -141,6 +141,68 @@
"lightLabel": "Светлая тема", "lightLabel": "Светлая тема",
"darkLabel": "Тёмная тема" "darkLabel": "Тёмная тема"
} }
},
"grid": {
"settings": {
"filter": "Фильтр",
"sortBy": "Сортировать",
"Properties": "Свойства"
},
"field": {
"hide": "Скрыть",
"insertLeft": "Вставить слева",
"insertRight": "Вставить справа",
"duplicate": "Дублировать",
"delete": "Удалить",
"textFieldName": "Текст",
"checkboxFieldName": "Checkbox",
"dateFieldName": "Дата",
"numberFieldName": "Число",
"singleSelectFieldName": "Выбор",
"multiSelectFieldName": "Выбор многих",
"urlFieldName": "URL",
"numberFormat": " Формат числа",
"dateFormat": " Формат даты",
"includeTime": " Время",
"dateFormatFriendly": "День Месяц, Год",
"dateFormatISO": "Год-Месяц-День",
"dateFormatLocal": "Год/Месяц/День",
"dateFormatUS": "Год/Месяц/День",
"timeFormat": " Форматировать время",
"invalidTimeFormat": "Неверный формат",
"timeFormatTwelveHour": "12 часов",
"timeFormatTwentyFourHour": "24 часа",
"addSelectOption": "Добавить вариант",
"optionTitle": "Варианты",
"addOption": "Добавить",
"editProperty": "Редактировать свойство"
},
"row": {
"duplicate": "Дублировать",
"delete": "Удалить",
"textPlaceholder": "Пусто",
"copyProperty": "Свойство скопировано"
},
"selectOption": {
"create": "Создать",
"purpleColor": "Фиолетовый",
"pinkColor": "Розовый",
"lightPinkColor": "Светло-розовый",
"orangeColor": "Оранжевый",
"yellowColor": "Желтый",
"limeColor": "Ярко-зелёный",
"greenColor": "Зелёный",
"aquaColor": "Морской волны",
"blueColor": "Синий",
"deleteTag": "Удалить вариант",
"colorPannelTitle": "Цвета",
"pannelTitle": "Выберите или создайте вариант",
"searchOption": "Поиск"
},
"date": {
"timeHintTextInTwelveHour": "12:00 AM",
"timeHintTextInTwentyFourHour": "12:00"
}
} }
} }

View File

@ -0,0 +1,39 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:flowy_sdk/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 'notification_helper.dart';
// Folder
typedef FolderNotificationCallback = void Function(FolderNotification, Either<Uint8List, FlowyError>);
class FolderNotificationParser extends NotificationParser<FolderNotification, FlowyError> {
FolderNotificationParser({String? id, required FolderNotificationCallback callback})
: super(
id: id,
callback: callback,
tyParser: (ty) => FolderNotification.valueOf(ty),
errorParser: (bytes) => FlowyError.fromBuffer(bytes),
);
}
typedef FolderNotificationHandler = Function(FolderNotification ty, Either<Uint8List, FlowyError> result);
class FolderNotificationListener {
StreamSubscription<SubscribeObject>? _subscription;
FolderNotificationParser? _parser;
FolderNotificationListener({required String objectId, required FolderNotificationHandler handler})
: _parser = FolderNotificationParser(id: objectId, callback: handler) {
_subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
}
Future<void> stop() async {
_parser = null;
await _subscription?.cancel();
}
}

View File

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

View File

@ -1,68 +1,6 @@
import 'dart:async';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flowy_sdk/protobuf/dart-notify/protobuf.dart'; import 'package:flowy_sdk/protobuf/dart-notify/protobuf.dart';
import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart';
import 'package:dartz/dartz.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/protobuf/flowy-grid/dart_notification.pb.dart';
import 'package:flowy_sdk/rust_stream.dart';
// User
typedef UserNotificationCallback = void Function(UserNotification, Either<Uint8List, FlowyError>);
class UserNotificationParser extends NotificationParser<UserNotification, FlowyError> {
UserNotificationParser({required String id, required UserNotificationCallback callback})
: super(
id: id,
callback: callback,
tyParser: (ty) => UserNotification.valueOf(ty),
errorParser: (bytes) => FlowyError.fromBuffer(bytes),
);
}
// Folder
typedef FolderNotificationCallback = void Function(FolderNotification, Either<Uint8List, FlowyError>);
class FolderNotificationParser extends NotificationParser<FolderNotification, FlowyError> {
FolderNotificationParser({String? id, required FolderNotificationCallback callback})
: super(
id: id,
callback: callback,
tyParser: (ty) => FolderNotification.valueOf(ty),
errorParser: (bytes) => FlowyError.fromBuffer(bytes),
);
}
// Grid
typedef GridNotificationCallback = void Function(GridNotification, Either<Uint8List, FlowyError>);
class GridNotificationParser extends NotificationParser<GridNotification, FlowyError> {
GridNotificationParser({String? id, required GridNotificationCallback callback})
: super(
id: id,
callback: callback,
tyParser: (ty) => GridNotification.valueOf(ty),
errorParser: (bytes) => FlowyError.fromBuffer(bytes),
);
}
typedef GridNotificationHandler = Function(GridNotification ty, Either<Uint8List, FlowyError> result);
class GridNotificationListener {
StreamSubscription<SubscribeObject>? _subscription;
GridNotificationParser? _parser;
GridNotificationListener({required String objectId, required GridNotificationHandler handler})
: _parser = GridNotificationParser(id: objectId, callback: handler) {
_subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
}
Future<void> stop() async {
_parser = null;
await _subscription?.cancel();
}
}
class NotificationParser<T, E> { class NotificationParser<T, E> {
String? id; String? id;

View File

@ -0,0 +1,39 @@
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:dartz/dartz.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/rust_stream.dart';
import 'notification_helper.dart';
// User
typedef UserNotificationCallback = void Function(UserNotification, Either<Uint8List, FlowyError>);
class UserNotificationParser extends NotificationParser<UserNotification, FlowyError> {
UserNotificationParser({required String id, required UserNotificationCallback callback})
: super(
id: id,
callback: callback,
tyParser: (ty) => UserNotification.valueOf(ty),
errorParser: (bytes) => FlowyError.fromBuffer(bytes),
);
}
typedef UserNotificationHandler = Function(UserNotification ty, Either<Uint8List, FlowyError> result);
class UserNotificationListener {
StreamSubscription<SubscribeObject>? _subscription;
UserNotificationParser? _parser;
UserNotificationListener({required String objectId, required UserNotificationHandler handler})
: _parser = UserNotificationParser(id: objectId, callback: handler) {
_subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
}
Future<void> stop() async {
_parser = null;
await _subscription?.cancel();
}
}

View File

@ -4,7 +4,7 @@ import 'package:app_flowy/plugin/plugin.dart';
import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/presentation/home/home_stack.dart'; import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
import 'package:flowy_infra/notifier.dart'; import 'package:flowy_infra/notifier.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
export "./src/sandbox.dart"; export "./src/sandbox.dart";
@ -14,6 +14,7 @@ enum DefaultPlugin {
blank, blank,
trash, trash,
grid, grid,
board,
} }
extension FlowyDefaultPluginExt on DefaultPlugin { extension FlowyDefaultPluginExt on DefaultPlugin {
@ -27,6 +28,8 @@ extension FlowyDefaultPluginExt on DefaultPlugin {
return 2; return 2;
case DefaultPlugin.grid: case DefaultPlugin.grid:
return 3; return 3;
case DefaultPlugin.board:
return 4;
} }
} }
} }

View File

@ -13,9 +13,9 @@ import 'package:app_flowy/user/application/prelude.dart';
import 'package:app_flowy/user/presentation/router.dart'; import 'package:app_flowy/user/presentation/router.dart';
import 'package:app_flowy/workspace/presentation/home/home_stack.dart'; import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
import 'package:app_flowy/workspace/presentation/home/menu/menu.dart'; import 'package:app_flowy/workspace/presentation/home/menu/menu.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/app.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
@ -51,37 +51,37 @@ void _resolveHomeDeps(GetIt getIt) {
getIt.registerSingleton(MenuSharedState()); getIt.registerSingleton(MenuSharedState());
getIt.registerFactoryParam<UserListener, UserProfile, void>( getIt.registerFactoryParam<UserListener, UserProfilePB, void>(
(user, _) => UserListener(user: user), (user, _) => UserListener(userProfile: user),
); );
// //
getIt.registerLazySingleton<HomeStackManager>(() => HomeStackManager()); getIt.registerLazySingleton<HomeStackManager>(() => HomeStackManager());
getIt.registerFactoryParam<WelcomeBloc, UserProfile, void>( getIt.registerFactoryParam<WelcomeBloc, UserProfilePB, void>(
(user, _) => WelcomeBloc( (user, _) => WelcomeBloc(
userService: UserService(), userService: UserService(userId: user.id),
userListener: getIt<UserListener>(param1: user), userWorkspaceListener: UserWorkspaceListener(userProfile: user),
), ),
); );
// share // share
getIt.registerLazySingleton<ShareService>(() => ShareService()); getIt.registerLazySingleton<ShareService>(() => ShareService());
getIt.registerFactoryParam<DocShareBloc, View, void>( getIt.registerFactoryParam<DocShareBloc, ViewPB, void>(
(view, _) => DocShareBloc(view: view, service: getIt<ShareService>())); (view, _) => DocShareBloc(view: view, service: getIt<ShareService>()));
} }
void _resolveFolderDeps(GetIt getIt) { void _resolveFolderDeps(GetIt getIt) {
//workspace //workspace
getIt.registerFactoryParam<WorkspaceListener, UserProfile, String>((user, workspaceId) => getIt.registerFactoryParam<WorkspaceListener, UserProfilePB, String>(
WorkspaceListener(service: WorkspaceListenerService(user: user, workspaceId: workspaceId))); (user, workspaceId) => WorkspaceListener(user: user, workspaceId: workspaceId));
// View // ViewPB
getIt.registerFactoryParam<ViewListener, View, void>( getIt.registerFactoryParam<ViewListener, ViewPB, void>(
(view, _) => ViewListener(view: view), (view, _) => ViewListener(view: view),
); );
getIt.registerFactoryParam<ViewBloc, View, void>( getIt.registerFactoryParam<ViewBloc, ViewPB, void>(
(view, _) => ViewBloc( (view, _) => ViewBloc(
view: view, view: view,
service: ViewService(), service: ViewService(),
@ -90,23 +90,19 @@ void _resolveFolderDeps(GetIt getIt) {
); );
//Menu //Menu
getIt.registerFactoryParam<MenuBloc, UserProfile, String>( getIt.registerFactoryParam<MenuBloc, UserProfilePB, String>(
(user, workspaceId) => MenuBloc( (user, workspaceId) => MenuBloc(
workspaceId: workspaceId, workspaceId: workspaceId,
listener: getIt<WorkspaceListener>(param1: user, param2: workspaceId), listener: getIt<WorkspaceListener>(param1: user, param2: workspaceId),
), ),
); );
getIt.registerFactoryParam<MenuUserBloc, UserProfile, void>( getIt.registerFactoryParam<MenuUserBloc, UserProfilePB, void>(
(user, _) => MenuUserBloc( (user, _) => MenuUserBloc(user),
user,
UserService(),
getIt<UserListener>(param1: user),
),
); );
// App // AppPB
getIt.registerFactoryParam<AppBloc, App, void>( getIt.registerFactoryParam<AppBloc, AppPB, void>(
(app, _) => AppBloc( (app, _) => AppBloc(
app: app, app: app,
appService: AppService(appId: app.id), appService: AppService(appId: app.id),
@ -127,7 +123,7 @@ void _resolveFolderDeps(GetIt getIt) {
void _resolveDocDeps(GetIt getIt) { void _resolveDocDeps(GetIt getIt) {
// Doc // Doc
getIt.registerFactoryParam<DocumentBloc, View, void>( getIt.registerFactoryParam<DocumentBloc, ViewPB, void>(
(view, _) => DocumentBloc( (view, _) => DocumentBloc(
view: view, view: view,
service: DocumentService(), service: DocumentService(),
@ -138,8 +134,8 @@ void _resolveDocDeps(GetIt getIt) {
} }
void _resolveGridDeps(GetIt getIt) { void _resolveGridDeps(GetIt getIt) {
// Grid // GridPB
getIt.registerFactoryParam<GridBloc, View, void>( getIt.registerFactoryParam<GridBloc, ViewPB, void>(
(view, _) => GridBloc(view: view), (view, _) => GridBloc(view: view),
); );
@ -157,31 +153,31 @@ void _resolveGridDeps(GetIt getIt) {
), ),
); );
getIt.registerFactoryParam<TextCellBloc, GridCellContext, void>( getIt.registerFactoryParam<TextCellBloc, GridCellController, void>(
(context, _) => TextCellBloc( (context, _) => TextCellBloc(
cellContext: context, cellContext: context,
), ),
); );
getIt.registerFactoryParam<SelectOptionCellBloc, GridSelectOptionCellContext, void>( getIt.registerFactoryParam<SelectOptionCellBloc, GridSelectOptionCellController, void>(
(context, _) => SelectOptionCellBloc( (context, _) => SelectOptionCellBloc(
cellContext: context, cellContext: context,
), ),
); );
getIt.registerFactoryParam<NumberCellBloc, GridCellContext, void>( getIt.registerFactoryParam<NumberCellBloc, GridCellController, void>(
(context, _) => NumberCellBloc( (context, _) => NumberCellBloc(
cellContext: context, cellContext: context,
), ),
); );
getIt.registerFactoryParam<DateCellBloc, GridDateCellContext, void>( getIt.registerFactoryParam<DateCellBloc, GridDateCellController, void>(
(context, _) => DateCellBloc( (context, _) => DateCellBloc(
cellContext: context, cellContext: context,
), ),
); );
getIt.registerFactoryParam<CheckboxCellBloc, GridCellContext, void>( getIt.registerFactoryParam<CheckboxCellBloc, GridCellController, void>(
(cellData, _) => CheckboxCellBloc( (cellData, _) => CheckboxCellBloc(
service: CellService(), service: CellService(),
cellContext: cellData, cellContext: cellData,

View File

@ -37,6 +37,7 @@ class InitAppWidgetTask extends LaunchTask {
Locale('fr', 'CA'), Locale('fr', 'CA'),
Locale('hu', 'HU'), Locale('hu', 'HU'),
Locale('it', 'IT'), Locale('it', 'IT'),
Locale('ja', 'JP'),
Locale('pt', 'BR'), Locale('pt', 'BR'),
Locale('ru', 'RU'), Locale('ru', 'RU'),
Locale('tr', 'TR'), Locale('tr', 'TR'),

View File

@ -1,6 +1,7 @@
import 'package:app_flowy/plugin/plugin.dart'; import 'package:app_flowy/plugin/plugin.dart';
import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/presentation/plugins/blank/blank.dart'; import 'package:app_flowy/workspace/presentation/plugins/blank/blank.dart';
import 'package:app_flowy/workspace/presentation/plugins/board/board.dart';
import 'package:app_flowy/workspace/presentation/plugins/doc/document.dart'; import 'package:app_flowy/workspace/presentation/plugins/doc/document.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/grid.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/grid.dart';
import 'package:app_flowy/workspace/presentation/plugins/trash/trash.dart'; import 'package:app_flowy/workspace/presentation/plugins/trash/trash.dart';
@ -15,5 +16,6 @@ class PluginLoadTask extends LaunchTask {
registerPlugin(builder: TrashPluginBuilder(), config: TrashPluginConfig()); registerPlugin(builder: TrashPluginBuilder(), config: TrashPluginConfig());
registerPlugin(builder: DocumentPluginBuilder()); registerPlugin(builder: DocumentPluginBuilder());
registerPlugin(builder: GridPluginBuilder(), config: GridPluginConfig()); registerPlugin(builder: GridPluginBuilder(), config: GridPluginConfig());
registerPlugin(builder: BoardPluginBuilder(), config: BoardPluginConfig());
} }
} }

View File

@ -1,21 +1,21 @@
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/protobuf/flowy-user-data-model/protobuf.dart' show SignInPayload, SignUpPayload, UserProfile; import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show SignInPayloadPB, SignUpPayloadPB, UserProfilePB;
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
class AuthService { class AuthService {
Future<Either<UserProfile, FlowyError>> signIn({required String? email, required String? password}) { Future<Either<UserProfilePB, FlowyError>> signIn({required String? email, required String? password}) {
// //
final request = SignInPayload.create() final request = SignInPayloadPB.create()
..email = email ?? '' ..email = email ?? ''
..password = password ?? ''; ..password = password ?? '';
return UserEventSignIn(request).send(); return UserEventSignIn(request).send();
} }
Future<Either<UserProfile, FlowyError>> signUp( Future<Either<UserProfilePB, FlowyError>> signUp(
{required String? name, required String? password, required String? email}) { {required String? name, required String? password, required String? email}) {
final request = SignUpPayload.create() final request = SignUpPayloadPB.create()
..email = email ?? '' ..email = email ?? ''
..name = name ?? '' ..name = name ?? ''
..password = password ?? ''; ..password = password ?? '';

View File

@ -1,7 +1,8 @@
import 'package:app_flowy/user/application/auth_service.dart'; import 'package:app_flowy/user/application/auth_service.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/protobuf/flowy-error-code/code.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-user-data-model/protobuf.dart' show UserProfile, ErrorCode; import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfilePB;
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -68,7 +69,7 @@ class SignInState with _$SignInState {
required bool isSubmitting, required bool isSubmitting,
required Option<String> passwordError, required Option<String> passwordError,
required Option<String> emailError, required Option<String> emailError,
required Option<Either<UserProfile, FlowyError>> successOrFail, required Option<Either<UserProfilePB, FlowyError>> successOrFail,
}) = _SignInState; }) = _SignInState;
factory SignInState.initial() => SignInState( factory SignInState.initial() => SignInState(

View File

@ -1,7 +1,8 @@
import 'package:app_flowy/user/application/auth_service.dart'; import 'package:app_flowy/user/application/auth_service.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_sdk/protobuf/flowy-user-data-model/protobuf.dart' show UserProfile, ErrorCode; import 'package:flowy_sdk/protobuf/flowy-error-code/code.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfilePB;
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -119,7 +120,7 @@ class SignUpState with _$SignUpState {
required Option<String> passwordError, required Option<String> passwordError,
required Option<String> repeatPasswordError, required Option<String> repeatPasswordError,
required Option<String> emailError, required Option<String> emailError,
required Option<Either<UserProfile, FlowyError>> successOrFail, required Option<Either<UserProfilePB, FlowyError>> successOrFail,
}) = _SignUpState; }) = _SignUpState;
factory SignUpState.initial() => SignUpState( factory SignUpState.initial() => SignUpState(

View File

@ -1,117 +1,72 @@
import 'dart:async'; import 'dart:async';
import 'package:app_flowy/core/folder_notification.dart';
import 'package:app_flowy/core/user_notification.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/workspace.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error-code/code.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder/workspace.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:app_flowy/core/notification_helper.dart';
import 'package:flowy_infra/notifier.dart'; import 'package:flowy_infra/notifier.dart';
import 'package:flowy_sdk/protobuf/dart-notify/protobuf.dart'; import 'package:flowy_sdk/protobuf/dart-notify/protobuf.dart';
import 'package:flowy_sdk/protobuf/flowy-folder/dart_notification.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/dart_notification.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-user-data-model/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-user/dart_notification.pb.dart' as user; import 'package:flowy_sdk/protobuf/flowy-user/dart_notification.pb.dart' as user;
import 'package:flowy_sdk/rust_stream.dart'; import 'package:flowy_sdk/rust_stream.dart';
typedef UserProfileNotifyValue = Either<UserProfile, FlowyError>; typedef UserProfileNotifyValue = Either<UserProfilePB, FlowyError>;
typedef AuthNotifyValue = Either<Unit, FlowyError>; typedef AuthNotifyValue = Either<Unit, FlowyError>;
typedef WorkspaceListNotifyValue = Either<List<Workspace>, FlowyError>;
typedef WorkspaceSettingNotifyValue = Either<CurrentWorkspaceSetting, FlowyError>;
class UserListener { class UserListener {
StreamSubscription<SubscribeObject>? _subscription; StreamSubscription<SubscribeObject>? _subscription;
final _profileNotifier = PublishNotifier<UserProfileNotifyValue>(); PublishNotifier<AuthNotifyValue>? _authNotifier = PublishNotifier();
final _authNotifier = PublishNotifier<AuthNotifyValue>(); PublishNotifier<UserProfileNotifyValue>? _profileNotifier = PublishNotifier();
final _workspaceListNotifier = PublishNotifier<WorkspaceListNotifyValue>();
final _workSettingNotifier = PublishNotifier<WorkspaceSettingNotifyValue>();
FolderNotificationParser? _workspaceParser;
UserNotificationParser? _userParser; UserNotificationParser? _userParser;
final UserProfile _user; final UserProfilePB _userProfile;
UserListener({ UserListener({
required UserProfile user, required UserProfilePB userProfile,
}) : _user = user; }) : _userProfile = userProfile;
void start({ void start({
void Function(AuthNotifyValue)? onAuthChanged, void Function(AuthNotifyValue)? onAuthChanged,
void Function(UserProfileNotifyValue)? onProfileUpdated, void Function(UserProfileNotifyValue)? onProfileUpdated,
void Function(WorkspaceListNotifyValue)? onWorkspaceListUpdated,
void Function(WorkspaceSettingNotifyValue)? onWorkspaceSettingUpdated,
}) { }) {
if (onAuthChanged != null) {
_authNotifier.addListener(() {
onAuthChanged(_authNotifier.currentValue!);
});
}
if (onProfileUpdated != null) { if (onProfileUpdated != null) {
_profileNotifier.addListener(() { _profileNotifier?.addPublishListener(onProfileUpdated);
onProfileUpdated(_profileNotifier.currentValue!);
});
} }
if (onWorkspaceListUpdated != null) { if (onAuthChanged != null) {
_workspaceListNotifier.addListener(() { _authNotifier?.addPublishListener(onAuthChanged);
onWorkspaceListUpdated(_workspaceListNotifier.currentValue!);
});
} }
if (onWorkspaceSettingUpdated != null) { _userParser = UserNotificationParser(id: _userProfile.token, callback: _userNotificationCallback);
_workSettingNotifier.addListener(() {
onWorkspaceSettingUpdated(_workSettingNotifier.currentValue!);
});
}
_workspaceParser = FolderNotificationParser(id: _user.token, callback: _notificationCallback);
_userParser = UserNotificationParser(id: _user.token, callback: _userNotificationCallback);
_subscription = RustStreamReceiver.listen((observable) { _subscription = RustStreamReceiver.listen((observable) {
_workspaceParser?.parse(observable);
_userParser?.parse(observable); _userParser?.parse(observable);
}); });
} }
Future<void> stop() async { Future<void> stop() async {
_workspaceParser = null;
_userParser = null; _userParser = null;
await _subscription?.cancel(); await _subscription?.cancel();
_profileNotifier.dispose(); _profileNotifier?.dispose();
_authNotifier.dispose(); _profileNotifier = null;
_workspaceListNotifier.dispose();
}
void _notificationCallback(FolderNotification ty, Either<Uint8List, FlowyError> result) { _authNotifier?.dispose();
switch (ty) { _authNotifier = null;
case FolderNotification.UserCreateWorkspace:
case FolderNotification.UserDeleteWorkspace:
case FolderNotification.WorkspaceListUpdated:
result.fold(
(payload) => _workspaceListNotifier.value = left(RepeatedWorkspace.fromBuffer(payload).items),
(error) => _workspaceListNotifier.value = right(error),
);
break;
case FolderNotification.WorkspaceSetting:
result.fold(
(payload) => _workSettingNotifier.value = left(CurrentWorkspaceSetting.fromBuffer(payload)),
(error) => _workSettingNotifier.value = right(error),
);
break;
case FolderNotification.UserUnauthorized:
result.fold(
(_) {},
(error) => _authNotifier.value = right(FlowyError.create()..code = ErrorCode.UserUnauthorized.value),
);
break;
default:
break;
}
} }
void _userNotificationCallback(user.UserNotification ty, Either<Uint8List, FlowyError> result) { void _userNotificationCallback(user.UserNotification ty, Either<Uint8List, FlowyError> result) {
switch (ty) { switch (ty) {
case user.UserNotification.UserUnauthorized: case user.UserNotification.UserUnauthorized:
result.fold( result.fold(
(payload) => _profileNotifier.value = left(UserProfile.fromBuffer(payload)), (_) {},
(error) => _profileNotifier.value = right(error), (error) => _authNotifier?.value = right(error),
);
break;
case user.UserNotification.UserProfileUpdated:
result.fold(
(payload) => _profileNotifier?.value = left(UserProfilePB.fromBuffer(payload)),
(error) => _profileNotifier?.value = right(error),
); );
break; break;
default: default:
@ -119,3 +74,81 @@ class UserListener {
} }
} }
} }
typedef WorkspaceListNotifyValue = Either<List<WorkspacePB>, FlowyError>;
typedef WorkspaceSettingNotifyValue = Either<CurrentWorkspaceSettingPB, FlowyError>;
class UserWorkspaceListener {
PublishNotifier<AuthNotifyValue>? _authNotifier = PublishNotifier();
PublishNotifier<WorkspaceListNotifyValue>? _workspacesChangedNotifier = PublishNotifier();
PublishNotifier<WorkspaceSettingNotifyValue>? _settingChangedNotifier = PublishNotifier();
FolderNotificationListener? _listener;
final UserProfilePB _userProfile;
UserWorkspaceListener({
required UserProfilePB userProfile,
}) : _userProfile = userProfile;
void start({
void Function(AuthNotifyValue)? onAuthChanged,
void Function(WorkspaceListNotifyValue)? onWorkspacesUpdated,
void Function(WorkspaceSettingNotifyValue)? onSettingUpdated,
}) {
if (onAuthChanged != null) {
_authNotifier?.addPublishListener(onAuthChanged);
}
if (onWorkspacesUpdated != null) {
_workspacesChangedNotifier?.addPublishListener(onWorkspacesUpdated);
}
if (onSettingUpdated != null) {
_settingChangedNotifier?.addPublishListener(onSettingUpdated);
}
_listener = FolderNotificationListener(
objectId: _userProfile.token,
handler: _handleObservableType,
);
}
void _handleObservableType(FolderNotification ty, Either<Uint8List, FlowyError> result) {
switch (ty) {
case FolderNotification.UserCreateWorkspace:
case FolderNotification.UserDeleteWorkspace:
case FolderNotification.WorkspaceListUpdated:
result.fold(
(payload) => _workspacesChangedNotifier?.value = left(RepeatedWorkspacePB.fromBuffer(payload).items),
(error) => _workspacesChangedNotifier?.value = right(error),
);
break;
case FolderNotification.WorkspaceSetting:
result.fold(
(payload) => _settingChangedNotifier?.value = left(CurrentWorkspaceSettingPB.fromBuffer(payload)),
(error) => _settingChangedNotifier?.value = right(error),
);
break;
case FolderNotification.UserUnauthorized:
result.fold(
(_) {},
(error) => _authNotifier?.value = right(FlowyError.create()..code = ErrorCode.UserUnauthorized.value),
);
break;
default:
break;
}
}
Future<void> stop() async {
await _listener?.stop();
_workspacesChangedNotifier?.dispose();
_workspacesChangedNotifier = null;
_settingChangedNotifier?.dispose();
_settingChangedNotifier = null;
_authNotifier?.dispose();
_authNotifier = null;
}
}

View File

@ -1,15 +1,42 @@
import 'dart:async'; import 'dart:async';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/workspace.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/workspace.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart';
class UserService { class UserService {
Future<Either<UserProfile, FlowyError>> fetchUserProfile({required String userId}) { final String userId;
UserService({
required this.userId,
});
Future<Either<UserProfilePB, FlowyError>> getUserProfile({required String userId}) {
return UserEventGetUserProfile().send(); return UserEventGetUserProfile().send();
} }
Future<Either<Unit, FlowyError>> updateUserProfile({
String? name,
String? password,
String? email,
}) {
var payload = UpdateUserProfilePayloadPB.create()..id = userId;
if (name != null) {
payload.name = name;
}
if (password != null) {
payload.password = password;
}
if (email != null) {
payload.email = email;
}
return UserEventUpdateUserProfile(payload).send();
}
Future<Either<Unit, FlowyError>> deleteWorkspace({required String workspaceId}) { Future<Either<Unit, FlowyError>> deleteWorkspace({required String workspaceId}) {
throw UnimplementedError(); throw UnimplementedError();
} }
@ -22,8 +49,8 @@ class UserService {
return UserEventInitUser().send(); return UserEventInitUser().send();
} }
Future<Either<List<Workspace>, FlowyError>> getWorkspaces() { Future<Either<List<WorkspacePB>, FlowyError>> getWorkspaces() {
final request = WorkspaceId.create(); final request = WorkspaceIdPB.create();
return FolderEventReadWorkspaces(request).send().then((result) { return FolderEventReadWorkspaces(request).send().then((result) {
return result.fold( return result.fold(
@ -33,8 +60,8 @@ class UserService {
}); });
} }
Future<Either<Workspace, FlowyError>> openWorkspace(String workspaceId) { Future<Either<WorkspacePB, FlowyError>> openWorkspace(String workspaceId) {
final request = WorkspaceId.create()..value = workspaceId; final request = WorkspaceIdPB.create()..value = workspaceId;
return FolderEventOpenWorkspace(request).send().then((result) { return FolderEventOpenWorkspace(request).send().then((result) {
return result.fold( return result.fold(
(workspace) => left(workspace), (workspace) => left(workspace),
@ -43,8 +70,8 @@ class UserService {
}); });
} }
Future<Either<Workspace, FlowyError>> createWorkspace(String name, String desc) { Future<Either<WorkspacePB, FlowyError>> createWorkspace(String name, String desc) {
final request = CreateWorkspacePayload.create() final request = CreateWorkspacePayloadPB.create()
..name = name ..name = name
..desc = desc; ..desc = desc;
return FolderEventCreateWorkspace(request).send().then((result) { return FolderEventCreateWorkspace(request).send().then((result) {

View File

@ -2,14 +2,14 @@ import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/flowy_sdk.dart'; import 'package:flowy_sdk/flowy_sdk.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_setting.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-user/user_setting.pb.dart';
class UserSettingsService { class UserSettingsService {
Future<AppearanceSettings> getAppearanceSettings() async { Future<AppearanceSettingsPB> getAppearanceSettings() async {
final result = await UserEventGetAppearanceSetting().send(); final result = await UserEventGetAppearanceSetting().send();
return result.fold( return result.fold(
(AppearanceSettings setting) { (AppearanceSettingsPB setting) {
return setting; return setting;
}, },
(error) { (error) {
@ -18,7 +18,7 @@ class UserSettingsService {
); );
} }
Future<Either<Unit, FlowyError>> setAppearanceSettings(AppearanceSettings settings) { Future<Either<Unit, FlowyError>> setAppearanceSettings(AppearanceSettingsPB settings) {
return UserEventSetAppearanceSetting(settings).send(); return UserEventSetAppearanceSetting(settings).send();
} }
} }

View File

@ -1,11 +1,11 @@
import 'package:flowy_sdk/protobuf/flowy-user-data-model/protobuf.dart' show UserProfile; import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfilePB;
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
part 'auth_state.freezed.dart'; part 'auth_state.freezed.dart';
@freezed @freezed
class AuthState with _$AuthState { class AuthState with _$AuthState {
const factory AuthState.authenticated(UserProfile userProfile) = Authenticated; const factory AuthState.authenticated(UserProfilePB userProfile) = Authenticated;
const factory AuthState.unauthenticated(FlowyError error) = Unauthenticated; const factory AuthState.unauthenticated(FlowyError error) = Unauthenticated;
const factory AuthState.initial() = _Initial; const factory AuthState.initial() = _Initial;
} }

View File

@ -7,8 +7,8 @@ import 'package:app_flowy/user/presentation/welcome_screen.dart';
import 'package:app_flowy/workspace/presentation/home/home_screen.dart'; import 'package:app_flowy/workspace/presentation/home/home_screen.dart';
import 'package:flowy_infra/time/duration.dart'; import 'package:flowy_infra/time/duration.dart';
import 'package:flowy_infra_ui/widget/route/animation.dart'; import 'package:flowy_infra_ui/widget/route/animation.dart';
import 'package:flowy_sdk/protobuf/flowy-user-data-model/protobuf.dart' show UserProfile; import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfilePB;
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/protobuf.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/protobuf.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class AuthRouter { class AuthRouter {
@ -16,7 +16,7 @@ class AuthRouter {
// TODO: implement showForgetPasswordScreen // TODO: implement showForgetPasswordScreen
} }
void pushWelcomeScreen(BuildContext context, UserProfile userProfile) { void pushWelcomeScreen(BuildContext context, UserProfilePB userProfile) {
getIt<SplashRoute>().pushWelcomeScreen(context, userProfile); getIt<SplashRoute>().pushWelcomeScreen(context, userProfile);
} }
@ -28,7 +28,7 @@ class AuthRouter {
); );
} }
void pushHomeScreen(BuildContext context, UserProfile profile, CurrentWorkspaceSetting workspaceSetting) { void pushHomeScreen(BuildContext context, UserProfilePB profile, CurrentWorkspaceSettingPB workspaceSetting) {
Navigator.push( Navigator.push(
context, context,
PageRoutes.fade(() => HomeScreen(profile, workspaceSetting), RouteDurations.slow.inMilliseconds * .001), PageRoutes.fade(() => HomeScreen(profile, workspaceSetting), RouteDurations.slow.inMilliseconds * .001),
@ -37,7 +37,7 @@ class AuthRouter {
} }
class SplashRoute { class SplashRoute {
Future<void> pushWelcomeScreen(BuildContext context, UserProfile userProfile) async { Future<void> pushWelcomeScreen(BuildContext context, UserProfilePB userProfile) async {
final screen = WelcomeScreen(userProfile: userProfile); final screen = WelcomeScreen(userProfile: userProfile);
final workspaceId = await Navigator.of(context).push( final workspaceId = await Navigator.of(context).push(
PageRoutes.fade( PageRoutes.fade(
@ -49,7 +49,7 @@ class SplashRoute {
pushHomeScreen(context, userProfile, workspaceId); pushHomeScreen(context, userProfile, workspaceId);
} }
void pushHomeScreen(BuildContext context, UserProfile userProfile, CurrentWorkspaceSetting workspaceSetting) { void pushHomeScreen(BuildContext context, UserProfilePB userProfile, CurrentWorkspaceSettingPB workspaceSetting) {
Navigator.push( Navigator.push(
context, context,
PageRoutes.fade(() => HomeScreen(userProfile, workspaceSetting), RouteDurations.slow.inMilliseconds * .001), PageRoutes.fade(() => HomeScreen(userProfile, workspaceSetting), RouteDurations.slow.inMilliseconds * .001),

View File

@ -10,7 +10,7 @@ import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_infra_ui/style_widget/snap_bar.dart'; import 'package:flowy_infra_ui/style_widget/snap_bar.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-user-data-model/protobuf.dart' show UserProfile; import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfilePB;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
@ -39,7 +39,7 @@ class SignInScreen extends StatelessWidget {
); );
} }
void _handleSuccessOrFail(Either<UserProfile, FlowyError> result, BuildContext context) { void _handleSuccessOrFail(Either<UserProfilePB, FlowyError> result, BuildContext context) {
result.fold( result.fold(
(user) => router.pushWelcomeScreen(context, user), (user) => router.pushWelcomeScreen(context, user),
(error) => showSnapBar(context, error.msg), (error) => showSnapBar(context, error.msg),

View File

@ -8,7 +8,7 @@ import 'package:flowy_infra_ui/widget/rounded_button.dart';
import 'package:flowy_infra_ui/widget/rounded_input_field.dart'; import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-user-data-model/protobuf.dart' show UserProfile; import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfilePB;
import 'package:flowy_infra_ui/style_widget/snap_bar.dart'; import 'package:flowy_infra_ui/style_widget/snap_bar.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -36,7 +36,7 @@ class SignUpScreen extends StatelessWidget {
); );
} }
void _handleSuccessOrFail(BuildContext context, Either<UserProfile, FlowyError> result) { void _handleSuccessOrFail(BuildContext context, Either<UserProfilePB, FlowyError> result) {
result.fold( result.fold(
(user) => router.pushWelcomeScreen(context, user), (user) => router.pushWelcomeScreen(context, user),
(error) => showSnapBar(context, error.msg), (error) => showSnapBar(context, error.msg),

View File

@ -1,5 +1,4 @@
import 'package:app_flowy/user/application/auth_service.dart'; import 'package:app_flowy/user/application/auth_service.dart';
import 'package:app_flowy/user/application/user_listener.dart';
import 'package:app_flowy/user/presentation/router.dart'; import 'package:app_flowy/user/presentation/router.dart';
import 'package:app_flowy/user/presentation/widgets/background.dart'; import 'package:app_flowy/user/presentation/widgets/background.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
@ -10,9 +9,9 @@ import 'package:flowy_infra_ui/widget/rounded_button.dart';
import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/protobuf.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/protobuf.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@ -34,8 +33,6 @@ class SkipLogInScreen extends StatefulWidget {
} }
class _SkipLogInScreenState extends State<SkipLogInScreen> { class _SkipLogInScreenState extends State<SkipLogInScreen> {
UserListener? userListener;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -119,8 +116,8 @@ class _SkipLogInScreenState extends State<SkipLogInScreen> {
void _openCurrentWorkspace( void _openCurrentWorkspace(
BuildContext context, BuildContext context,
UserProfile user, UserProfilePB user,
dartz.Either<CurrentWorkspaceSetting, FlowyError> workspacesOrError, dartz.Either<CurrentWorkspaceSettingPB, FlowyError> workspacesOrError,
) { ) {
workspacesOrError.fold( workspacesOrError.fold(
(workspaceSetting) { (workspaceSetting) {

View File

@ -4,7 +4,7 @@ import 'package:app_flowy/user/domain/auth_state.dart';
import 'package:app_flowy/user/presentation/router.dart'; import 'package:app_flowy/user/presentation/router.dart';
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/protobuf/error-code/error_code.pbenum.dart'; import 'package:flowy_sdk/protobuf/flowy-error-code/code.pb.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';

View File

@ -5,14 +5,14 @@ import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/widget/error_page.dart'; import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/workspace.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/workspace.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:app_flowy/generated/locale_keys.g.dart';
class WelcomeScreen extends StatelessWidget { class WelcomeScreen extends StatelessWidget {
final UserProfile userProfile; final UserProfilePB userProfile;
const WelcomeScreen({ const WelcomeScreen({
Key? key, Key? key,
required this.userProfile, required this.userProfile,
@ -65,7 +65,7 @@ class WelcomeScreen extends StatelessWidget {
); );
} }
Widget _renderList(List<Workspace> workspaces) { Widget _renderList(List<WorkspacePB> workspaces) {
return Expanded( return Expanded(
child: StyledListView( child: StyledListView(
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
@ -80,7 +80,7 @@ class WelcomeScreen extends StatelessWidget {
); );
} }
void _handleOnPress(BuildContext context, Workspace workspace) { void _handleOnPress(BuildContext context, WorkspacePB workspace) {
context.read<WelcomeBloc>().add(WelcomeEvent.openWorkspace(workspace)); context.read<WelcomeBloc>().add(WelcomeEvent.openWorkspace(workspace));
Navigator.of(context).pop(workspace.id); Navigator.of(context).pop(workspace.id);
@ -88,8 +88,8 @@ class WelcomeScreen extends StatelessWidget {
} }
class WorkspaceItem extends StatelessWidget { class WorkspaceItem extends StatelessWidget {
final Workspace workspace; final WorkspacePB workspace;
final void Function(Workspace workspace) onPressed; final void Function(WorkspacePB workspace) onPressed;
const WorkspaceItem({Key? key, required this.workspace, required this.onPressed}) : super(key: key); const WorkspaceItem({Key? key, required this.workspace, required this.onPressed}) : super(key: key);
@override @override

View File

@ -7,8 +7,8 @@ import 'package:app_flowy/workspace/application/app/app_service.dart';
import 'package:app_flowy/workspace/presentation/home/menu/menu.dart'; import 'package:app_flowy/workspace/presentation/home/menu/menu.dart';
import 'package:expandable/expandable.dart'; import 'package:expandable/expandable.dart';
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/app.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.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-error/errors.pb.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
@ -18,7 +18,7 @@ import 'package:dartz/dartz.dart';
part 'app_bloc.freezed.dart'; part 'app_bloc.freezed.dart';
class AppBloc extends Bloc<AppEvent, AppState> { class AppBloc extends Bloc<AppEvent, AppState> {
final App app; final AppPB app;
final AppService appService; final AppService appService;
final AppListener appListener; final AppListener appListener;
@ -103,7 +103,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
return super.close(); return super.close();
} }
Future<void> _didReceiveViewUpdated(List<View> views, Emitter<AppState> emit) async { Future<void> _didReceiveViewUpdated(List<ViewPB> views, Emitter<AppState> emit) async {
final latestCreatedView = state.latestCreatedView; final latestCreatedView = state.latestCreatedView;
AppState newState = state.copyWith(views: views); AppState newState = state.copyWith(views: views);
if (latestCreatedView != null) { if (latestCreatedView != null) {
@ -139,20 +139,20 @@ class AppEvent with _$AppEvent {
) = CreateView; ) = CreateView;
const factory AppEvent.delete() = Delete; const factory AppEvent.delete() = Delete;
const factory AppEvent.rename(String newName) = Rename; const factory AppEvent.rename(String newName) = Rename;
const factory AppEvent.didReceiveViewUpdated(List<View> views) = ReceiveViews; const factory AppEvent.didReceiveViewUpdated(List<ViewPB> views) = ReceiveViews;
const factory AppEvent.appDidUpdate(App app) = AppDidUpdate; const factory AppEvent.appDidUpdate(AppPB app) = AppDidUpdate;
} }
@freezed @freezed
class AppState with _$AppState { class AppState with _$AppState {
const factory AppState({ const factory AppState({
required App app, required AppPB app,
required List<View> views, required List<ViewPB> views,
View? latestCreatedView, ViewPB? latestCreatedView,
required Either<Unit, FlowyError> successOrFailure, required Either<Unit, FlowyError> successOrFailure,
}) = _AppState; }) = _AppState;
factory AppState.initial(App app) => AppState( factory AppState.initial(AppPB app) => AppState(
app: app, app: app,
views: [], views: [],
successOrFailure: left(unit), successOrFailure: left(unit),
@ -161,8 +161,8 @@ class AppState with _$AppState {
class AppViewDataContext extends ChangeNotifier { class AppViewDataContext extends ChangeNotifier {
final String appId; final String appId;
final ValueNotifier<List<View>> _viewsNotifier = ValueNotifier([]); final ValueNotifier<List<ViewPB>> _viewsNotifier = ValueNotifier([]);
final ValueNotifier<View?> _selectedViewNotifier = ValueNotifier(null); final ValueNotifier<ViewPB?> _selectedViewNotifier = ValueNotifier(null);
VoidCallback? _menuSharedStateListener; VoidCallback? _menuSharedStateListener;
ExpandableController expandController = ExpandableController(initialExpanded: false); ExpandableController expandController = ExpandableController(initialExpanded: false);
@ -173,7 +173,7 @@ class AppViewDataContext extends ChangeNotifier {
}); });
} }
VoidCallback addSelectedViewChangeListener(void Function(View?) callback) { VoidCallback addSelectedViewChangeListener(void Function(ViewPB?) callback) {
listener() { listener() {
callback(_selectedViewNotifier.value); callback(_selectedViewNotifier.value);
} }
@ -186,7 +186,7 @@ class AppViewDataContext extends ChangeNotifier {
_selectedViewNotifier.removeListener(listener); _selectedViewNotifier.removeListener(listener);
} }
void _setLatestView(View? view) { void _setLatestView(ViewPB? view) {
view?.freeze(); view?.freeze();
if (_selectedViewNotifier.value != view) { if (_selectedViewNotifier.value != view) {
@ -196,9 +196,9 @@ class AppViewDataContext extends ChangeNotifier {
} }
} }
View? get selectedView => _selectedViewNotifier.value; ViewPB? get selectedView => _selectedViewNotifier.value;
set views(List<View> views) { set views(List<ViewPB> views) {
if (_viewsNotifier.value != views) { if (_viewsNotifier.value != views) {
_viewsNotifier.value = views; _viewsNotifier.value = views;
_expandIfNeed(); _expandIfNeed();
@ -206,9 +206,9 @@ class AppViewDataContext extends ChangeNotifier {
} }
} }
UnmodifiableListView<View> get views => UnmodifiableListView(_viewsNotifier.value); UnmodifiableListView<ViewPB> get views => UnmodifiableListView(_viewsNotifier.value);
VoidCallback addViewsChangeListener(void Function(UnmodifiableListView<View>) callback) { VoidCallback addViewsChangeListener(void Function(UnmodifiableListView<ViewPB>) callback) {
listener() { listener() {
callback(views); callback(views);
} }

View File

@ -1,17 +1,17 @@
import 'dart:async'; import 'dart:async';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:app_flowy/core/folder_notification.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:app_flowy/core/notification_helper.dart';
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/dart-notify/subject.pb.dart'; import 'package:flowy_sdk/protobuf/dart-notify/subject.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/app.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder/dart_notification.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/dart_notification.pb.dart';
import 'package:flowy_sdk/rust_stream.dart'; import 'package:flowy_sdk/rust_stream.dart';
typedef AppDidUpdateCallback = void Function(App app); typedef AppDidUpdateCallback = void Function(AppPB app);
typedef ViewsDidChangeCallback = void Function(Either<List<View>, FlowyError> viewsOrFailed); typedef ViewsDidChangeCallback = void Function(Either<List<ViewPB>, FlowyError> viewsOrFailed);
class AppListener { class AppListener {
StreamSubscription<SubscribeObject>? _subscription; StreamSubscription<SubscribeObject>? _subscription;
@ -37,7 +37,7 @@ class AppListener {
if (_viewsChanged != null) { if (_viewsChanged != null) {
result.fold( result.fold(
(payload) { (payload) {
final repeatedView = RepeatedView.fromBuffer(payload); final repeatedView = RepeatedViewPB.fromBuffer(payload);
_viewsChanged!(left(repeatedView.items)); _viewsChanged!(left(repeatedView.items));
}, },
(error) => _viewsChanged!(right(error)), (error) => _viewsChanged!(right(error)),
@ -48,7 +48,7 @@ class AppListener {
if (_updated != null) { if (_updated != null) {
result.fold( result.fold(
(payload) { (payload) {
final app = App.fromBuffer(payload); final app = AppPB.fromBuffer(payload);
_updated!(app); _updated!(app);
}, },
(error) => Log.error(error), (error) => Log.error(error),

View File

@ -3,8 +3,8 @@ import 'dart:async';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/app.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
import 'package:app_flowy/plugin/plugin.dart'; import 'package:app_flowy/plugin/plugin.dart';
@ -14,20 +14,20 @@ class AppService {
required this.appId, required this.appId,
}); });
Future<Either<App, FlowyError>> getAppDesc({required String appId}) { Future<Either<AppPB, FlowyError>> getAppDesc({required String appId}) {
final payload = AppId.create()..value = appId; final payload = AppIdPB.create()..value = appId;
return FolderEventReadApp(payload).send(); return FolderEventReadApp(payload).send();
} }
Future<Either<View, FlowyError>> createView({ Future<Either<ViewPB, FlowyError>> createView({
required String appId, required String appId,
required String name, required String name,
required String desc, required String desc,
required PluginDataType dataType, required PluginDataType dataType,
required PluginType pluginType, required PluginType pluginType,
}) { }) {
final payload = CreateViewPayload.create() final payload = CreateViewPayloadPB.create()
..belongToId = appId ..belongToId = appId
..name = name ..name = name
..desc = desc ..desc = desc
@ -37,8 +37,8 @@ class AppService {
return FolderEventCreateView(payload).send(); return FolderEventCreateView(payload).send();
} }
Future<Either<List<View>, FlowyError>> getViews({required String appId}) { Future<Either<List<ViewPB>, FlowyError>> getViews({required String appId}) {
final payload = AppId.create()..value = appId; final payload = AppIdPB.create()..value = appId;
return FolderEventReadApp(payload).send().then((result) { return FolderEventReadApp(payload).send().then((result) {
return result.fold( return result.fold(
@ -49,12 +49,12 @@ class AppService {
} }
Future<Either<Unit, FlowyError>> delete({required String appId}) { Future<Either<Unit, FlowyError>> delete({required String appId}) {
final request = AppId.create()..value = appId; final request = AppIdPB.create()..value = appId;
return FolderEventDeleteApp(request).send(); return FolderEventDeleteApp(request).send();
} }
Future<Either<Unit, FlowyError>> updateApp({required String appId, String? name}) { Future<Either<Unit, FlowyError>> updateApp({required String appId, String? name}) {
UpdateAppPayload payload = UpdateAppPayload.create()..appId = appId; UpdateAppPayloadPB payload = UpdateAppPayloadPB.create()..appId = appId;
if (name != null) { if (name != null) {
payload.name = name; payload.name = name;
@ -67,7 +67,7 @@ class AppService {
required int fromIndex, required int fromIndex,
required int toIndex, required int toIndex,
}) { }) {
final payload = MoveFolderItemPayload.create() final payload = MoveFolderItemPayloadPB.create()
..itemId = viewId ..itemId = viewId
..from = fromIndex ..from = fromIndex
..to = toIndex ..to = toIndex

View File

@ -4,12 +4,12 @@ import 'package:app_flowy/user/application/user_settings_service.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra/theme.dart';
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_setting.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-user/user_setting.pb.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
class AppearanceSettingModel extends ChangeNotifier with EquatableMixin { class AppearanceSettingModel extends ChangeNotifier with EquatableMixin {
AppearanceSettings setting; AppearanceSettingsPB setting;
AppTheme _theme; AppTheme _theme;
Locale _locale; Locale _locale;
Timer? _saveOperation; Timer? _saveOperation;

View File

@ -2,9 +2,9 @@ import 'dart:convert';
import 'package:app_flowy/workspace/application/doc/doc_service.dart'; import 'package:app_flowy/workspace/application/doc/doc_service.dart';
import 'package:app_flowy/workspace/application/trash/trash_service.dart'; import 'package:app_flowy/workspace/application/trash/trash_service.dart';
import 'package:app_flowy/workspace/application/view/view_listener.dart'; import 'package:app_flowy/workspace/application/view/view_listener.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/trash.pb.dart'; 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-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
import 'package:flutter_quill/flutter_quill.dart' show Document, Delta; import 'package:flutter_quill/flutter_quill.dart' show Document, Delta;
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -17,7 +17,7 @@ part 'doc_bloc.freezed.dart';
typedef FlutterQuillDocument = Document; typedef FlutterQuillDocument = Document;
class DocumentBloc extends Bloc<DocumentEvent, DocumentState> { class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
final View view; final ViewPB view;
final DocumentService service; final DocumentService service;
final ViewListener listener; final ViewListener listener;

View File

@ -1,29 +1,29 @@
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.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-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-sync/text_block_info.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-sync/text_block.pb.dart';
class DocumentService { class DocumentService {
Future<Either<TextBlockDelta, FlowyError>> openDocument({ Future<Either<TextBlockDeltaPB, FlowyError>> openDocument({
required String docId, required String docId,
}) async { }) async {
await FolderEventSetLatestView(ViewId(value: docId)).send(); await FolderEventSetLatestView(ViewIdPB(value: docId)).send();
final payload = TextBlockId(value: docId); final payload = TextBlockIdPB(value: docId);
return TextBlockEventGetBlockData(payload).send(); return TextBlockEventGetBlockData(payload).send();
} }
Future<Either<TextBlockDelta, FlowyError>> composeDelta({required String docId, required String data}) { Future<Either<TextBlockDeltaPB, FlowyError>> composeDelta({required String docId, required String data}) {
final payload = TextBlockDelta.create() final payload = TextBlockDeltaPB.create()
..blockId = docId ..blockId = docId
..deltaStr = data; ..deltaStr = data;
return TextBlockEventApplyDelta(payload).send(); return TextBlockEventApplyDelta(payload).send();
} }
Future<Either<Unit, FlowyError>> closeDocument({required String docId}) { Future<Either<Unit, FlowyError>> closeDocument({required String docId}) {
final request = ViewId(value: docId); final request = ViewIdPB(value: docId);
return FolderEventCloseView(request).send(); return FolderEventCloseView(request).send();
} }
} }

View File

@ -1,7 +1,10 @@
import 'dart:async';
import 'dart:io';
import 'package:app_flowy/startup/tasks/rust_sdk.dart';
import 'package:app_flowy/workspace/application/doc/share_service.dart'; import 'package:app_flowy/workspace/application/doc/share_service.dart';
import 'package:app_flowy/workspace/application/markdown/delta_markdown.dart'; import 'package:app_flowy/workspace/application/markdown/delta_markdown.dart';
import 'package:flowy_sdk/protobuf/flowy-text-block/entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-text-block/entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.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-error/errors.pb.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -10,7 +13,7 @@ part 'share_bloc.freezed.dart';
class DocShareBloc extends Bloc<DocShareEvent, DocShareState> { class DocShareBloc extends Bloc<DocShareEvent, DocShareState> {
ShareService service; ShareService service;
View view; ViewPB view;
DocShareBloc({required this.view, required this.service}) : super(const DocShareState.initial()) { DocShareBloc({required this.view, required this.service}) : super(const DocShareState.initial()) {
on<DocShareEvent>((event, emit) async { on<DocShareEvent>((event, emit) async {
await event.map( await event.map(
@ -30,11 +33,33 @@ class DocShareBloc extends Bloc<DocShareEvent, DocShareState> {
}); });
} }
ExportData _convertDeltaToMarkdown(ExportData value) { ExportDataPB _convertDeltaToMarkdown(ExportDataPB value) {
final result = deltaToMarkdown(value.data); final result = deltaToMarkdown(value.data);
value.data = result; value.data = result;
writeFile(result);
return value; return value;
} }
Future<Directory> get _exportDir async {
Directory documentsDir = await appFlowyDocumentDirectory();
return documentsDir;
}
Future<String> get _localPath async {
final dir = await _exportDir;
return dir.path;
}
Future<File> get _localFile async {
final path = await _localPath;
return File('$path/${view.name}.md');
}
Future<File> writeFile(String md) async {
final file = await _localFile;
return file.writeAsString(md);
}
} }
@freezed @freezed
@ -48,5 +73,5 @@ class DocShareEvent with _$DocShareEvent {
class DocShareState with _$DocShareState { class DocShareState with _$DocShareState {
const factory DocShareState.initial() = _Initial; const factory DocShareState.initial() = _Initial;
const factory DocShareState.loading() = _Loading; const factory DocShareState.loading() = _Loading;
const factory DocShareState.finish(Either<ExportData, FlowyError> successOrFail) = _Finish; const factory DocShareState.finish(Either<ExportDataPB, FlowyError> successOrFail) = _Finish;
} }

View File

@ -5,23 +5,23 @@ import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-text-block/protobuf.dart'; import 'package:flowy_sdk/protobuf/flowy-text-block/protobuf.dart';
class ShareService { class ShareService {
Future<Either<ExportData, FlowyError>> export(String docId, ExportType type) { Future<Either<ExportDataPB, FlowyError>> export(String docId, ExportType type) {
final request = ExportPayload.create() final request = ExportPayloadPB.create()
..viewId = docId ..viewId = docId
..exportType = type; ..exportType = type;
return TextBlockEventExportDocument(request).send(); return TextBlockEventExportDocument(request).send();
} }
Future<Either<ExportData, FlowyError>> exportText(String docId) { Future<Either<ExportDataPB, FlowyError>> exportText(String docId) {
return export(docId, ExportType.Text); return export(docId, ExportType.Text);
} }
Future<Either<ExportData, FlowyError>> exportMarkdown(String docId) { Future<Either<ExportDataPB, FlowyError>> exportMarkdown(String docId) {
return export(docId, ExportType.Markdown); return export(docId, ExportType.Markdown);
} }
Future<Either<ExportData, FlowyError>> exportURL(String docId) { Future<Either<ExportDataPB, FlowyError>> exportURL(String docId) {
return export(docId, ExportType.Link); return export(docId, ExportType.Link);
} }
} }

View File

@ -0,0 +1,56 @@
import 'dart:async';
import 'package:app_flowy/workspace/application/grid/grid_service.dart';
import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
import 'block_listener.dart';
/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid for more information
class GridBlockCache {
final String gridId;
final GridBlockPB block;
late GridRowCache _rowCache;
late GridBlockListener _listener;
List<GridRowInfo> get rows => _rowCache.rows;
GridRowCache get rowCache => _rowCache;
GridBlockCache({
required this.gridId,
required this.block,
required GridFieldCache fieldCache,
}) {
_rowCache = GridRowCache(
gridId: gridId,
block: block,
notifier: GridRowCacheFieldNotifierImpl(fieldCache),
);
_listener = GridBlockListener(blockId: block.id);
_listener.start((result) {
result.fold(
(changesets) => _rowCache.applyChangesets(changesets),
(err) => Log.error(err),
);
});
}
Future<void> dispose() async {
await _listener.stop();
await _rowCache.dispose();
}
void addListener({
required void Function(GridRowChangeReason) onChangeReason,
bool Function()? listenWhen,
}) {
_rowCache.onRowsChanged((reason) {
if (listenWhen != null && listenWhen() == false) {
return;
}
onChangeReason(reason);
});
}
}

View File

@ -0,0 +1,51 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:app_flowy/core/grid_notification.dart';
import 'package:dartz/dartz.dart';
import 'package:flowy_infra/notifier.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
typedef GridBlockUpdateNotifierValue = Either<List<GridBlockChangesetPB>, FlowyError>;
class GridBlockListener {
final String blockId;
PublishNotifier<GridBlockUpdateNotifierValue>? _rowsUpdateNotifier = PublishNotifier();
GridNotificationListener? _listener;
GridBlockListener({required this.blockId});
void start(void Function(GridBlockUpdateNotifierValue) onBlockChanged) {
if (_listener != null) {
_listener?.stop();
}
_listener = GridNotificationListener(
objectId: blockId,
handler: _handler,
);
_rowsUpdateNotifier?.addPublishListener(onBlockChanged);
}
void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) {
switch (ty) {
case GridNotification.DidUpdateGridBlock:
result.fold(
(payload) => _rowsUpdateNotifier?.value = left([GridBlockChangesetPB.fromBuffer(payload)]),
(error) => _rowsUpdateNotifier?.value = right(error),
);
break;
default:
break;
}
}
Future<void> stop() async {
await _listener?.stop();
_rowsUpdateNotifier?.dispose();
_rowsUpdateNotifier = null;
}
}

View File

@ -1,10 +1,10 @@
import 'package:app_flowy/core/grid_notification.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.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/dart_notification.pb.dart';
import 'package:flowy_infra/notifier.dart'; import 'package:flowy_infra/notifier.dart';
import 'dart:async'; import 'dart:async';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:app_flowy/core/notification_helper.dart';
typedef UpdateFieldNotifiedValue = Either<Unit, FlowyError>; typedef UpdateFieldNotifiedValue = Either<Unit, FlowyError>;

View File

@ -0,0 +1,70 @@
part of 'cell_service.dart';
typedef GridCellMap = LinkedHashMap<String, GridCellIdentifier>;
class GridCell {
dynamic object;
GridCell({
required this.object,
});
}
/// Use to index the cell in the grid.
/// We use [fieldId + rowId] to identify the cell.
class GridCellCacheKey {
final String fieldId;
final String rowId;
GridCellCacheKey({
required this.fieldId,
required this.rowId,
});
}
/// GridCellCache is used to cache cell data of each block.
/// We use GridCellCacheKey to index the cell in the cache.
/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid
/// for more information
class GridCellCache {
final String gridId;
/// fieldId: {cacheKey: GridCell}
final Map<String, Map<String, dynamic>> _cellDataByFieldId = {};
GridCellCache({
required this.gridId,
});
void remove(String fieldId) {
_cellDataByFieldId.remove(fieldId);
}
void insert<T extends GridCell>(GridCellCacheKey key, T value) {
var map = _cellDataByFieldId[key.fieldId];
if (map == null) {
_cellDataByFieldId[key.fieldId] = {};
map = _cellDataByFieldId[key.fieldId];
}
map![key.rowId] = value.object;
}
T? get<T>(GridCellCacheKey key) {
final map = _cellDataByFieldId[key.fieldId];
if (map == null) {
return null;
} else {
final value = map[key.rowId];
if (value is T) {
return value;
} else {
if (value != null) {
Log.error("Expected value type: $T, but receive $value");
}
return null;
}
}
}
Future<void> dispose() async {
_cellDataByFieldId.clear();
}
}

View File

@ -0,0 +1,79 @@
part of 'cell_service.dart';
abstract class IGridCellDataConfig {
// The cell data will reload if it receives the field's change notification.
bool get reloadOnFieldChanged;
}
abstract class IGridCellDataParser<T> {
T? parserData(List<int> data);
}
class GridCellDataLoader<T> {
final CellService service = CellService();
final GridCellIdentifier cellId;
final IGridCellDataParser<T> parser;
final bool reloadOnFieldChanged;
GridCellDataLoader({
required this.cellId,
required this.parser,
this.reloadOnFieldChanged = false,
});
Future<T?> loadData() {
final fut = service.getCell(cellId: cellId);
return fut.then(
(result) => result.fold((GridCellPB cell) {
try {
return parser.parserData(cell.data);
} catch (e, s) {
Log.error('$parser parser cellData failed, $e');
Log.error('Stack trace \n $s');
return null;
}
}, (err) {
Log.error(err);
return null;
}),
);
}
}
class StringCellDataParser implements IGridCellDataParser<String> {
@override
String? parserData(List<int> data) {
final s = utf8.decode(data);
return s;
}
}
class DateCellDataParser implements IGridCellDataParser<DateCellDataPB> {
@override
DateCellDataPB? parserData(List<int> data) {
if (data.isEmpty) {
return null;
}
return DateCellDataPB.fromBuffer(data);
}
}
class SelectOptionCellDataParser implements IGridCellDataParser<SelectOptionCellDataPB> {
@override
SelectOptionCellDataPB? parserData(List<int> data) {
if (data.isEmpty) {
return null;
}
return SelectOptionCellDataPB.fromBuffer(data);
}
}
class URLCellDataParser implements IGridCellDataParser<URLCellDataPB> {
@override
URLCellDataPB? parserData(List<int> data) {
if (data.isEmpty) {
return null;
}
return URLCellDataPB.fromBuffer(data);
}
}

View File

@ -1,25 +1,22 @@
part of 'cell_service.dart'; part of 'cell_service.dart';
abstract class _GridCellDataPersistence<D> { /// Save the cell data to disk
/// You can extend this class to do custom operations. For example, the DateCellDataPersistence.
abstract class IGridCellDataPersistence<D> {
Future<Option<FlowyError>> save(D data); Future<Option<FlowyError>> save(D data);
} }
class CellDataPersistence implements _GridCellDataPersistence<String> { class CellDataPersistence implements IGridCellDataPersistence<String> {
final GridCell gridCell; final GridCellIdentifier cellId;
CellDataPersistence({ CellDataPersistence({
required this.gridCell, required this.cellId,
}); });
final CellService _cellService = CellService(); final CellService _cellService = CellService();
@override @override
Future<Option<FlowyError>> save(String data) async { Future<Option<FlowyError>> save(String data) async {
final fut = _cellService.updateCell( final fut = _cellService.updateCell(cellId: cellId, data: data);
gridId: gridCell.gridId,
fieldId: gridCell.field.id,
rowId: gridCell.rowId,
data: data,
);
return fut.then((result) { return fut.then((result) {
return result.fold( return result.fold(
@ -35,15 +32,15 @@ class CalendarData with _$CalendarData {
const factory CalendarData({required DateTime date, String? time}) = _CalendarData; const factory CalendarData({required DateTime date, String? time}) = _CalendarData;
} }
class DateCellDataPersistence implements _GridCellDataPersistence<CalendarData> { class DateCellDataPersistence implements IGridCellDataPersistence<CalendarData> {
final GridCell gridCell; final GridCellIdentifier cellId;
DateCellDataPersistence({ DateCellDataPersistence({
required this.gridCell, required this.cellId,
}); });
@override @override
Future<Option<FlowyError>> save(CalendarData data) { Future<Option<FlowyError>> save(CalendarData data) {
var payload = DateChangesetPayload.create()..cellIdentifier = _cellIdentifier(gridCell); var payload = DateChangesetPayloadPB.create()..cellIdentifier = _makeCellIdPayload(cellId);
final date = (data.date.millisecondsSinceEpoch ~/ 1000).toString(); final date = (data.date.millisecondsSinceEpoch ~/ 1000).toString();
payload.date = date; payload.date = date;
@ -61,9 +58,9 @@ class DateCellDataPersistence implements _GridCellDataPersistence<CalendarData>
} }
} }
CellIdentifierPayload _cellIdentifier(GridCell gridCell) { GridCellIdentifierPayloadPB _makeCellIdPayload(GridCellIdentifier cellId) {
return CellIdentifierPayload.create() return GridCellIdentifierPayloadPB.create()
..gridId = gridCell.gridId ..gridId = cellId.gridId
..fieldId = gridCell.field.id ..fieldId = cellId.fieldId
..rowId = gridCell.rowId; ..rowId = cellId.rowId;
} }

View File

@ -0,0 +1,60 @@
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flutter/foundation.dart';
import 'cell_service.dart';
abstract class GridFieldChangedNotifier {
void onFieldChanged(void Function(GridFieldPB) callback);
void dispose();
}
/// GridPB's cell helper wrapper that enables each cell will get notified when the corresponding field was changed.
/// You Register an onFieldChanged callback to listen to the cell changes, and unregister if you don't want to listen.
class GridCellFieldNotifier {
/// fieldId: {objectId: callback}
final Map<String, Map<String, List<VoidCallback>>> _fieldListenerByFieldId = {};
GridCellFieldNotifier({required GridFieldChangedNotifier notifier}) {
notifier.onFieldChanged(
(field) {
final map = _fieldListenerByFieldId[field.id];
if (map != null) {
for (final callbacks in map.values) {
for (final callback in callbacks) {
callback();
}
}
}
},
);
}
///
void register(GridCellCacheKey cacheKey, VoidCallback onFieldChanged) {
var map = _fieldListenerByFieldId[cacheKey.fieldId];
if (map == null) {
_fieldListenerByFieldId[cacheKey.fieldId] = {};
map = _fieldListenerByFieldId[cacheKey.fieldId];
map![cacheKey.rowId] = [onFieldChanged];
} else {
var objects = map[cacheKey.rowId];
if (objects == null) {
map[cacheKey.rowId] = [onFieldChanged];
} else {
objects.add(onFieldChanged);
}
}
}
void unregister(GridCellCacheKey cacheKey, VoidCallback fn) {
var callbacks = _fieldListenerByFieldId[cacheKey.fieldId]?[cacheKey.rowId];
final index = callbacks?.indexWhere((callback) => callback == fn);
if (index != null && index != -1) {
callbacks?.removeAt(index);
}
}
Future<void> dispose() async {
_fieldListenerByFieldId.clear();
}
}

View File

@ -1,27 +1,30 @@
import 'dart:async'; import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'package:app_flowy/workspace/application/grid/grid_service.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.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:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option_entities.pb.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart'; import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
import 'package:app_flowy/workspace/application/grid/cell/select_option_service.dart';
import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
import 'dart:convert' show utf8; import 'dart:convert' show utf8;
import '../../field/type_option/type_option_service.dart';
import 'cell_field_notifier.dart';
part 'cell_service.freezed.dart'; part 'cell_service.freezed.dart';
part 'data_loader.dart'; part 'cell_data_loader.dart';
part 'context_builder.dart'; part 'context_builder.dart';
part 'data_cache.dart'; part 'cell_cache.dart';
part 'data_persistence.dart'; part 'cell_data_persistence.dart';
// key: rowId // key: rowId
@ -29,45 +32,46 @@ class CellService {
CellService(); CellService();
Future<Either<void, FlowyError>> updateCell({ Future<Either<void, FlowyError>> updateCell({
required String gridId, required GridCellIdentifier cellId,
required String fieldId,
required String rowId,
required String data, required String data,
}) { }) {
final payload = CellChangeset.create() final payload = CellChangesetPB.create()
..gridId = gridId ..gridId = cellId.gridId
..fieldId = fieldId ..fieldId = cellId.fieldId
..rowId = rowId ..rowId = cellId.rowId
..cellContentChangeset = data; ..content = data;
return GridEventUpdateCell(payload).send(); return GridEventUpdateCell(payload).send();
} }
Future<Either<Cell, FlowyError>> getCell({ Future<Either<GridCellPB, FlowyError>> getCell({
required String gridId, required GridCellIdentifier cellId,
required String fieldId,
required String rowId,
}) { }) {
final payload = CellIdentifierPayload.create() final payload = GridCellIdentifierPayloadPB.create()
..gridId = gridId ..gridId = cellId.gridId
..fieldId = fieldId ..fieldId = cellId.fieldId
..rowId = rowId; ..rowId = cellId.rowId;
return GridEventGetCell(payload).send(); return GridEventGetCell(payload).send();
} }
} }
/// Id of the cell
/// We can locate the cell by using gridId + rowId + field.id.
@freezed @freezed
class GridCell with _$GridCell { class GridCellIdentifier with _$GridCellIdentifier {
const factory GridCell({ const factory GridCellIdentifier({
required String gridId, required String gridId,
required String rowId, required String rowId,
required Field field, required GridFieldPB field,
Cell? cell, }) = _GridCellIdentifier;
}) = _GridCell;
// ignore: unused_element // ignore: unused_element
const GridCell._(); const GridCellIdentifier._();
String cellId() { String get fieldId => field.id;
return rowId + field.id + "${field.fieldType}";
FieldType get fieldType => field.fieldType;
ValueKey key() {
return ValueKey(rowId + fieldId + "${field.fieldType}");
} }
} }

View File

@ -1,154 +1,183 @@
part of 'cell_service.dart'; part of 'cell_service.dart';
typedef GridCellContext = _GridCellContext<String, String>; typedef GridCellController = IGridCellController<String, String>;
typedef GridSelectOptionCellContext = _GridCellContext<SelectOptionCellData, String>; typedef GridSelectOptionCellController = IGridCellController<SelectOptionCellDataPB, String>;
typedef GridDateCellContext = _GridCellContext<DateCellData, CalendarData>; typedef GridDateCellController = IGridCellController<DateCellDataPB, CalendarData>;
typedef GridURLCellContext = _GridCellContext<URLCellData, String>; typedef GridURLCellController = IGridCellController<URLCellDataPB, String>;
class GridCellContextBuilder { class GridCellControllerBuilder {
final GridCellIdentifier _cellId;
final GridCellCache _cellCache; final GridCellCache _cellCache;
final GridCell _gridCell; final GridFieldCache _fieldCache;
GridCellContextBuilder({
required GridCellCache cellCache,
required GridCell gridCell,
}) : _cellCache = cellCache,
_gridCell = gridCell;
_GridCellContext build() { GridCellControllerBuilder({
switch (_gridCell.field.fieldType) { required GridCellIdentifier cellId,
required GridCellCache cellCache,
required GridFieldCache fieldCache,
}) : _cellCache = cellCache,
_fieldCache = fieldCache,
_cellId = cellId;
IGridCellController build() {
final cellFieldNotifier = GridCellFieldNotifier(notifier: _GridFieldChangedNotifierImpl(_fieldCache));
switch (_cellId.fieldType) {
case FieldType.Checkbox: case FieldType.Checkbox:
final cellDataLoader = GridCellDataLoader( final cellDataLoader = GridCellDataLoader(
gridCell: _gridCell, cellId: _cellId,
parser: StringCellDataParser(), parser: StringCellDataParser(),
); );
return GridCellContext( return GridCellController(
gridCell: _gridCell, cellId: _cellId,
cellCache: _cellCache, cellCache: _cellCache,
cellDataLoader: cellDataLoader, cellDataLoader: cellDataLoader,
cellDataPersistence: CellDataPersistence(gridCell: _gridCell), fieldNotifier: cellFieldNotifier,
cellDataPersistence: CellDataPersistence(cellId: _cellId),
); );
case FieldType.DateTime: case FieldType.DateTime:
final cellDataLoader = GridCellDataLoader( final cellDataLoader = GridCellDataLoader(
gridCell: _gridCell, cellId: _cellId,
parser: DateCellDataParser(), parser: DateCellDataParser(),
config: const GridCellDataConfig(reloadOnFieldChanged: true), reloadOnFieldChanged: true,
); );
return GridDateCellContext( return GridDateCellController(
gridCell: _gridCell, cellId: _cellId,
cellCache: _cellCache, cellCache: _cellCache,
cellDataLoader: cellDataLoader, cellDataLoader: cellDataLoader,
cellDataPersistence: DateCellDataPersistence(gridCell: _gridCell), fieldNotifier: cellFieldNotifier,
cellDataPersistence: DateCellDataPersistence(cellId: _cellId),
); );
case FieldType.Number: case FieldType.Number:
final cellDataLoader = GridCellDataLoader( final cellDataLoader = GridCellDataLoader(
gridCell: _gridCell, cellId: _cellId,
parser: StringCellDataParser(), parser: StringCellDataParser(),
config: const GridCellDataConfig(reloadOnCellChanged: true, reloadOnFieldChanged: true), reloadOnFieldChanged: true,
); );
return GridCellContext( return GridCellController(
gridCell: _gridCell, cellId: _cellId,
cellCache: _cellCache, cellCache: _cellCache,
cellDataLoader: cellDataLoader, cellDataLoader: cellDataLoader,
cellDataPersistence: CellDataPersistence(gridCell: _gridCell), fieldNotifier: cellFieldNotifier,
cellDataPersistence: CellDataPersistence(cellId: _cellId),
); );
case FieldType.RichText: case FieldType.RichText:
final cellDataLoader = GridCellDataLoader( final cellDataLoader = GridCellDataLoader(
gridCell: _gridCell, cellId: _cellId,
parser: StringCellDataParser(), parser: StringCellDataParser(),
); );
return GridCellContext( return GridCellController(
gridCell: _gridCell, cellId: _cellId,
cellCache: _cellCache, cellCache: _cellCache,
cellDataLoader: cellDataLoader, cellDataLoader: cellDataLoader,
cellDataPersistence: CellDataPersistence(gridCell: _gridCell), fieldNotifier: cellFieldNotifier,
cellDataPersistence: CellDataPersistence(cellId: _cellId),
); );
case FieldType.MultiSelect: case FieldType.MultiSelect:
case FieldType.SingleSelect: case FieldType.SingleSelect:
final cellDataLoader = GridCellDataLoader( final cellDataLoader = GridCellDataLoader(
gridCell: _gridCell, cellId: _cellId,
parser: SelectOptionCellDataParser(), parser: SelectOptionCellDataParser(),
config: const GridCellDataConfig(reloadOnFieldChanged: true), reloadOnFieldChanged: true,
); );
return GridSelectOptionCellContext( return GridSelectOptionCellController(
gridCell: _gridCell, cellId: _cellId,
cellCache: _cellCache, cellCache: _cellCache,
cellDataLoader: cellDataLoader, cellDataLoader: cellDataLoader,
cellDataPersistence: CellDataPersistence(gridCell: _gridCell), fieldNotifier: cellFieldNotifier,
cellDataPersistence: CellDataPersistence(cellId: _cellId),
); );
case FieldType.URL: case FieldType.URL:
final cellDataLoader = GridCellDataLoader( final cellDataLoader = GridCellDataLoader(
gridCell: _gridCell, cellId: _cellId,
parser: URLCellDataParser(), parser: URLCellDataParser(),
); );
return GridURLCellContext( return GridURLCellController(
gridCell: _gridCell, cellId: _cellId,
cellCache: _cellCache, cellCache: _cellCache,
cellDataLoader: cellDataLoader, cellDataLoader: cellDataLoader,
cellDataPersistence: CellDataPersistence(gridCell: _gridCell), fieldNotifier: cellFieldNotifier,
cellDataPersistence: CellDataPersistence(cellId: _cellId),
); );
} }
throw UnimplementedError; throw UnimplementedError;
} }
} }
// T: the type of the CellData /// IGridCellController is used to manipulate the cell and receive notifications.
// D: the type of the data that will be save to disk /// * Read/Write cell data
/// * Listen on field/cell notifications.
///
/// Generic T represents the type of the cell data.
/// Generic D represents the type of data that will be saved to the disk
///
// ignore: must_be_immutable // ignore: must_be_immutable
class _GridCellContext<T, D> extends Equatable { class IGridCellController<T, D> extends Equatable {
final GridCell gridCell; final GridCellIdentifier cellId;
final GridCellCache cellCache; final GridCellCache _cellsCache;
final GridCellCacheKey _cacheKey; final GridCellCacheKey _cacheKey;
final IGridCellDataLoader<T> cellDataLoader;
final _GridCellDataPersistence<D> cellDataPersistence;
final FieldService _fieldService; final FieldService _fieldService;
final GridCellFieldNotifier _fieldNotifier;
final GridCellDataLoader<T> _cellDataLoader;
final IGridCellDataPersistence<D> _cellDataPersistence;
late final CellListener _cellListener; late final CellListener _cellListener;
late final ValueNotifier<T?>? _cellDataNotifier; ValueNotifier<T?>? _cellDataNotifier;
bool isListening = false; bool isListening = false;
VoidCallback? _onFieldChangedFn; VoidCallback? _onFieldChangedFn;
Timer? _loadDataOperation; Timer? _loadDataOperation;
Timer? _saveDataOperation; Timer? _saveDataOperation;
bool _isDispose = false;
_GridCellContext({ IGridCellController({
required this.gridCell, required this.cellId,
required this.cellCache, required GridCellCache cellCache,
required this.cellDataLoader, required GridCellFieldNotifier fieldNotifier,
required this.cellDataPersistence, required GridCellDataLoader<T> cellDataLoader,
}) : _fieldService = FieldService(gridId: gridCell.gridId, fieldId: gridCell.field.id), required IGridCellDataPersistence<D> cellDataPersistence,
_cacheKey = GridCellCacheKey(objectId: gridCell.rowId, fieldId: gridCell.field.id); }) : _cellsCache = cellCache,
_cellDataLoader = cellDataLoader,
_cellDataPersistence = cellDataPersistence,
_fieldNotifier = fieldNotifier,
_fieldService = FieldService(gridId: cellId.gridId, fieldId: cellId.field.id),
_cacheKey = GridCellCacheKey(rowId: cellId.rowId, fieldId: cellId.field.id);
_GridCellContext<T, D> clone() { IGridCellController<T, D> clone() {
return _GridCellContext( return IGridCellController(
gridCell: gridCell, cellId: cellId,
cellDataLoader: cellDataLoader, cellDataLoader: _cellDataLoader,
cellCache: cellCache, cellCache: _cellsCache,
cellDataPersistence: cellDataPersistence); fieldNotifier: _fieldNotifier,
cellDataPersistence: _cellDataPersistence);
} }
String get gridId => gridCell.gridId; String get gridId => cellId.gridId;
String get rowId => gridCell.rowId; String get rowId => cellId.rowId;
String get cellId => gridCell.rowId + gridCell.field.id; String get fieldId => cellId.field.id;
String get fieldId => gridCell.field.id; GridFieldPB get field => cellId.field;
Field get field => gridCell.field; FieldType get fieldType => cellId.field.fieldType;
FieldType get fieldType => gridCell.field.fieldType; VoidCallback? startListening({required void Function(T?) onCellChanged, VoidCallback? onCellFieldChanged}) {
VoidCallback? startListening({required void Function(T?) onCellChanged}) {
if (isListening) { if (isListening) {
Log.error("Already started. It seems like you should call clone first"); Log.error("Already started. It seems like you should call clone first");
return null; return null;
} }
isListening = true; isListening = true;
_cellDataNotifier = ValueNotifier(cellCache.get(_cacheKey));
_cellListener = CellListener(rowId: gridCell.rowId, fieldId: gridCell.field.id); _cellDataNotifier = ValueNotifier(_cellsCache.get(_cacheKey));
_cellListener = CellListener(rowId: cellId.rowId, fieldId: cellId.field.id);
/// 1.Listen on user edit event and load the new cell data if needed.
/// For example:
/// user input: 12
/// cell display: $12
_cellListener.start(onCellChanged: (result) { _cellListener.start(onCellChanged: (result) {
result.fold( result.fold(
(_) => _loadData(), (_) => _loadData(),
@ -156,22 +185,27 @@ class _GridCellContext<T, D> extends Equatable {
); );
}); });
if (cellDataLoader.config.reloadOnFieldChanged) { /// 2.Listen on the field event and load the cell data if needed.
_onFieldChangedFn = () { _onFieldChangedFn = () {
_loadData(); if (onCellFieldChanged != null) {
}; onCellFieldChanged();
cellCache.addFieldListener(_cacheKey, _onFieldChangedFn!); }
}
onCellChangedFn() { /// reloadOnFieldChanged should be true if you need to load the data when the corresponding field is changed
onCellChanged(_cellDataNotifier?.value); /// For example:
/// 12 -> $12
if (cellDataLoader.config.reloadOnCellChanged) { if (_cellDataLoader.reloadOnFieldChanged) {
_loadData(); _loadData();
} }
} };
_fieldNotifier.register(_cacheKey, _onFieldChangedFn!);
/// Notify the listener, the cell data was changed.
onCellChangedFn() => onCellChanged(_cellDataNotifier?.value);
_cellDataNotifier?.addListener(onCellChangedFn); _cellDataNotifier?.addListener(onCellChangedFn);
// Return the function pointer that can be used when calling removeListener.
return onCellChangedFn; return onCellChangedFn;
} }
@ -179,29 +213,45 @@ class _GridCellContext<T, D> extends Equatable {
_cellDataNotifier?.removeListener(fn); _cellDataNotifier?.removeListener(fn);
} }
T? getCellData({bool loadIfNoCache = true}) { /// Return the cell data.
final data = cellCache.get(_cacheKey); /// The cell data will be read from the Cache first, and load from disk if it does not exist.
if (data == null && loadIfNoCache) { /// You can set [loadIfNotExist] to false (default is true) to disable loading the cell data.
T? getCellData({bool loadIfNotExist = true}) {
final data = _cellsCache.get(_cacheKey);
if (data == null && loadIfNotExist) {
_loadData(); _loadData();
} }
return data; return data;
} }
Future<Either<FieldTypeOptionData, FlowyError>> getTypeOptionData() { /// Return the FieldTypeOptionDataPB that can be parsed into corresponding class using the [parser].
return _fieldService.getFieldTypeOptionData(fieldType: fieldType); /// [PD] is the type that the parser return.
Future<Either<PD, FlowyError>> getFieldTypeOption<PD, P extends TypeOptionDataParser>(P parser) {
return _fieldService.getFieldTypeOptionData(fieldType: fieldType).then((result) {
return result.fold(
(data) => parser.fromBuffer(data.typeOptionData),
(err) => right(err),
);
});
} }
/// Save the cell data to disk
/// You can set [dedeplicate] to true (default is false) to reduce the save operation.
/// It's useful when you call this method when user editing the [TextField].
/// The default debounce interval is 300 milliseconds.
void saveCellData(D data, {bool deduplicate = false, void Function(Option<FlowyError>)? resultCallback}) async { void saveCellData(D data, {bool deduplicate = false, void Function(Option<FlowyError>)? resultCallback}) async {
if (deduplicate) { if (deduplicate) {
_loadDataOperation?.cancel(); _loadDataOperation?.cancel();
_loadDataOperation = Timer(const Duration(milliseconds: 300), () async {
final result = await cellDataPersistence.save(data); _saveDataOperation?.cancel();
_saveDataOperation = Timer(const Duration(milliseconds: 300), () async {
final result = await _cellDataPersistence.save(data);
if (resultCallback != null) { if (resultCallback != null) {
resultCallback(result); resultCallback(result);
} }
}); });
} else { } else {
final result = await cellDataPersistence.save(data); final result = await _cellDataPersistence.save(data);
if (resultCallback != null) { if (resultCallback != null) {
resultCallback(result); resultCallback(result);
} }
@ -209,26 +259,59 @@ class _GridCellContext<T, D> extends Equatable {
} }
void _loadData() { void _loadData() {
_saveDataOperation?.cancel();
_loadDataOperation?.cancel(); _loadDataOperation?.cancel();
_loadDataOperation = Timer(const Duration(milliseconds: 10), () { _loadDataOperation = Timer(const Duration(milliseconds: 10), () {
cellDataLoader.loadData().then((data) { _cellDataLoader.loadData().then((data) {
_cellDataNotifier?.value = data; _cellDataNotifier?.value = data;
cellCache.insert(GridCellCacheData(key: _cacheKey, object: data)); _cellsCache.insert(_cacheKey, GridCell(object: data));
}); });
}); });
} }
void dispose() { void dispose() {
if (_isDispose) {
Log.error("$this should only dispose once");
return;
}
_isDispose = true;
_cellListener.stop(); _cellListener.stop();
_loadDataOperation?.cancel(); _loadDataOperation?.cancel();
_saveDataOperation?.cancel(); _saveDataOperation?.cancel();
_cellDataNotifier = null;
if (_onFieldChangedFn != null) { if (_onFieldChangedFn != null) {
cellCache.removeFieldListener(_cacheKey, _onFieldChangedFn!); _fieldNotifier.unregister(_cacheKey, _onFieldChangedFn!);
_onFieldChangedFn = null; _onFieldChangedFn = null;
} }
} }
@override @override
List<Object> get props => [cellCache.get(_cacheKey) ?? "", cellId]; List<Object> get props => [_cellsCache.get(_cacheKey) ?? "", cellId.rowId + cellId.field.id];
}
class _GridFieldChangedNotifierImpl extends GridFieldChangedNotifier {
final GridFieldCache _cache;
FieldChangesetCallback? _onChangesetFn;
_GridFieldChangedNotifierImpl(GridFieldCache cache) : _cache = cache;
@override
void dispose() {
if (_onChangesetFn != null) {
_cache.removeListener(onChangsetListener: _onChangesetFn!);
_onChangesetFn = null;
}
}
@override
void onFieldChanged(void Function(GridFieldPB p1) callback) {
_onChangesetFn = (GridFieldChangesetPB changeset) {
for (final updatedField in changeset.updatedFields) {
callback(updatedField);
}
};
_cache.addListener(onChangeset: _onChangesetFn);
}
} }

View File

@ -1,111 +0,0 @@
part of 'cell_service.dart';
typedef GridCellMap = LinkedHashMap<String, GridCell>;
class GridCellCacheData {
GridCellCacheKey key;
dynamic object;
GridCellCacheData({
required this.key,
required this.object,
});
}
class GridCellCacheKey {
final String fieldId;
final String objectId;
GridCellCacheKey({
required this.fieldId,
required this.objectId,
});
}
abstract class GridCellFieldDelegate {
void onFieldChanged(void Function(String) callback);
void dispose();
}
class GridCellCache {
final String gridId;
final GridCellFieldDelegate fieldDelegate;
/// fieldId: {objectId: callback}
final Map<String, Map<String, List<VoidCallback>>> _fieldListenerByFieldId = {};
/// fieldId: {cacheKey: cacheData}
final Map<String, Map<String, dynamic>> _cellDataByFieldId = {};
GridCellCache({
required this.gridId,
required this.fieldDelegate,
}) {
fieldDelegate.onFieldChanged((fieldId) {
_cellDataByFieldId.remove(fieldId);
final map = _fieldListenerByFieldId[fieldId];
if (map != null) {
for (final callbacks in map.values) {
for (final callback in callbacks) {
callback();
}
}
}
});
}
void addFieldListener(GridCellCacheKey cacheKey, VoidCallback onFieldChanged) {
var map = _fieldListenerByFieldId[cacheKey.fieldId];
if (map == null) {
_fieldListenerByFieldId[cacheKey.fieldId] = {};
map = _fieldListenerByFieldId[cacheKey.fieldId];
map![cacheKey.objectId] = [onFieldChanged];
} else {
var objects = map[cacheKey.objectId];
if (objects == null) {
map[cacheKey.objectId] = [onFieldChanged];
} else {
objects.add(onFieldChanged);
}
}
}
void removeFieldListener(GridCellCacheKey cacheKey, VoidCallback fn) {
var callbacks = _fieldListenerByFieldId[cacheKey.fieldId]?[cacheKey.objectId];
final index = callbacks?.indexWhere((callback) => callback == fn);
if (index != null && index != -1) {
callbacks?.removeAt(index);
}
}
void insert<T extends GridCellCacheData>(T item) {
var map = _cellDataByFieldId[item.key.fieldId];
if (map == null) {
_cellDataByFieldId[item.key.fieldId] = {};
map = _cellDataByFieldId[item.key.fieldId];
}
map![item.key.objectId] = item.object;
}
T? get<T>(GridCellCacheKey key) {
final map = _cellDataByFieldId[key.fieldId];
if (map == null) {
return null;
} else {
final object = map[key.objectId];
if (object is T) {
return object;
} else {
if (object != null) {
Log.error("Cache data type does not match the cache data type");
}
return null;
}
}
}
Future<void> dispose() async {
_fieldListenerByFieldId.clear();
_cellDataByFieldId.clear();
fieldDelegate.dispose();
}
}

View File

@ -1,134 +0,0 @@
part of 'cell_service.dart';
abstract class IGridCellDataConfig {
// The cell data will reload if it receives the field's change notification.
bool get reloadOnFieldChanged;
// When the reloadOnCellChanged is true, it will load the cell data after user input.
// For example: The number cell reload the cell data that carries the format
// user input: 12
// cell display: $12
bool get reloadOnCellChanged;
}
class GridCellDataConfig implements IGridCellDataConfig {
@override
final bool reloadOnCellChanged;
@override
final bool reloadOnFieldChanged;
const GridCellDataConfig({
this.reloadOnCellChanged = false,
this.reloadOnFieldChanged = false,
});
}
abstract class IGridCellDataLoader<T> {
Future<T?> loadData();
IGridCellDataConfig get config;
}
abstract class ICellDataParser<T> {
T? parserData(List<int> data);
}
class GridCellDataLoader<T> extends IGridCellDataLoader<T> {
final CellService service = CellService();
final GridCell gridCell;
final ICellDataParser<T> parser;
@override
final IGridCellDataConfig config;
GridCellDataLoader({
required this.gridCell,
required this.parser,
this.config = const GridCellDataConfig(),
});
@override
Future<T?> loadData() {
final fut = service.getCell(
gridId: gridCell.gridId,
fieldId: gridCell.field.id,
rowId: gridCell.rowId,
);
return fut.then(
(result) => result.fold((Cell cell) {
try {
return parser.parserData(cell.data);
} catch (e, s) {
Log.error('$parser parser cellData failed, $e');
Log.error('Stack trace \n $s');
return null;
}
}, (err) {
Log.error(err);
return null;
}),
);
}
}
class SelectOptionCellDataLoader extends IGridCellDataLoader<SelectOptionCellData> {
final SelectOptionService service;
final GridCell gridCell;
SelectOptionCellDataLoader({
required this.gridCell,
}) : service = SelectOptionService(gridCell: gridCell);
@override
Future<SelectOptionCellData?> loadData() async {
return service.getOpitonContext().then((result) {
return result.fold(
(data) => data,
(err) {
Log.error(err);
return null;
},
);
});
}
@override
IGridCellDataConfig get config => const GridCellDataConfig(reloadOnFieldChanged: true);
}
class StringCellDataParser implements ICellDataParser<String> {
@override
String? parserData(List<int> data) {
final s = utf8.decode(data);
return s;
}
}
class DateCellDataParser implements ICellDataParser<DateCellData> {
@override
DateCellData? parserData(List<int> data) {
if (data.isEmpty) {
return null;
}
return DateCellData.fromBuffer(data);
}
}
class SelectOptionCellDataParser implements ICellDataParser<SelectOptionCellData> {
@override
SelectOptionCellData? parserData(List<int> data) {
if (data.isEmpty) {
return null;
}
return SelectOptionCellData.fromBuffer(data);
}
}
class URLCellDataParser implements ICellDataParser<URLCellData> {
@override
URLCellData? parserData(List<int> data) {
if (data.isEmpty) {
return null;
}
return URLCellData.fromBuffer(data);
}
}

View File

@ -6,7 +6,7 @@ import 'cell_service/cell_service.dart';
part 'checkbox_cell_bloc.freezed.dart'; part 'checkbox_cell_bloc.freezed.dart';
class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> { class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
final GridCellContext cellContext; final GridCellController cellContext;
void Function()? _onCellChangedFn; void Function()? _onCellChangedFn;
CheckboxCellBloc({ CheckboxCellBloc({
@ -67,7 +67,7 @@ class CheckboxCellState with _$CheckboxCellState {
required bool isSelected, required bool isSelected,
}) = _CheckboxCellState; }) = _CheckboxCellState;
factory CheckboxCellState.initial(GridCellContext context) { factory CheckboxCellState.initial(GridCellController context) {
return CheckboxCellState(isSelected: _isSelected(context.getCellData())); return CheckboxCellState(isSelected: _isSelected(context.getCellData()));
} }
} }

View File

@ -5,6 +5,7 @@ import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error-code/code.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error-code/code.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.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.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:table_calendar/table_calendar.dart'; import 'package:table_calendar/table_calendar.dart';
@ -16,12 +17,12 @@ import 'package:fixnum/fixnum.dart' as $fixnum;
part 'date_cal_bloc.freezed.dart'; part 'date_cal_bloc.freezed.dart';
class DateCalBloc extends Bloc<DateCalEvent, DateCalState> { class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
final GridDateCellContext cellContext; final GridDateCellController cellContext;
void Function()? _onCellChangedFn; void Function()? _onCellChangedFn;
DateCalBloc({ DateCalBloc({
required DateTypeOption dateTypeOption, required DateTypeOption dateTypeOption,
required DateCellData? cellData, required DateCellDataPB? cellData,
required this.cellContext, required this.cellContext,
}) : super(DateCalState.initial(dateTypeOption, cellData)) { }) : super(DateCalState.initial(dateTypeOption, cellData)) {
on<DateCalEvent>( on<DateCalEvent>(
@ -37,7 +38,7 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
setFocusedDay: (focusedDay) { setFocusedDay: (focusedDay) {
emit(state.copyWith(focusedDay: focusedDay)); emit(state.copyWith(focusedDay: focusedDay));
}, },
didReceiveCellUpdate: (DateCellData? cellData) { didReceiveCellUpdate: (DateCellDataPB? cellData) {
final calData = calDataFromCellData(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)); emit(state.copyWith(calData: calData, time: time));
@ -187,7 +188,7 @@ class DateCalEvent with _$DateCalEvent {
const factory DateCalEvent.setDateFormat(DateFormat dateFormat) = _DateFormat; const factory DateCalEvent.setDateFormat(DateFormat dateFormat) = _DateFormat;
const factory DateCalEvent.setIncludeTime(bool includeTime) = _IncludeTime; const factory DateCalEvent.setIncludeTime(bool includeTime) = _IncludeTime;
const factory DateCalEvent.setTime(String time) = _Time; const factory DateCalEvent.setTime(String time) = _Time;
const factory DateCalEvent.didReceiveCellUpdate(DateCellData? data) = _DidReceiveCellUpdate; const factory DateCalEvent.didReceiveCellUpdate(DateCellDataPB? data) = _DidReceiveCellUpdate;
const factory DateCalEvent.didUpdateCalData(Option<CalendarData> data, Option<String> timeFormatError) = const factory DateCalEvent.didUpdateCalData(Option<CalendarData> data, Option<String> timeFormatError) =
_DidUpdateCalData; _DidUpdateCalData;
} }
@ -206,7 +207,7 @@ class DateCalState with _$DateCalState {
factory DateCalState.initial( factory DateCalState.initial(
DateTypeOption dateTypeOption, DateTypeOption dateTypeOption,
DateCellData? cellData, DateCellDataPB? cellData,
) { ) {
Option<CalendarData> calData = calDataFromCellData(cellData); Option<CalendarData> calData = calDataFromCellData(cellData);
final time = calData.foldRight("", (dateData, previous) => dateData.time); final time = calData.foldRight("", (dateData, previous) => dateData.time);
@ -225,14 +226,14 @@ class DateCalState with _$DateCalState {
String _timeHintText(DateTypeOption typeOption) { String _timeHintText(DateTypeOption typeOption) {
switch (typeOption.timeFormat) { switch (typeOption.timeFormat) {
case TimeFormat.TwelveHour: case TimeFormat.TwelveHour:
return LocaleKeys.grid_date_timeHintTextInTwelveHour.tr(); return LocaleKeys.document_date_timeHintTextInTwelveHour.tr();
case TimeFormat.TwentyFourHour: case TimeFormat.TwentyFourHour:
return LocaleKeys.grid_date_timeHintTextInTwentyFourHour.tr(); return LocaleKeys.document_date_timeHintTextInTwentyFourHour.tr();
} }
return ""; return "";
} }
Option<CalendarData> calDataFromCellData(DateCellData? cellData) { Option<CalendarData> calDataFromCellData(DateCellDataPB? cellData) {
String? time = timeFromCellData(cellData); String? time = timeFromCellData(cellData);
Option<CalendarData> calData = none(); Option<CalendarData> calData = none();
if (cellData != null) { if (cellData != null) {
@ -248,7 +249,7 @@ $fixnum.Int64 timestampFromDateTime(DateTime dateTime) {
return $fixnum.Int64(timestamp); return $fixnum.Int64(timestamp);
} }
String? timeFromCellData(DateCellData? cellData) { String? timeFromCellData(DateCellDataPB? cellData) {
String? time; String? time;
if (cellData?.hasTime() ?? false) { if (cellData?.hasTime() ?? false) {
time = cellData?.time; time = cellData?.time;

View File

@ -1,5 +1,5 @@
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field; import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.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:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
@ -7,7 +7,7 @@ import 'cell_service/cell_service.dart';
part 'date_cell_bloc.freezed.dart'; part 'date_cell_bloc.freezed.dart';
class DateCellBloc extends Bloc<DateCellEvent, DateCellState> { class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
final GridDateCellContext cellContext; final GridDateCellController cellContext;
void Function()? _onCellChangedFn; void Function()? _onCellChangedFn;
DateCellBloc({required this.cellContext}) : super(DateCellState.initial(cellContext)) { DateCellBloc({required this.cellContext}) : super(DateCellState.initial(cellContext)) {
@ -15,10 +15,10 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
(event, emit) async { (event, emit) async {
event.when( event.when(
initial: () => _startListening(), initial: () => _startListening(),
didReceiveCellUpdate: (DateCellData? cellData) { didReceiveCellUpdate: (DateCellDataPB? cellData) {
emit(state.copyWith(data: cellData, dateStr: _dateStrFromCellData(cellData))); emit(state.copyWith(data: cellData, dateStr: _dateStrFromCellData(cellData)));
}, },
didReceiveFieldUpdate: (Field value) => emit(state.copyWith(field: value)), didReceiveFieldUpdate: (GridFieldPB value) => emit(state.copyWith(field: value)),
); );
}, },
); );
@ -48,19 +48,19 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
@freezed @freezed
class DateCellEvent with _$DateCellEvent { class DateCellEvent with _$DateCellEvent {
const factory DateCellEvent.initial() = _InitialCell; const factory DateCellEvent.initial() = _InitialCell;
const factory DateCellEvent.didReceiveCellUpdate(DateCellData? data) = _DidReceiveCellUpdate; const factory DateCellEvent.didReceiveCellUpdate(DateCellDataPB? data) = _DidReceiveCellUpdate;
const factory DateCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate; const factory DateCellEvent.didReceiveFieldUpdate(GridFieldPB field) = _DidReceiveFieldUpdate;
} }
@freezed @freezed
class DateCellState with _$DateCellState { class DateCellState with _$DateCellState {
const factory DateCellState({ const factory DateCellState({
required DateCellData? data, required DateCellDataPB? data,
required String dateStr, required String dateStr,
required Field field, required GridFieldPB field,
}) = _DateCellState; }) = _DateCellState;
factory DateCellState.initial(GridDateCellContext context) { factory DateCellState.initial(GridDateCellController context) {
final cellData = context.getCellData(); final cellData = context.getCellData();
return DateCellState( return DateCellState(
@ -71,7 +71,7 @@ class DateCellState with _$DateCellState {
} }
} }
String _dateStrFromCellData(DateCellData? cellData) { String _dateStrFromCellData(DateCellDataPB? cellData) {
String dateStr = ""; String dateStr = "";
if (cellData != null) { if (cellData != null) {
dateStr = cellData.date + " " + cellData.time; dateStr = cellData.date + " " + cellData.time;

View File

@ -8,7 +8,7 @@ import 'cell_service/cell_service.dart';
part 'number_cell_bloc.freezed.dart'; part 'number_cell_bloc.freezed.dart';
class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> { class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
final GridCellContext cellContext; final GridCellController cellContext;
void Function()? _onCellChangedFn; void Function()? _onCellChangedFn;
NumberCellBloc({ NumberCellBloc({
@ -72,7 +72,7 @@ class NumberCellState with _$NumberCellState {
required Either<String, FlowyError> content, required Either<String, FlowyError> content,
}) = _NumberCellState; }) = _NumberCellState;
factory NumberCellState.initial(GridCellContext context) { factory NumberCellState.initial(GridCellController context) {
final cellContent = context.getCellData() ?? ""; final cellContent = context.getCellData() ?? "";
return NumberCellState( return NumberCellState(
content: left(cellContent), content: left(cellContent),

View File

@ -1,5 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart'; import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
@ -7,7 +7,7 @@ import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_serv
part 'select_option_cell_bloc.freezed.dart'; part 'select_option_cell_bloc.freezed.dart';
class SelectOptionCellBloc extends Bloc<SelectOptionCellEvent, SelectOptionCellState> { class SelectOptionCellBloc extends Bloc<SelectOptionCellEvent, SelectOptionCellState> {
final GridSelectOptionCellContext cellContext; final GridSelectOptionCellController cellContext;
void Function()? _onCellChangedFn; void Function()? _onCellChangedFn;
SelectOptionCellBloc({ SelectOptionCellBloc({
@ -56,17 +56,17 @@ class SelectOptionCellBloc extends Bloc<SelectOptionCellEvent, SelectOptionCellS
class SelectOptionCellEvent with _$SelectOptionCellEvent { class SelectOptionCellEvent with _$SelectOptionCellEvent {
const factory SelectOptionCellEvent.initial() = _InitialCell; const factory SelectOptionCellEvent.initial() = _InitialCell;
const factory SelectOptionCellEvent.didReceiveOptions( const factory SelectOptionCellEvent.didReceiveOptions(
List<SelectOption> selectedOptions, List<SelectOptionPB> selectedOptions,
) = _DidReceiveOptions; ) = _DidReceiveOptions;
} }
@freezed @freezed
class SelectOptionCellState with _$SelectOptionCellState { class SelectOptionCellState with _$SelectOptionCellState {
const factory SelectOptionCellState({ const factory SelectOptionCellState({
required List<SelectOption> selectedOptions, required List<SelectOptionPB> selectedOptions,
}) = _SelectOptionCellState; }) = _SelectOptionCellState;
factory SelectOptionCellState.initial(GridSelectOptionCellContext context) { factory SelectOptionCellState.initial(GridSelectOptionCellController context) {
final data = context.getCellData(); final data = context.getCellData();
return SelectOptionCellState( return SelectOptionCellState(

View File

@ -1,8 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:app_flowy/workspace/application/grid/field/grid_listenr.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart'; import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
@ -13,16 +12,13 @@ part 'select_option_editor_bloc.freezed.dart';
class SelectOptionCellEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionEditorState> { class SelectOptionCellEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionEditorState> {
final SelectOptionService _selectOptionService; final SelectOptionService _selectOptionService;
final GridSelectOptionCellContext cellContext; final GridSelectOptionCellController cellController;
late final GridFieldsListener _fieldListener;
void Function()? _onCellChangedFn;
Timer? _delayOperation; Timer? _delayOperation;
SelectOptionCellEditorBloc({ SelectOptionCellEditorBloc({
required this.cellContext, required this.cellController,
}) : _selectOptionService = SelectOptionService(gridCell: cellContext.gridCell), }) : _selectOptionService = SelectOptionService(cellId: cellController.cellId),
_fieldListener = GridFieldsListener(gridId: cellContext.gridId), super(SelectOptionEditorState.initial(cellController)) {
super(SelectOptionEditorState.initial(cellContext)) {
on<SelectOptionEditorEvent>( on<SelectOptionEditorEvent>(
(event, emit) async { (event, emit) async {
await event.map( await event.map(
@ -64,13 +60,8 @@ class SelectOptionCellEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOpt
@override @override
Future<void> close() async { Future<void> close() async {
if (_onCellChangedFn != null) {
cellContext.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
_delayOperation?.cancel(); _delayOperation?.cancel();
await _fieldListener.stop(); cellController.dispose();
cellContext.dispose();
return super.close(); return super.close();
} }
@ -79,7 +70,7 @@ class SelectOptionCellEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOpt
result.fold((l) => {}, (err) => Log.error(err)); result.fold((l) => {}, (err) => Log.error(err));
} }
void _deleteOption(SelectOption option) async { void _deleteOption(SelectOptionPB option) async {
final result = await _selectOptionService.delete( final result = await _selectOptionService.delete(
option: option, option: option,
); );
@ -87,7 +78,7 @@ class SelectOptionCellEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOpt
result.fold((l) => null, (err) => Log.error(err)); result.fold((l) => null, (err) => Log.error(err));
} }
void _updateOption(SelectOption option) async { void _updateOption(SelectOptionPB option) async {
final result = await _selectOptionService.update( final result = await _selectOptionService.update(
option: option, option: option,
); );
@ -131,8 +122,8 @@ class SelectOptionCellEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOpt
}); });
} }
_MakeOptionResult _makeOptions(Option<String> filter, List<SelectOption> allOptions) { _MakeOptionResult _makeOptions(Option<String> filter, List<SelectOptionPB> allOptions) {
final List<SelectOption> options = List.from(allOptions); final List<SelectOptionPB> options = List.from(allOptions);
Option<String> createOption = filter; Option<String> createOption = filter;
filter.foldRight(null, (filter, previous) { filter.foldRight(null, (filter, previous) {
@ -157,24 +148,16 @@ class SelectOptionCellEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOpt
} }
void _startListening() { void _startListening() {
_onCellChangedFn = cellContext.startListening( cellController.startListening(
onCellChanged: ((selectOptionContext) { onCellChanged: ((selectOptionContext) {
if (!isClosed) { if (!isClosed) {
_loadOptions(); _loadOptions();
} }
}), }),
onCellFieldChanged: () {
_loadOptions();
},
); );
_fieldListener.start(onFieldsChanged: (result) {
result.fold(
(changeset) {
if (changeset.updatedFields.isNotEmpty) {
_loadOptions();
}
},
(err) => Log.error(err),
);
});
} }
} }
@ -182,26 +165,26 @@ class SelectOptionCellEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOpt
class SelectOptionEditorEvent with _$SelectOptionEditorEvent { class SelectOptionEditorEvent with _$SelectOptionEditorEvent {
const factory SelectOptionEditorEvent.initial() = _Initial; const factory SelectOptionEditorEvent.initial() = _Initial;
const factory SelectOptionEditorEvent.didReceiveOptions( const factory SelectOptionEditorEvent.didReceiveOptions(
List<SelectOption> options, List<SelectOption> selectedOptions) = _DidReceiveOptions; List<SelectOptionPB> options, List<SelectOptionPB> selectedOptions) = _DidReceiveOptions;
const factory SelectOptionEditorEvent.newOption(String optionName) = _NewOption; const factory SelectOptionEditorEvent.newOption(String optionName) = _NewOption;
const factory SelectOptionEditorEvent.selectOption(String optionId) = _SelectOption; const factory SelectOptionEditorEvent.selectOption(String optionId) = _SelectOption;
const factory SelectOptionEditorEvent.updateOption(SelectOption option) = _UpdateOption; const factory SelectOptionEditorEvent.updateOption(SelectOptionPB option) = _UpdateOption;
const factory SelectOptionEditorEvent.deleteOption(SelectOption option) = _DeleteOption; const factory SelectOptionEditorEvent.deleteOption(SelectOptionPB option) = _DeleteOption;
const factory SelectOptionEditorEvent.filterOption(String optionName) = _SelectOptionFilter; const factory SelectOptionEditorEvent.filterOption(String optionName) = _SelectOptionFilter;
} }
@freezed @freezed
class SelectOptionEditorState with _$SelectOptionEditorState { class SelectOptionEditorState with _$SelectOptionEditorState {
const factory SelectOptionEditorState({ const factory SelectOptionEditorState({
required List<SelectOption> options, required List<SelectOptionPB> options,
required List<SelectOption> allOptions, required List<SelectOptionPB> allOptions,
required List<SelectOption> selectedOptions, required List<SelectOptionPB> selectedOptions,
required Option<String> createOption, required Option<String> createOption,
required Option<String> filter, required Option<String> filter,
}) = _SelectOptionEditorState; }) = _SelectOptionEditorState;
factory SelectOptionEditorState.initial(GridSelectOptionCellContext context) { factory SelectOptionEditorState.initial(GridSelectOptionCellController context) {
final data = context.getCellData(loadIfNoCache: false); final data = context.getCellData(loadIfNotExist: false);
return SelectOptionEditorState( return SelectOptionEditorState(
options: data?.options ?? [], options: data?.options ?? [],
allOptions: data?.options ?? [], allOptions: data?.options ?? [],
@ -213,7 +196,7 @@ class SelectOptionEditorState with _$SelectOptionEditorState {
} }
class _MakeOptionResult { class _MakeOptionResult {
List<SelectOption> options; List<SelectOptionPB> options;
Option<String> createOption; Option<String> createOption;
_MakeOptionResult({ _MakeOptionResult({

View File

@ -2,28 +2,28 @@ import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.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/cell_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart'; import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
import 'cell_service/cell_service.dart'; import 'cell_service/cell_service.dart';
class SelectOptionService { class SelectOptionService {
final GridCell gridCell; final GridCellIdentifier cellId;
SelectOptionService({required this.gridCell}); SelectOptionService({required this.cellId});
String get gridId => gridCell.gridId; String get gridId => cellId.gridId;
String get fieldId => gridCell.field.id; String get fieldId => cellId.field.id;
String get rowId => gridCell.rowId; String get rowId => cellId.rowId;
Future<Either<Unit, FlowyError>> create({required String name}) { Future<Either<Unit, FlowyError>> create({required String name}) {
return TypeOptionService(gridId: gridId, fieldId: fieldId).newOption(name: name).then( return TypeOptionService(gridId: gridId, fieldId: fieldId).newOption(name: name).then(
(result) { (result) {
return result.fold( return result.fold(
(option) { (option) {
final cellIdentifier = CellIdentifierPayload.create() final cellIdentifier = GridCellIdentifierPayloadPB.create()
..gridId = gridId ..gridId = gridId
..fieldId = fieldId ..fieldId = fieldId
..rowId = rowId; ..rowId = rowId;
final payload = SelectOptionChangesetPayload.create() final payload = SelectOptionChangesetPayloadPB.create()
..insertOption = option ..insertOption = option
..cellIdentifier = cellIdentifier; ..cellIdentifier = cellIdentifier;
return GridEventUpdateSelectOption(payload).send(); return GridEventUpdateSelectOption(payload).send();
@ -35,26 +35,26 @@ class SelectOptionService {
} }
Future<Either<Unit, FlowyError>> update({ Future<Either<Unit, FlowyError>> update({
required SelectOption option, required SelectOptionPB option,
}) { }) {
final payload = SelectOptionChangesetPayload.create() final payload = SelectOptionChangesetPayloadPB.create()
..updateOption = option ..updateOption = option
..cellIdentifier = _cellIdentifier(); ..cellIdentifier = _cellIdentifier();
return GridEventUpdateSelectOption(payload).send(); return GridEventUpdateSelectOption(payload).send();
} }
Future<Either<Unit, FlowyError>> delete({ Future<Either<Unit, FlowyError>> delete({
required SelectOption option, required SelectOptionPB option,
}) { }) {
final payload = SelectOptionChangesetPayload.create() final payload = SelectOptionChangesetPayloadPB.create()
..deleteOption = option ..deleteOption = option
..cellIdentifier = _cellIdentifier(); ..cellIdentifier = _cellIdentifier();
return GridEventUpdateSelectOption(payload).send(); return GridEventUpdateSelectOption(payload).send();
} }
Future<Either<SelectOptionCellData, FlowyError>> getOpitonContext() { Future<Either<SelectOptionCellDataPB, FlowyError>> getOpitonContext() {
final payload = CellIdentifierPayload.create() final payload = GridCellIdentifierPayloadPB.create()
..gridId = gridId ..gridId = gridId
..fieldId = fieldId ..fieldId = fieldId
..rowId = rowId; ..rowId = rowId;
@ -63,21 +63,21 @@ class SelectOptionService {
} }
Future<Either<void, FlowyError>> select({required String optionId}) { Future<Either<void, FlowyError>> select({required String optionId}) {
final payload = SelectOptionCellChangesetPayload.create() final payload = SelectOptionCellChangesetPayloadPB.create()
..cellIdentifier = _cellIdentifier() ..cellIdentifier = _cellIdentifier()
..insertOptionId = optionId; ..insertOptionId = optionId;
return GridEventUpdateSelectOptionCell(payload).send(); return GridEventUpdateSelectOptionCell(payload).send();
} }
Future<Either<void, FlowyError>> unSelect({required String optionId}) { Future<Either<void, FlowyError>> unSelect({required String optionId}) {
final payload = SelectOptionCellChangesetPayload.create() final payload = SelectOptionCellChangesetPayloadPB.create()
..cellIdentifier = _cellIdentifier() ..cellIdentifier = _cellIdentifier()
..deleteOptionId = optionId; ..deleteOptionId = optionId;
return GridEventUpdateSelectOptionCell(payload).send(); return GridEventUpdateSelectOptionCell(payload).send();
} }
CellIdentifierPayload _cellIdentifier() { GridCellIdentifierPayloadPB _cellIdentifier() {
return CellIdentifierPayload.create() return GridCellIdentifierPayloadPB.create()
..gridId = gridId ..gridId = gridId
..fieldId = fieldId ..fieldId = fieldId
..rowId = rowId; ..rowId = rowId;

View File

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

View File

@ -1,4 +1,4 @@
import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
@ -7,7 +7,7 @@ import 'cell_service/cell_service.dart';
part 'url_cell_bloc.freezed.dart'; part 'url_cell_bloc.freezed.dart';
class URLCellBloc extends Bloc<URLCellEvent, URLCellState> { class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
final GridURLCellContext cellContext; final GridURLCellController cellContext;
void Function()? _onCellChangedFn; void Function()? _onCellChangedFn;
URLCellBloc({ URLCellBloc({
required this.cellContext, required this.cellContext,
@ -57,7 +57,7 @@ class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
class URLCellEvent with _$URLCellEvent { class URLCellEvent with _$URLCellEvent {
const factory URLCellEvent.initial() = _InitialCell; const factory URLCellEvent.initial() = _InitialCell;
const factory URLCellEvent.updateURL(String url) = _UpdateURL; const factory URLCellEvent.updateURL(String url) = _UpdateURL;
const factory URLCellEvent.didReceiveCellUpdate(URLCellData? cell) = _DidReceiveCellUpdate; const factory URLCellEvent.didReceiveCellUpdate(URLCellDataPB? cell) = _DidReceiveCellUpdate;
} }
@freezed @freezed
@ -67,7 +67,7 @@ class URLCellState with _$URLCellState {
required String url, required String url,
}) = _URLCellState; }) = _URLCellState;
factory URLCellState.initial(GridURLCellContext context) { factory URLCellState.initial(GridURLCellController context) {
final cellData = context.getCellData(); final cellData = context.getCellData();
return URLCellState( return URLCellState(
content: cellData?.content ?? "", content: cellData?.content ?? "",

View File

@ -1,4 +1,4 @@
import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
@ -7,7 +7,7 @@ import 'cell_service/cell_service.dart';
part 'url_cell_editor_bloc.freezed.dart'; part 'url_cell_editor_bloc.freezed.dart';
class URLCellEditorBloc extends Bloc<URLCellEditorEvent, URLCellEditorState> { class URLCellEditorBloc extends Bloc<URLCellEditorEvent, URLCellEditorState> {
final GridURLCellContext cellContext; final GridURLCellController cellContext;
void Function()? _onCellChangedFn; void Function()? _onCellChangedFn;
URLCellEditorBloc({ URLCellEditorBloc({
required this.cellContext, required this.cellContext,
@ -54,7 +54,7 @@ class URLCellEditorBloc extends Bloc<URLCellEditorEvent, URLCellEditorState> {
@freezed @freezed
class URLCellEditorEvent with _$URLCellEditorEvent { class URLCellEditorEvent with _$URLCellEditorEvent {
const factory URLCellEditorEvent.initial() = _InitialCell; const factory URLCellEditorEvent.initial() = _InitialCell;
const factory URLCellEditorEvent.didReceiveCellUpdate(URLCellData? cell) = _DidReceiveCellUpdate; const factory URLCellEditorEvent.didReceiveCellUpdate(URLCellDataPB? cell) = _DidReceiveCellUpdate;
const factory URLCellEditorEvent.updateText(String text) = _UpdateText; const factory URLCellEditorEvent.updateText(String text) = _UpdateText;
} }
@ -64,7 +64,7 @@ class URLCellEditorState with _$URLCellEditorState {
required String content, required String content,
}) = _URLCellEditorState; }) = _URLCellEditorState;
factory URLCellEditorState.initial(GridURLCellContext context) { factory URLCellEditorState.initial(GridURLCellController context) {
final cellData = context.getCellData(); final cellData = context.getCellData();
return URLCellEditorState( return URLCellEditorState(
content: cellData?.content ?? "", content: cellData?.content ?? "",

View File

@ -1,5 +1,5 @@
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
@ -10,8 +10,8 @@ part 'field_action_sheet_bloc.freezed.dart';
class FieldActionSheetBloc extends Bloc<FieldActionSheetEvent, FieldActionSheetState> { class FieldActionSheetBloc extends Bloc<FieldActionSheetEvent, FieldActionSheetState> {
final FieldService fieldService; final FieldService fieldService;
FieldActionSheetBloc({required Field field, required this.fieldService}) FieldActionSheetBloc({required GridFieldPB field, required this.fieldService})
: super(FieldActionSheetState.initial(FieldTypeOptionData.create()..field_2 = field)) { : super(FieldActionSheetState.initial(FieldTypeOptionDataPB.create()..field_2 = field)) {
on<FieldActionSheetEvent>( on<FieldActionSheetEvent>(
(event, emit) async { (event, emit) async {
await event.map( await event.map(
@ -67,12 +67,12 @@ class FieldActionSheetEvent with _$FieldActionSheetEvent {
@freezed @freezed
class FieldActionSheetState with _$FieldActionSheetState { class FieldActionSheetState with _$FieldActionSheetState {
const factory FieldActionSheetState({ const factory FieldActionSheetState({
required FieldTypeOptionData fieldTypeOptionData, required FieldTypeOptionDataPB fieldTypeOptionData,
required String errorText, required String errorText,
required String fieldName, required String fieldName,
}) = _FieldActionSheetState; }) = _FieldActionSheetState;
factory FieldActionSheetState.initial(FieldTypeOptionData data) => FieldActionSheetState( factory FieldActionSheetState.initial(FieldTypeOptionDataPB data) => FieldActionSheetState(
fieldTypeOptionData: data, fieldTypeOptionData: data,
errorText: '', errorText: '',
fieldName: data.field_2.name, fieldName: data.field_2.name,

View File

@ -1,7 +1,7 @@
import 'package:app_flowy/workspace/application/grid/field/field_listener.dart'; import 'package:app_flowy/workspace/application/grid/field/field_listener.dart';
import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
@ -62,7 +62,7 @@ class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
@freezed @freezed
class FieldCellEvent with _$FieldCellEvent { class FieldCellEvent with _$FieldCellEvent {
const factory FieldCellEvent.initial() = _InitialCell; const factory FieldCellEvent.initial() = _InitialCell;
const factory FieldCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate; const factory FieldCellEvent.didReceiveFieldUpdate(GridFieldPB field) = _DidReceiveFieldUpdate;
const factory FieldCellEvent.startUpdateWidth(double offset) = _StartUpdateWidth; const factory FieldCellEvent.startUpdateWidth(double offset) = _StartUpdateWidth;
const factory FieldCellEvent.endUpdateWidth() = _EndUpdateWidth; const factory FieldCellEvent.endUpdateWidth() = _EndUpdateWidth;
} }
@ -71,7 +71,7 @@ class FieldCellEvent with _$FieldCellEvent {
class FieldCellState with _$FieldCellState { class FieldCellState with _$FieldCellState {
const factory FieldCellState({ const factory FieldCellState({
required String gridId, required String gridId,
required Field field, required GridFieldPB field,
required double width, required double width,
}) = _FieldCellState; }) = _FieldCellState;

View File

@ -1,3 +1,4 @@
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
@ -6,27 +7,32 @@ import 'package:dartz/dartz.dart';
part 'field_editor_bloc.freezed.dart'; part 'field_editor_bloc.freezed.dart';
class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> { class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
final TypeOptionDataController dataController;
FieldEditorBloc({ FieldEditorBloc({
required String gridId, required String gridId,
required String fieldName, required String fieldName,
required IFieldContextLoader fieldContextLoader, required IFieldTypeOptionLoader loader,
}) : super(FieldEditorState.initial(gridId, fieldName, fieldContextLoader)) { }) : dataController = TypeOptionDataController(gridId: gridId, loader: loader),
super(FieldEditorState.initial(gridId, fieldName)) {
on<FieldEditorEvent>( on<FieldEditorEvent>(
(event, emit) async { (event, emit) async {
await event.when( await event.when(
initial: () async { initial: () async {
final fieldContext = GridFieldContext(gridId: gridId, loader: fieldContextLoader); dataController.addFieldListener((field) {
await fieldContext.loadData().then((result) { if (!isClosed) {
result.fold( add(FieldEditorEvent.didReceiveFieldChanged(field));
(l) => emit(state.copyWith(fieldContext: Some(fieldContext), name: fieldContext.field.name)), }
(r) => null,
);
}); });
await dataController.loadData();
}, },
updateName: (name) { updateName: (name) {
state.fieldContext.fold(() => null, (fieldContext) => fieldContext.fieldName = name); dataController.fieldName = name;
emit(state.copyWith(name: name)); emit(state.copyWith(name: name));
}, },
didReceiveFieldChanged: (GridFieldPB field) {
emit(state.copyWith(field: Some(field)));
},
); );
}, },
); );
@ -42,6 +48,7 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
class FieldEditorEvent with _$FieldEditorEvent { class FieldEditorEvent with _$FieldEditorEvent {
const factory FieldEditorEvent.initial() = _InitialField; const factory FieldEditorEvent.initial() = _InitialField;
const factory FieldEditorEvent.updateName(String name) = _UpdateName; const factory FieldEditorEvent.updateName(String name) = _UpdateName;
const factory FieldEditorEvent.didReceiveFieldChanged(GridFieldPB field) = _DidReceiveFieldChanged;
} }
@freezed @freezed
@ -50,13 +57,17 @@ class FieldEditorState with _$FieldEditorState {
required String gridId, required String gridId,
required String errorText, required String errorText,
required String name, required String name,
required Option<GridFieldContext> fieldContext, required Option<GridFieldPB> field,
}) = _FieldEditorState; }) = _FieldEditorState;
factory FieldEditorState.initial(String gridId, String fieldName, IFieldContextLoader loader) => FieldEditorState( factory FieldEditorState.initial(
String gridId,
String fieldName,
) =>
FieldEditorState(
gridId: gridId, gridId: gridId,
fieldContext: none(),
errorText: '', errorText: '',
field: none(),
name: fieldName, name: fieldName,
); );
} }

View File

@ -1,57 +0,0 @@
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'field_service.dart';
part 'field_editor_pannel_bloc.freezed.dart';
class FieldEditorPannelBloc extends Bloc<FieldEditorPannelEvent, FieldEditorPannelState> {
final GridFieldContext _fieldContext;
void Function()? _fieldListenFn;
FieldEditorPannelBloc(GridFieldContext fieldContext)
: _fieldContext = fieldContext,
super(FieldEditorPannelState.initial(fieldContext)) {
on<FieldEditorPannelEvent>(
(event, emit) async {
event.when(
initial: () {
_fieldListenFn = fieldContext.addFieldListener((field) {
add(FieldEditorPannelEvent.didReceiveFieldUpdated(field));
});
},
didReceiveFieldUpdated: (field) {
emit(state.copyWith(field: field));
},
);
},
);
}
@override
Future<void> close() async {
if (_fieldListenFn != null) {
_fieldContext.removeFieldListener(_fieldListenFn!);
}
return super.close();
}
}
@freezed
class FieldEditorPannelEvent with _$FieldEditorPannelEvent {
const factory FieldEditorPannelEvent.initial() = _Initial;
const factory FieldEditorPannelEvent.didReceiveFieldUpdated(Field field) = _DidReceiveFieldUpdated;
}
@freezed
class FieldEditorPannelState with _$FieldEditorPannelState {
const factory FieldEditorPannelState({
required Field field,
}) = _FieldEditorPannelState;
factory FieldEditorPannelState.initial(GridFieldContext fieldContext) => FieldEditorPannelState(
field: fieldContext.field,
);
}

View File

@ -1,13 +1,13 @@
import 'package:app_flowy/core/grid_notification.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.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/dart_notification.pb.dart';
import 'package:flowy_infra/notifier.dart'; import 'package:flowy_infra/notifier.dart';
import 'dart:async'; import 'dart:async';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:app_flowy/core/notification_helper.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
typedef UpdateFieldNotifiedValue = Either<Field, FlowyError>; typedef UpdateFieldNotifiedValue = Either<GridFieldPB, FlowyError>;
class SingleFieldListener { class SingleFieldListener {
final String fieldId; final String fieldId;
@ -31,7 +31,7 @@ class SingleFieldListener {
switch (ty) { switch (ty) {
case GridNotification.DidUpdateField: case GridNotification.DidUpdateField:
result.fold( result.fold(
(payload) => _updateFieldNotifier?.value = left(Field.fromBuffer(payload)), (payload) => _updateFieldNotifier?.value = left(GridFieldPB.fromBuffer(payload)),
(error) => _updateFieldNotifier?.value = right(error), (error) => _updateFieldNotifier?.value = right(error),
); );
break; break;

View File

@ -1,14 +1,19 @@
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:flowy_infra/notifier.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:protobuf/protobuf.dart'; import 'package:protobuf/protobuf.dart';
part 'field_service.freezed.dart'; part 'field_service.freezed.dart';
/// FieldService consists of lots of event functions. We define the events in the backend(Rust),
/// you can find the corresponding event implementation in event_map.rs of the corresponding crate.
///
/// You could check out the rust-lib/flowy-grid/event_map.rs for more information.
class FieldService { class FieldService {
final String gridId; final String gridId;
final String fieldId; final String fieldId;
@ -16,10 +21,10 @@ class FieldService {
FieldService({required this.gridId, required this.fieldId}); FieldService({required this.gridId, required this.fieldId});
Future<Either<Unit, FlowyError>> moveField(int fromIndex, int toIndex) { Future<Either<Unit, FlowyError>> moveField(int fromIndex, int toIndex) {
final payload = MoveItemPayload.create() final payload = MoveItemPayloadPB.create()
..gridId = gridId ..gridId = gridId
..itemId = fieldId ..itemId = fieldId
..ty = MoveItemType.MoveField ..ty = MoveItemTypePB.MoveField
..fromIndex = fromIndex ..fromIndex = fromIndex
..toIndex = toIndex; ..toIndex = toIndex;
@ -34,7 +39,7 @@ class FieldService {
double? width, double? width,
List<int>? typeOptionData, List<int>? typeOptionData,
}) { }) {
var payload = FieldChangesetPayload.create() var payload = FieldChangesetPayloadPB.create()
..gridId = gridId ..gridId = gridId
..fieldId = fieldId; ..fieldId = fieldId;
@ -68,11 +73,11 @@ class FieldService {
// Create the field if it does not exist. Otherwise, update the field. // Create the field if it does not exist. Otherwise, update the field.
static Future<Either<Unit, FlowyError>> insertField({ static Future<Either<Unit, FlowyError>> insertField({
required String gridId, required String gridId,
required Field field, required GridFieldPB field,
List<int>? typeOptionData, List<int>? typeOptionData,
String? startFieldId, String? startFieldId,
}) { }) {
var payload = InsertFieldPayload.create() var payload = InsertFieldPayloadPB.create()
..gridId = gridId ..gridId = gridId
..field_2 = field ..field_2 = field
..typeOptionData = typeOptionData ?? []; ..typeOptionData = typeOptionData ?? [];
@ -89,7 +94,7 @@ class FieldService {
required String fieldId, required String fieldId,
required List<int> typeOptionData, required List<int> typeOptionData,
}) { }) {
var payload = UpdateFieldTypeOptionPayload.create() var payload = UpdateFieldTypeOptionPayloadPB.create()
..gridId = gridId ..gridId = gridId
..fieldId = fieldId ..fieldId = fieldId
..typeOptionData = typeOptionData; ..typeOptionData = typeOptionData;
@ -98,7 +103,7 @@ class FieldService {
} }
Future<Either<Unit, FlowyError>> deleteField() { Future<Either<Unit, FlowyError>> deleteField() {
final payload = FieldIdentifierPayload.create() final payload = GridFieldIdentifierPayloadPB.create()
..gridId = gridId ..gridId = gridId
..fieldId = fieldId; ..fieldId = fieldId;
@ -106,17 +111,17 @@ class FieldService {
} }
Future<Either<Unit, FlowyError>> duplicateField() { Future<Either<Unit, FlowyError>> duplicateField() {
final payload = FieldIdentifierPayload.create() final payload = GridFieldIdentifierPayloadPB.create()
..gridId = gridId ..gridId = gridId
..fieldId = fieldId; ..fieldId = fieldId;
return GridEventDuplicateField(payload).send(); return GridEventDuplicateField(payload).send();
} }
Future<Either<FieldTypeOptionData, FlowyError>> getFieldTypeOptionData({ Future<Either<FieldTypeOptionDataPB, FlowyError>> getFieldTypeOptionData({
required FieldType fieldType, required FieldType fieldType,
}) { }) {
final payload = EditFieldPayload.create() final payload = EditFieldPayloadPB.create()
..gridId = gridId ..gridId = gridId
..fieldId = fieldId ..fieldId = fieldId
..fieldType = fieldType; ..fieldType = fieldType;
@ -133,16 +138,16 @@ class FieldService {
class GridFieldCellContext with _$GridFieldCellContext { class GridFieldCellContext with _$GridFieldCellContext {
const factory GridFieldCellContext({ const factory GridFieldCellContext({
required String gridId, required String gridId,
required Field field, required GridFieldPB field,
}) = _GridFieldCellContext; }) = _GridFieldCellContext;
} }
abstract class IFieldContextLoader { abstract class IFieldTypeOptionLoader {
String get gridId; String get gridId;
Future<Either<FieldTypeOptionData, FlowyError>> load(); Future<Either<FieldTypeOptionDataPB, FlowyError>> load();
Future<Either<FieldTypeOptionData, FlowyError>> switchToField(String fieldId, FieldType fieldType) { Future<Either<FieldTypeOptionDataPB, FlowyError>> switchToField(String fieldId, FieldType fieldType) {
final payload = EditFieldPayload.create() final payload = EditFieldPayloadPB.create()
..gridId = gridId ..gridId = gridId
..fieldId = fieldId ..fieldId = fieldId
..fieldType = fieldType; ..fieldType = fieldType;
@ -151,16 +156,16 @@ abstract class IFieldContextLoader {
} }
} }
class NewFieldContextLoader extends IFieldContextLoader { class NewFieldTypeOptionLoader extends IFieldTypeOptionLoader {
@override @override
final String gridId; final String gridId;
NewFieldContextLoader({ NewFieldTypeOptionLoader({
required this.gridId, required this.gridId,
}); });
@override @override
Future<Either<FieldTypeOptionData, FlowyError>> load() { Future<Either<FieldTypeOptionDataPB, FlowyError>> load() {
final payload = EditFieldPayload.create() final payload = EditFieldPayloadPB.create()
..gridId = gridId ..gridId = gridId
..fieldType = FieldType.RichText; ..fieldType = FieldType.RichText;
@ -168,19 +173,19 @@ class NewFieldContextLoader extends IFieldContextLoader {
} }
} }
class FieldContextLoader extends IFieldContextLoader { class FieldTypeOptionLoader extends IFieldTypeOptionLoader {
@override @override
final String gridId; final String gridId;
final Field field; final GridFieldPB field;
FieldContextLoader({ FieldTypeOptionLoader({
required this.gridId, required this.gridId,
required this.field, required this.field,
}); });
@override @override
Future<Either<FieldTypeOptionData, FlowyError>> load() { Future<Either<FieldTypeOptionDataPB, FlowyError>> load() {
final payload = EditFieldPayload.create() final payload = EditFieldPayloadPB.create()
..gridId = gridId ..gridId = gridId
..fieldId = field.id ..fieldId = field.id
..fieldType = field.fieldType; ..fieldType = field.fieldType;
@ -189,16 +194,16 @@ class FieldContextLoader extends IFieldContextLoader {
} }
} }
class GridFieldContext { class TypeOptionDataController {
final String gridId; final String gridId;
final IFieldContextLoader _loader; final IFieldTypeOptionLoader _loader;
late FieldTypeOptionData _data; late FieldTypeOptionDataPB _data;
ValueNotifier<Field>? _fieldNotifier; final PublishNotifier<GridFieldPB> _fieldNotifier = PublishNotifier();
GridFieldContext({ TypeOptionDataController({
required this.gridId, required this.gridId,
required IFieldContextLoader loader, required IFieldTypeOptionLoader loader,
}) : _loader = loader; }) : _loader = loader;
Future<Either<Unit, FlowyError>> loadData() async { Future<Either<Unit, FlowyError>> loadData() async {
@ -207,13 +212,7 @@ class GridFieldContext {
(data) { (data) {
data.freeze(); data.freeze();
_data = data; _data = data;
_fieldNotifier.value = data.field_2;
if (_fieldNotifier == null) {
_fieldNotifier = ValueNotifier(data.field_2);
} else {
_fieldNotifier?.value = data.field_2;
}
return left(unit); return left(unit);
}, },
(err) { (err) {
@ -223,9 +222,9 @@ class GridFieldContext {
); );
} }
Field get field => _data.field_2; GridFieldPB get field => _data.field_2;
set field(Field field) { set field(GridFieldPB field) {
_updateData(newField: field); _updateData(newField: field);
} }
@ -239,7 +238,7 @@ class GridFieldContext {
_updateData(newTypeOptionData: typeOptionData); _updateData(newTypeOptionData: typeOptionData);
} }
void _updateData({String? newName, Field? newField, List<int>? newTypeOptionData}) { void _updateData({String? newName, GridFieldPB? newField, List<int>? newTypeOptionData}) {
_data = _data.rebuild((rebuildData) { _data = _data.rebuild((rebuildData) {
if (newName != null) { if (newName != null) {
rebuildData.field_2 = rebuildData.field_2.rebuild((rebuildField) { rebuildData.field_2 = rebuildData.field_2.rebuild((rebuildField) {
@ -256,9 +255,7 @@ class GridFieldContext {
} }
}); });
if (_data.field_2 != _fieldNotifier?.value) { _fieldNotifier.value = _data.field_2;
_fieldNotifier?.value = _data.field_2;
}
FieldService.insertField( FieldService.insertField(
gridId: gridId, gridId: gridId,
@ -283,16 +280,16 @@ class GridFieldContext {
}); });
} }
void Function() addFieldListener(void Function(Field) callback) { void Function() addFieldListener(void Function(GridFieldPB) callback) {
listener() { listener() {
callback(field); callback(field);
} }
_fieldNotifier?.addListener(listener); _fieldNotifier.addListener(listener);
return listener; return listener;
} }
void removeFieldListener(void Function() listener) { void removeFieldListener(void Function() listener) {
_fieldNotifier?.removeListener(listener); _fieldNotifier.removeListener(listener);
} }
} }

View File

@ -0,0 +1,57 @@
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'field_service.dart';
part 'field_type_option_edit_bloc.freezed.dart';
class FieldTypeOptionEditBloc extends Bloc<FieldTypeOptionEditEvent, FieldTypeOptionEditState> {
final TypeOptionDataController _dataController;
void Function()? _fieldListenFn;
FieldTypeOptionEditBloc(TypeOptionDataController dataController)
: _dataController = dataController,
super(FieldTypeOptionEditState.initial(dataController)) {
on<FieldTypeOptionEditEvent>(
(event, emit) async {
event.when(
initial: () {
_fieldListenFn = dataController.addFieldListener((field) {
add(FieldTypeOptionEditEvent.didReceiveFieldUpdated(field));
});
},
didReceiveFieldUpdated: (field) {
emit(state.copyWith(field: field));
},
);
},
);
}
@override
Future<void> close() async {
if (_fieldListenFn != null) {
_dataController.removeFieldListener(_fieldListenFn!);
}
return super.close();
}
}
@freezed
class FieldTypeOptionEditEvent with _$FieldTypeOptionEditEvent {
const factory FieldTypeOptionEditEvent.initial() = _Initial;
const factory FieldTypeOptionEditEvent.didReceiveFieldUpdated(GridFieldPB field) = _DidReceiveFieldUpdated;
}
@freezed
class FieldTypeOptionEditState with _$FieldTypeOptionEditState {
const factory FieldTypeOptionEditState({
required GridFieldPB field,
}) = _FieldTypeOptionEditState;
factory FieldTypeOptionEditState.initial(TypeOptionDataController fieldContext) => FieldTypeOptionEditState(
field: fieldContext.field,
);
}

View File

@ -1,13 +1,13 @@
import 'package:app_flowy/core/grid_notification.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.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/dart_notification.pb.dart';
import 'package:flowy_infra/notifier.dart'; import 'package:flowy_infra/notifier.dart';
import 'dart:async'; import 'dart:async';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:app_flowy/core/notification_helper.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
typedef UpdateFieldNotifiedValue = Either<GridFieldChangeset, FlowyError>; typedef UpdateFieldNotifiedValue = Either<GridFieldChangesetPB, FlowyError>;
class GridFieldsListener { class GridFieldsListener {
final String gridId; final String gridId;
@ -27,7 +27,7 @@ class GridFieldsListener {
switch (ty) { switch (ty) {
case GridNotification.DidUpdateGridField: case GridNotification.DidUpdateGridField:
result.fold( result.fold(
(payload) => updateFieldsNotifier?.value = left(GridFieldChangeset.fromBuffer(payload)), (payload) => updateFieldsNotifier?.value = left(GridFieldChangesetPB.fromBuffer(payload)),
(error) => updateFieldsNotifier?.value = right(error), (error) => updateFieldsNotifier?.value = right(error),
); );
break; break;

View File

@ -1,14 +1,15 @@
import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart'; import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
import 'package:protobuf/protobuf.dart'; import 'package:protobuf/protobuf.dart';
part 'date_bloc.freezed.dart'; part 'date_bloc.freezed.dart';
typedef DateTypeOptionContext = TypeOptionContext<DateTypeOption>; typedef DateTypeOptionContext = TypeOptionWidgetContext<DateTypeOption>;
class DateTypeOptionDataBuilder extends TypeOptionDataBuilder<DateTypeOption> { class DateTypeOptionDataParser extends TypeOptionDataParser<DateTypeOption> {
@override @override
DateTypeOption fromBuffer(List<int> buffer) { DateTypeOption fromBuffer(List<int> buffer) {
return DateTypeOption.fromBuffer(buffer); return DateTypeOption.fromBuffer(buffer);

View File

@ -1,4 +1,4 @@
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
@ -7,7 +7,7 @@ import 'package:dartz/dartz.dart';
part 'edit_select_option_bloc.freezed.dart'; part 'edit_select_option_bloc.freezed.dart';
class EditSelectOptionBloc extends Bloc<EditSelectOptionEvent, EditSelectOptionState> { class EditSelectOptionBloc extends Bloc<EditSelectOptionEvent, EditSelectOptionState> {
EditSelectOptionBloc({required SelectOption option}) : super(EditSelectOptionState.initial(option)) { EditSelectOptionBloc({required SelectOptionPB option}) : super(EditSelectOptionState.initial(option)) {
on<EditSelectOptionEvent>( on<EditSelectOptionEvent>(
(event, emit) async { (event, emit) async {
event.map( event.map(
@ -30,14 +30,14 @@ class EditSelectOptionBloc extends Bloc<EditSelectOptionEvent, EditSelectOptionS
return super.close(); return super.close();
} }
SelectOption _updateColor(SelectOptionColor color) { SelectOptionPB _updateColor(SelectOptionColorPB color) {
state.option.freeze(); state.option.freeze();
return state.option.rebuild((option) { return state.option.rebuild((option) {
option.color = color; option.color = color;
}); });
} }
SelectOption _updateName(String name) { SelectOptionPB _updateName(String name) {
state.option.freeze(); state.option.freeze();
return state.option.rebuild((option) { return state.option.rebuild((option) {
option.name = name; option.name = name;
@ -48,18 +48,18 @@ class EditSelectOptionBloc extends Bloc<EditSelectOptionEvent, EditSelectOptionS
@freezed @freezed
class EditSelectOptionEvent with _$EditSelectOptionEvent { class EditSelectOptionEvent with _$EditSelectOptionEvent {
const factory EditSelectOptionEvent.updateName(String name) = _UpdateName; const factory EditSelectOptionEvent.updateName(String name) = _UpdateName;
const factory EditSelectOptionEvent.updateColor(SelectOptionColor color) = _UpdateColor; const factory EditSelectOptionEvent.updateColor(SelectOptionColorPB color) = _UpdateColor;
const factory EditSelectOptionEvent.delete() = _Delete; const factory EditSelectOptionEvent.delete() = _Delete;
} }
@freezed @freezed
class EditSelectOptionState with _$EditSelectOptionState { class EditSelectOptionState with _$EditSelectOptionState {
const factory EditSelectOptionState({ const factory EditSelectOptionState({
required SelectOption option, required SelectOptionPB option,
required Option<bool> deleted, required Option<bool> deleted,
}) = _EditSelectOptionState; }) = _EditSelectOptionState;
factory EditSelectOptionState.initial(SelectOption option) => EditSelectOptionState( factory EditSelectOptionState.initial(SelectOptionPB option) => EditSelectOptionState(
option: option, option: option,
deleted: none(), deleted: none(),
); );

View File

@ -1,26 +1,28 @@
import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/multi_select_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
import 'dart:async'; import 'dart:async';
import 'package:protobuf/protobuf.dart'; import 'package:protobuf/protobuf.dart';
import 'select_option_type_option_bloc.dart'; import 'select_option_type_option_bloc.dart';
import 'type_option_service.dart'; import 'type_option_service.dart';
class MultiSelectTypeOptionContext extends TypeOptionContext<MultiSelectTypeOption> with SelectOptionTypeOptionAction { class MultiSelectTypeOptionContext extends TypeOptionWidgetContext<MultiSelectTypeOption>
with SelectOptionTypeOptionAction {
final TypeOptionService service; final TypeOptionService service;
MultiSelectTypeOptionContext({ MultiSelectTypeOptionContext({
required MultiSelectTypeOptionDataBuilder dataBuilder, required MultiSelectTypeOptionWidgetDataParser dataBuilder,
required GridFieldContext fieldContext, required TypeOptionDataController dataController,
}) : service = TypeOptionService( }) : service = TypeOptionService(
gridId: fieldContext.gridId, gridId: dataController.gridId,
fieldId: fieldContext.field.id, fieldId: dataController.field.id,
), ),
super(dataBuilder: dataBuilder, fieldContext: fieldContext); super(dataParser: dataBuilder, dataController: dataController);
@override @override
List<SelectOption> Function(SelectOption) get deleteOption { List<SelectOptionPB> Function(SelectOptionPB) get deleteOption {
return (SelectOption option) { return (SelectOptionPB option) {
typeOption.freeze(); typeOption.freeze();
typeOption = typeOption.rebuild((typeOption) { typeOption = typeOption.rebuild((typeOption) {
final index = typeOption.options.indexWhere((element) => element.id == option.id); final index = typeOption.options.indexWhere((element) => element.id == option.id);
@ -33,7 +35,7 @@ class MultiSelectTypeOptionContext extends TypeOptionContext<MultiSelectTypeOpti
} }
@override @override
Future<List<SelectOption>> Function(String) get insertOption { Future<List<SelectOptionPB>> Function(String) get insertOption {
return (String optionName) { return (String optionName) {
return service.newOption(name: optionName).then((result) { return service.newOption(name: optionName).then((result) {
return result.fold( return result.fold(
@ -55,8 +57,8 @@ class MultiSelectTypeOptionContext extends TypeOptionContext<MultiSelectTypeOpti
} }
@override @override
List<SelectOption> Function(SelectOption) get udpateOption { List<SelectOptionPB> Function(SelectOptionPB) get udpateOption {
return (SelectOption option) { return (SelectOptionPB option) {
typeOption.freeze(); typeOption.freeze();
typeOption = typeOption.rebuild((typeOption) { typeOption = typeOption.rebuild((typeOption) {
final index = typeOption.options.indexWhere((element) => element.id == option.id); final index = typeOption.options.indexWhere((element) => element.id == option.id);
@ -69,7 +71,7 @@ class MultiSelectTypeOptionContext extends TypeOptionContext<MultiSelectTypeOpti
} }
} }
class MultiSelectTypeOptionDataBuilder extends TypeOptionDataBuilder<MultiSelectTypeOption> { class MultiSelectTypeOptionWidgetDataParser extends TypeOptionDataParser<MultiSelectTypeOption> {
@override @override
MultiSelectTypeOption fromBuffer(List<int> buffer) { MultiSelectTypeOption fromBuffer(List<int> buffer) {
return MultiSelectTypeOption.fromBuffer(buffer); return MultiSelectTypeOption.fromBuffer(buffer);

View File

@ -8,9 +8,9 @@ import 'package:protobuf/protobuf.dart';
part 'number_bloc.freezed.dart'; part 'number_bloc.freezed.dart';
typedef NumberTypeOptionContext = TypeOptionContext<NumberTypeOption>; typedef NumberTypeOptionContext = TypeOptionWidgetContext<NumberTypeOption>;
class NumberTypeOptionDataBuilder extends TypeOptionDataBuilder<NumberTypeOption> { class NumberTypeOptionWidgetDataParser extends TypeOptionDataParser<NumberTypeOption> {
@override @override
NumberTypeOption fromBuffer(List<int> buffer) { NumberTypeOption fromBuffer(List<int> buffer) {
return NumberTypeOption.fromBuffer(buffer); return NumberTypeOption.fromBuffer(buffer);

View File

@ -86,7 +86,7 @@ extension NumberFormatExtension on NumberFormat {
return "New Zealand dollar"; return "New Zealand dollar";
case NumberFormat.NorwegianKrone: case NumberFormat.NorwegianKrone:
return "Norwegian krone"; return "Norwegian krone";
case NumberFormat.Number: case NumberFormat.Num:
return "Number"; return "Number";
case NumberFormat.Percent: case NumberFormat.Percent:
return "Percent"; return "Percent";

View File

@ -1,4 +1,4 @@
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
@ -6,25 +6,25 @@ import 'package:dartz/dartz.dart';
part 'select_option_type_option_bloc.freezed.dart'; part 'select_option_type_option_bloc.freezed.dart';
abstract class SelectOptionTypeOptionAction { abstract class SelectOptionTypeOptionAction {
Future<List<SelectOption>> Function(String) get insertOption; Future<List<SelectOptionPB>> Function(String) get insertOption;
List<SelectOption> Function(SelectOption) get deleteOption; List<SelectOptionPB> Function(SelectOptionPB) get deleteOption;
List<SelectOption> Function(SelectOption) get udpateOption; List<SelectOptionPB> Function(SelectOptionPB) get udpateOption;
} }
class SelectOptionTypeOptionBloc extends Bloc<SelectOptionTypeOptionEvent, SelectOptionTypeOptionState> { class SelectOptionTypeOptionBloc extends Bloc<SelectOptionTypeOptionEvent, SelectOptionTypeOptionState> {
final SelectOptionTypeOptionAction typeOptionAction; final SelectOptionTypeOptionAction typeOptionAction;
SelectOptionTypeOptionBloc({ SelectOptionTypeOptionBloc({
required List<SelectOption> options, required List<SelectOptionPB> options,
required this.typeOptionAction, required this.typeOptionAction,
}) : super(SelectOptionTypeOptionState.initial(options)) { }) : super(SelectOptionTypeOptionState.initial(options)) {
on<SelectOptionTypeOptionEvent>( on<SelectOptionTypeOptionEvent>(
(event, emit) async { (event, emit) async {
await event.when( await event.when(
createOption: (optionName) async { createOption: (optionName) async {
final List<SelectOption> options = await typeOptionAction.insertOption(optionName); final List<SelectOptionPB> options = await typeOptionAction.insertOption(optionName);
emit(state.copyWith(options: options)); emit(state.copyWith(options: options));
}, },
addingOption: () { addingOption: () {
@ -34,11 +34,11 @@ class SelectOptionTypeOptionBloc extends Bloc<SelectOptionTypeOptionEvent, Selec
emit(state.copyWith(isEditingOption: false, newOptionName: none())); emit(state.copyWith(isEditingOption: false, newOptionName: none()));
}, },
updateOption: (option) { updateOption: (option) {
final List<SelectOption> options = typeOptionAction.udpateOption(option); final List<SelectOptionPB> options = typeOptionAction.udpateOption(option);
emit(state.copyWith(options: options)); emit(state.copyWith(options: options));
}, },
deleteOption: (option) { deleteOption: (option) {
final List<SelectOption> options = typeOptionAction.deleteOption(option); final List<SelectOptionPB> options = typeOptionAction.deleteOption(option);
emit(state.copyWith(options: options)); emit(state.copyWith(options: options));
}, },
); );
@ -57,19 +57,19 @@ class SelectOptionTypeOptionEvent with _$SelectOptionTypeOptionEvent {
const factory SelectOptionTypeOptionEvent.createOption(String optionName) = _CreateOption; const factory SelectOptionTypeOptionEvent.createOption(String optionName) = _CreateOption;
const factory SelectOptionTypeOptionEvent.addingOption() = _AddingOption; const factory SelectOptionTypeOptionEvent.addingOption() = _AddingOption;
const factory SelectOptionTypeOptionEvent.endAddingOption() = _EndAddingOption; const factory SelectOptionTypeOptionEvent.endAddingOption() = _EndAddingOption;
const factory SelectOptionTypeOptionEvent.updateOption(SelectOption option) = _UpdateOption; const factory SelectOptionTypeOptionEvent.updateOption(SelectOptionPB option) = _UpdateOption;
const factory SelectOptionTypeOptionEvent.deleteOption(SelectOption option) = _DeleteOption; const factory SelectOptionTypeOptionEvent.deleteOption(SelectOptionPB option) = _DeleteOption;
} }
@freezed @freezed
class SelectOptionTypeOptionState with _$SelectOptionTypeOptionState { class SelectOptionTypeOptionState with _$SelectOptionTypeOptionState {
const factory SelectOptionTypeOptionState({ const factory SelectOptionTypeOptionState({
required List<SelectOption> options, required List<SelectOptionPB> options,
required bool isEditingOption, required bool isEditingOption,
required Option<String> newOptionName, required Option<String> newOptionName,
}) = _SelectOptionTyepOptionState; }) = _SelectOptionTyepOptionState;
factory SelectOptionTypeOptionState.initial(List<SelectOption> options) => SelectOptionTypeOptionState( factory SelectOptionTypeOptionState.initial(List<SelectOptionPB> options) => SelectOptionTypeOptionState(
options: options, options: options,
isEditingOption: false, isEditingOption: false,
newOptionName: none(), newOptionName: none(),

View File

@ -1,27 +1,28 @@
import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/single_select_type_option.pb.dart';
import 'dart:async'; import 'dart:async';
import 'package:protobuf/protobuf.dart'; import 'package:protobuf/protobuf.dart';
import 'select_option_type_option_bloc.dart'; import 'select_option_type_option_bloc.dart';
import 'type_option_service.dart'; import 'type_option_service.dart';
class SingleSelectTypeOptionContext extends TypeOptionContext<SingleSelectTypeOption> class SingleSelectTypeOptionContext extends TypeOptionWidgetContext<SingleSelectTypeOptionPB>
with SelectOptionTypeOptionAction { with SelectOptionTypeOptionAction {
final TypeOptionService service; final TypeOptionService service;
SingleSelectTypeOptionContext({ SingleSelectTypeOptionContext({
required SingleSelectTypeOptionDataBuilder dataBuilder, required SingleSelectTypeOptionWidgetDataParser dataBuilder,
required GridFieldContext fieldContext, required TypeOptionDataController fieldContext,
}) : service = TypeOptionService( }) : service = TypeOptionService(
gridId: fieldContext.gridId, gridId: fieldContext.gridId,
fieldId: fieldContext.field.id, fieldId: fieldContext.field.id,
), ),
super(dataBuilder: dataBuilder, fieldContext: fieldContext); super(dataParser: dataBuilder, dataController: fieldContext);
@override @override
List<SelectOption> Function(SelectOption) get deleteOption { List<SelectOptionPB> Function(SelectOptionPB) get deleteOption {
return (SelectOption option) { return (SelectOptionPB option) {
typeOption.freeze(); typeOption.freeze();
typeOption = typeOption.rebuild((typeOption) { typeOption = typeOption.rebuild((typeOption) {
final index = typeOption.options.indexWhere((element) => element.id == option.id); final index = typeOption.options.indexWhere((element) => element.id == option.id);
@ -34,7 +35,7 @@ class SingleSelectTypeOptionContext extends TypeOptionContext<SingleSelectTypeOp
} }
@override @override
Future<List<SelectOption>> Function(String) get insertOption { Future<List<SelectOptionPB>> Function(String) get insertOption {
return (String optionName) { return (String optionName) {
return service.newOption(name: optionName).then((result) { return service.newOption(name: optionName).then((result) {
return result.fold( return result.fold(
@ -56,8 +57,8 @@ class SingleSelectTypeOptionContext extends TypeOptionContext<SingleSelectTypeOp
} }
@override @override
List<SelectOption> Function(SelectOption) get udpateOption { List<SelectOptionPB> Function(SelectOptionPB) get udpateOption {
return (SelectOption option) { return (SelectOptionPB option) {
typeOption.freeze(); typeOption.freeze();
typeOption = typeOption.rebuild((typeOption) { typeOption = typeOption.rebuild((typeOption) {
final index = typeOption.options.indexWhere((element) => element.id == option.id); final index = typeOption.options.indexWhere((element) => element.id == option.id);
@ -70,9 +71,9 @@ class SingleSelectTypeOptionContext extends TypeOptionContext<SingleSelectTypeOp
} }
} }
class SingleSelectTypeOptionDataBuilder extends TypeOptionDataBuilder<SingleSelectTypeOption> { class SingleSelectTypeOptionWidgetDataParser extends TypeOptionDataParser<SingleSelectTypeOptionPB> {
@override @override
SingleSelectTypeOption fromBuffer(List<int> buffer) { SingleSelectTypeOptionPB fromBuffer(List<int> buffer) {
return SingleSelectTypeOption.fromBuffer(buffer); return SingleSelectTypeOptionPB.fromBuffer(buffer);
} }
} }

View File

@ -4,10 +4,9 @@ import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
import 'package:protobuf/protobuf.dart'; import 'package:protobuf/protobuf.dart';
class TypeOptionService { class TypeOptionService {
@ -19,14 +18,14 @@ class TypeOptionService {
required this.fieldId, required this.fieldId,
}); });
Future<Either<SelectOption, FlowyError>> newOption({ Future<Either<SelectOptionPB, FlowyError>> newOption({
required String name, required String name,
}) { }) {
final fieldIdentifier = FieldIdentifierPayload.create() final fieldIdentifier = GridFieldIdentifierPayloadPB.create()
..gridId = gridId ..gridId = gridId
..fieldId = fieldId; ..fieldId = fieldId;
final payload = CreateSelectOptionPayload.create() final payload = CreateSelectOptionPayloadPB.create()
..optionName = name ..optionName = name
..fieldIdentifier = fieldIdentifier; ..fieldIdentifier = fieldIdentifier;
@ -34,36 +33,36 @@ class TypeOptionService {
} }
} }
abstract class TypeOptionDataBuilder<T> { abstract class TypeOptionDataParser<T> {
T fromBuffer(List<int> buffer); T fromBuffer(List<int> buffer);
} }
class TypeOptionContext<T extends GeneratedMessage> { class TypeOptionWidgetContext<T extends GeneratedMessage> {
T? _typeOptionObject; T? _typeOptionObject;
final GridFieldContext _fieldContext; final TypeOptionDataController _dataController;
final TypeOptionDataBuilder<T> dataBuilder; final TypeOptionDataParser<T> dataParser;
TypeOptionContext({ TypeOptionWidgetContext({
required this.dataBuilder, required this.dataParser,
required GridFieldContext fieldContext, required TypeOptionDataController dataController,
}) : _fieldContext = fieldContext; }) : _dataController = dataController;
String get gridId => _fieldContext.gridId; String get gridId => _dataController.gridId;
Field get field => _fieldContext.field; GridFieldPB get field => _dataController.field;
T get typeOption { T get typeOption {
if (_typeOptionObject != null) { if (_typeOptionObject != null) {
return _typeOptionObject!; return _typeOptionObject!;
} }
final T object = dataBuilder.fromBuffer(_fieldContext.typeOptionData); final T object = dataParser.fromBuffer(_dataController.typeOptionData);
_typeOptionObject = object; _typeOptionObject = object;
return object; return object;
} }
set typeOption(T typeOption) { set typeOption(T typeOption) {
_fieldContext.typeOptionData = typeOption.writeToBuffer(); _dataController.typeOptionData = typeOption.writeToBuffer();
_typeOptionObject = typeOption; _typeOptionObject = typeOption;
} }
} }
@ -75,10 +74,10 @@ abstract class TypeOptionFieldDelegate {
class TypeOptionContext2<T> { class TypeOptionContext2<T> {
final String gridId; final String gridId;
final Field field; final GridFieldPB field;
final FieldService _fieldService; final FieldService _fieldService;
T? _data; T? _data;
final TypeOptionDataBuilder dataBuilder; final TypeOptionDataParser dataBuilder;
TypeOptionContext2({ TypeOptionContext2({
required this.gridId, required this.gridId,

View File

@ -1,12 +1,13 @@
import 'dart:async'; import 'dart:async';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.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-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/protobuf.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'cell/cell_service/cell_service.dart'; import 'block/block_cache.dart';
import 'grid_service.dart'; import 'grid_service.dart';
import 'row/row_service.dart'; import 'row/row_service.dart';
import 'dart:collection'; import 'dart:collection';
@ -14,25 +15,27 @@ import 'dart:collection';
part 'grid_bloc.freezed.dart'; part 'grid_bloc.freezed.dart';
class GridBloc extends Bloc<GridEvent, GridState> { class GridBloc extends Bloc<GridEvent, GridState> {
final String gridId;
final GridService _gridService; final GridService _gridService;
final GridFieldCache fieldCache; final GridFieldCache fieldCache;
late final GridRowCache rowCache;
late final GridCellCache cellCache;
GridBloc({required View view}) // key: the block id
: _gridService = GridService(gridId: view.id), final LinkedHashMap<String, GridBlockCache> _blocks;
List<GridRowInfo> get rowInfos {
final List<GridRowInfo> rows = [];
for (var block in _blocks.values) {
rows.addAll(block.rows);
}
return rows;
}
GridBloc({required ViewPB view})
: gridId = view.id,
_blocks = LinkedHashMap.identity(),
_gridService = GridService(gridId: view.id),
fieldCache = GridFieldCache(gridId: view.id), fieldCache = GridFieldCache(gridId: view.id),
super(GridState.initial(view.id)) { super(GridState.initial(view.id)) {
rowCache = GridRowCache(
gridId: view.id,
fieldDelegate: GridRowCacheDelegateImpl(fieldCache),
);
cellCache = GridCellCache(
gridId: view.id,
fieldDelegate: GridCellCacheDelegateImpl(fieldCache),
);
on<GridEvent>( on<GridEvent>(
(event, emit) async { (event, emit) async {
await event.when( await event.when(
@ -43,11 +46,11 @@ class GridBloc extends Bloc<GridEvent, GridState> {
createRow: () { createRow: () {
_gridService.createRow(); _gridService.createRow();
}, },
didReceiveRowUpdate: (rows, listState) { didReceiveRowUpdate: (newRowInfos, reason) {
emit(state.copyWith(rows: rows, listState: listState)); emit(state.copyWith(rowInfos: newRowInfos, reason: reason));
}, },
didReceiveFieldUpdate: (fields) { didReceiveFieldUpdate: (fields) {
emit(state.copyWith(rows: rowCache.clonedRows, fields: GridFieldEquatable(fields))); emit(state.copyWith(rowInfos: rowInfos, fields: GridFieldEquatable(fields)));
}, },
); );
}, },
@ -57,21 +60,23 @@ class GridBloc extends Bloc<GridEvent, GridState> {
@override @override
Future<void> close() async { Future<void> close() async {
await _gridService.closeGrid(); await _gridService.closeGrid();
await cellCache.dispose();
await rowCache.dispose();
await fieldCache.dispose(); await fieldCache.dispose();
for (final blockCache in _blocks.values) {
blockCache.dispose();
}
return super.close(); return super.close();
} }
GridRowCache? getRowCache(String blockId, String rowId) {
final GridBlockCache? blockCache = _blocks[blockId];
return blockCache?.rowCache;
}
void _startListening() { void _startListening() {
fieldCache.addListener( fieldCache.addListener(
listenWhen: () => !isClosed, listenWhen: () => !isClosed,
onChanged: (fields) => add(GridEvent.didReceiveFieldUpdate(fields)), onFields: (fields) => add(GridEvent.didReceiveFieldUpdate(fields)),
);
rowCache.addListener(
listenWhen: () => !isClosed,
onChanged: (rows, listState) => add(GridEvent.didReceiveRowUpdate(rowCache.clonedRows, listState)),
); );
} }
@ -79,24 +84,26 @@ class GridBloc extends Bloc<GridEvent, GridState> {
final result = await _gridService.loadGrid(); final result = await _gridService.loadGrid();
return Future( return Future(
() => result.fold( () => result.fold(
(grid) async => await _loadFields(grid, emit), (grid) async {
_initialBlocks(grid.blocks);
await _loadFields(grid, emit);
},
(err) => emit(state.copyWith(loadingState: GridLoadingState.finish(right(err)))), (err) => emit(state.copyWith(loadingState: GridLoadingState.finish(right(err)))),
), ),
); );
} }
Future<void> _loadFields(Grid grid, Emitter<GridState> emit) async { Future<void> _loadFields(GridPB grid, Emitter<GridState> emit) async {
final result = await _gridService.getFields(fieldOrders: grid.fieldOrders); final result = await _gridService.getFields(fieldIds: grid.fields);
return Future( return Future(
() => result.fold( () => result.fold(
(fields) { (fields) {
fieldCache.fields = fields.items; fieldCache.fields = fields.items;
rowCache.resetRows(grid.blockOrders);
emit(state.copyWith( emit(state.copyWith(
grid: Some(grid), grid: Some(grid),
fields: GridFieldEquatable(fieldCache.fields), fields: GridFieldEquatable(fieldCache.fields),
rows: rowCache.clonedRows, rowInfos: rowInfos,
loadingState: GridLoadingState.finish(left(unit)), loadingState: GridLoadingState.finish(left(unit)),
)); ));
}, },
@ -104,34 +111,57 @@ class GridBloc extends Bloc<GridEvent, GridState> {
), ),
); );
} }
void _initialBlocks(List<GridBlockPB> blocks) {
for (final block in blocks) {
if (_blocks[block.id] != null) {
Log.warn("Intial duplicate block's cache: ${block.id}");
return;
}
final cache = GridBlockCache(
gridId: gridId,
block: block,
fieldCache: fieldCache,
);
cache.addListener(
listenWhen: () => !isClosed,
onChangeReason: (reason) => add(GridEvent.didReceiveRowUpdate(rowInfos, reason)),
);
_blocks[block.id] = cache;
}
}
} }
@freezed @freezed
class GridEvent with _$GridEvent { class GridEvent with _$GridEvent {
const factory GridEvent.initial() = InitialGrid; const factory GridEvent.initial() = InitialGrid;
const factory GridEvent.createRow() = _CreateRow; const factory GridEvent.createRow() = _CreateRow;
const factory GridEvent.didReceiveRowUpdate(List<GridRow> rows, GridRowChangeReason listState) = _DidReceiveRowUpdate; const factory GridEvent.didReceiveRowUpdate(List<GridRowInfo> rows, GridRowChangeReason listState) =
const factory GridEvent.didReceiveFieldUpdate(List<Field> fields) = _DidReceiveFieldUpdate; _DidReceiveRowUpdate;
const factory GridEvent.didReceiveFieldUpdate(List<GridFieldPB> fields) = _DidReceiveFieldUpdate;
} }
@freezed @freezed
class GridState with _$GridState { class GridState with _$GridState {
const factory GridState({ const factory GridState({
required String gridId, required String gridId,
required Option<Grid> grid, required Option<GridPB> grid,
required GridFieldEquatable fields, required GridFieldEquatable fields,
required List<GridRow> rows, required List<GridRowInfo> rowInfos,
required GridLoadingState loadingState, required GridLoadingState loadingState,
required GridRowChangeReason listState, required GridRowChangeReason reason,
}) = _GridState; }) = _GridState;
factory GridState.initial(String gridId) => GridState( factory GridState.initial(String gridId) => GridState(
fields: const GridFieldEquatable([]), fields: const GridFieldEquatable([]),
rows: [], rowInfos: [],
grid: none(), grid: none(),
gridId: gridId, gridId: gridId,
loadingState: const _Loading(), loadingState: const _Loading(),
listState: const InitialListState(), reason: const InitialListState(),
); );
} }
@ -142,9 +172,8 @@ class GridLoadingState with _$GridLoadingState {
} }
class GridFieldEquatable extends Equatable { class GridFieldEquatable extends Equatable {
final List<Field> _fields; final List<GridFieldPB> _fields;
const GridFieldEquatable(List<GridFieldPB> fields) : _fields = fields;
const GridFieldEquatable(List<Field> fields) : _fields = fields;
@override @override
List<Object?> get props { List<Object?> get props {
@ -154,5 +183,5 @@ class GridFieldEquatable extends Equatable {
]; ];
} }
UnmodifiableListView<Field> get value => UnmodifiableListView(_fields); UnmodifiableListView<GridFieldPB> get value => UnmodifiableListView(_fields);
} }

View File

@ -1,6 +1,6 @@
import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
@ -34,7 +34,7 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
} }
Future<void> _moveField(_MoveField value, Emitter<GridHeaderState> emit) async { Future<void> _moveField(_MoveField value, Emitter<GridHeaderState> emit) async {
final fields = List<Field>.from(state.fields); final fields = List<GridFieldPB>.from(state.fields);
fields.insert(value.toIndex, fields.removeAt(value.fromIndex)); fields.insert(value.toIndex, fields.removeAt(value.fromIndex));
emit(state.copyWith(fields: fields)); emit(state.copyWith(fields: fields));
@ -48,7 +48,7 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
Future<void> _startListening() async { Future<void> _startListening() async {
fieldCache.addListener( fieldCache.addListener(
onChanged: (fields) => add(GridHeaderEvent.didReceiveFieldUpdate(fields)), onFields: (fields) => add(GridHeaderEvent.didReceiveFieldUpdate(fields)),
listenWhen: () => !isClosed, listenWhen: () => !isClosed,
); );
} }
@ -62,16 +62,16 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
@freezed @freezed
class GridHeaderEvent with _$GridHeaderEvent { class GridHeaderEvent with _$GridHeaderEvent {
const factory GridHeaderEvent.initial() = _InitialHeader; const factory GridHeaderEvent.initial() = _InitialHeader;
const factory GridHeaderEvent.didReceiveFieldUpdate(List<Field> fields) = _DidReceiveFieldUpdate; const factory GridHeaderEvent.didReceiveFieldUpdate(List<GridFieldPB> fields) = _DidReceiveFieldUpdate;
const factory GridHeaderEvent.moveField(Field field, int fromIndex, int toIndex) = _MoveField; const factory GridHeaderEvent.moveField(GridFieldPB field, int fromIndex, int toIndex) = _MoveField;
} }
@freezed @freezed
class GridHeaderState with _$GridHeaderState { class GridHeaderState with _$GridHeaderState {
const factory GridHeaderState({required List<Field> fields}) = _GridHeaderState; const factory GridHeaderState({required List<GridFieldPB> fields}) = _GridHeaderState;
factory GridHeaderState.initial(List<Field> fields) { factory GridHeaderState.initial(List<GridFieldPB> fields) {
// final List<Field> newFields = List.from(fields); // final List<GridFieldPB> newFields = List.from(fields);
// newFields.retainWhere((field) => field.visibility); // newFields.retainWhere((field) => field.visibility);
return GridHeaderState(fields: fields); return GridHeaderState(fields: fields);
} }

View File

@ -1,42 +0,0 @@
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.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_infra/notifier.dart';
import 'dart:async';
import 'dart:typed_data';
import 'package:app_flowy/core/notification_helper.dart';
class GridRowListener {
final String gridId;
PublishNotifier<Either<List<GridRowsChangeset>, FlowyError>> rowsUpdateNotifier = PublishNotifier(comparable: null);
GridNotificationListener? _listener;
GridRowListener({required this.gridId});
void start() {
_listener = GridNotificationListener(
objectId: gridId,
handler: _handler,
);
}
void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) {
switch (ty) {
case GridNotification.DidUpdateGridRow:
result.fold(
(payload) => rowsUpdateNotifier.value = left([GridRowsChangeset.fromBuffer(payload)]),
(error) => rowsUpdateNotifier.value = right(error),
);
break;
default:
break;
}
}
Future<void> stop() async {
await _listener?.stop();
rowsUpdateNotifier.dispose();
}
}

View File

@ -5,10 +5,12 @@ import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.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/row_entities.pb.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'cell/cell_service/cell_service.dart';
import 'row/row_service.dart'; import 'row/row_service.dart';
class GridService { class GridService {
@ -17,60 +19,61 @@ class GridService {
required this.gridId, required this.gridId,
}); });
Future<Either<Grid, FlowyError>> loadGrid() async { Future<Either<GridPB, FlowyError>> loadGrid() async {
await FolderEventSetLatestView(ViewId(value: gridId)).send(); await FolderEventSetLatestView(ViewIdPB(value: gridId)).send();
final payload = GridId(value: gridId); final payload = GridIdPB(value: gridId);
return GridEventGetGridData(payload).send(); return GridEventGetGrid(payload).send();
} }
Future<Either<Row, FlowyError>> createRow({Option<String>? startRowId}) { Future<Either<GridRowPB, FlowyError>> createRow({Option<String>? startRowId}) {
CreateRowPayload payload = CreateRowPayload.create()..gridId = gridId; CreateRowPayloadPB payload = CreateRowPayloadPB.create()..gridId = gridId;
startRowId?.fold(() => null, (id) => payload.startRowId = id); startRowId?.fold(() => null, (id) => payload.startRowId = id);
return GridEventCreateRow(payload).send(); return GridEventCreateRow(payload).send();
} }
Future<Either<RepeatedField, FlowyError>> getFields({required List<FieldOrder> fieldOrders}) { Future<Either<RepeatedGridFieldPB, FlowyError>> getFields({required List<GridFieldIdPB> fieldIds}) {
final payload = QueryFieldPayload.create() final payload = QueryFieldPayloadPB.create()
..gridId = gridId ..gridId = gridId
..fieldOrders = RepeatedFieldOrder(items: fieldOrders); ..fieldIds = RepeatedGridFieldIdPB(items: fieldIds);
return GridEventGetFields(payload).send(); return GridEventGetFields(payload).send();
} }
Future<Either<Unit, FlowyError>> closeGrid() { Future<Either<Unit, FlowyError>> closeGrid() {
final request = ViewId(value: gridId); final request = ViewIdPB(value: gridId);
return FolderEventCloseView(request).send(); return FolderEventCloseView(request).send();
} }
} }
class FieldsNotifier extends ChangeNotifier { class FieldsNotifier extends ChangeNotifier {
List<Field> _fields = []; List<GridFieldPB> _fields = [];
set fields(List<Field> fields) { set fields(List<GridFieldPB> fields) {
_fields = fields; _fields = fields;
notifyListeners(); notifyListeners();
} }
List<Field> get fields => _fields; List<GridFieldPB> get fields => _fields;
} }
typedef ChangesetListener = void Function(GridFieldChangeset); typedef FieldChangesetCallback = void Function(GridFieldChangesetPB);
typedef FieldsCallback = void Function(List<GridFieldPB>);
class GridFieldCache { class GridFieldCache {
final String gridId; final String gridId;
late final GridFieldsListener _fieldListener; final GridFieldsListener _fieldListener;
FieldsNotifier? _fieldNotifier = FieldsNotifier(); FieldsNotifier? _fieldNotifier = FieldsNotifier();
final List<ChangesetListener> _changesetListener = []; final Map<FieldsCallback, VoidCallback> _fieldsCallbackMap = {};
final Map<FieldChangesetCallback, FieldChangesetCallback> _changesetCallbackMap = {};
GridFieldCache({required this.gridId}) { GridFieldCache({required this.gridId}) : _fieldListener = GridFieldsListener(gridId: gridId) {
_fieldListener = GridFieldsListener(gridId: gridId);
_fieldListener.start(onFieldsChanged: (result) { _fieldListener.start(onFieldsChanged: (result) {
result.fold( result.fold(
(changeset) { (changeset) {
_deleteFields(changeset.deletedFields); _deleteFields(changeset.deletedFields);
_insertFields(changeset.insertedFields); _insertFields(changeset.insertedFields);
_updateFields(changeset.updatedFields); _updateFields(changeset.updatedFields);
for (final listener in _changesetListener) { for (final listener in _changesetCallbackMap.values) {
listener(changeset); listener(changeset);
} }
}, },
@ -85,55 +88,65 @@ class GridFieldCache {
_fieldNotifier = null; _fieldNotifier = null;
} }
UnmodifiableListView<Field> get unmodifiableFields => UnmodifiableListView(_fieldNotifier?.fields ?? []); UnmodifiableListView<GridFieldPB> get unmodifiableFields => UnmodifiableListView(_fieldNotifier?.fields ?? []);
List<Field> get fields => [..._fieldNotifier?.fields ?? []]; List<GridFieldPB> get fields => [..._fieldNotifier?.fields ?? []];
set fields(List<Field> fields) { set fields(List<GridFieldPB> fields) {
_fieldNotifier?.fields = [...fields]; _fieldNotifier?.fields = [...fields];
} }
VoidCallback addListener( void addListener({
{VoidCallback? listener, void Function(List<Field>)? onChanged, bool Function()? listenWhen}) { FieldsCallback? onFields,
f() { FieldChangesetCallback? onChangeset,
if (listenWhen != null && listenWhen() == false) { bool Function()? listenWhen,
return; }) {
if (onChangeset != null) {
fn(c) {
if (listenWhen != null && listenWhen() == false) {
return;
}
onChangeset(c);
} }
if (onChanged != null) { _changesetCallbackMap[onChangeset] = fn;
onChanged(fields);
}
if (listener != null) {
listener();
}
} }
_fieldNotifier?.addListener(f); if (onFields != null) {
return f; fn() {
} if (listenWhen != null && listenWhen() == false) {
return;
}
onFields(fields);
}
void removeListener(VoidCallback f) { _fieldsCallbackMap[onFields] = fn;
_fieldNotifier?.removeListener(f); _fieldNotifier?.addListener(fn);
}
void addChangesetListener(ChangesetListener listener) {
_changesetListener.add(listener);
}
void removeChangesetListener(ChangesetListener listener) {
final index = _changesetListener.indexWhere((element) => element == listener);
if (index != -1) {
_changesetListener.removeAt(index);
} }
} }
void _deleteFields(List<FieldOrder> deletedFields) { void removeListener({
FieldsCallback? onFieldsListener,
FieldChangesetCallback? onChangsetListener,
}) {
if (onFieldsListener != null) {
final fn = _fieldsCallbackMap.remove(onFieldsListener);
if (fn != null) {
_fieldNotifier?.removeListener(fn);
}
}
if (onChangsetListener != null) {
_changesetCallbackMap.remove(onChangsetListener);
}
}
void _deleteFields(List<GridFieldIdPB> deletedFields) {
if (deletedFields.isEmpty) { if (deletedFields.isEmpty) {
return; return;
} }
final List<Field> newFields = fields; final List<GridFieldPB> newFields = fields;
final Map<String, FieldOrder> deletedFieldMap = { final Map<String, GridFieldIdPB> deletedFieldMap = {
for (var fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder for (var fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder
}; };
@ -141,11 +154,11 @@ class GridFieldCache {
_fieldNotifier?.fields = newFields; _fieldNotifier?.fields = newFields;
} }
void _insertFields(List<IndexField> insertedFields) { void _insertFields(List<IndexFieldPB> insertedFields) {
if (insertedFields.isEmpty) { if (insertedFields.isEmpty) {
return; return;
} }
final List<Field> newFields = fields; final List<GridFieldPB> newFields = fields;
for (final indexField in insertedFields) { for (final indexField in insertedFields) {
if (newFields.length > indexField.index) { if (newFields.length > indexField.index) {
newFields.insert(indexField.index, indexField.field_1); newFields.insert(indexField.index, indexField.field_1);
@ -156,11 +169,11 @@ class GridFieldCache {
_fieldNotifier?.fields = newFields; _fieldNotifier?.fields = newFields;
} }
void _updateFields(List<Field> updatedFields) { void _updateFields(List<GridFieldPB> updatedFields) {
if (updatedFields.isEmpty) { if (updatedFields.isEmpty) {
return; return;
} }
final List<Field> newFields = fields; final List<GridFieldPB> newFields = fields;
for (final updatedField in updatedFields) { for (final updatedField in updatedFields) {
final index = newFields.indexWhere((field) => field.id == updatedField.id); final index = newFields.indexWhere((field) => field.id == updatedField.id);
if (index != -1) { if (index != -1) {
@ -172,43 +185,42 @@ class GridFieldCache {
} }
} }
class GridRowCacheDelegateImpl extends GridRowFieldDelegate { class GridRowCacheFieldNotifierImpl extends GridRowCacheFieldNotifier {
final GridFieldCache _cache; final GridFieldCache _cache;
GridRowCacheDelegateImpl(GridFieldCache cache) : _cache = cache; FieldChangesetCallback? _onChangesetFn;
FieldsCallback? _onFieldFn;
GridRowCacheFieldNotifierImpl(GridFieldCache cache) : _cache = cache;
@override @override
UnmodifiableListView<Field> get fields => _cache.unmodifiableFields; UnmodifiableListView<GridFieldPB> get fields => _cache.unmodifiableFields;
@override @override
void onFieldChanged(FieldDidUpdateCallback callback) { void onFieldsChanged(VoidCallback callback) {
_cache.addListener(listener: () { _onFieldFn = (_) => callback();
callback(); _cache.addListener(onFields: _onFieldFn);
});
} }
}
class GridCellCacheDelegateImpl extends GridCellFieldDelegate {
final GridFieldCache _cache;
ChangesetListener? _changesetFn;
GridCellCacheDelegateImpl(GridFieldCache cache) : _cache = cache;
@override @override
void onFieldChanged(void Function(String) callback) { void onFieldChanged(void Function(GridFieldPB) callback) {
changesetFn(GridFieldChangeset changeset) { _onChangesetFn = (GridFieldChangesetPB changeset) {
for (final updatedField in changeset.updatedFields) { for (final updatedField in changeset.updatedFields) {
callback(updatedField.id); callback(updatedField);
} }
} };
_cache.addChangesetListener(changesetFn); _cache.addListener(onChangeset: _onChangesetFn);
_changesetFn = changesetFn;
} }
@override @override
void dispose() { void dispose() {
if (_changesetFn != null) { if (_onFieldFn != null) {
_cache.removeChangesetListener(_changesetFn!); _cache.removeListener(onFieldsListener: _onFieldFn!);
_changesetFn = null; _onFieldFn = null;
}
if (_onChangesetFn != null) {
_cache.removeListener(onChangsetListener: _onChangesetFn!);
_onChangesetFn = null;
} }
} }
} }

View File

@ -4,18 +4,18 @@ export 'row/row_service.dart';
export 'grid_service.dart'; export 'grid_service.dart';
export 'grid_header_bloc.dart'; export 'grid_header_bloc.dart';
// Field // GridFieldPB
export 'field/field_service.dart'; export 'field/field_service.dart';
export 'field/field_action_sheet_bloc.dart'; export 'field/field_action_sheet_bloc.dart';
export 'field/field_editor_bloc.dart'; export 'field/field_editor_bloc.dart';
export 'field/field_editor_pannel_bloc.dart'; export 'field/field_type_option_edit_bloc.dart';
// Field Type Option // GridFieldPB Type Option
export 'field/type_option/date_bloc.dart'; export 'field/type_option/date_bloc.dart';
export 'field/type_option/number_bloc.dart'; export 'field/type_option/number_bloc.dart';
export 'field/type_option/single_select_type_option.dart'; export 'field/type_option/single_select_type_option.dart';
// Cell // GridCellPB
export 'cell/text_cell_bloc.dart'; export 'cell/text_cell_bloc.dart';
export 'cell/number_cell_bloc.dart'; export 'cell/number_cell_bloc.dart';
export 'cell/select_option_cell_bloc.dart'; export 'cell/select_option_cell_bloc.dart';

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