Merge branch 'main' into drag_drop_docs
4
.github/workflows/dart_lint.yml
vendored
@ -8,12 +8,8 @@ name: Flutter lint
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- 'frontend/app_flowy'
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- 'frontend/app_flowy'
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
4
.github/workflows/release.yml
vendored
@ -66,7 +66,7 @@ jobs:
|
||||
working-directory: frontend
|
||||
run: |
|
||||
flutter config --enable-linux-desktop
|
||||
cargo make --profile production-linux-x86 appflowy
|
||||
cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-linux-x86 appflowy
|
||||
|
||||
- name: Upload Release Asset
|
||||
id: upload-release-asset
|
||||
@ -111,7 +111,7 @@ jobs:
|
||||
working-directory: frontend
|
||||
run: |
|
||||
flutter config --enable-macos-desktop
|
||||
cargo make --profile production-mac-x86 appflowy
|
||||
cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-mac-x86 appflowy
|
||||
|
||||
- name: Archive macOS app
|
||||
working-directory: ${{ env.MACOS_APP_RELEASE_PATH }}
|
||||
|
6
.github/workflows/rust_lint.yml
vendored
@ -3,14 +3,8 @@ name: Rust lint
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- 'frontend/rust-lib'
|
||||
- 'shared-lib'
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- 'frontend/rust-lib'
|
||||
- 'shared-lib'
|
||||
|
||||
|
||||
env:
|
||||
|
10
.github/workflows/rust_test.yml
vendored
@ -4,15 +4,9 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
paths:
|
||||
- 'frontend/rust-lib'
|
||||
- 'shared-lib'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'main'
|
||||
paths:
|
||||
- 'frontend/rust-lib'
|
||||
- 'shared-lib'
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@ -50,8 +44,8 @@ jobs:
|
||||
|
||||
- name: Run rust-lib tests
|
||||
working-directory: frontend/rust-lib
|
||||
run: cargo test --no-default-features
|
||||
run: RUST_LOG=info cargo test --no-default-features --features="sync"
|
||||
|
||||
- name: Run shared-lib tests
|
||||
working-directory: shared-lib
|
||||
run: cargo test --no-default-features
|
||||
run: RUST_LOG=info cargo test --no-default-features
|
@ -1,5 +1,14 @@
|
||||
# Release Notes
|
||||
|
||||
## Version 0.0.4 - beta.1 - 2022-04-08
|
||||
v0.0.4 - beta.1 is pre-release
|
||||
|
||||
New features
|
||||
- Table-view database
|
||||
- supported column types: Text, Checbox, Single-select, Multi-select, Numbers
|
||||
- hide / delete columns
|
||||
- insert rows
|
||||
|
||||
## Version 0.0.3 - 2022-02-23
|
||||
v0.0.3 is production ready, available on Linux, macOS, and Windows
|
||||
|
||||
|
4
frontend/.vscode/launch.json
vendored
@ -11,7 +11,7 @@
|
||||
"type": "dart",
|
||||
"preLaunchTask": "build_flowy_sdk",
|
||||
"env":{
|
||||
"RUST_LOG":"info",
|
||||
"RUST_LOG":"info"
|
||||
},
|
||||
"cwd": "${workspaceRoot}/app_flowy"
|
||||
},
|
||||
@ -22,7 +22,7 @@
|
||||
"type": "dart",
|
||||
"preLaunchTask": "build_flowy_sdk",
|
||||
"env":{
|
||||
"RUST_LOG":"trace",
|
||||
"RUST_LOG":"trace"
|
||||
},
|
||||
"cwd": "${workspaceRoot}/app_flowy"
|
||||
},
|
||||
|
@ -9,24 +9,43 @@ extend = [
|
||||
{ path = "scripts/makefile/flutter.toml" },
|
||||
]
|
||||
|
||||
[config]
|
||||
on_error_task = "catch"
|
||||
|
||||
[tasks.catch]
|
||||
run_task = {name = ["restore-crate-type"]}
|
||||
|
||||
[env]
|
||||
RUST_LOG = "info"
|
||||
CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
|
||||
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
|
||||
CARGO_MAKE_CRATE_NAME = "dart-ffi"
|
||||
LIB_NAME = "dart_ffi"
|
||||
VERSION = "0.0.3"
|
||||
CURRENT_APP_VERSION = "0.0.4"
|
||||
FEATURES = "flutter"
|
||||
PRODUCT_NAME = "AppFlowy"
|
||||
#CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html
|
||||
CRATE_TYPE = "cdylib"
|
||||
SDK_EXT = "dylib"
|
||||
# CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html
|
||||
# If you update the macOS's CRATE_TYPE, don't forget to update the
|
||||
# flowy_sdk.podspec
|
||||
# for staticlib:
|
||||
# s.static_framework = true
|
||||
# s.vendored_libraries = "libdart_ffi.a"
|
||||
# for cdylib:
|
||||
# s.vendored_libraries = "libdart_ffi.dylib"
|
||||
#
|
||||
# Remember to update the ffi.dart:
|
||||
# for staticlib:
|
||||
# if (Platform.isMacOS) return DynamicLibrary.open('${prefix}/libdart_ffi.a');
|
||||
# for cdylib:
|
||||
# if (Platform.isMacOS) return DynamicLibrary.open('${prefix}/libdart_ffi.dylib');
|
||||
CRATE_TYPE = "staticlib"
|
||||
SDK_EXT = "a"
|
||||
APP_ENVIRONMENT = "local"
|
||||
FLUTTER_FLOWY_SDK_PATH="app_flowy/packages/flowy_sdk"
|
||||
PROTOBUF_DERIVE_CACHE="../shared-lib/flowy-derive/src/derive_cache/derive_cache.rs"
|
||||
|
||||
[env.development-mac]
|
||||
RUST_LOG = "trace"
|
||||
RUST_LOG = "info"
|
||||
TARGET_OS = "macos"
|
||||
RUST_COMPILE_TARGET = "x86_64-apple-darwin"
|
||||
BUILD_FLAG = "debug"
|
||||
|
@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.2304 4.2561H10.0184L8.02638 7.9401H8.00238L5.99838 4.2561H4.78638L7.06638 8.2401H5.43438V8.9601H7.47438L7.48638 8.9841V9.8241H5.43438V10.5321H7.48638V12.5001H8.53038V10.5321H10.4984V9.8241H8.53038V8.9841L8.54238 8.9601H10.4984V8.2401H8.95038L11.2304 4.2561Z" fill="#333333"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 392 B |
3
frontend/app_flowy/assets/images/grid/checkmark.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="10" height="8" viewBox="0 0 10 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 5.2L2.84615 7L9 1" stroke="#00BCF0" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 198 B |
6
frontend/app_flowy/assets/images/grid/delete.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 4.3999H4.11111H13" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5.77775 4.4V3.2C5.77775 2.88174 5.89481 2.57652 6.10319 2.35147C6.31156 2.12643 6.59418 2 6.88886 2H9.11108C9.40577 2 9.68838 2.12643 9.89676 2.35147C10.1051 2.57652 10.2222 2.88174 10.2222 3.2V4.4M11.8889 4.4V12.8C11.8889 13.1183 11.7718 13.4235 11.5634 13.6485C11.3551 13.8736 11.0724 14 10.7778 14H5.2222C4.92751 14 4.64489 13.8736 4.43652 13.6485C4.22815 13.4235 4.11108 13.1183 4.11108 12.8V4.4H11.8889Z" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.88892 7.3999V10.9999" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.11108 7.3999V10.9999" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 886 B |
4
frontend/app_flowy/assets/images/grid/details.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="8" cy="6" r="1" fill="#333333"/>
|
||||
<circle cx="8" cy="10" r="1" fill="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 194 B |
4
frontend/app_flowy/assets/images/grid/duplicate.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.9743 6.33301H7.35889C6.79245 6.33301 6.33325 6.7922 6.33325 7.35865V11.974C6.33325 12.5405 6.79245 12.9997 7.35889 12.9997H11.9743C12.5407 12.9997 12.9999 12.5405 12.9999 11.974V7.35865C12.9999 6.7922 12.5407 6.33301 11.9743 6.33301Z" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4.53846 9.66667H4.02564C3.75362 9.66667 3.49275 9.55861 3.3004 9.36626C3.10806 9.17392 3 8.91304 3 8.64103V4.02564C3 3.75362 3.10806 3.49275 3.3004 3.3004C3.49275 3.10806 3.75362 3 4.02564 3H8.64103C8.91304 3 9.17392 3.10806 9.36626 3.3004C9.55861 3.49275 9.66667 3.75362 9.66667 4.02564V4.53846" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 790 B |
6
frontend/app_flowy/assets/images/grid/expander.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 13H3V10" stroke="#00BCF0" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10 3H13V6" stroke="#00BCF0" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 13L7 9" stroke="#00BCF0" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13 3L9 7" stroke="#00BCF0" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 449 B |
4
frontend/app_flowy/assets/images/grid/field/checkbox.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.5 8L8.11538 9.5L13.5 4.5" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13 8.5V11.8889C13 12.1836 12.8829 12.4662 12.6746 12.6746C12.4662 12.8829 12.1836 13 11.8889 13H4.11111C3.81643 13 3.53381 12.8829 3.32544 12.6746C3.11706 12.4662 3 12.1836 3 11.8889V4.11111C3 3.81643 3.11706 3.53381 3.32544 3.32544C3.53381 3.11706 3.81643 3 4.11111 3H10.2222" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 561 B |
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.5 8L8.11538 9.5L13.5 4.5" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.5 8C13.5 11.0376 11.0376 13.5 8 13.5C4.96243 13.5 2.5 11.0376 2.5 8C2.5 4.96243 4.96243 2.5 8 2.5C8.81896 2.5 9.59612 2.679 10.2945 3" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 421 B |
6
frontend/app_flowy/assets/images/grid/field/date.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.8889 3.5H4.11111C3.49746 3.5 3 3.94772 3 4.5V11.5C3 12.0523 3.49746 12.5 4.11111 12.5H11.8889C12.5025 12.5 13 12.0523 13 11.5V4.5C13 3.94772 12.5025 3.5 11.8889 3.5Z" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10 2.5V4.58181" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6 2.5V4.58181" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 6.5H13" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 618 B |
3
frontend/app_flowy/assets/images/grid/field/euro.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.9083 10.9011C10.4173 11.2751 9.83813 11.4814 9.09532 11.4814C7.93705 11.4814 6.82914 10.8367 6.45144 9.2765H10.027V8.50287H6.32554C6.31295 8.34814 6.31295 8.19341 6.31295 8.02579C6.31295 7.84527 6.32554 7.67765 6.33813 7.51003H10.027V6.73639H6.45144C6.81655 5.25358 7.82374 4.50573 9.09532 4.50573C9.72482 4.50573 10.304 4.69914 10.8201 5.06017L11.4371 4.24785C10.7194 3.73209 9.91367 3.5 9.09532 3.5C7.10612 3.5 5.72122 4.69914 5.31835 6.73639H4.5V7.51003H5.21763C5.20504 7.67765 5.20504 7.84527 5.20504 8.02579C5.20504 8.19341 5.20504 8.34814 5.21763 8.50287H4.5V9.2765H5.31835C5.7464 11.5716 7.40827 12.5 9.09532 12.5C10.0773 12.5 10.8705 12.1777 11.5 11.7264L10.9083 10.9011Z" fill="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 814 B |
@ -0,0 +1,8 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.5 4L12.5 4" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.5 8H12.5" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.5 12H12.5" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="4" cy="4" r="0.5" fill="#333333"/>
|
||||
<circle cx="4" cy="8" r="0.5" fill="#333333"/>
|
||||
<circle cx="4" cy="12" r="0.5" fill="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 512 B |
3
frontend/app_flowy/assets/images/grid/field/number.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.201 6.4H3.001V12H2.081V7.384L0.953 7.704L0.729 6.92L2.201 6.4ZM3.91156 12V11.1L6.35156 8.61C6.9449 8.01667 7.24156 7.50333 7.24156 7.07C7.24156 6.73 7.13823 6.46667 6.93156 6.28C6.73156 6.08667 6.4749 5.99 6.16156 5.99C5.5749 5.99 5.14156 6.28 4.86156 6.86L3.89156 6.29C4.11156 5.82333 4.42156 5.47 4.82156 5.23C5.22156 4.99 5.6649 4.87 6.15156 4.87C6.7649 4.87 7.29156 5.06333 7.73156 5.45C8.17156 5.83667 8.39156 6.36333 8.39156 7.03C8.39156 7.74333 7.9949 8.50333 7.20156 9.31L5.62156 10.89H8.52156V12H3.91156ZM12.9025 7.032C13.5105 7.176 14.0025 7.46 14.3785 7.884C14.7625 8.3 14.9545 8.824 14.9545 9.456C14.9545 10.296 14.6705 10.956 14.1025 11.436C13.5345 11.916 12.8385 12.156 12.0145 12.156C11.3745 12.156 10.7985 12.008 10.2865 11.712C9.78253 11.416 9.41853 10.984 9.19453 10.416L10.3705 9.732C10.6185 10.452 11.1665 10.812 12.0145 10.812C12.4945 10.812 12.8745 10.692 13.1545 10.452C13.4345 10.204 13.5745 9.872 13.5745 9.456C13.5745 9.04 13.4345 8.712 13.1545 8.472C12.8745 8.232 12.4945 8.112 12.0145 8.112H11.7025L11.1505 7.284L12.9625 4.896H9.44653V3.6H14.6065V4.776L12.9025 7.032Z" fill="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
3
frontend/app_flowy/assets/images/grid/field/numbers.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.201 6.4H3.001V12H2.081V7.384L0.953 7.704L0.729 6.92L2.201 6.4ZM3.91156 12V11.1L6.35156 8.61C6.9449 8.01667 7.24156 7.50333 7.24156 7.07C7.24156 6.73 7.13823 6.46667 6.93156 6.28C6.73156 6.08667 6.4749 5.99 6.16156 5.99C5.5749 5.99 5.14156 6.28 4.86156 6.86L3.89156 6.29C4.11156 5.82333 4.42156 5.47 4.82156 5.23C5.22156 4.99 5.6649 4.87 6.15156 4.87C6.7649 4.87 7.29156 5.06333 7.73156 5.45C8.17156 5.83667 8.39156 6.36333 8.39156 7.03C8.39156 7.74333 7.9949 8.50333 7.20156 9.31L5.62156 10.89H8.52156V12H3.91156ZM12.9025 7.032C13.5105 7.176 14.0025 7.46 14.3785 7.884C14.7625 8.3 14.9545 8.824 14.9545 9.456C14.9545 10.296 14.6705 10.956 14.1025 11.436C13.5345 11.916 12.8385 12.156 12.0145 12.156C11.3745 12.156 10.7985 12.008 10.2865 11.712C9.78253 11.416 9.41853 10.984 9.19453 10.416L10.3705 9.732C10.6185 10.452 11.1665 10.812 12.0145 10.812C12.4945 10.812 12.8745 10.692 13.1545 10.452C13.4345 10.204 13.5745 9.872 13.5745 9.456C13.5745 9.04 13.4345 8.712 13.1545 8.472C12.8745 8.232 12.4945 8.112 12.0145 8.112H11.7025L11.1505 7.284L12.9625 4.896H9.44653V3.6H14.6065V4.776L12.9025 7.032Z" fill="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.78787 8.78787L6.51213 7.51213C6.32314 7.32314 6.45699 7 6.72426 7H9.27574C9.54301 7 9.67686 7.32314 9.48787 7.51213L8.21213 8.78787C8.09497 8.90503 7.90503 8.90503 7.78787 8.78787Z" fill="#333333"/>
|
||||
<path d="M8 13C10.7614 13 13 10.7614 13 8C13 5.23858 10.7614 3 8 3C5.23858 3 3 5.23858 3 8C3 10.7614 5.23858 13 8 13Z" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 499 B |
4
frontend/app_flowy/assets/images/grid/field/text.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.15625 11.8359L6.43768 9.85414H2.46662L1.74805 11.8359H0.5L3.7903 3H5.11399L8.4043 11.8359H7.15625ZM2.87003 8.75596H6.03427L4.44584 4.40112L2.87003 8.75596Z" fill="#333333"/>
|
||||
<path d="M14.4032 5.52454H15.5V11.8359H14.4032V10.7504C13.8569 11.5835 13.0627 12 12.0206 12C11.1381 12 10.386 11.6802 9.76403 11.0407C9.14211 10.3927 8.83114 9.60589 8.83114 8.68022C8.83114 7.75456 9.14211 6.97195 9.76403 6.3324C10.386 5.68443 11.1381 5.36045 12.0206 5.36045C13.0627 5.36045 13.8569 5.777 14.4032 6.6101V5.52454ZM12.1593 10.9397C12.798 10.9397 13.3317 10.7251 13.7603 10.2959C14.1889 9.85835 14.4032 9.31978 14.4032 8.68022C14.4032 8.04067 14.1889 7.50631 13.7603 7.07714C13.3317 6.63955 12.798 6.42076 12.1593 6.42076C11.5289 6.42076 10.9995 6.63955 10.5708 7.07714C10.1422 7.50631 9.92791 8.04067 9.92791 8.68022C9.92791 9.31978 10.1422 9.85835 10.5708 10.2959C10.9995 10.7251 11.5289 10.9397 12.1593 10.9397Z" fill="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.716 9.828C10.716 8.292 9.6 7.896 8.124 7.416C7.128 7.092 6.396 6.828 6.396 6.072C6.396 5.376 6.984 4.968 7.824 4.968C8.412 4.968 9.216 5.22 9.84 5.676L10.44 4.944C9.9 4.512 9.096 4.176 8.352 4.056V2.508H7.572V4.008C6.12 4.092 5.304 4.98 5.304 6.132C5.304 7.56 6.516 7.98 7.824 8.388C8.928 8.748 9.612 9.036 9.612 9.9C9.612 10.644 8.964 11.088 8.076 11.088C7.308 11.088 6.444 10.752 5.772 10.092L5.136 10.836C5.844 11.532 6.684 11.904 7.572 12.012V13.476H8.352V12.024C9.84 11.928 10.716 11.052 10.716 9.828Z" fill="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 641 B |
3
frontend/app_flowy/assets/images/grid/field/yen.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.2301 4.25586H10.0181L8.02613 7.93986H8.00213L5.99813 4.25586H4.78613L7.06613 8.23986H5.43413V8.95986H7.47413L7.48613 8.98386V9.82386H5.43413V10.5319H7.48613V12.4999H8.53013V10.5319H10.4981V9.82386H8.53013V8.98386L8.54213 8.95986H10.4981V8.23986H8.95013L11.2301 4.25586Z" fill="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 404 B |
4
frontend/app_flowy/assets/images/grid/hide.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.12265 11.5847C5.92255 12.1165 6.88538 12.5 8.00024 12.5C10.4842 12.5 12.2135 10.596 13.0675 9.39083C13.6624 8.55146 13.6624 7.44854 13.0675 6.60917C12.7341 6.13867 12.2673 5.56168 11.6743 5.03305L10.9661 5.74127C11.4908 6.20089 11.9225 6.72296 12.2516 7.18736C12.601 7.68035 12.601 8.31965 12.2516 8.81264C11.4276 9.97552 9.9599 11.5 8.00024 11.5C7.19618 11.5 6.47495 11.2434 5.84702 10.8603L5.12265 11.5847ZM5.03441 10.2587L4.32618 10.967C3.73316 10.4383 3.26636 9.86133 2.93294 9.39083C2.33811 8.55146 2.33811 7.44854 2.93294 6.60917C3.78701 5.40397 5.51627 3.5 8.00024 3.5C9.1151 3.5 10.0779 3.88354 10.8778 4.4153L10.1535 5.13966C9.52554 4.75665 8.80431 4.5 8.00024 4.5C6.04059 4.5 4.57293 6.02448 3.74884 7.18736C3.39948 7.68035 3.39948 8.31965 3.74884 8.81264C4.07794 9.27704 4.50968 9.79911 5.03441 10.2587ZM6.99269 9.71466C7.28548 9.8954 7.62952 10 8.00036 10C9.09422 10 9.95491 9.08996 9.95491 8C9.95491 7.64165 9.86187 7.30275 9.69811 7.00924L8.93118 7.77618C8.94668 7.84779 8.95491 7.92265 8.95491 8C8.95491 8.5669 8.51315 9 8.00036 9C7.91225 9 7.82623 8.98721 7.7442 8.96316L6.99269 9.71466ZM7.06951 8.22363L6.30253 8.99061C6.13882 8.69713 6.04582 8.35829 6.04582 8C6.04582 6.91005 6.9065 6 8.00036 6C8.37114 6 8.71513 6.10456 9.00789 6.28525L8.25635 7.03679C8.17436 7.01277 8.08841 7 8.00036 7C7.48757 7 7.04582 7.4331 7.04582 8C7.04582 8.07728 7.05403 8.15208 7.06951 8.22363Z" fill="#333333"/>
|
||||
<path d="M11.667 3.33398L3.33366 11.6673" stroke="#333333" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
5
frontend/app_flowy/assets/images/grid/left.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 11.7778L3 4" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.5 4.5L6 8L9.5 11.5" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6 8L13 8" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 378 B |
3
frontend/app_flowy/assets/images/grid/more.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.39568 7.6963L6.91032 5.56599C6.65085 5.34358 6.25 5.52795 6.25 5.86969L6.25 10.1303C6.25 10.4721 6.65085 10.6564 6.91032 10.434L9.39568 8.3037C9.58192 8.14406 9.58192 7.85594 9.39568 7.6963Z" fill="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 324 B |
5
frontend/app_flowy/assets/images/grid/right.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13 11.7778L13 4" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.5 4.5L10 8L6.5 11.5" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10 8L3 8" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 381 B |
Before Width: | Height: | Size: 238 B After Width: | Height: | Size: 238 B |
Before Width: | Height: | Size: 281 B After Width: | Height: | Size: 281 B |
Before Width: | Height: | Size: 512 B After Width: | Height: | Size: 512 B |
Before Width: | Height: | Size: 499 B After Width: | Height: | Size: 499 B |
@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.5221 3.21932C11.4366 2.6913 12.5634 2.6913 13.4779 3.21932L18.8653 6.32972C19.7799 6.85774 20.3433 7.83356 20.3433 8.88959V15.1104C20.3433 16.1664 19.7799 17.1423 18.8653 17.6703L13.4779 20.7807C12.5634 21.3087 11.4366 21.3087 10.5221 20.7807L5.13468 17.6703C4.22012 17.1423 3.65673 16.1664 3.65673 15.1104V8.88959C3.65673 7.83356 4.22012 6.85774 5.13467 6.32972L10.5221 3.21932Z" stroke="#333333" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="12" r="3.75" stroke="#333333" stroke-width="1.5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 606 B |
4
frontend/app_flowy/assets/images/grid/setting/sort.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2.5" y="3" width="4" height="10" rx="1" stroke="#333333"/>
|
||||
<rect x="9.5" y="7" width="4" height="6" rx="1" stroke="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 238 B |
@ -141,5 +141,59 @@
|
||||
"lightLabel": "Light Mode",
|
||||
"darkLabel": "Dark Mode"
|
||||
}
|
||||
},
|
||||
"grid": {
|
||||
"settings": {
|
||||
"filter": "Filter",
|
||||
"sortBy": "Sort by",
|
||||
"Properties": "Properties"
|
||||
},
|
||||
"field": {
|
||||
"hide": "Hide",
|
||||
"insertLeft": "Insert Left",
|
||||
"insertRight": "Insert Right",
|
||||
"duplicate": "Duplicate",
|
||||
"delete": "Delete",
|
||||
"textFieldName": "Text",
|
||||
"checkboxFieldName": "Checkbox",
|
||||
"dateFieldName": "Date",
|
||||
"numberFieldName": "Numbers",
|
||||
"singleSelectFieldName": "Select",
|
||||
"multiSelectFieldName": "Multiselect",
|
||||
"numberFormat": " Number format",
|
||||
"dateFormat": " Date format",
|
||||
"includeTime": " Include time",
|
||||
"dateFormatFriendly": "Month Day,Year",
|
||||
"dateFormatISO": "Year-Month-Day",
|
||||
"dateFormatLocal": "Month/Month/Day",
|
||||
"dateFormatUS": "Month/Month/Day",
|
||||
"timeFormat": " Time format",
|
||||
"timeFormatTwelveHour": "12 hour",
|
||||
"timeFormatTwentyFourHour": "24 hour",
|
||||
"addSelectOption": "Add an option",
|
||||
"optionTitle": "Options",
|
||||
"addOption": "Add option",
|
||||
"editProperty": "Edit property"
|
||||
},
|
||||
"row": {
|
||||
"duplicate": "Duplicate",
|
||||
"delete": "Delete",
|
||||
"textPlaceholder": "Empty"
|
||||
},
|
||||
"selectOption": {
|
||||
"purpleColor": "Purple",
|
||||
"pinkColor": "Pink",
|
||||
"lightPinkColor": "Light Pink",
|
||||
"orangeColor": "Orange",
|
||||
"yellowColor": "Yellow",
|
||||
"limeColor": "Lime",
|
||||
"greenColor": "Green",
|
||||
"aquaColor": "Aqua",
|
||||
"blueColor": "Blue",
|
||||
"deleteTag": "Delete tag",
|
||||
"colorPannelTitle": "Colors",
|
||||
"pannelTitle": "Select an option or create one",
|
||||
"searchOption": "Search for an option"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
145
frontend/app_flowy/assets/translations/tr-TR.json
Normal file
@ -0,0 +1,145 @@
|
||||
{
|
||||
"appName": "AppFlowy",
|
||||
"defaultUsername": "Ben",
|
||||
"welcomeText": "@:appName'e Hoş Geldiniz!",
|
||||
"githubStarText": "GitHub Yıldızı!",
|
||||
"subscribeNewsletterText": "Bültene Abone Ol",
|
||||
"letsGoButtonText": "Hadi başlayalım.",
|
||||
"title": "Başlık",
|
||||
"signUp": {
|
||||
"buttonText": "Kayıt Ol",
|
||||
"title": "@:appName'e kaydolun.",
|
||||
"getStartedText": "başlayalım",
|
||||
"emptyPasswordError": "Parola boş olamaz",
|
||||
"repeatPasswordEmptyError": "Parola (tekrar) boş olamaz",
|
||||
"unmatchedPasswordError": "Parolalar eşleşmiyor",
|
||||
"alreadyHaveAnAccount": "Zaten hesabınız var mı?",
|
||||
"emailHint": "E-Posta",
|
||||
"passwordHint": "Parola",
|
||||
"repeatPasswordHint": "Tekrar parola"
|
||||
},
|
||||
"signIn": {
|
||||
"loginTitle": "@:appName oturum aç",
|
||||
"loginButtonText": "Giriş",
|
||||
"buttonText": "Oturum Aç",
|
||||
"forgotPassword": "Parolanızı mı Unuttunuz?",
|
||||
"emailHint": "E-Posta",
|
||||
"passwordHint": "Parola",
|
||||
"dontHaveAnAccount": "Hesabınız yok mu?",
|
||||
"repeatPasswordEmptyError": "Parola (tekrar) boş olamaz",
|
||||
"unmatchedPasswordError": "Parolalar eşleşmiyor"
|
||||
},
|
||||
"workspace": {
|
||||
"create": "Çalışma alanı oluştur",
|
||||
"hint": "Çalışma alanı",
|
||||
"notFoundError": "Çalışma alanı bulunamadı"
|
||||
},
|
||||
"shareAction": {
|
||||
"buttonText": "Paylaş",
|
||||
"workInProgress": "Yakında",
|
||||
"markdown": "Markdown",
|
||||
"copyLink": "Link'i Kopyala"
|
||||
},
|
||||
"disclosureAction": {
|
||||
"rename": "Yeniden adlandır",
|
||||
"delete": "Sil",
|
||||
"duplicate": "Çoğalt"
|
||||
},
|
||||
"blankPageTitle": "Boş sayfa",
|
||||
"newPageText": "Yeni sayfa",
|
||||
"trash": {
|
||||
"text": "Çöp",
|
||||
"restoreAll": "Geri Yükle",
|
||||
"deleteAll": "Sil",
|
||||
"pageHeader": {
|
||||
"fileName": "Dosya adı",
|
||||
"lastModified": "Son Değiştirme",
|
||||
"created": "Oluşturuldu"
|
||||
}
|
||||
},
|
||||
"deletePagePrompt": {
|
||||
"text": "Bu sayfa Çöp Kutusu'nda",
|
||||
"restore": "Sayfayı geri yükle",
|
||||
"deletePermanent": "Kalıcı olarak sil"
|
||||
},
|
||||
"dialogCreatePageNameHint": "Sayfa adı",
|
||||
"questionBubble": {
|
||||
"whatsNew": "Yeni ne var?",
|
||||
"help": "Yardım & Destek",
|
||||
"debug": {
|
||||
"name": "Hata Ayıklama",
|
||||
"success": "Hata ayıklama bilgileri panoya kopyalandı!",
|
||||
"fail": "Hata ayıklama bilgileri panoya kopyalanamıyor"
|
||||
}
|
||||
},
|
||||
"menuAppHeader": {
|
||||
"addPageTooltip": "Yeni bir sayfa ekleyin",
|
||||
"defaultNewPageName": "Başlıksız",
|
||||
"renameDialog": "Yeniden adlandır"
|
||||
},
|
||||
"toolbar": {
|
||||
"undo": "Geri",
|
||||
"redo": "İleri",
|
||||
"bold": "Kalın",
|
||||
"italic": "İtalik",
|
||||
"underline": "Altı Çizili",
|
||||
"strike": "Üstü Çizili",
|
||||
"numList": "Numaralı Liste",
|
||||
"bulletList": "Madde İşaretli Liste",
|
||||
"checkList": "Yapılacaklar Listesi",
|
||||
"inlineCode": "Kod",
|
||||
"quote": "Alıntı",
|
||||
"header": "Başlık",
|
||||
"highlight": "Vurgu"
|
||||
},
|
||||
"tooltip": {
|
||||
"lightMode": "Aydınlık Mod'a Geç",
|
||||
"darkMode": "Karanlık Mod'a Geç"
|
||||
},
|
||||
"contactsPage": {
|
||||
"title": "İletişim",
|
||||
"whatsHappening": "Bu hafta neler var?",
|
||||
"addContact": "Kişi Ekle",
|
||||
"editContact": "Kişiyi Düzenle"
|
||||
},
|
||||
"button": {
|
||||
"OK": "TAMAM",
|
||||
"Cancel": "İptal",
|
||||
"signIn": "Oturum Aç",
|
||||
"signOut": "Oturum Kapat",
|
||||
"complete": "Tamamlandı",
|
||||
"save": "Kaydet"
|
||||
},
|
||||
"label": {
|
||||
"welcome": "Merhaba!",
|
||||
"firstName": "Ad",
|
||||
"middleName": "İkinci Ad",
|
||||
"lastName": "Soyad",
|
||||
"stepX": "Aşama {X}"
|
||||
},
|
||||
"oAuth": {
|
||||
"err": {
|
||||
"failedTitle": "Hesabınıza bağlanılamıyor.",
|
||||
"failedMsg": "Lütfen, tarayıcınızda oturum açma işlemini tamamladığınızdan emin olun."
|
||||
},
|
||||
"google": {
|
||||
"title": "GOOGLE OTURUM AÇMA",
|
||||
"instruction1": "Google Kişilerinizi içe aktarmak için web tarayıcınızı kullanarak bu uygulamaya izin vermeniz gerekir.",
|
||||
"instruction2": "Simgeyi tıklayarak veya metni seçerek bu kodu panonuza kopyalayın:",
|
||||
"instruction3": "Web tarayıcınızda aşağıdaki bağlantıyı açın ve yukarıdaki kodu girin:",
|
||||
"instruction4": "Kayıt işlemini tamamladığınızda aşağıdaki düğmeye basın:"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Ayarlar",
|
||||
"menu": {
|
||||
"appearance": "Görünüm",
|
||||
"language": "Dil",
|
||||
"open": "Ayarları Aç"
|
||||
},
|
||||
"appearance": {
|
||||
"lightLabel": "Aydınlık Mod",
|
||||
"darkLabel": "Karanlık Mod"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,14 @@
|
||||
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/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> {
|
||||
@ -17,10 +21,11 @@ class UserNotificationParser extends NotificationParser<UserNotification, FlowyE
|
||||
);
|
||||
}
|
||||
|
||||
typedef NotificationCallback = void Function(FolderNotification, Either<Uint8List, FlowyError>);
|
||||
// Folder
|
||||
typedef FolderNotificationCallback = void Function(FolderNotification, Either<Uint8List, FlowyError>);
|
||||
|
||||
class FolderNotificationParser extends NotificationParser<FolderNotification, FlowyError> {
|
||||
FolderNotificationParser({String? id, required NotificationCallback callback})
|
||||
FolderNotificationParser({String? id, required FolderNotificationCallback callback})
|
||||
: super(
|
||||
id: id,
|
||||
callback: callback,
|
||||
@ -29,6 +34,36 @@ class FolderNotificationParser extends NotificationParser<FolderNotification, Fl
|
||||
);
|
||||
}
|
||||
|
||||
// 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> {
|
||||
String? id;
|
||||
void Function(T, Either<Uint8List, E>) callback;
|
||||
|
@ -3,27 +3,46 @@ library flowy_plugin;
|
||||
import 'package:app_flowy/plugin/plugin.dart';
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
||||
import 'package:flowy_infra/notifier.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
export "./src/sandbox.dart";
|
||||
|
||||
enum DefaultPlugin {
|
||||
quill,
|
||||
blank,
|
||||
trash,
|
||||
grid,
|
||||
}
|
||||
|
||||
extension FlowyDefaultPluginExt on DefaultPlugin {
|
||||
int type() {
|
||||
switch (this) {
|
||||
case DefaultPlugin.quill:
|
||||
return 0;
|
||||
case DefaultPlugin.blank:
|
||||
return 1;
|
||||
case DefaultPlugin.trash:
|
||||
return 2;
|
||||
case DefaultPlugin.grid:
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef PluginType = int;
|
||||
|
||||
typedef PluginDataType = ViewDataType;
|
||||
|
||||
typedef PluginId = String;
|
||||
|
||||
abstract class Plugin {
|
||||
PluginId get pluginId;
|
||||
PluginId get id;
|
||||
|
||||
PluginDisplay get pluginDisplay;
|
||||
PluginDisplay get display;
|
||||
|
||||
PluginType get pluginType;
|
||||
PluginType get ty;
|
||||
|
||||
ChangeNotifier? get displayNotifier => null;
|
||||
|
||||
void dispose();
|
||||
void dispose() {}
|
||||
}
|
||||
|
||||
abstract class PluginBuilder {
|
||||
@ -33,22 +52,19 @@ abstract class PluginBuilder {
|
||||
|
||||
PluginType get pluginType;
|
||||
|
||||
ViewDataType get dataType => ViewDataType.PlainText;
|
||||
ViewDataType get dataType => ViewDataType.TextBlock;
|
||||
}
|
||||
|
||||
abstract class PluginConfig {
|
||||
// Return false will disable the user to create it. For example, a trash plugin shouldn't be created by the user,
|
||||
bool get creatable => true;
|
||||
}
|
||||
|
||||
abstract class PluginDisplay with NavigationItem {
|
||||
@override
|
||||
Widget get leftBarItem;
|
||||
|
||||
@override
|
||||
Widget? get rightBarItem;
|
||||
|
||||
abstract class PluginDisplay<T> with NavigationItem {
|
||||
List<NavigationItem> get navigationItems;
|
||||
|
||||
PublishNotifier<T>? get notifier => null;
|
||||
|
||||
Widget buildWidget();
|
||||
}
|
||||
|
||||
|
217
frontend/app_flowy/lib/startup/deps_resolver.dart
Normal file
@ -0,0 +1,217 @@
|
||||
import 'package:app_flowy/core/network_monitor.dart';
|
||||
import 'package:app_flowy/user/application/user_listener.dart';
|
||||
import 'package:app_flowy/user/application/user_service.dart';
|
||||
import 'package:app_flowy/workspace/application/app/prelude.dart';
|
||||
import 'package:app_flowy/workspace/application/doc/prelude.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
||||
import 'package:app_flowy/workspace/application/trash/prelude.dart';
|
||||
import 'package:app_flowy/workspace/application/workspace/prelude.dart';
|
||||
import 'package:app_flowy/workspace/application/edit_pannel/edit_pannel_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/home/home_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/view/prelude.dart';
|
||||
import 'package:app_flowy/workspace/application/home/prelude.dart';
|
||||
import 'package:app_flowy/workspace/application/menu/prelude.dart';
|
||||
import 'package:app_flowy/user/application/prelude.dart';
|
||||
import 'package:app_flowy/user/presentation/router.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
class DependencyResolver {
|
||||
static Future<void> resolve(GetIt getIt) async {
|
||||
_resolveUserDeps(getIt);
|
||||
|
||||
_resolveHomeDeps(getIt);
|
||||
|
||||
_resolveFolderDeps(getIt);
|
||||
|
||||
_resolveDocDeps(getIt);
|
||||
|
||||
_resolveGridDeps(getIt);
|
||||
}
|
||||
}
|
||||
|
||||
void _resolveUserDeps(GetIt getIt) {
|
||||
getIt.registerFactory<AuthService>(() => AuthService());
|
||||
getIt.registerFactory<AuthRouter>(() => AuthRouter());
|
||||
|
||||
getIt.registerFactory<SignInBloc>(() => SignInBloc(getIt<AuthService>()));
|
||||
getIt.registerFactory<SignUpBloc>(() => SignUpBloc(getIt<AuthService>()));
|
||||
|
||||
getIt.registerFactory<SplashRoute>(() => SplashRoute());
|
||||
getIt.registerFactory<HomeBloc>(() => HomeBloc());
|
||||
getIt.registerFactory<EditPannelBloc>(() => EditPannelBloc());
|
||||
getIt.registerFactory<SplashBloc>(() => SplashBloc());
|
||||
getIt.registerLazySingleton<NetworkListener>(() => NetworkListener());
|
||||
}
|
||||
|
||||
void _resolveHomeDeps(GetIt getIt) {
|
||||
getIt.registerFactoryParam<UserListener, UserProfile, void>(
|
||||
(user, _) => UserListener(user: user),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<HomeListenBloc, UserProfile, void>(
|
||||
(user, _) => HomeListenBloc(getIt<UserListener>(param1: user)),
|
||||
);
|
||||
|
||||
//
|
||||
getIt.registerLazySingleton<HomeStackManager>(() => HomeStackManager());
|
||||
|
||||
getIt.registerFactoryParam<WelcomeBloc, UserProfile, void>(
|
||||
(user, _) => WelcomeBloc(
|
||||
userService: UserService(),
|
||||
userListener: getIt<UserListener>(param1: user),
|
||||
),
|
||||
);
|
||||
|
||||
// share
|
||||
getIt.registerLazySingleton<ShareService>(() => ShareService());
|
||||
getIt.registerFactoryParam<DocShareBloc, View, void>(
|
||||
(view, _) => DocShareBloc(view: view, service: getIt<ShareService>()));
|
||||
}
|
||||
|
||||
void _resolveFolderDeps(GetIt getIt) {
|
||||
//workspace
|
||||
getIt.registerFactoryParam<WorkspaceListener, UserProfile, String>((user, workspaceId) =>
|
||||
WorkspaceListener(service: WorkspaceListenerService(user: user, workspaceId: workspaceId)));
|
||||
|
||||
// View
|
||||
getIt.registerFactoryParam<ViewListener, View, void>(
|
||||
(view, _) => ViewListener(view: view),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<ViewBloc, View, void>(
|
||||
(view, _) => ViewBloc(
|
||||
view: view,
|
||||
service: ViewService(),
|
||||
listener: getIt<ViewListener>(param1: view),
|
||||
),
|
||||
);
|
||||
|
||||
//Menu
|
||||
getIt.registerFactoryParam<MenuBloc, UserProfile, String>(
|
||||
(user, workspaceId) => MenuBloc(
|
||||
workspaceId: workspaceId,
|
||||
service: WorkspaceService(),
|
||||
listener: getIt<WorkspaceListener>(param1: user, param2: workspaceId),
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<MenuUserBloc, UserProfile, void>(
|
||||
(user, _) => MenuUserBloc(
|
||||
user,
|
||||
UserService(),
|
||||
getIt<UserListener>(param1: user),
|
||||
),
|
||||
);
|
||||
|
||||
// App
|
||||
getIt.registerFactoryParam<AppBloc, App, void>(
|
||||
(app, _) => AppBloc(
|
||||
app: app,
|
||||
service: AppService(),
|
||||
listener: AppListener(appId: app.id),
|
||||
),
|
||||
);
|
||||
|
||||
// trash
|
||||
getIt.registerLazySingleton<TrashService>(() => TrashService());
|
||||
getIt.registerLazySingleton<TrashListener>(() => TrashListener());
|
||||
getIt.registerFactory<TrashBloc>(
|
||||
() => TrashBloc(
|
||||
service: getIt<TrashService>(),
|
||||
listener: getIt<TrashListener>(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _resolveDocDeps(GetIt getIt) {
|
||||
// Doc
|
||||
getIt.registerFactoryParam<DocumentBloc, View, void>(
|
||||
(view, _) => DocumentBloc(
|
||||
view: view,
|
||||
service: DocumentService(),
|
||||
listener: getIt<ViewListener>(param1: view),
|
||||
trashService: getIt<TrashService>(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _resolveGridDeps(GetIt getIt) {
|
||||
// Grid
|
||||
getIt.registerFactoryParam<GridBloc, View, void>(
|
||||
(view, _) => GridBloc(view: view),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<GridHeaderBloc, String, GridFieldCache>(
|
||||
(gridId, fieldCache) => GridHeaderBloc(
|
||||
gridId: gridId,
|
||||
fieldCache: fieldCache,
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<FieldActionSheetBloc, GridFieldCellContext, void>(
|
||||
(data, _) => FieldActionSheetBloc(
|
||||
field: data.field,
|
||||
service: FieldService(gridId: data.gridId),
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<FieldEditorBloc, String, EditFieldContextLoader>(
|
||||
(gridId, fieldLoader) => FieldEditorBloc(
|
||||
service: FieldService(gridId: gridId),
|
||||
fieldLoader: fieldLoader,
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<TextCellBloc, GridCell, void>(
|
||||
(cellData, _) => TextCellBloc(
|
||||
cellData: cellData,
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<SelectionCellBloc, GridCell, void>(
|
||||
(cellData, _) => SelectionCellBloc(
|
||||
cellData: cellData,
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<NumberCellBloc, GridCell, void>(
|
||||
(cellData, _) => NumberCellBloc(
|
||||
cellData: cellData,
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<DateCellBloc, GridCell, void>(
|
||||
(cellData, _) => DateCellBloc(
|
||||
cellData: cellData,
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<CheckboxCellBloc, GridCell, void>(
|
||||
(cellData, _) => CheckboxCellBloc(
|
||||
service: CellService(),
|
||||
cellData: cellData,
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<FieldSwitcherBloc, SwitchFieldContext, void>(
|
||||
(context, _) => FieldSwitcherBloc(context),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<DateTypeOptionBloc, DateTypeOption, void>(
|
||||
(typeOption, _) => DateTypeOptionBloc(typeOption: typeOption),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<NumberTypeOptionBloc, NumberTypeOption, void>(
|
||||
(typeOption, _) => NumberTypeOptionBloc(typeOption: typeOption),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<GridPropertyBloc, String, GridFieldCache>(
|
||||
(gridId, cache) => GridPropertyBloc(gridId: gridId, fieldCache: cache),
|
||||
);
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
import 'package:app_flowy/user/application/user_listener.dart';
|
||||
import 'package:app_flowy/user/application/user_service.dart';
|
||||
import 'package:app_flowy/workspace/application/app/app_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/app/app_listener.dart';
|
||||
import 'package:app_flowy/workspace/application/app/app_service.dart';
|
||||
import 'package:app_flowy/workspace/application/doc/doc_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/doc/doc_service.dart';
|
||||
import 'package:app_flowy/workspace/application/doc/share_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/doc/share_service.dart';
|
||||
import 'package:app_flowy/workspace/application/home/home_listen_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/menu/menu_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/menu/menu_user_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/trash/trash_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/trash/trash_listener.dart';
|
||||
import 'package:app_flowy/workspace/application/trash/trash_service.dart';
|
||||
import 'package:app_flowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/view/view_listener.dart';
|
||||
import 'package:app_flowy/workspace/application/view/view_service.dart';
|
||||
import 'package:app_flowy/workspace/application/workspace/welcome_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/workspace/workspace_listener.dart';
|
||||
import 'package:app_flowy/workspace/application/workspace/workspace_service.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
class HomeDepsResolver {
|
||||
static Future<void> resolve(GetIt getIt) async {
|
||||
getIt.registerFactoryParam<UserListener, UserProfile, void>(
|
||||
(user, _) => UserListener(user: user),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<HomeListenBloc, UserProfile, void>(
|
||||
(user, _) => HomeListenBloc(getIt<UserListener>(param1: user)),
|
||||
);
|
||||
|
||||
//
|
||||
getIt.registerLazySingleton<HomeStackManager>(() => HomeStackManager());
|
||||
getIt.registerFactoryParam<WelcomeBloc, UserProfile, void>(
|
||||
(user, _) => WelcomeBloc(
|
||||
userService: UserService(),
|
||||
userListener: getIt<UserListener>(param1: user),
|
||||
),
|
||||
);
|
||||
|
||||
//workspace
|
||||
getIt.registerFactoryParam<WorkspaceListener, UserProfile, String>((user, workspaceId) =>
|
||||
WorkspaceListener(service: WorkspaceListenerService(user: user, workspaceId: workspaceId)));
|
||||
|
||||
// View
|
||||
getIt.registerFactoryParam<ViewListener, View, void>(
|
||||
(view, _) => ViewListener(view: view),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<ViewBloc, View, void>(
|
||||
(view, _) => ViewBloc(
|
||||
view: view,
|
||||
service: ViewService(),
|
||||
listener: getIt<ViewListener>(param1: view),
|
||||
),
|
||||
);
|
||||
|
||||
//Menu Bloc
|
||||
getIt.registerFactoryParam<MenuBloc, UserProfile, String>(
|
||||
(user, workspaceId) => MenuBloc(
|
||||
workspaceId: workspaceId,
|
||||
service: WorkspaceService(),
|
||||
listener: getIt<WorkspaceListener>(param1: user, param2: workspaceId),
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<MenuUserBloc, UserProfile, void>(
|
||||
(user, _) => MenuUserBloc(
|
||||
user,
|
||||
UserService(),
|
||||
getIt<UserListener>(param1: user),
|
||||
),
|
||||
);
|
||||
|
||||
// App
|
||||
getIt.registerFactoryParam<AppBloc, App, void>(
|
||||
(app, _) => AppBloc(
|
||||
app: app,
|
||||
service: AppService(),
|
||||
listener: AppListener(appId: app.id),
|
||||
),
|
||||
);
|
||||
|
||||
// Doc
|
||||
getIt.registerFactoryParam<DocumentBloc, View, void>(
|
||||
(view, _) => DocumentBloc(
|
||||
view: view,
|
||||
service: DocumentService(),
|
||||
listener: getIt<ViewListener>(param1: view),
|
||||
trashService: getIt<TrashService>(),
|
||||
),
|
||||
);
|
||||
|
||||
// trash
|
||||
getIt.registerLazySingleton<TrashService>(() => TrashService());
|
||||
getIt.registerLazySingleton<TrashListener>(() => TrashListener());
|
||||
getIt.registerFactory<TrashBloc>(
|
||||
() => TrashBloc(
|
||||
service: getIt<TrashService>(),
|
||||
listener: getIt<TrashListener>(),
|
||||
),
|
||||
);
|
||||
|
||||
// share
|
||||
getIt.registerLazySingleton<ShareService>(() => ShareService());
|
||||
getIt.registerFactoryParam<DocShareBloc, View, void>(
|
||||
(view, _) => DocShareBloc(view: view, service: getIt<ShareService>()));
|
||||
}
|
||||
}
|
@ -2,8 +2,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:app_flowy/plugin/plugin.dart';
|
||||
import 'package:app_flowy/startup/tasks/prelude.dart';
|
||||
import 'package:app_flowy/startup/home_deps_resolver.dart';
|
||||
import 'package:app_flowy/startup/user_deps_resolver.dart';
|
||||
import 'package:app_flowy/startup/deps_resolver.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
@ -62,8 +61,7 @@ Future<void> initGetIt(
|
||||
getIt.registerLazySingleton<AppLauncher>(() => AppLauncher(env, getIt));
|
||||
getIt.registerSingleton<PluginSandbox>(PluginSandbox());
|
||||
|
||||
await UserDepsResolver.resolve(getIt);
|
||||
await HomeDepsResolver.resolve(getIt);
|
||||
await DependencyResolver.resolve(getIt);
|
||||
}
|
||||
|
||||
class LaunchContext {
|
||||
|
@ -39,6 +39,7 @@ class InitAppWidgetTask extends LaunchTask {
|
||||
Locale('it', 'IT'),
|
||||
Locale('pt', 'BR'),
|
||||
Locale('ru', 'RU'),
|
||||
Locale('tr', 'TR'),
|
||||
Locale('zh', 'CN'),
|
||||
],
|
||||
path: 'assets/translations',
|
||||
@ -112,7 +113,7 @@ class ApplicationBlocObserver extends BlocObserver {
|
||||
// ignore: unnecessary_overrides
|
||||
void onTransition(Bloc bloc, Transition transition) {
|
||||
// Log.debug("[current]: ${transition.currentState} \n\n[next]: ${transition.nextState}");
|
||||
//Log.debug("${transition.nextState}");
|
||||
// Log.debug("${transition.nextState}");
|
||||
super.onTransition(bloc, transition);
|
||||
}
|
||||
|
||||
@ -122,9 +123,9 @@ class ApplicationBlocObserver extends BlocObserver {
|
||||
super.onError(bloc, error, stackTrace);
|
||||
}
|
||||
|
||||
@override
|
||||
void onEvent(Bloc bloc, Object? event) {
|
||||
Log.debug("$event");
|
||||
super.onEvent(bloc, event);
|
||||
}
|
||||
// @override
|
||||
// void onEvent(Bloc bloc, Object? event) {
|
||||
// Log.debug("$event");
|
||||
// super.onEvent(bloc, event);
|
||||
// }
|
||||
}
|
||||
|
@ -2,27 +2,9 @@ import 'package:app_flowy/plugin/plugin.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/doc/document.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/grid.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/trash/trash.dart';
|
||||
|
||||
enum DefaultPlugin {
|
||||
quillEditor,
|
||||
blank,
|
||||
trash,
|
||||
}
|
||||
|
||||
extension FlowyDefaultPluginExt on DefaultPlugin {
|
||||
int type() {
|
||||
switch (this) {
|
||||
case DefaultPlugin.quillEditor:
|
||||
return 0;
|
||||
case DefaultPlugin.blank:
|
||||
return 1;
|
||||
case DefaultPlugin.trash:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PluginLoadTask extends LaunchTask {
|
||||
@override
|
||||
LaunchTaskType get type => LaunchTaskType.dataProcessing;
|
||||
@ -32,5 +14,6 @@ class PluginLoadTask extends LaunchTask {
|
||||
registerPlugin(builder: BlankPluginBuilder(), config: BlankPluginConfig());
|
||||
registerPlugin(builder: TrashPluginBuilder(), config: TrashPluginConfig());
|
||||
registerPlugin(builder: DocumentPluginBuilder());
|
||||
registerPlugin(builder: GridPluginBuilder(), config: GridPluginConfig());
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +0,0 @@
|
||||
import 'package:app_flowy/user/application/auth_service.dart';
|
||||
import 'package:app_flowy/user/application/sign_in_bloc.dart';
|
||||
import 'package:app_flowy/user/application/sign_up_bloc.dart';
|
||||
import 'package:app_flowy/user/application/splash_bloc.dart';
|
||||
import 'package:app_flowy/user/presentation/router.dart';
|
||||
import 'package:app_flowy/workspace/application/edit_pannel/edit_pannel_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/home/home_bloc.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
import '../core/network_monitor.dart';
|
||||
|
||||
class UserDepsResolver {
|
||||
static Future<void> resolve(GetIt getIt) async {
|
||||
getIt.registerFactory<AuthService>(() => AuthService());
|
||||
|
||||
//Interface implementation
|
||||
getIt.registerFactory<AuthRouter>(() => AuthRouter());
|
||||
|
||||
//Bloc
|
||||
getIt.registerFactory<SignInBloc>(() => SignInBloc(getIt<AuthService>()));
|
||||
getIt.registerFactory<SignUpBloc>(() => SignUpBloc(getIt<AuthService>()));
|
||||
|
||||
getIt.registerFactory<SplashRoute>(() => SplashRoute());
|
||||
getIt.registerFactory<HomeBloc>(() => HomeBloc());
|
||||
getIt.registerFactory<EditPannelBloc>(() => EditPannelBloc());
|
||||
getIt.registerFactory<SplashBloc>(() => SplashBloc());
|
||||
getIt.registerLazySingleton<NetworkListener>(() => NetworkListener());
|
||||
}
|
||||
}
|
4
frontend/app_flowy/lib/user/application/prelude.dart
Normal file
@ -0,0 +1,4 @@
|
||||
export './auth_service.dart';
|
||||
export './sign_in_bloc.dart';
|
||||
export './sign_up_bloc.dart';
|
||||
export './splash_bloc.dart';
|
@ -54,14 +54,14 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class SignInEvent with _$SignInEvent {
|
||||
class SignInEvent with _$SignInEvent {
|
||||
const factory SignInEvent.signedInWithUserEmailAndPassword() = SignedInWithUserEmailAndPassword;
|
||||
const factory SignInEvent.emailChanged(String email) = EmailChanged;
|
||||
const factory SignInEvent.passwordChanged(String password) = PasswordChanged;
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class SignInState with _$SignInState {
|
||||
class SignInState with _$SignInState {
|
||||
const factory SignInState({
|
||||
String? email,
|
||||
String? password,
|
||||
|
@ -15,11 +15,11 @@ class SignUpBloc extends Bloc<SignUpEvent, SignUpState> {
|
||||
on<SignUpEvent>((event, emit) async {
|
||||
await event.map(signUpWithUserEmailAndPassword: (e) async {
|
||||
await _performActionOnSignUp(emit);
|
||||
}, emailChanged: (EmailChanged value) async {
|
||||
}, emailChanged: (_EmailChanged value) async {
|
||||
emit(state.copyWith(email: value.email, emailError: none(), successOrFail: none()));
|
||||
}, passwordChanged: (PasswordChanged value) async {
|
||||
}, passwordChanged: (_PasswordChanged value) async {
|
||||
emit(state.copyWith(password: value.password, passwordError: none(), successOrFail: none()));
|
||||
}, repeatPasswordChanged: (RepeatPasswordChanged value) async {
|
||||
}, repeatPasswordChanged: (_RepeatPasswordChanged value) async {
|
||||
emit(state.copyWith(repeatedPassword: value.password, repeatPasswordError: none(), successOrFail: none()));
|
||||
});
|
||||
});
|
||||
@ -104,9 +104,9 @@ class SignUpBloc extends Bloc<SignUpEvent, SignUpState> {
|
||||
@freezed
|
||||
class SignUpEvent with _$SignUpEvent {
|
||||
const factory SignUpEvent.signUpWithUserEmailAndPassword() = SignUpWithUserEmailAndPassword;
|
||||
const factory SignUpEvent.emailChanged(String email) = EmailChanged;
|
||||
const factory SignUpEvent.passwordChanged(String password) = PasswordChanged;
|
||||
const factory SignUpEvent.repeatPasswordChanged(String password) = RepeatPasswordChanged;
|
||||
const factory SignUpEvent.emailChanged(String email) = _EmailChanged;
|
||||
const factory SignUpEvent.passwordChanged(String password) = _PasswordChanged;
|
||||
const factory SignUpEvent.repeatPasswordChanged(String password) = _RepeatPasswordChanged;
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
@ -12,7 +12,6 @@ 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/rust_stream.dart';
|
||||
|
||||
|
||||
typedef UserProfileUpdatedNotifierValue = Either<UserProfile, FlowyError>;
|
||||
typedef AuthNotifierValue = Either<Unit, FlowyError>;
|
||||
typedef WorkspaceUpdatedNotifierValue = Either<List<Workspace>, FlowyError>;
|
||||
@ -23,8 +22,8 @@ class UserListener {
|
||||
final authDidChangedNotifier = PublishNotifier<AuthNotifierValue>();
|
||||
final workspaceUpdatedNotifier = PublishNotifier<WorkspaceUpdatedNotifierValue>();
|
||||
|
||||
late FolderNotificationParser _workspaceParser;
|
||||
late UserNotificationParser _userParser;
|
||||
FolderNotificationParser? _workspaceParser;
|
||||
UserNotificationParser? _userParser;
|
||||
late UserProfile _user;
|
||||
UserListener({
|
||||
required UserProfile user,
|
||||
@ -36,12 +35,14 @@ class UserListener {
|
||||
_workspaceParser = FolderNotificationParser(id: _user.token, callback: _notificationCallback);
|
||||
_userParser = UserNotificationParser(id: _user.token, callback: _userNotificationCallback);
|
||||
_subscription = RustStreamReceiver.listen((observable) {
|
||||
_workspaceParser.parse(observable);
|
||||
_userParser.parse(observable);
|
||||
_workspaceParser?.parse(observable);
|
||||
_userParser?.parse(observable);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> stop() async {
|
||||
_workspaceParser = null;
|
||||
_userParser = null;
|
||||
await _subscription?.cancel();
|
||||
profileUpdatedNotifier.dispose();
|
||||
authDidChangedNotifier.dispose();
|
||||
|
@ -4,7 +4,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'auth_state.freezed.dart';
|
||||
|
||||
@freezed
|
||||
abstract class AuthState with _$AuthState {
|
||||
class AuthState with _$AuthState {
|
||||
const factory AuthState.authenticated(UserProfile userProfile) = Authenticated;
|
||||
const factory AuthState.unauthenticated(FlowyError error) = Unauthenticated;
|
||||
const factory AuthState.initial() = _Initial;
|
||||
|
@ -170,11 +170,11 @@ class PasswordTextField extends StatelessWidget {
|
||||
return RoundedInputField(
|
||||
obscureText: true,
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
obscureIcon: svg("home/hide"),
|
||||
obscureHideIcon: svg("home/show"),
|
||||
obscureIcon: svgWidget("home/hide"),
|
||||
obscureHideIcon: svgWidget("home/show"),
|
||||
hintText: LocaleKeys.signIn_passwordHint.tr(),
|
||||
normalBorderColor: theme.shader4,
|
||||
highlightBorderColor: theme.red,
|
||||
errorBorderColor: theme.red,
|
||||
cursorColor: theme.main1,
|
||||
errorText: context.read<SignInBloc>().state.passwordError.fold(() => "", (error) => error),
|
||||
onChanged: (value) => context.read<SignInBloc>().add(SignInEvent.passwordChanged(value)),
|
||||
@ -199,7 +199,7 @@ class EmailTextField extends StatelessWidget {
|
||||
hintText: LocaleKeys.signIn_emailHint.tr(),
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
normalBorderColor: theme.shader4,
|
||||
highlightBorderColor: theme.red,
|
||||
errorBorderColor: theme.red,
|
||||
cursorColor: theme.main1,
|
||||
errorText: context.read<SignInBloc>().state.emailError.fold(() => "", (error) => error),
|
||||
onChanged: (value) => context.read<SignInBloc>().add(SignInEvent.emailChanged(value)),
|
||||
|
@ -134,12 +134,12 @@ class PasswordTextField extends StatelessWidget {
|
||||
builder: (context, state) {
|
||||
return RoundedInputField(
|
||||
obscureText: true,
|
||||
obscureIcon: svg("home/hide"),
|
||||
obscureHideIcon: svg("home/show"),
|
||||
obscureIcon: svgWidget("home/hide"),
|
||||
obscureHideIcon: svgWidget("home/show"),
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
hintText: LocaleKeys.signUp_passwordHint.tr(),
|
||||
normalBorderColor: theme.shader4,
|
||||
highlightBorderColor: theme.red,
|
||||
errorBorderColor: theme.red,
|
||||
cursorColor: theme.main1,
|
||||
errorText: context.read<SignUpBloc>().state.passwordError.fold(() => "", (error) => error),
|
||||
onChanged: (value) => context.read<SignUpBloc>().add(SignUpEvent.passwordChanged(value)),
|
||||
@ -162,12 +162,12 @@ class RepeatPasswordTextField extends StatelessWidget {
|
||||
builder: (context, state) {
|
||||
return RoundedInputField(
|
||||
obscureText: true,
|
||||
obscureIcon: svg("home/hide"),
|
||||
obscureHideIcon: svg("home/show"),
|
||||
obscureIcon: svgWidget("home/hide"),
|
||||
obscureHideIcon: svgWidget("home/show"),
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
hintText: LocaleKeys.signUp_repeatPasswordHint.tr(),
|
||||
normalBorderColor: theme.shader4,
|
||||
highlightBorderColor: theme.red,
|
||||
errorBorderColor: theme.red,
|
||||
cursorColor: theme.main1,
|
||||
errorText: context.read<SignUpBloc>().state.repeatPasswordError.fold(() => "", (error) => error),
|
||||
onChanged: (value) => context.read<SignUpBloc>().add(SignUpEvent.repeatPasswordChanged(value)),
|
||||
@ -192,7 +192,7 @@ class EmailTextField extends StatelessWidget {
|
||||
hintText: LocaleKeys.signUp_emailHint.tr(),
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
normalBorderColor: theme.shader4,
|
||||
highlightBorderColor: theme.red,
|
||||
errorBorderColor: theme.red,
|
||||
cursorColor: theme.main1,
|
||||
errorText: context.read<SignUpBloc>().state.emailError.fold(() => "", (error) => error),
|
||||
onChanged: (value) => context.read<SignUpBloc>().add(SignUpEvent.emailChanged(value)),
|
||||
|
@ -4,7 +4,7 @@ import 'package:app_flowy/user/domain/auth_state.dart';
|
||||
import 'package:app_flowy/user/presentation/router.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/error-code/error_code.pbenum.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
|
@ -44,7 +44,7 @@ class FlowyLogoTitle extends StatelessWidget {
|
||||
children: [
|
||||
SizedBox.fromSize(
|
||||
size: logoSize,
|
||||
child: svg("flowy_logo"),
|
||||
child: svgWidget("flowy_logo"),
|
||||
),
|
||||
const VSpace(30),
|
||||
Text(
|
||||
|
@ -19,7 +19,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
|
||||
AppBloc({required this.app, required this.service, required this.listener}) : super(AppState.initial(app)) {
|
||||
on<AppEvent>((event, emit) async {
|
||||
await event.map(initial: (e) async {
|
||||
listener.startListening(
|
||||
listener.start(
|
||||
viewsChanged: _handleViewsChanged,
|
||||
appUpdated: (app) => add(AppEvent.appDidUpdate(app)),
|
||||
);
|
||||
|
@ -17,18 +17,18 @@ class AppListener {
|
||||
StreamSubscription<SubscribeObject>? _subscription;
|
||||
ViewsDidChangeCallback? _viewsChanged;
|
||||
AppDidUpdateCallback? _updated;
|
||||
late FolderNotificationParser _parser;
|
||||
FolderNotificationParser? _parser;
|
||||
String appId;
|
||||
|
||||
AppListener({
|
||||
required this.appId,
|
||||
});
|
||||
|
||||
void startListening({ViewsDidChangeCallback? viewsChanged, AppDidUpdateCallback? appUpdated}) {
|
||||
void start({ViewsDidChangeCallback? viewsChanged, AppDidUpdateCallback? appUpdated}) {
|
||||
_viewsChanged = viewsChanged;
|
||||
_updated = appUpdated;
|
||||
_parser = FolderNotificationParser(id: appId, callback: _bservableCallback);
|
||||
_subscription = RustStreamReceiver.listen((observable) => _parser.parse(observable));
|
||||
_subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
|
||||
}
|
||||
|
||||
void _bservableCallback(FolderNotification ty, Either<Uint8List, FlowyError> result) {
|
||||
@ -61,6 +61,7 @@ class AppListener {
|
||||
}
|
||||
|
||||
Future<void> close() async {
|
||||
_parser = null;
|
||||
await _subscription?.cancel();
|
||||
_viewsChanged = null;
|
||||
_updated = null;
|
||||
|
@ -0,0 +1,3 @@
|
||||
export 'app_bloc.dart';
|
||||
export 'app_listener.dart';
|
||||
export 'app_service.dart';
|
@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:app_flowy/user/application/user_settings_service.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
@ -5,13 +7,12 @@ import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_setting.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:async/async.dart';
|
||||
|
||||
class AppearanceSettingModel extends ChangeNotifier with EquatableMixin {
|
||||
AppearanceSettings setting;
|
||||
AppTheme _theme;
|
||||
Locale _locale;
|
||||
CancelableOperation? _saveOperation;
|
||||
Timer? _saveOperation;
|
||||
|
||||
AppearanceSettingModel(this.setting)
|
||||
: _theme = AppTheme.fromName(name: setting.theme),
|
||||
@ -21,12 +22,10 @@ class AppearanceSettingModel extends ChangeNotifier with EquatableMixin {
|
||||
Locale get locale => _locale;
|
||||
|
||||
Future<void> save() async {
|
||||
_saveOperation?.cancel;
|
||||
_saveOperation = CancelableOperation.fromFuture(
|
||||
Future.delayed(const Duration(seconds: 1), () async {
|
||||
_saveOperation?.cancel();
|
||||
_saveOperation = Timer(const Duration(seconds: 2), () async {
|
||||
await UserSettingsService().setAppearanceSettings(setting);
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -65,7 +65,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||
await _subscription?.cancel();
|
||||
}
|
||||
|
||||
service.closeDocument(docId: view.id);
|
||||
await service.closeDocument(docId: view.id);
|
||||
return super.close();
|
||||
}
|
||||
|
||||
@ -88,7 +88,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||
final result = await service.openDocument(docId: view.id);
|
||||
result.fold(
|
||||
(block) {
|
||||
document = _decodeJsonToDocument(block.deltaJson);
|
||||
document = _decodeJsonToDocument(block.deltaStr);
|
||||
_subscription = document.changes.listen((event) {
|
||||
final delta = event.item2;
|
||||
final documentDelta = document.toDelta();
|
||||
@ -115,7 +115,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||
|
||||
result.fold((rustDoc) {
|
||||
// final json = utf8.decode(doc.data);
|
||||
final rustDelta = Delta.fromJson(jsonDecode(rustDoc.deltaJson));
|
||||
final rustDelta = Delta.fromJson(jsonDecode(rustDoc.deltaStr));
|
||||
if (documentDelta != rustDelta) {
|
||||
Log.error("Receive : $rustDelta");
|
||||
Log.error("Expected : $documentDelta");
|
||||
|
@ -1,20 +1,25 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-collaboration/document_info.pb.dart';
|
||||
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-sync/text_block_info.pb.dart';
|
||||
|
||||
class DocumentService {
|
||||
Future<Either<BlockDelta, FlowyError>> openDocument({required String docId}) {
|
||||
final request = ViewId(value: docId);
|
||||
return FolderEventOpenView(request).send();
|
||||
Future<Either<TextBlockDelta, FlowyError>> openDocument({
|
||||
required String docId,
|
||||
}) async {
|
||||
await FolderEventSetLatestView(ViewId(value: docId)).send();
|
||||
|
||||
final payload = TextBlockId(value: docId);
|
||||
return BlockEventGetBlockData(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<BlockDelta, FlowyError>> composeDelta({required String docId, required String data}) {
|
||||
final request = BlockDelta.create()
|
||||
Future<Either<TextBlockDelta, FlowyError>> composeDelta({required String docId, required String data}) {
|
||||
final payload = TextBlockDelta.create()
|
||||
..blockId = docId
|
||||
..deltaJson = data;
|
||||
return FolderEventApplyDocDelta(request).send();
|
||||
..deltaStr = data;
|
||||
return BlockEventApplyDelta(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> closeDocument({required String docId}) {
|
||||
|
@ -0,0 +1,4 @@
|
||||
export 'doc_bloc.dart';
|
||||
export 'doc_service.dart';
|
||||
export 'share_bloc.dart';
|
||||
export 'share_service.dart';
|
@ -1,6 +1,6 @@
|
||||
import 'package:app_flowy/workspace/application/doc/share_service.dart';
|
||||
import 'package:app_flowy/workspace/application/markdown/delta_markdown.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/share.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-error/errors.pb.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'dart:async';
|
||||
import 'package:dartz/dartz.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-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-text-block/protobuf.dart';
|
||||
|
||||
class ShareService {
|
||||
Future<Either<ExportData, FlowyError>> export(String docId, ExportType type) {
|
||||
@ -10,7 +10,7 @@ class ShareService {
|
||||
..viewId = docId
|
||||
..exportType = type;
|
||||
|
||||
return FolderEventExportDocument(request).send();
|
||||
return BlockEventExportDocument(request).send();
|
||||
}
|
||||
|
||||
Future<Either<ExportData, FlowyError>> exportText(String docId) {
|
||||
|
@ -5,19 +5,8 @@ abstract class EditPannelContext extends Equatable {
|
||||
final String identifier;
|
||||
final String title;
|
||||
final Widget child;
|
||||
const EditPannelContext(
|
||||
{required this.child, required this.identifier, required this.title});
|
||||
const EditPannelContext({required this.child, required this.identifier, required this.title});
|
||||
|
||||
@override
|
||||
List<Object> get props => [identifier];
|
||||
}
|
||||
|
||||
class BlankEditPannelContext extends EditPannelContext {
|
||||
const BlankEditPannelContext()
|
||||
: super(child: const Text('Blank'), identifier: '1', title: '');
|
||||
}
|
||||
|
||||
class CellEditPannelContext extends EditPannelContext {
|
||||
const CellEditPannelContext()
|
||||
: super(child: const Text('shit'), identifier: 'test', title: 'test');
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'package:app_flowy/workspace/application/edit_pannel/edit_context.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
// ignore: import_of_legacy_library_into_null_safe
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
part 'edit_pannel_bloc.freezed.dart';
|
||||
|
@ -0,0 +1,41 @@
|
||||
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';
|
||||
|
||||
typedef UpdateFieldNotifiedValue = Either<CellNotificationData, FlowyError>;
|
||||
|
||||
class CellListener {
|
||||
final String rowId;
|
||||
final String fieldId;
|
||||
PublishNotifier<UpdateFieldNotifiedValue>? updateCellNotifier = PublishNotifier();
|
||||
GridNotificationListener? _listener;
|
||||
CellListener({required this.rowId, required this.fieldId});
|
||||
|
||||
void start() {
|
||||
_listener = GridNotificationListener(objectId: "$rowId:$fieldId", handler: _handler);
|
||||
}
|
||||
|
||||
void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) {
|
||||
switch (ty) {
|
||||
case GridNotification.DidUpdateCell:
|
||||
result.fold(
|
||||
(payload) => updateCellNotifier?.value = left(CellNotificationData.fromBuffer(payload)),
|
||||
(error) => updateCellNotifier?.value = right(error),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> stop() async {
|
||||
await _listener?.stop();
|
||||
updateCellNotifier?.dispose();
|
||||
updateCellNotifier = null;
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
|
||||
|
||||
class CellService {
|
||||
CellService();
|
||||
|
||||
Future<Either<void, FlowyError>> updateCell({
|
||||
required String gridId,
|
||||
required String fieldId,
|
||||
required String rowId,
|
||||
required String data,
|
||||
}) {
|
||||
final payload = CellChangeset.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId
|
||||
..rowId = rowId
|
||||
..data = data;
|
||||
return GridEventUpdateCell(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Cell, FlowyError>> getCell({
|
||||
required String gridId,
|
||||
required String fieldId,
|
||||
required String rowId,
|
||||
}) {
|
||||
final payload = CellIdentifierPayload.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId
|
||||
..rowId = rowId;
|
||||
return GridEventGetCell(payload).send();
|
||||
}
|
||||
}
|
||||
|
||||
class CellCache {
|
||||
final CellService _cellService;
|
||||
final HashMap<String, Cell> _cellDataMap = HashMap();
|
||||
|
||||
CellCache() : _cellService = CellService();
|
||||
|
||||
Future<Option<Cell>> getCellData(GridCell identifier) async {
|
||||
final cellId = _cellId(identifier);
|
||||
final Cell? data = _cellDataMap[cellId];
|
||||
if (data != null) {
|
||||
return Future(() => Some(data));
|
||||
}
|
||||
|
||||
final result = await _cellService.getCell(
|
||||
gridId: identifier.gridId,
|
||||
fieldId: identifier.field.id,
|
||||
rowId: identifier.rowId,
|
||||
);
|
||||
|
||||
return result.fold(
|
||||
(cell) {
|
||||
_cellDataMap[_cellId(identifier)] = cell;
|
||||
return Some(cell);
|
||||
},
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return none();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
String _cellId(GridCell identifier) {
|
||||
return "${identifier.rowId}/${identifier.field.id}";
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_listener.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-data-model/grid.pb.dart' show Cell;
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'cell_service.dart';
|
||||
|
||||
part 'checkbox_cell_bloc.freezed.dart';
|
||||
|
||||
class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
|
||||
final CellService _service;
|
||||
final CellListener _cellListener;
|
||||
|
||||
CheckboxCellBloc({
|
||||
required CellService service,
|
||||
required GridCell cellData,
|
||||
}) : _service = service,
|
||||
_cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
|
||||
super(CheckboxCellState.initial(cellData)) {
|
||||
on<CheckboxCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
initial: (_Initial value) {
|
||||
_startListening();
|
||||
},
|
||||
select: (_Selected value) async {
|
||||
_updateCellData();
|
||||
},
|
||||
didReceiveCellUpdate: (_DidReceiveCellUpdate value) {
|
||||
emit(state.copyWith(isSelected: _isSelected(value.cell)));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _cellListener.stop();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_cellListener.updateCellNotifier?.addPublishListener((result) {
|
||||
result.fold(
|
||||
(notificationData) async => await _loadCellData(),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
_cellListener.start();
|
||||
}
|
||||
|
||||
Future<void> _loadCellData() async {
|
||||
final result = await _service.getCell(
|
||||
gridId: state.cellData.gridId,
|
||||
fieldId: state.cellData.field.id,
|
||||
rowId: state.cellData.rowId,
|
||||
);
|
||||
if (isClosed) {
|
||||
return;
|
||||
}
|
||||
result.fold(
|
||||
(cell) => add(CheckboxCellEvent.didReceiveCellUpdate(cell)),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}
|
||||
|
||||
void _updateCellData() {
|
||||
_service.updateCell(
|
||||
gridId: state.cellData.gridId,
|
||||
fieldId: state.cellData.field.id,
|
||||
rowId: state.cellData.rowId,
|
||||
data: !state.isSelected ? "Yes" : "No",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class CheckboxCellEvent with _$CheckboxCellEvent {
|
||||
const factory CheckboxCellEvent.initial() = _Initial;
|
||||
const factory CheckboxCellEvent.select() = _Selected;
|
||||
const factory CheckboxCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class CheckboxCellState with _$CheckboxCellState {
|
||||
const factory CheckboxCellState({
|
||||
required GridCell cellData,
|
||||
required bool isSelected,
|
||||
}) = _CheckboxCellState;
|
||||
|
||||
factory CheckboxCellState.initial(GridCell cellData) {
|
||||
return CheckboxCellState(cellData: cellData, isSelected: _isSelected(cellData.cell));
|
||||
}
|
||||
}
|
||||
|
||||
bool _isSelected(Cell? cell) {
|
||||
final content = cell?.content ?? "";
|
||||
return content == "Yes";
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/field/field_listener.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-data-model/grid.pb.dart' show Cell, Field;
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'cell_service.dart';
|
||||
|
||||
part 'date_cell_bloc.freezed.dart';
|
||||
|
||||
class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
|
||||
final CellService _service;
|
||||
final CellListener _cellListener;
|
||||
final SingleFieldListener _fieldListener;
|
||||
|
||||
DateCellBloc({required GridCell cellData})
|
||||
: _service = CellService(),
|
||||
_cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
|
||||
_fieldListener = SingleFieldListener(fieldId: cellData.field.id),
|
||||
super(DateCellState.initial(cellData)) {
|
||||
on<DateCellEvent>(
|
||||
(event, emit) async {
|
||||
event.map(
|
||||
initial: (_InitialCell value) {
|
||||
_startListening();
|
||||
},
|
||||
selectDay: (_SelectDay value) {
|
||||
_updateCellData(value.day);
|
||||
},
|
||||
didReceiveCellUpdate: (_DidReceiveCellUpdate value) {
|
||||
emit(state.copyWith(
|
||||
cellData: state.cellData.copyWith(cell: value.cell),
|
||||
content: value.cell.content,
|
||||
));
|
||||
},
|
||||
didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
|
||||
emit(state.copyWith(field: value.field));
|
||||
_loadCellData();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _cellListener.stop();
|
||||
await _fieldListener.stop();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_cellListener.updateCellNotifier?.addPublishListener((result) {
|
||||
result.fold(
|
||||
(notificationData) => _loadCellData(),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}, listenWhen: () => !isClosed);
|
||||
_cellListener.start();
|
||||
|
||||
_fieldListener.updateFieldNotifier?.addPublishListener((result) {
|
||||
result.fold(
|
||||
(field) => add(DateCellEvent.didReceiveFieldUpdate(field)),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}, listenWhen: () => !isClosed);
|
||||
_fieldListener.start();
|
||||
}
|
||||
|
||||
Future<void> _loadCellData() async {
|
||||
final result = await _service.getCell(
|
||||
gridId: state.cellData.gridId,
|
||||
fieldId: state.cellData.field.id,
|
||||
rowId: state.cellData.rowId,
|
||||
);
|
||||
if (isClosed) {
|
||||
return;
|
||||
}
|
||||
result.fold(
|
||||
(cell) => add(DateCellEvent.didReceiveCellUpdate(cell)),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}
|
||||
|
||||
void _updateCellData(DateTime day) {
|
||||
final data = day.millisecondsSinceEpoch ~/ 1000;
|
||||
_service.updateCell(
|
||||
gridId: state.cellData.gridId,
|
||||
fieldId: state.cellData.field.id,
|
||||
rowId: state.cellData.rowId,
|
||||
data: data.toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class DateCellEvent with _$DateCellEvent {
|
||||
const factory DateCellEvent.initial() = _InitialCell;
|
||||
const factory DateCellEvent.selectDay(DateTime day) = _SelectDay;
|
||||
const factory DateCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate;
|
||||
const factory DateCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class DateCellState with _$DateCellState {
|
||||
const factory DateCellState({
|
||||
required GridCell cellData,
|
||||
required String content,
|
||||
required Field field,
|
||||
DateTime? selectedDay,
|
||||
}) = _DateCellState;
|
||||
|
||||
factory DateCellState.initial(GridCell cellData) => DateCellState(
|
||||
cellData: cellData,
|
||||
field: cellData.field,
|
||||
content: cellData.cell?.content ?? "",
|
||||
);
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/field/field_listener.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-data-model/grid.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'cell_service.dart';
|
||||
|
||||
part 'number_cell_bloc.freezed.dart';
|
||||
|
||||
class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
|
||||
final CellService _service;
|
||||
final CellListener _cellListener;
|
||||
final SingleFieldListener _fieldListener;
|
||||
|
||||
NumberCellBloc({
|
||||
required GridCell cellData,
|
||||
}) : _service = CellService(),
|
||||
_cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
|
||||
_fieldListener = SingleFieldListener(fieldId: cellData.field.id),
|
||||
super(NumberCellState.initial(cellData)) {
|
||||
on<NumberCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
initial: (_Initial value) async {
|
||||
_startListening();
|
||||
},
|
||||
didReceiveCellUpdate: (_DidReceiveCellUpdate value) {
|
||||
emit(state.copyWith(content: value.cell.content));
|
||||
},
|
||||
updateCell: (_UpdateCell value) async {
|
||||
await _updateCellValue(value, emit);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _updateCellValue(_UpdateCell value, Emitter<NumberCellState> emit) async {
|
||||
final result = await _service.updateCell(
|
||||
gridId: state.cellData.gridId,
|
||||
fieldId: state.cellData.field.id,
|
||||
rowId: state.cellData.rowId,
|
||||
data: value.text,
|
||||
);
|
||||
result.fold(
|
||||
(field) => _getCellData(),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _cellListener.stop();
|
||||
await _fieldListener.stop();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_cellListener.updateCellNotifier?.addPublishListener((result) {
|
||||
result.fold(
|
||||
(notificationData) async {
|
||||
await _getCellData();
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
_cellListener.start();
|
||||
|
||||
_fieldListener.updateFieldNotifier?.addPublishListener((result) {
|
||||
result.fold(
|
||||
(field) => _getCellData(),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
_fieldListener.start();
|
||||
}
|
||||
|
||||
Future<void> _getCellData() async {
|
||||
final result = await _service.getCell(
|
||||
gridId: state.cellData.gridId,
|
||||
fieldId: state.cellData.field.id,
|
||||
rowId: state.cellData.rowId,
|
||||
);
|
||||
|
||||
if (isClosed) {
|
||||
return;
|
||||
}
|
||||
result.fold(
|
||||
(cell) => add(NumberCellEvent.didReceiveCellUpdate(cell)),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class NumberCellEvent with _$NumberCellEvent {
|
||||
const factory NumberCellEvent.initial() = _Initial;
|
||||
const factory NumberCellEvent.updateCell(String text) = _UpdateCell;
|
||||
const factory NumberCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class NumberCellState with _$NumberCellState {
|
||||
const factory NumberCellState({
|
||||
required GridCell cellData,
|
||||
required String content,
|
||||
}) = _NumberCellState;
|
||||
|
||||
factory NumberCellState.initial(GridCell cellData) {
|
||||
return NumberCellState(cellData: cellData, content: cellData.cell?.content ?? "");
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||
|
||||
class SelectOptionService {
|
||||
SelectOptionService();
|
||||
|
||||
Future<Either<Unit, FlowyError>> create({
|
||||
required String gridId,
|
||||
required String fieldId,
|
||||
required String rowId,
|
||||
required String name,
|
||||
}) {
|
||||
return TypeOptionService(gridId: gridId, fieldId: fieldId).newOption(name: name).then(
|
||||
(result) {
|
||||
return result.fold(
|
||||
(option) {
|
||||
final cellIdentifier = CellIdentifierPayload.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId
|
||||
..rowId = rowId;
|
||||
final payload = SelectOptionChangesetPayload.create()
|
||||
..insertOption = option
|
||||
..cellIdentifier = cellIdentifier;
|
||||
return GridEventUpdateSelectOption(payload).send();
|
||||
},
|
||||
(r) => right(r),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> update({
|
||||
required String gridId,
|
||||
required String fieldId,
|
||||
required String rowId,
|
||||
required SelectOption option,
|
||||
}) {
|
||||
final cellIdentifier = CellIdentifierPayload.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId
|
||||
..rowId = rowId;
|
||||
final payload = SelectOptionChangesetPayload.create()
|
||||
..updateOption = option
|
||||
..cellIdentifier = cellIdentifier;
|
||||
return GridEventUpdateSelectOption(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> delete({
|
||||
required String gridId,
|
||||
required String fieldId,
|
||||
required String rowId,
|
||||
required SelectOption option,
|
||||
}) {
|
||||
final cellIdentifier = CellIdentifierPayload.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId
|
||||
..rowId = rowId;
|
||||
|
||||
final payload = SelectOptionChangesetPayload.create()
|
||||
..deleteOption = option
|
||||
..cellIdentifier = cellIdentifier;
|
||||
|
||||
return GridEventUpdateSelectOption(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<SelectOptionContext, FlowyError>> getOpitonContext({
|
||||
required String gridId,
|
||||
required String fieldId,
|
||||
required String rowId,
|
||||
}) {
|
||||
final payload = CellIdentifierPayload.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId
|
||||
..rowId = rowId;
|
||||
|
||||
return GridEventGetSelectOptionContext(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<void, FlowyError>> select({
|
||||
required String gridId,
|
||||
required String fieldId,
|
||||
required String rowId,
|
||||
required String optionId,
|
||||
}) {
|
||||
final payload = SelectOptionCellChangesetPayload.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId
|
||||
..rowId = rowId
|
||||
..insertOptionId = optionId;
|
||||
return GridEventUpdateCellSelectOption(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<void, FlowyError>> remove({
|
||||
required String gridId,
|
||||
required String fieldId,
|
||||
required String rowId,
|
||||
required String optionId,
|
||||
}) {
|
||||
final payload = SelectOptionCellChangesetPayload.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId
|
||||
..rowId = rowId
|
||||
..deleteOptionId = optionId;
|
||||
return GridEventUpdateCellSelectOption(payload).send();
|
||||
}
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
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_listener.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/selection_type_option.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
part 'selection_cell_bloc.freezed.dart';
|
||||
|
||||
class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
|
||||
final SelectOptionService _service;
|
||||
final CellListener _cellListener;
|
||||
final SingleFieldListener _fieldListener;
|
||||
|
||||
SelectionCellBloc({
|
||||
required GridCell cellData,
|
||||
}) : _service = SelectOptionService(),
|
||||
_cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
|
||||
_fieldListener = SingleFieldListener(fieldId: cellData.field.id),
|
||||
super(SelectionCellState.initial(cellData)) {
|
||||
on<SelectionCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
initial: (_InitialCell value) async {
|
||||
_loadOptions();
|
||||
_startListening();
|
||||
},
|
||||
didReceiveOptions: (_DidReceiveOptions value) {
|
||||
emit(state.copyWith(options: value.options, selectedOptions: value.selectedOptions));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _cellListener.stop();
|
||||
await _fieldListener.stop();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _loadOptions() async {
|
||||
final result = await _service.getOpitonContext(
|
||||
gridId: state.cellData.gridId,
|
||||
fieldId: state.cellData.field.id,
|
||||
rowId: state.cellData.rowId,
|
||||
);
|
||||
if (isClosed) {
|
||||
return;
|
||||
}
|
||||
|
||||
result.fold(
|
||||
(selectOptionContext) => add(SelectionCellEvent.didReceiveOptions(
|
||||
selectOptionContext.options,
|
||||
selectOptionContext.selectOptions,
|
||||
)),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_cellListener.updateCellNotifier?.addPublishListener((result) {
|
||||
result.fold(
|
||||
(notificationData) => _loadOptions(),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
_cellListener.start();
|
||||
|
||||
_fieldListener.updateFieldNotifier?.addPublishListener((result) {
|
||||
result.fold(
|
||||
(field) => _loadOptions(),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
_fieldListener.start();
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SelectionCellEvent with _$SelectionCellEvent {
|
||||
const factory SelectionCellEvent.initial() = _InitialCell;
|
||||
const factory SelectionCellEvent.didReceiveOptions(
|
||||
List<SelectOption> options,
|
||||
List<SelectOption> selectedOptions,
|
||||
) = _DidReceiveOptions;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SelectionCellState with _$SelectionCellState {
|
||||
const factory SelectionCellState({
|
||||
required GridCell cellData,
|
||||
required List<SelectOption> options,
|
||||
required List<SelectOption> selectedOptions,
|
||||
}) = _SelectionCellState;
|
||||
|
||||
factory SelectionCellState.initial(GridCell cellData) => SelectionCellState(
|
||||
cellData: cellData,
|
||||
options: [],
|
||||
selectedOptions: [],
|
||||
);
|
||||
}
|
@ -0,0 +1,189 @@
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/field/field_listener.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-data-model/grid.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'select_option_service.dart';
|
||||
|
||||
part 'selection_editor_bloc.freezed.dart';
|
||||
|
||||
class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionEditorState> {
|
||||
final SelectOptionService _selectOptionService;
|
||||
final SingleFieldListener _fieldListener;
|
||||
final CellListener _cellListener;
|
||||
Timer? _delayOperation;
|
||||
|
||||
SelectOptionEditorBloc({
|
||||
required GridCell cellData,
|
||||
required List<SelectOption> options,
|
||||
required List<SelectOption> selectedOptions,
|
||||
}) : _selectOptionService = SelectOptionService(),
|
||||
_fieldListener = SingleFieldListener(fieldId: cellData.field.id),
|
||||
_cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
|
||||
super(SelectOptionEditorState.initial(cellData, options, selectedOptions)) {
|
||||
on<SelectOptionEditorEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
initial: (_Initial value) async {
|
||||
_startListening();
|
||||
},
|
||||
didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
|
||||
emit(state.copyWith(field: value.field));
|
||||
_loadOptions();
|
||||
},
|
||||
didReceiveOptions: (_DidReceiveOptions value) {
|
||||
emit(state.copyWith(
|
||||
options: value.options,
|
||||
selectedOptions: value.selectedOptions,
|
||||
));
|
||||
},
|
||||
newOption: (_NewOption value) {
|
||||
_createOption(value.optionName);
|
||||
},
|
||||
deleteOption: (_DeleteOption value) {
|
||||
_deleteOption(value.option);
|
||||
},
|
||||
updateOption: (_UpdateOption value) {
|
||||
_updateOption(value.option);
|
||||
},
|
||||
selectOption: (_SelectOption value) {
|
||||
_makeOptionAsSelected(value.optionId);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
_delayOperation?.cancel();
|
||||
await _fieldListener.stop();
|
||||
await _cellListener.stop();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _createOption(String name) async {
|
||||
final result = await _selectOptionService.create(
|
||||
gridId: state.gridId,
|
||||
fieldId: state.field.id,
|
||||
rowId: state.rowId,
|
||||
name: name,
|
||||
);
|
||||
result.fold((l) => _loadOptions(), (err) => Log.error(err));
|
||||
}
|
||||
|
||||
void _deleteOption(SelectOption option) async {
|
||||
final result = await _selectOptionService.delete(
|
||||
gridId: state.gridId,
|
||||
fieldId: state.field.id,
|
||||
rowId: state.rowId,
|
||||
option: option,
|
||||
);
|
||||
|
||||
result.fold((l) => null, (err) => Log.error(err));
|
||||
}
|
||||
|
||||
void _updateOption(SelectOption option) async {
|
||||
final result = await _selectOptionService.update(
|
||||
gridId: state.gridId,
|
||||
fieldId: state.field.id,
|
||||
rowId: state.rowId,
|
||||
option: option,
|
||||
);
|
||||
|
||||
result.fold((l) => null, (err) => Log.error(err));
|
||||
}
|
||||
|
||||
void _makeOptionAsSelected(String optionId) {
|
||||
_selectOptionService.select(
|
||||
gridId: state.gridId,
|
||||
fieldId: state.field.id,
|
||||
rowId: state.rowId,
|
||||
optionId: optionId,
|
||||
);
|
||||
}
|
||||
|
||||
void _loadOptions() async {
|
||||
_delayOperation?.cancel();
|
||||
_delayOperation = Timer(
|
||||
const Duration(milliseconds: 1),
|
||||
() async {
|
||||
final result = await _selectOptionService.getOpitonContext(
|
||||
gridId: state.gridId,
|
||||
fieldId: state.field.id,
|
||||
rowId: state.rowId,
|
||||
);
|
||||
if (isClosed) {
|
||||
return;
|
||||
}
|
||||
|
||||
result.fold(
|
||||
(selectOptionContext) => add(SelectOptionEditorEvent.didReceiveOptions(
|
||||
selectOptionContext.options,
|
||||
selectOptionContext.selectOptions,
|
||||
)),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_cellListener.updateCellNotifier?.addPublishListener((result) {
|
||||
result.fold(
|
||||
(notificationData) => _loadOptions(),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
_cellListener.start();
|
||||
|
||||
_fieldListener.updateFieldNotifier?.addPublishListener((result) {
|
||||
result.fold(
|
||||
(field) => add(SelectOptionEditorEvent.didReceiveFieldUpdate(field)),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}, listenWhen: () => !isClosed);
|
||||
_fieldListener.start();
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SelectOptionEditorEvent with _$SelectOptionEditorEvent {
|
||||
const factory SelectOptionEditorEvent.initial() = _Initial;
|
||||
const factory SelectOptionEditorEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
|
||||
const factory SelectOptionEditorEvent.didReceiveOptions(
|
||||
List<SelectOption> options, List<SelectOption> selectedOptions) = _DidReceiveOptions;
|
||||
const factory SelectOptionEditorEvent.newOption(String optionName) = _NewOption;
|
||||
const factory SelectOptionEditorEvent.selectOption(String optionId) = _SelectOption;
|
||||
const factory SelectOptionEditorEvent.updateOption(SelectOption option) = _UpdateOption;
|
||||
const factory SelectOptionEditorEvent.deleteOption(SelectOption option) = _DeleteOption;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SelectOptionEditorState with _$SelectOptionEditorState {
|
||||
const factory SelectOptionEditorState({
|
||||
required String gridId,
|
||||
required Field field,
|
||||
required String rowId,
|
||||
required List<SelectOption> options,
|
||||
required List<SelectOption> selectedOptions,
|
||||
}) = _SelectOptionEditorState;
|
||||
|
||||
factory SelectOptionEditorState.initial(
|
||||
GridCell cellData,
|
||||
List<SelectOption> options,
|
||||
List<SelectOption> selectedOptions,
|
||||
) {
|
||||
return SelectOptionEditorState(
|
||||
gridId: cellData.gridId,
|
||||
field: cellData.field,
|
||||
rowId: cellData.rowId,
|
||||
options: options,
|
||||
selectedOptions: selectedOptions,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell;
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'cell_listener.dart';
|
||||
import 'cell_service.dart';
|
||||
|
||||
part 'text_cell_bloc.freezed.dart';
|
||||
|
||||
class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
|
||||
final CellService _service;
|
||||
final CellListener _cellListener;
|
||||
|
||||
TextCellBloc({
|
||||
required GridCell cellData,
|
||||
}) : _service = CellService(),
|
||||
_cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
|
||||
super(TextCellState.initial(cellData)) {
|
||||
on<TextCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
initial: (_InitialCell value) async {
|
||||
_startListening();
|
||||
},
|
||||
updateText: (_UpdateText value) {
|
||||
updateCellContent(value.text);
|
||||
emit(state.copyWith(content: value.text));
|
||||
},
|
||||
didReceiveCellData: (_DidReceiveCellData value) {
|
||||
emit(state.copyWith(
|
||||
cellData: value.cellData,
|
||||
content: value.cellData.cell?.content ?? "",
|
||||
));
|
||||
},
|
||||
didReceiveCellUpdate: (_DidReceiveCellUpdate value) {
|
||||
emit(state.copyWith(
|
||||
cellData: state.cellData.copyWith(cell: value.cell),
|
||||
content: value.cell.content,
|
||||
));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _cellListener.stop();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void updateCellContent(String content) {
|
||||
final fieldId = state.cellData.field.id;
|
||||
final gridId = state.cellData.gridId;
|
||||
final rowId = state.cellData.rowId;
|
||||
_service.updateCell(
|
||||
data: content,
|
||||
fieldId: fieldId,
|
||||
gridId: gridId,
|
||||
rowId: rowId,
|
||||
);
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_cellListener.updateCellNotifier?.addPublishListener((result) {
|
||||
result.fold(
|
||||
(notificationData) async => await _loadCellData(),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
_cellListener.start();
|
||||
}
|
||||
|
||||
Future<void> _loadCellData() async {
|
||||
final result = await _service.getCell(
|
||||
gridId: state.cellData.gridId,
|
||||
fieldId: state.cellData.field.id,
|
||||
rowId: state.cellData.rowId,
|
||||
);
|
||||
if (isClosed) {
|
||||
return;
|
||||
}
|
||||
result.fold(
|
||||
(cell) => add(TextCellEvent.didReceiveCellUpdate(cell)),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class TextCellEvent with _$TextCellEvent {
|
||||
const factory TextCellEvent.initial() = _InitialCell;
|
||||
const factory TextCellEvent.didReceiveCellData(GridCell cellData) = _DidReceiveCellData;
|
||||
const factory TextCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate;
|
||||
const factory TextCellEvent.updateText(String text) = _UpdateText;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class TextCellState with _$TextCellState {
|
||||
const factory TextCellState({
|
||||
required String content,
|
||||
required GridCell cellData,
|
||||
}) = _TextCellState;
|
||||
|
||||
factory TextCellState.initial(GridCell cellData) => TextCellState(
|
||||
content: cellData.cell?.content ?? "",
|
||||
cellData: cellData,
|
||||
);
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
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_action_sheet_bloc.freezed.dart';
|
||||
|
||||
class FieldActionSheetBloc extends Bloc<FieldActionSheetEvent, FieldActionSheetState> {
|
||||
final FieldService service;
|
||||
|
||||
FieldActionSheetBloc({required Field field, required this.service})
|
||||
: super(FieldActionSheetState.initial(EditFieldContext.create()..gridField = field)) {
|
||||
on<FieldActionSheetEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
updateFieldName: (_UpdateFieldName value) async {
|
||||
final result = await service.updateField(fieldId: field.id, name: value.name);
|
||||
result.fold(
|
||||
(l) => null,
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
},
|
||||
hideField: (_HideField value) async {
|
||||
final result = await service.updateField(fieldId: field.id, visibility: false);
|
||||
result.fold(
|
||||
(l) => null,
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
},
|
||||
deleteField: (_DeleteField value) async {
|
||||
final result = await service.deleteField(fieldId: field.id);
|
||||
result.fold(
|
||||
(l) => null,
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
},
|
||||
duplicateField: (_DuplicateField value) async {
|
||||
final result = await service.duplicateField(fieldId: field.id);
|
||||
result.fold(
|
||||
(l) => null,
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
},
|
||||
saveField: (_SaveField value) {},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class FieldActionSheetEvent with _$FieldActionSheetEvent {
|
||||
const factory FieldActionSheetEvent.updateFieldName(String name) = _UpdateFieldName;
|
||||
const factory FieldActionSheetEvent.hideField() = _HideField;
|
||||
const factory FieldActionSheetEvent.duplicateField() = _DuplicateField;
|
||||
const factory FieldActionSheetEvent.deleteField() = _DeleteField;
|
||||
const factory FieldActionSheetEvent.saveField() = _SaveField;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class FieldActionSheetState with _$FieldActionSheetState {
|
||||
const factory FieldActionSheetState({
|
||||
required EditFieldContext editContext,
|
||||
required String errorText,
|
||||
required String fieldName,
|
||||
}) = _FieldActionSheetState;
|
||||
|
||||
factory FieldActionSheetState.initial(EditFieldContext editContext) => FieldActionSheetState(
|
||||
editContext: editContext,
|
||||
errorText: '',
|
||||
fieldName: editContext.gridField.name,
|
||||
);
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
import 'package:app_flowy/workspace/application/grid/field/field_listener.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field;
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
part 'field_cell_bloc.freezed.dart';
|
||||
|
||||
class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
|
||||
final SingleFieldListener _fieldListener;
|
||||
final FieldService _fieldService;
|
||||
|
||||
FieldCellBloc({
|
||||
required GridFieldCellContext cellContext,
|
||||
}) : _fieldListener = SingleFieldListener(fieldId: cellContext.field.id),
|
||||
_fieldService = FieldService(gridId: cellContext.gridId),
|
||||
super(FieldCellState.initial(cellContext)) {
|
||||
on<FieldCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
initial: (_InitialCell value) async {
|
||||
_startListening();
|
||||
},
|
||||
didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
|
||||
emit(state.copyWith(field: value.field));
|
||||
},
|
||||
updateWidth: (_UpdateWidth value) {
|
||||
final defaultWidth = state.field.width.toDouble();
|
||||
final width = defaultWidth + value.offset;
|
||||
if (width > defaultWidth && width < 300) {
|
||||
_fieldService.updateField(fieldId: state.field.id, width: width);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _fieldListener.stop();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_fieldListener.updateFieldNotifier?.addPublishListener((result) {
|
||||
result.fold(
|
||||
(field) => add(FieldCellEvent.didReceiveFieldUpdate(field)),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}, listenWhen: () => !isClosed);
|
||||
_fieldListener.start();
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class FieldCellEvent with _$FieldCellEvent {
|
||||
const factory FieldCellEvent.initial() = _InitialCell;
|
||||
const factory FieldCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
|
||||
const factory FieldCellEvent.updateWidth(double offset) = _UpdateWidth;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class FieldCellState with _$FieldCellState {
|
||||
const factory FieldCellState({
|
||||
required String gridId,
|
||||
required Field field,
|
||||
}) = _FieldCellState;
|
||||
|
||||
factory FieldCellState.initial(GridFieldCellContext cellContext) => FieldCellState(
|
||||
gridId: cellContext.gridId,
|
||||
field: cellContext.field,
|
||||
);
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
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';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
part 'field_editor_bloc.freezed.dart';
|
||||
|
||||
class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
||||
final FieldService service;
|
||||
final EditFieldContextLoader _loader;
|
||||
|
||||
FieldEditorBloc({
|
||||
required this.service,
|
||||
required EditFieldContextLoader fieldLoader,
|
||||
}) : _loader = fieldLoader,
|
||||
super(FieldEditorState.initial(service.gridId)) {
|
||||
on<FieldEditorEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
initial: (_InitialField value) async {
|
||||
await _getEditFieldContext(emit);
|
||||
},
|
||||
updateName: (_UpdateName value) {
|
||||
emit(state.copyWith(fieldName: value.name));
|
||||
},
|
||||
switchField: (_SwitchField value) {
|
||||
emit(state.copyWith(field: Some(value.field), typeOptionData: value.typeOptionData));
|
||||
},
|
||||
done: (_Done value) async {
|
||||
await _saveField(emit);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
return super.close();
|
||||
}
|
||||
|
||||
Future<void> _saveField(Emitter<FieldEditorState> emit) async {
|
||||
await state.field.fold(
|
||||
() async => null,
|
||||
(field) async {
|
||||
field.name = state.fieldName;
|
||||
final result = await service.insertField(
|
||||
field: field,
|
||||
typeOptionData: state.typeOptionData,
|
||||
);
|
||||
result.fold((l) => null, (r) => null);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _getEditFieldContext(Emitter<FieldEditorState> emit) async {
|
||||
final result = await _loader.load();
|
||||
result.fold(
|
||||
(editContext) {
|
||||
emit(state.copyWith(
|
||||
field: Some(editContext.gridField),
|
||||
typeOptionData: editContext.typeOptionData,
|
||||
fieldName: editContext.gridField.name,
|
||||
));
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class FieldEditorEvent with _$FieldEditorEvent {
|
||||
const factory FieldEditorEvent.initial() = _InitialField;
|
||||
const factory FieldEditorEvent.updateName(String name) = _UpdateName;
|
||||
const factory FieldEditorEvent.switchField(Field field, Uint8List typeOptionData) = _SwitchField;
|
||||
const factory FieldEditorEvent.done() = _Done;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class FieldEditorState with _$FieldEditorState {
|
||||
const factory FieldEditorState({
|
||||
required String fieldName,
|
||||
required String gridId,
|
||||
required String errorText,
|
||||
required Option<Field> field,
|
||||
required List<int> typeOptionData,
|
||||
}) = _FieldEditorState;
|
||||
|
||||
factory FieldEditorState.initial(String gridId) => FieldEditorState(
|
||||
gridId: gridId,
|
||||
fieldName: '',
|
||||
field: none(),
|
||||
errorText: '',
|
||||
typeOptionData: List<int>.empty(),
|
||||
);
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
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';
|
||||
|
||||
typedef UpdateFieldNotifiedValue = Either<Field, FlowyError>;
|
||||
|
||||
class SingleFieldListener {
|
||||
final String fieldId;
|
||||
PublishNotifier<UpdateFieldNotifiedValue>? updateFieldNotifier = PublishNotifier();
|
||||
GridNotificationListener? _listener;
|
||||
|
||||
SingleFieldListener({required this.fieldId});
|
||||
|
||||
void start() {
|
||||
_listener = GridNotificationListener(
|
||||
objectId: fieldId,
|
||||
handler: _handler,
|
||||
);
|
||||
}
|
||||
|
||||
void _handler(
|
||||
GridNotification ty,
|
||||
Either<Uint8List, FlowyError> result,
|
||||
) {
|
||||
switch (ty) {
|
||||
case GridNotification.DidUpdateField:
|
||||
result.fold(
|
||||
(payload) => updateFieldNotifier?.value = left(Field.fromBuffer(payload)),
|
||||
(error) => updateFieldNotifier?.value = right(error),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> stop() async {
|
||||
await _listener?.stop();
|
||||
updateFieldNotifier?.dispose();
|
||||
updateFieldNotifier = null;
|
||||
}
|
||||
}
|
@ -0,0 +1,185 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'field_service.freezed.dart';
|
||||
|
||||
class FieldService {
|
||||
final String gridId;
|
||||
|
||||
FieldService({required this.gridId});
|
||||
|
||||
Future<Either<EditFieldContext, FlowyError>> switchToField(String fieldId, FieldType fieldType) {
|
||||
final payload = EditFieldPayload.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId
|
||||
..fieldType = fieldType;
|
||||
|
||||
return GridEventSwitchToField(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<EditFieldContext, FlowyError>> getEditFieldContext(String fieldId, FieldType fieldType) {
|
||||
final payload = GetEditFieldContextPayload.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId
|
||||
..fieldType = fieldType;
|
||||
|
||||
return GridEventGetEditFieldContext(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> moveField(String fieldId, int fromIndex, int toIndex) {
|
||||
final payload = MoveItemPayload.create()
|
||||
..gridId = gridId
|
||||
..itemId = fieldId
|
||||
..ty = MoveItemType.MoveField
|
||||
..fromIndex = fromIndex
|
||||
..toIndex = toIndex;
|
||||
|
||||
return GridEventMoveItem(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> updateField({
|
||||
required String fieldId,
|
||||
String? name,
|
||||
FieldType? fieldType,
|
||||
bool? frozen,
|
||||
bool? visibility,
|
||||
double? width,
|
||||
List<int>? typeOptionData,
|
||||
}) {
|
||||
var payload = FieldChangesetPayload.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId;
|
||||
|
||||
if (name != null) {
|
||||
payload.name = name;
|
||||
}
|
||||
|
||||
if (fieldType != null) {
|
||||
payload.fieldType = fieldType;
|
||||
}
|
||||
|
||||
if (frozen != null) {
|
||||
payload.frozen = frozen;
|
||||
}
|
||||
|
||||
if (visibility != null) {
|
||||
payload.visibility = visibility;
|
||||
}
|
||||
|
||||
if (width != null) {
|
||||
payload.width = width.toInt();
|
||||
}
|
||||
|
||||
if (typeOptionData != null) {
|
||||
payload.typeOptionData = typeOptionData;
|
||||
}
|
||||
|
||||
return GridEventUpdateField(payload).send();
|
||||
}
|
||||
|
||||
// Create the field if it does not exist. Otherwise, update the field.
|
||||
Future<Either<Unit, FlowyError>> insertField({
|
||||
required Field field,
|
||||
List<int>? typeOptionData,
|
||||
String? startFieldId,
|
||||
}) {
|
||||
var payload = InsertFieldPayload.create()
|
||||
..gridId = gridId
|
||||
..field_2 = field
|
||||
..typeOptionData = typeOptionData ?? [];
|
||||
|
||||
if (startFieldId != null) {
|
||||
payload.startFieldId = startFieldId;
|
||||
}
|
||||
|
||||
return GridEventInsertField(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> deleteField({
|
||||
required String fieldId,
|
||||
}) {
|
||||
final payload = FieldIdentifierPayload.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId;
|
||||
|
||||
return GridEventDeleteField(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> duplicateField({
|
||||
required String fieldId,
|
||||
}) {
|
||||
final payload = FieldIdentifierPayload.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId;
|
||||
|
||||
return GridEventDuplicateField(payload).send();
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridFieldCellContext with _$GridFieldCellContext {
|
||||
const factory GridFieldCellContext({
|
||||
required String gridId,
|
||||
required Field field,
|
||||
}) = _GridFieldCellContext;
|
||||
}
|
||||
|
||||
abstract class EditFieldContextLoader {
|
||||
Future<Either<EditFieldContext, FlowyError>> load();
|
||||
|
||||
Future<Either<EditFieldContext, FlowyError>> switchToField(String fieldId, FieldType fieldType);
|
||||
}
|
||||
|
||||
class NewFieldContextLoader extends EditFieldContextLoader {
|
||||
final String gridId;
|
||||
NewFieldContextLoader({
|
||||
required this.gridId,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<Either<EditFieldContext, FlowyError>> load() {
|
||||
final payload = GetEditFieldContextPayload.create()
|
||||
..gridId = gridId
|
||||
..fieldType = FieldType.RichText;
|
||||
|
||||
return GridEventGetEditFieldContext(payload).send();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<EditFieldContext, FlowyError>> switchToField(String fieldId, FieldType fieldType) {
|
||||
final payload = GetEditFieldContextPayload.create()
|
||||
..gridId = gridId
|
||||
..fieldType = fieldType;
|
||||
|
||||
return GridEventGetEditFieldContext(payload).send();
|
||||
}
|
||||
}
|
||||
|
||||
class FieldContextLoaderAdaptor extends EditFieldContextLoader {
|
||||
final String gridId;
|
||||
final Field field;
|
||||
|
||||
FieldContextLoaderAdaptor({
|
||||
required this.gridId,
|
||||
required this.field,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<Either<EditFieldContext, FlowyError>> load() {
|
||||
final payload = GetEditFieldContextPayload.create()
|
||||
..gridId = gridId
|
||||
..fieldId = field.id
|
||||
..fieldType = field.fieldType;
|
||||
|
||||
return GridEventGetEditFieldContext(payload).send();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<EditFieldContext, FlowyError>> switchToField(String fieldId, FieldType fieldType) async {
|
||||
final fieldService = FieldService(gridId: gridId);
|
||||
return fieldService.switchToField(fieldId, fieldType);
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
import 'dart:typed_data';
|
||||
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';
|
||||
|
||||
part 'field_switch_bloc.freezed.dart';
|
||||
|
||||
class FieldSwitcherBloc extends Bloc<FieldSwitchEvent, FieldSwitchState> {
|
||||
FieldSwitcherBloc(SwitchFieldContext editContext) : super(FieldSwitchState.initial(editContext)) {
|
||||
on<FieldSwitchEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
toFieldType: (_ToFieldType value) async {
|
||||
emit(state.copyWith(
|
||||
field: value.field,
|
||||
typeOptionData: Uint8List.fromList(value.typeOptionData),
|
||||
));
|
||||
},
|
||||
didUpdateTypeOptionData: (_DidUpdateTypeOptionData value) {
|
||||
emit(state.copyWith(typeOptionData: value.typeOptionData));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class FieldSwitchEvent with _$FieldSwitchEvent {
|
||||
const factory FieldSwitchEvent.toFieldType(Field field, List<int> typeOptionData) = _ToFieldType;
|
||||
const factory FieldSwitchEvent.didUpdateTypeOptionData(Uint8List typeOptionData) = _DidUpdateTypeOptionData;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class FieldSwitchState with _$FieldSwitchState {
|
||||
const factory FieldSwitchState({
|
||||
required String gridId,
|
||||
required Field field,
|
||||
required Uint8List typeOptionData,
|
||||
}) = _FieldSwitchState;
|
||||
|
||||
factory FieldSwitchState.initial(SwitchFieldContext switchContext) => FieldSwitchState(
|
||||
gridId: switchContext.gridId,
|
||||
field: switchContext.field,
|
||||
typeOptionData: Uint8List.fromList(switchContext.typeOptionData),
|
||||
);
|
||||
}
|
||||
|
||||
class SwitchFieldContext {
|
||||
final String gridId;
|
||||
final Field field;
|
||||
final List<int> typeOptionData;
|
||||
|
||||
SwitchFieldContext(this.gridId, this.field, this.typeOptionData);
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
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';
|
||||
|
||||
typedef UpdateFieldNotifiedValue = Either<GridFieldChangeset, FlowyError>;
|
||||
|
||||
class GridFieldsListener {
|
||||
final String gridId;
|
||||
PublishNotifier<UpdateFieldNotifiedValue>? updateFieldsNotifier = PublishNotifier();
|
||||
GridNotificationListener? _listener;
|
||||
GridFieldsListener({required this.gridId});
|
||||
|
||||
void start() {
|
||||
_listener = GridNotificationListener(
|
||||
objectId: gridId,
|
||||
handler: _handler,
|
||||
);
|
||||
}
|
||||
|
||||
void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) {
|
||||
switch (ty) {
|
||||
case GridNotification.DidUpdateGridField:
|
||||
result.fold(
|
||||
(payload) => updateFieldsNotifier?.value = left(GridFieldChangeset.fromBuffer(payload)),
|
||||
(error) => updateFieldsNotifier?.value = right(error),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> stop() async {
|
||||
await _listener?.stop();
|
||||
updateFieldsNotifier?.dispose();
|
||||
updateFieldsNotifier = null;
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
part 'date_bloc.freezed.dart';
|
||||
|
||||
class DateTypeOptionBloc extends Bloc<DateTypeOptionEvent, DateTypeOptionState> {
|
||||
DateTypeOptionBloc({required DateTypeOption typeOption}) : super(DateTypeOptionState.initial(typeOption)) {
|
||||
on<DateTypeOptionEvent>(
|
||||
(event, emit) async {
|
||||
event.map(
|
||||
didSelectDateFormat: (_DidSelectDateFormat value) {
|
||||
emit(state.copyWith(typeOption: _updateTypeOption(dateFormat: value.format)));
|
||||
},
|
||||
didSelectTimeFormat: (_DidSelectTimeFormat value) {
|
||||
emit(state.copyWith(typeOption: _updateTypeOption(timeFormat: value.format)));
|
||||
},
|
||||
includeTime: (_IncludeTime value) {
|
||||
emit(state.copyWith(typeOption: _updateTypeOption(includeTime: value.includeTime)));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
DateTypeOption _updateTypeOption({
|
||||
DateFormat? dateFormat,
|
||||
TimeFormat? timeFormat,
|
||||
bool? includeTime,
|
||||
}) {
|
||||
state.typeOption.freeze();
|
||||
return state.typeOption.rebuild((typeOption) {
|
||||
if (dateFormat != null) {
|
||||
typeOption.dateFormat = dateFormat;
|
||||
}
|
||||
|
||||
if (timeFormat != null) {
|
||||
typeOption.timeFormat = timeFormat;
|
||||
}
|
||||
|
||||
if (includeTime != null) {
|
||||
typeOption.includeTime = includeTime;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class DateTypeOptionEvent with _$DateTypeOptionEvent {
|
||||
const factory DateTypeOptionEvent.didSelectDateFormat(DateFormat format) = _DidSelectDateFormat;
|
||||
const factory DateTypeOptionEvent.didSelectTimeFormat(TimeFormat format) = _DidSelectTimeFormat;
|
||||
const factory DateTypeOptionEvent.includeTime(bool includeTime) = _IncludeTime;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class DateTypeOptionState with _$DateTypeOptionState {
|
||||
const factory DateTypeOptionState({
|
||||
required DateTypeOption typeOption,
|
||||
}) = _DateTypeOptionState;
|
||||
|
||||
factory DateTypeOptionState.initial(DateTypeOption typeOption) => DateTypeOptionState(typeOption: typeOption);
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
part 'edit_select_option_bloc.freezed.dart';
|
||||
|
||||
class EditSelectOptionBloc extends Bloc<EditSelectOptionEvent, EditSelectOptionState> {
|
||||
EditSelectOptionBloc({required SelectOption option}) : super(EditSelectOptionState.initial(option)) {
|
||||
on<EditSelectOptionEvent>(
|
||||
(event, emit) async {
|
||||
event.map(
|
||||
updateName: (_UpdateName value) {
|
||||
emit(state.copyWith(option: _updateName(value.name)));
|
||||
},
|
||||
updateColor: (_UpdateColor value) {
|
||||
emit(state.copyWith(option: _updateColor(value.color)));
|
||||
},
|
||||
delete: (_Delete value) {
|
||||
emit(state.copyWith(deleted: const Some(true)));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
return super.close();
|
||||
}
|
||||
|
||||
SelectOption _updateColor(SelectOptionColor color) {
|
||||
state.option.freeze();
|
||||
return state.option.rebuild((option) {
|
||||
option.color = color;
|
||||
});
|
||||
}
|
||||
|
||||
SelectOption _updateName(String name) {
|
||||
state.option.freeze();
|
||||
return state.option.rebuild((option) {
|
||||
option.name = name;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class EditSelectOptionEvent with _$EditSelectOptionEvent {
|
||||
const factory EditSelectOptionEvent.updateName(String name) = _UpdateName;
|
||||
const factory EditSelectOptionEvent.updateColor(SelectOptionColor color) = _UpdateColor;
|
||||
const factory EditSelectOptionEvent.delete() = _Delete;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class EditSelectOptionState with _$EditSelectOptionState {
|
||||
const factory EditSelectOptionState({
|
||||
required SelectOption option,
|
||||
required Option<bool> deleted,
|
||||
}) = _EditSelectOptionState;
|
||||
|
||||
factory EditSelectOptionState.initial(SelectOption option) => EditSelectOptionState(
|
||||
option: option,
|
||||
deleted: none(),
|
||||
);
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'package:dartz/dartz.dart';
|
||||
part 'field_option_pannel_bloc.freezed.dart';
|
||||
|
||||
class FieldOptionPannelBloc extends Bloc<FieldOptionPannelEvent, FieldOptionPannelState> {
|
||||
FieldOptionPannelBloc({required List<SelectOption> options}) : super(FieldOptionPannelState.initial(options)) {
|
||||
on<FieldOptionPannelEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
createOption: (_CreateOption value) async {
|
||||
emit(state.copyWith(isEditingOption: false, newOptionName: Some(value.optionName)));
|
||||
},
|
||||
beginAddingOption: (_BeginAddingOption value) {
|
||||
emit(state.copyWith(isEditingOption: true, newOptionName: none()));
|
||||
},
|
||||
endAddingOption: (_EndAddingOption value) {
|
||||
emit(state.copyWith(isEditingOption: false, newOptionName: none()));
|
||||
},
|
||||
updateOption: (_UpdateOption value) {
|
||||
emit(state.copyWith(updateOption: Some(value.option)));
|
||||
},
|
||||
deleteOption: (_DeleteOption value) {
|
||||
emit(state.copyWith(deleteOption: Some(value.option)));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class FieldOptionPannelEvent with _$FieldOptionPannelEvent {
|
||||
const factory FieldOptionPannelEvent.createOption(String optionName) = _CreateOption;
|
||||
const factory FieldOptionPannelEvent.beginAddingOption() = _BeginAddingOption;
|
||||
const factory FieldOptionPannelEvent.endAddingOption() = _EndAddingOption;
|
||||
const factory FieldOptionPannelEvent.updateOption(SelectOption option) = _UpdateOption;
|
||||
const factory FieldOptionPannelEvent.deleteOption(SelectOption option) = _DeleteOption;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class FieldOptionPannelState with _$FieldOptionPannelState {
|
||||
const factory FieldOptionPannelState({
|
||||
required List<SelectOption> options,
|
||||
required bool isEditingOption,
|
||||
required Option<String> newOptionName,
|
||||
required Option<SelectOption> updateOption,
|
||||
required Option<SelectOption> deleteOption,
|
||||
}) = _FieldOptionPannelState;
|
||||
|
||||
factory FieldOptionPannelState.initial(List<SelectOption> options) => FieldOptionPannelState(
|
||||
options: options,
|
||||
isEditingOption: false,
|
||||
newOptionName: none(),
|
||||
updateOption: none(),
|
||||
deleteOption: none(),
|
||||
);
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
import 'type_option_service.dart';
|
||||
|
||||
part 'multi_select_bloc.freezed.dart';
|
||||
|
||||
class MultiSelectTypeOptionBloc extends Bloc<MultiSelectTypeOptionEvent, MultiSelectTypeOptionState> {
|
||||
final TypeOptionService service;
|
||||
|
||||
MultiSelectTypeOptionBloc(TypeOptionContext typeOptionContext)
|
||||
: service = TypeOptionService(gridId: typeOptionContext.gridId, fieldId: typeOptionContext.field.id),
|
||||
super(MultiSelectTypeOptionState.initial(MultiSelectTypeOption.fromBuffer(typeOptionContext.data))) {
|
||||
on<MultiSelectTypeOptionEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
createOption: (_CreateOption value) async {
|
||||
final result = await service.newOption(name: value.optionName);
|
||||
result.fold(
|
||||
(option) {
|
||||
emit(state.copyWith(typeOption: _insertOption(option)));
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
},
|
||||
updateOption: (_UpdateOption value) async {
|
||||
emit(state.copyWith(typeOption: _updateOption(value.option)));
|
||||
},
|
||||
deleteOption: (_DeleteOption value) {
|
||||
emit(state.copyWith(typeOption: _deleteOption(value.option)));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
return super.close();
|
||||
}
|
||||
|
||||
MultiSelectTypeOption _insertOption(SelectOption option) {
|
||||
state.typeOption.freeze();
|
||||
return state.typeOption.rebuild((typeOption) {
|
||||
typeOption.options.insert(0, option);
|
||||
});
|
||||
}
|
||||
|
||||
MultiSelectTypeOption _updateOption(SelectOption option) {
|
||||
state.typeOption.freeze();
|
||||
return state.typeOption.rebuild((typeOption) {
|
||||
final index = typeOption.options.indexWhere((element) => element.id == option.id);
|
||||
if (index != -1) {
|
||||
typeOption.options[index] = option;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
MultiSelectTypeOption _deleteOption(SelectOption option) {
|
||||
state.typeOption.freeze();
|
||||
return state.typeOption.rebuild((typeOption) {
|
||||
final index = typeOption.options.indexWhere((element) => element.id == option.id);
|
||||
if (index != -1) {
|
||||
typeOption.options.removeAt(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class MultiSelectTypeOptionEvent with _$MultiSelectTypeOptionEvent {
|
||||
const factory MultiSelectTypeOptionEvent.createOption(String optionName) = _CreateOption;
|
||||
const factory MultiSelectTypeOptionEvent.updateOption(SelectOption option) = _UpdateOption;
|
||||
const factory MultiSelectTypeOptionEvent.deleteOption(SelectOption option) = _DeleteOption;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class MultiSelectTypeOptionState with _$MultiSelectTypeOptionState {
|
||||
const factory MultiSelectTypeOptionState({
|
||||
required MultiSelectTypeOption typeOption,
|
||||
}) = _MultiSelectTypeOptionState;
|
||||
|
||||
factory MultiSelectTypeOptionState.initial(MultiSelectTypeOption typeOption) => MultiSelectTypeOptionState(
|
||||
typeOption: typeOption,
|
||||
);
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
part 'number_bloc.freezed.dart';
|
||||
|
||||
class NumberTypeOptionBloc extends Bloc<NumberTypeOptionEvent, NumberTypeOptionState> {
|
||||
NumberTypeOptionBloc({required NumberTypeOption typeOption}) : super(NumberTypeOptionState.initial(typeOption)) {
|
||||
on<NumberTypeOptionEvent>(
|
||||
(event, emit) async {
|
||||
event.map(
|
||||
didSelectFormat: (_DidSelectFormat value) {
|
||||
emit(state.copyWith(typeOption: _updateNumberFormat(value.format)));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
NumberTypeOption _updateNumberFormat(NumberFormat format) {
|
||||
state.typeOption.freeze();
|
||||
return state.typeOption.rebuild((typeOption) {
|
||||
typeOption.format = format;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class NumberTypeOptionEvent with _$NumberTypeOptionEvent {
|
||||
const factory NumberTypeOptionEvent.didSelectFormat(NumberFormat format) = _DidSelectFormat;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class NumberTypeOptionState with _$NumberTypeOptionState {
|
||||
const factory NumberTypeOptionState({
|
||||
required NumberTypeOption typeOption,
|
||||
}) = _NumberTypeOptionState;
|
||||
|
||||
factory NumberTypeOptionState.initial(NumberTypeOption typeOption) => NumberTypeOptionState(
|
||||
typeOption: typeOption,
|
||||
);
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
import 'type_option_service.dart';
|
||||
|
||||
part 'single_select_bloc.freezed.dart';
|
||||
|
||||
class SingleSelectTypeOptionBloc extends Bloc<SingleSelectTypeOptionEvent, SingleSelectTypeOptionState> {
|
||||
final TypeOptionService service;
|
||||
|
||||
SingleSelectTypeOptionBloc(
|
||||
TypeOptionContext typeOptionContext,
|
||||
) : service = TypeOptionService(gridId: typeOptionContext.gridId, fieldId: typeOptionContext.field.id),
|
||||
super(
|
||||
SingleSelectTypeOptionState.initial(SingleSelectTypeOption.fromBuffer(typeOptionContext.data)),
|
||||
) {
|
||||
on<SingleSelectTypeOptionEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
createOption: (_CreateOption value) async {
|
||||
final result = await service.newOption(name: value.optionName);
|
||||
result.fold(
|
||||
(option) {
|
||||
emit(state.copyWith(typeOption: _insertOption(option)));
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
},
|
||||
updateOption: (_UpdateOption value) async {
|
||||
emit(state.copyWith(typeOption: _updateOption(value.option)));
|
||||
},
|
||||
deleteOption: (_DeleteOption value) {
|
||||
emit(state.copyWith(typeOption: _deleteOption(value.option)));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
return super.close();
|
||||
}
|
||||
|
||||
SingleSelectTypeOption _insertOption(SelectOption option) {
|
||||
state.typeOption.freeze();
|
||||
return state.typeOption.rebuild((typeOption) {
|
||||
typeOption.options.insert(0, option);
|
||||
});
|
||||
}
|
||||
|
||||
SingleSelectTypeOption _updateOption(SelectOption option) {
|
||||
state.typeOption.freeze();
|
||||
return state.typeOption.rebuild((typeOption) {
|
||||
final index = typeOption.options.indexWhere((element) => element.id == option.id);
|
||||
if (index != -1) {
|
||||
typeOption.options[index] = option;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
SingleSelectTypeOption _deleteOption(SelectOption option) {
|
||||
state.typeOption.freeze();
|
||||
return state.typeOption.rebuild((typeOption) {
|
||||
final index = typeOption.options.indexWhere((element) => element.id == option.id);
|
||||
if (index != -1) {
|
||||
typeOption.options.removeAt(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SingleSelectTypeOptionEvent with _$SingleSelectTypeOptionEvent {
|
||||
const factory SingleSelectTypeOptionEvent.createOption(String optionName) = _CreateOption;
|
||||
const factory SingleSelectTypeOptionEvent.updateOption(SelectOption option) = _UpdateOption;
|
||||
const factory SingleSelectTypeOptionEvent.deleteOption(SelectOption option) = _DeleteOption;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SingleSelectTypeOptionState with _$SingleSelectTypeOptionState {
|
||||
const factory SingleSelectTypeOptionState({
|
||||
required SingleSelectTypeOption typeOption,
|
||||
}) = _SingleSelectTypeOptionState;
|
||||
|
||||
factory SingleSelectTypeOptionState.initial(SingleSelectTypeOption typeOption) => SingleSelectTypeOptionState(
|
||||
typeOption: typeOption,
|
||||
);
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.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/selection_type_option.pb.dart';
|
||||
|
||||
class TypeOptionService {
|
||||
final String gridId;
|
||||
final String fieldId;
|
||||
|
||||
TypeOptionService({
|
||||
required this.gridId,
|
||||
required this.fieldId,
|
||||
});
|
||||
|
||||
Future<Either<SelectOption, FlowyError>> newOption({
|
||||
required String name,
|
||||
}) {
|
||||
final fieldIdentifier = FieldIdentifierPayload.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId;
|
||||
|
||||
final payload = CreateSelectOptionPayload.create()
|
||||
..optionName = name
|
||||
..fieldIdentifier = fieldIdentifier;
|
||||
|
||||
return GridEventNewSelectOption(payload).send();
|
||||
}
|
||||
}
|
||||
|
||||
class TypeOptionContext {
|
||||
final String gridId;
|
||||
final Field field;
|
||||
final Uint8List data;
|
||||
const TypeOptionContext({
|
||||
required this.gridId,
|
||||
required this.field,
|
||||
required this.data,
|
||||
});
|
||||
}
|
132
frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart
Normal file
@ -0,0 +1,132 @@
|
||||
import 'dart:async';
|
||||
import 'package:dartz/dartz.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-grid-data-model/protobuf.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'grid_service.dart';
|
||||
import 'row/row_service.dart';
|
||||
|
||||
part 'grid_bloc.freezed.dart';
|
||||
|
||||
class GridBloc extends Bloc<GridEvent, GridState> {
|
||||
final GridService _gridService;
|
||||
final GridFieldCache fieldCache;
|
||||
late final GridRowCache rowCache;
|
||||
|
||||
GridBloc({required View view})
|
||||
: _gridService = GridService(gridId: view.id),
|
||||
fieldCache = GridFieldCache(gridId: view.id),
|
||||
super(GridState.initial(view.id)) {
|
||||
rowCache = GridRowCache(
|
||||
gridId: view.id,
|
||||
dataDelegate: GridRowDataDelegateAdaptor(fieldCache),
|
||||
);
|
||||
|
||||
on<GridEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
initial: (InitialGrid value) async {
|
||||
_startListening();
|
||||
await _loadGrid(emit);
|
||||
},
|
||||
createRow: (_CreateRow value) {
|
||||
_gridService.createRow();
|
||||
},
|
||||
didReceiveRowUpdate: (_DidReceiveRowUpdate value) {
|
||||
emit(state.copyWith(rows: value.rows, listState: value.listState));
|
||||
},
|
||||
didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
|
||||
emit(state.copyWith(rows: rowCache.clonedRows, fields: value.fields));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _gridService.closeGrid();
|
||||
await fieldCache.dispose();
|
||||
await rowCache.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
fieldCache.addListener(
|
||||
listenWhen: () => !isClosed,
|
||||
onChanged: (fields) => add(GridEvent.didReceiveFieldUpdate(fields)),
|
||||
);
|
||||
|
||||
rowCache.addListener(
|
||||
listenWhen: () => !isClosed,
|
||||
onChanged: (rows, listState) => add(GridEvent.didReceiveRowUpdate(rowCache.clonedRows, listState)),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _loadGrid(Emitter<GridState> emit) async {
|
||||
final result = await _gridService.loadGrid();
|
||||
return Future(
|
||||
() => result.fold(
|
||||
(grid) async => await _loadFields(grid, emit),
|
||||
(err) => emit(state.copyWith(loadingState: GridLoadingState.finish(right(err)))),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _loadFields(Grid grid, Emitter<GridState> emit) async {
|
||||
final result = await _gridService.getFields(fieldOrders: grid.fieldOrders);
|
||||
return Future(
|
||||
() => result.fold(
|
||||
(fields) {
|
||||
fieldCache.fields = fields.items;
|
||||
rowCache.updateWithBlock(grid.blockOrders);
|
||||
|
||||
emit(state.copyWith(
|
||||
grid: Some(grid),
|
||||
fields: fieldCache.clonedFields,
|
||||
rows: rowCache.clonedRows,
|
||||
loadingState: GridLoadingState.finish(left(unit)),
|
||||
));
|
||||
},
|
||||
(err) => emit(state.copyWith(loadingState: GridLoadingState.finish(right(err)))),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridEvent with _$GridEvent {
|
||||
const factory GridEvent.initial() = InitialGrid;
|
||||
const factory GridEvent.createRow() = _CreateRow;
|
||||
const factory GridEvent.didReceiveRowUpdate(List<GridRow> rows, GridRowChangeReason listState) = _DidReceiveRowUpdate;
|
||||
const factory GridEvent.didReceiveFieldUpdate(List<Field> fields) = _DidReceiveFieldUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridState with _$GridState {
|
||||
const factory GridState({
|
||||
required String gridId,
|
||||
required Option<Grid> grid,
|
||||
required List<Field> fields,
|
||||
required List<GridRow> rows,
|
||||
required GridLoadingState loadingState,
|
||||
required GridRowChangeReason listState,
|
||||
}) = _GridState;
|
||||
|
||||
factory GridState.initial(String gridId) => GridState(
|
||||
fields: [],
|
||||
rows: [],
|
||||
grid: none(),
|
||||
gridId: gridId,
|
||||
loadingState: const _Loading(),
|
||||
listState: const InitialListState(),
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridLoadingState with _$GridLoadingState {
|
||||
const factory GridLoadingState.loading() = _Loading;
|
||||
const factory GridLoadingState.finish(Either<Unit, FlowyError> successOrFail) = _Finish;
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
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 'grid_service.dart';
|
||||
|
||||
part 'grid_header_bloc.freezed.dart';
|
||||
|
||||
class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
|
||||
final FieldService _fieldService;
|
||||
final GridFieldCache fieldCache;
|
||||
|
||||
GridHeaderBloc({
|
||||
required String gridId,
|
||||
required this.fieldCache,
|
||||
}) : _fieldService = FieldService(gridId: gridId),
|
||||
super(GridHeaderState.initial(fieldCache.clonedFields)) {
|
||||
on<GridHeaderEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
initial: (_InitialHeader value) async {
|
||||
_startListening();
|
||||
},
|
||||
didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
|
||||
emit(state.copyWith(fields: value.fields));
|
||||
},
|
||||
moveField: (_MoveField value) async {
|
||||
final result = await _fieldService.moveField(value.field.id, value.fromIndex, value.toIndex);
|
||||
result.fold((l) {}, (err) => Log.error(err));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _startListening() async {
|
||||
fieldCache.addListener(
|
||||
onChanged: (fields) => add(GridHeaderEvent.didReceiveFieldUpdate(fields)),
|
||||
listenWhen: () => !isClosed,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridHeaderEvent with _$GridHeaderEvent {
|
||||
const factory GridHeaderEvent.initial() = _InitialHeader;
|
||||
const factory GridHeaderEvent.didReceiveFieldUpdate(List<Field> fields) = _DidReceiveFieldUpdate;
|
||||
const factory GridHeaderEvent.moveField(Field field, int fromIndex, int toIndex) = _MoveField;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridHeaderState with _$GridHeaderState {
|
||||
const factory GridHeaderState({required List<Field> fields}) = _GridHeaderState;
|
||||
|
||||
factory GridHeaderState.initial(List<Field> fields) {
|
||||
// final List<Field> newFields = List.from(fields);
|
||||
// newFields.retainWhere((field) => field.visibility);
|
||||
return GridHeaderState(fields: fields);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
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();
|
||||
}
|
||||
}
|
@ -0,0 +1,197 @@
|
||||
import 'package:app_flowy/workspace/application/grid/field/grid_listenr.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import 'row/row_service.dart';
|
||||
|
||||
class GridService {
|
||||
final String gridId;
|
||||
GridService({
|
||||
required this.gridId,
|
||||
});
|
||||
|
||||
Future<Either<Grid, FlowyError>> loadGrid() async {
|
||||
await FolderEventSetLatestView(ViewId(value: gridId)).send();
|
||||
|
||||
final payload = GridId(value: gridId);
|
||||
return GridEventGetGridData(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Row, FlowyError>> createRow({Option<String>? startRowId}) {
|
||||
CreateRowPayload payload = CreateRowPayload.create()..gridId = gridId;
|
||||
startRowId?.fold(() => null, (id) => payload.startRowId = id);
|
||||
return GridEventCreateRow(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<RepeatedField, FlowyError>> getFields({required List<FieldOrder> fieldOrders}) {
|
||||
final payload = QueryFieldPayload.create()
|
||||
..gridId = gridId
|
||||
..fieldOrders = RepeatedFieldOrder(items: fieldOrders);
|
||||
return GridEventGetFields(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> closeGrid() {
|
||||
final request = ViewId(value: gridId);
|
||||
return FolderEventCloseView(request).send();
|
||||
}
|
||||
}
|
||||
|
||||
class FieldsNotifier extends ChangeNotifier {
|
||||
List<Field> _fields = [];
|
||||
|
||||
set fields(List<Field> fields) {
|
||||
_fields = fields;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
List<Field> get fields => _fields;
|
||||
}
|
||||
|
||||
class GridFieldCache {
|
||||
final String gridId;
|
||||
late final GridFieldsListener _fieldListener;
|
||||
final FieldsNotifier _fieldNotifier = FieldsNotifier();
|
||||
GridFieldCache({required this.gridId}) {
|
||||
_fieldListener = GridFieldsListener(gridId: gridId);
|
||||
_fieldListener.updateFieldsNotifier?.addPublishListener((result) {
|
||||
result.fold(
|
||||
(changeset) {
|
||||
_deleteFields(changeset.deletedFields);
|
||||
_insertFields(changeset.insertedFields);
|
||||
_updateFields(changeset.updatedFields);
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
_fieldListener.start();
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
await _fieldListener.stop();
|
||||
_fieldNotifier.dispose();
|
||||
}
|
||||
|
||||
void applyChangeset(GridFieldChangeset changeset) {}
|
||||
|
||||
UnmodifiableListView<Field> get unmodifiableFields => UnmodifiableListView(_fieldNotifier.fields);
|
||||
|
||||
List<Field> get clonedFields => [..._fieldNotifier.fields];
|
||||
|
||||
set fields(List<Field> fields) {
|
||||
_fieldNotifier.fields = [...fields];
|
||||
}
|
||||
|
||||
VoidCallback addListener(
|
||||
{VoidCallback? listener, void Function(List<Field>)? onChanged, bool Function()? listenWhen}) {
|
||||
f() {
|
||||
if (listenWhen != null && listenWhen() == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (onChanged != null) {
|
||||
onChanged(clonedFields);
|
||||
}
|
||||
|
||||
if (listener != null) {
|
||||
listener();
|
||||
}
|
||||
}
|
||||
|
||||
_fieldNotifier.addListener(f);
|
||||
return f;
|
||||
}
|
||||
|
||||
void removeListener(VoidCallback f) {
|
||||
_fieldNotifier.removeListener(f);
|
||||
}
|
||||
|
||||
void _deleteFields(List<FieldOrder> deletedFields) {
|
||||
if (deletedFields.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final List<Field> fields = _fieldNotifier.fields;
|
||||
final Map<String, FieldOrder> deletedFieldMap = {
|
||||
for (var fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder
|
||||
};
|
||||
|
||||
fields.retainWhere((field) => (deletedFieldMap[field.id] == null));
|
||||
_fieldNotifier.fields = fields;
|
||||
}
|
||||
|
||||
void _insertFields(List<IndexField> insertedFields) {
|
||||
if (insertedFields.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final List<Field> fields = _fieldNotifier.fields;
|
||||
for (final indexField in insertedFields) {
|
||||
if (fields.length > indexField.index) {
|
||||
fields.insert(indexField.index, indexField.field_1);
|
||||
} else {
|
||||
fields.add(indexField.field_1);
|
||||
}
|
||||
}
|
||||
_fieldNotifier.fields = fields;
|
||||
}
|
||||
|
||||
void _updateFields(List<Field> updatedFields) {
|
||||
if (updatedFields.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final List<Field> fields = _fieldNotifier.fields;
|
||||
for (final updatedField in updatedFields) {
|
||||
final index = fields.indexWhere((field) => field.id == updatedField.id);
|
||||
if (index != -1) {
|
||||
fields.removeAt(index);
|
||||
fields.insert(index, updatedField);
|
||||
}
|
||||
}
|
||||
_fieldNotifier.fields = fields;
|
||||
}
|
||||
}
|
||||
|
||||
class GridRowDataDelegateAdaptor extends GridRowDataDelegate {
|
||||
final GridFieldCache _cache;
|
||||
|
||||
GridRowDataDelegateAdaptor(GridFieldCache cache) : _cache = cache;
|
||||
@override
|
||||
UnmodifiableListView<Field> get fields => _cache.unmodifiableFields;
|
||||
|
||||
@override
|
||||
GridRow buildGridRow(RowOrder rowOrder) {
|
||||
return GridRow(
|
||||
gridId: _cache.gridId,
|
||||
fields: _cache.unmodifiableFields,
|
||||
rowId: rowOrder.rowId,
|
||||
height: rowOrder.height.toDouble(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onFieldChanged(FieldDidUpdateCallback callback) {
|
||||
_cache.addListener(listener: () {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
CellDataMap buildCellDataMap(Row rowData) {
|
||||
var map = CellDataMap.new();
|
||||
for (final field in fields) {
|
||||
if (field.visibility) {
|
||||
map[field.id] = GridCell(
|
||||
rowId: rowData.id,
|
||||
gridId: _cache.gridId,
|
||||
cell: rowData.cellByFieldId[field.id],
|
||||
field: field,
|
||||
);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
export 'grid_bloc.dart';
|
||||
export 'row/row_bloc.dart';
|
||||
export 'row/row_service.dart';
|
||||
export 'grid_service.dart';
|
||||
export 'grid_header_bloc.dart';
|
||||
|
||||
// Field
|
||||
export 'field/field_service.dart';
|
||||
export 'field/field_action_sheet_bloc.dart';
|
||||
export 'field/field_editor_bloc.dart';
|
||||
export 'field/field_switch_bloc.dart';
|
||||
|
||||
// Field Type Option
|
||||
export 'field/type_option/date_bloc.dart';
|
||||
export 'field/type_option/number_bloc.dart';
|
||||
export 'field/type_option/single_select_bloc.dart';
|
||||
|
||||
// Cell
|
||||
export 'cell/text_cell_bloc.dart';
|
||||
export 'cell/number_cell_bloc.dart';
|
||||
export 'cell/selection_cell_bloc.dart';
|
||||
export 'cell/date_cell_bloc.dart';
|
||||
export 'cell/checkbox_cell_bloc.dart';
|
||||
export 'cell/cell_service.dart';
|
||||
|
||||
// Setting
|
||||
export 'setting/setting_bloc.dart';
|
||||
export 'setting/property_bloc.dart';
|
@ -0,0 +1,58 @@
|
||||
import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
part 'row_action_sheet_bloc.freezed.dart';
|
||||
|
||||
class RowActionSheetBloc extends Bloc<RowActionSheetEvent, RowActionSheetState> {
|
||||
final RowService _rowService;
|
||||
|
||||
RowActionSheetBloc({required GridRow rowData})
|
||||
: _rowService = RowService(gridId: rowData.gridId, rowId: rowData.rowId),
|
||||
super(RowActionSheetState.initial(rowData)) {
|
||||
on<RowActionSheetEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
deleteRow: (_DeleteRow value) async {
|
||||
final result = await _rowService.deleteRow();
|
||||
logResult(result);
|
||||
},
|
||||
duplicateRow: (_DuplicateRow value) async {
|
||||
final result = await _rowService.duplicateRow();
|
||||
logResult(result);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void logResult(Either<Unit, FlowyError> result) {
|
||||
result.fold((l) => null, (err) => Log.error(err));
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class RowActionSheetEvent with _$RowActionSheetEvent {
|
||||
const factory RowActionSheetEvent.duplicateRow() = _DuplicateRow;
|
||||
const factory RowActionSheetEvent.deleteRow() = _DeleteRow;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class RowActionSheetState with _$RowActionSheetState {
|
||||
const factory RowActionSheetState({
|
||||
required GridRow rowData,
|
||||
}) = _RowActionSheetState;
|
||||
|
||||
factory RowActionSheetState.initial(GridRow rowData) => RowActionSheetState(
|
||||
rowData: rowData,
|
||||
);
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
import 'dart:collection';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'row_service.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
part 'row_bloc.freezed.dart';
|
||||
|
||||
class RowBloc extends Bloc<RowEvent, RowState> {
|
||||
final RowService _rowService;
|
||||
final GridRowCache _rowCache;
|
||||
void Function()? _rowListenFn;
|
||||
|
||||
RowBloc({
|
||||
required GridRow rowData,
|
||||
required GridRowCache rowCache,
|
||||
}) : _rowService = RowService(gridId: rowData.gridId, rowId: rowData.rowId),
|
||||
_rowCache = rowCache,
|
||||
super(RowState.initial(rowData)) {
|
||||
on<RowEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
initial: (_InitialRow value) async {
|
||||
await _startListening();
|
||||
await _loadRow(emit);
|
||||
},
|
||||
createRow: (_CreateRow value) {
|
||||
_rowService.createRow();
|
||||
},
|
||||
didReceiveCellDatas: (_DidReceiveCellDatas value) async {
|
||||
emit(state.copyWith(cellDataMap: Some(value.cellData)));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_rowListenFn != null) {
|
||||
_rowCache.removeRowListener(_rowListenFn!);
|
||||
}
|
||||
return super.close();
|
||||
}
|
||||
|
||||
Future<void> _startListening() async {
|
||||
_rowListenFn = _rowCache.addRowListener(
|
||||
rowId: state.rowData.rowId,
|
||||
onUpdated: (cellDatas) => add(RowEvent.didReceiveCellDatas(cellDatas)),
|
||||
listenWhen: () => !isClosed,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _loadRow(Emitter<RowState> emit) async {
|
||||
final data = _rowCache.loadCellData(state.rowData.rowId);
|
||||
data.foldRight(null, (cellDatas, _) {
|
||||
if (!isClosed) {
|
||||
add(RowEvent.didReceiveCellDatas(cellDatas));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class RowEvent with _$RowEvent {
|
||||
const factory RowEvent.initial() = _InitialRow;
|
||||
const factory RowEvent.createRow() = _CreateRow;
|
||||
const factory RowEvent.didReceiveCellDatas(CellDataMap cellData) = _DidReceiveCellDatas;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class RowState with _$RowState {
|
||||
const factory RowState({
|
||||
required GridRow rowData,
|
||||
required Option<CellDataMap> cellDataMap,
|
||||
}) = _RowState;
|
||||
|
||||
factory RowState.initial(GridRow rowData) => RowState(
|
||||
rowData: rowData,
|
||||
cellDataMap: none(),
|
||||
);
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'row_service.dart';
|
||||
|
||||
part 'row_detail_bloc.freezed.dart';
|
||||
|
||||
class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
|
||||
final GridRow rowData;
|
||||
final GridRowCache _rowCache;
|
||||
void Function()? _rowListenFn;
|
||||
|
||||
RowDetailBloc({
|
||||
required this.rowData,
|
||||
required GridRowCache rowCache,
|
||||
}) : _rowCache = rowCache,
|
||||
super(RowDetailState.initial()) {
|
||||
on<RowDetailEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
initial: (_Initial value) async {
|
||||
await _startListening();
|
||||
_loadCellData();
|
||||
},
|
||||
didReceiveCellDatas: (_DidReceiveCellDatas value) {
|
||||
emit(state.copyWith(cellDatas: value.cellDatas));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_rowListenFn != null) {
|
||||
_rowCache.removeRowListener(_rowListenFn!);
|
||||
}
|
||||
return super.close();
|
||||
}
|
||||
|
||||
Future<void> _startListening() async {
|
||||
_rowListenFn = _rowCache.addRowListener(
|
||||
rowId: rowData.rowId,
|
||||
onUpdated: (cellDatas) => add(RowDetailEvent.didReceiveCellDatas(cellDatas.values.toList())),
|
||||
listenWhen: () => !isClosed,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _loadCellData() async {
|
||||
final data = _rowCache.loadCellData(rowData.rowId);
|
||||
data.foldRight(null, (cellDataMap, _) {
|
||||
if (!isClosed) {
|
||||
add(RowDetailEvent.didReceiveCellDatas(cellDataMap.values.toList()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class RowDetailEvent with _$RowDetailEvent {
|
||||
const factory RowDetailEvent.initial() = _Initial;
|
||||
const factory RowDetailEvent.didReceiveCellDatas(List<GridCell> cellDatas) = _DidReceiveCellDatas;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class RowDetailState with _$RowDetailState {
|
||||
const factory RowDetailState({
|
||||
required List<GridCell> cellDatas,
|
||||
}) = _RowDetailState;
|
||||
|
||||
factory RowDetailState.initial() => RowDetailState(
|
||||
cellDatas: List.empty(),
|
||||
);
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
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/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';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
typedef UpdateRowNotifiedValue = Either<Row, FlowyError>;
|
||||
typedef UpdateFieldNotifiedValue = Either<List<Field>, FlowyError>;
|
||||
|
||||
class RowListener {
|
||||
final String rowId;
|
||||
PublishNotifier<UpdateRowNotifiedValue>? updateRowNotifier = PublishNotifier();
|
||||
GridNotificationListener? _listener;
|
||||
|
||||
RowListener({required this.rowId});
|
||||
|
||||
void start() {
|
||||
_listener = GridNotificationListener(objectId: rowId, handler: _handler);
|
||||
}
|
||||
|
||||
void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) {
|
||||
switch (ty) {
|
||||
case GridNotification.DidUpdateRow:
|
||||
result.fold(
|
||||
(payload) => updateRowNotifier?.value = left(Row.fromBuffer(payload)),
|
||||
(error) => updateRowNotifier?.value = right(error),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> stop() async {
|
||||
await _listener?.stop();
|
||||
updateRowNotifier?.dispose();
|
||||
updateRowNotifier = null;
|
||||
}
|
||||
}
|
@ -0,0 +1,374 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/grid_listener.dart';
|
||||
part 'row_service.freezed.dart';
|
||||
|
||||
typedef RowUpdateCallback = void Function();
|
||||
typedef FieldDidUpdateCallback = void Function();
|
||||
typedef CellDataMap = LinkedHashMap<String, GridCell>;
|
||||
|
||||
abstract class GridRowDataDelegate {
|
||||
UnmodifiableListView<Field> get fields;
|
||||
GridRow buildGridRow(RowOrder rowOrder);
|
||||
CellDataMap buildCellDataMap(Row rowData);
|
||||
void onFieldChanged(FieldDidUpdateCallback callback);
|
||||
}
|
||||
|
||||
class GridRowCache {
|
||||
final String gridId;
|
||||
final RowsNotifier _rowNotifier;
|
||||
final GridRowListener _rowsListener;
|
||||
final GridRowDataDelegate _dataDelegate;
|
||||
|
||||
List<GridRow> get clonedRows => _rowNotifier.clonedRows;
|
||||
|
||||
GridRowCache({required this.gridId, required GridRowDataDelegate dataDelegate})
|
||||
: _rowNotifier = RowsNotifier(rowBuilder: dataDelegate.buildGridRow),
|
||||
_rowsListener = GridRowListener(gridId: gridId),
|
||||
_dataDelegate = dataDelegate {
|
||||
//
|
||||
dataDelegate.onFieldChanged(() => _rowNotifier.fieldDidChange());
|
||||
|
||||
// listen on the row update
|
||||
_rowsListener.rowsUpdateNotifier.addPublishListener((result) {
|
||||
result.fold(
|
||||
(changesets) {
|
||||
for (final changeset in changesets) {
|
||||
_rowNotifier.deleteRows(changeset.deletedRows);
|
||||
_rowNotifier.insertRows(changeset.insertedRows);
|
||||
_rowNotifier.updateRows(changeset.updatedRows);
|
||||
}
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
_rowsListener.start();
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
await _rowsListener.stop();
|
||||
_rowNotifier.dispose();
|
||||
}
|
||||
|
||||
void addListener({
|
||||
void Function(List<GridRow>, GridRowChangeReason)? onChanged,
|
||||
bool Function()? listenWhen,
|
||||
}) {
|
||||
_rowNotifier.addListener(() {
|
||||
if (onChanged == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (listenWhen != null && listenWhen() == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
onChanged(clonedRows, _rowNotifier._changeReason);
|
||||
});
|
||||
}
|
||||
|
||||
RowUpdateCallback addRowListener({
|
||||
required String rowId,
|
||||
void Function(CellDataMap)? onUpdated,
|
||||
bool Function()? listenWhen,
|
||||
}) {
|
||||
listenrHandler() {
|
||||
if (onUpdated == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (listenWhen != null && listenWhen() == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
notify() {
|
||||
final row = _rowNotifier.rowDataWithId(rowId);
|
||||
if (row != null) {
|
||||
final cellDataMap = _dataDelegate.buildCellDataMap(row);
|
||||
onUpdated(cellDataMap);
|
||||
}
|
||||
}
|
||||
|
||||
_rowNotifier._changeReason.whenOrNull(
|
||||
update: (indexs) {
|
||||
if (indexs[rowId] != null) {
|
||||
notify();
|
||||
}
|
||||
},
|
||||
fieldDidChange: () => notify(),
|
||||
);
|
||||
}
|
||||
|
||||
_rowNotifier.addListener(listenrHandler);
|
||||
return listenrHandler;
|
||||
}
|
||||
|
||||
void removeRowListener(VoidCallback callback) {
|
||||
_rowNotifier.removeListener(callback);
|
||||
}
|
||||
|
||||
Option<CellDataMap> loadCellData(String rowId) {
|
||||
final Row? data = _rowNotifier.rowDataWithId(rowId);
|
||||
if (data != null) {
|
||||
return Some(_dataDelegate.buildCellDataMap(data));
|
||||
}
|
||||
|
||||
final payload = RowIdentifierPayload.create()
|
||||
..gridId = gridId
|
||||
..rowId = rowId;
|
||||
|
||||
GridEventGetRow(payload).send().then((result) {
|
||||
result.fold(
|
||||
(rowData) => _rowNotifier.rowData = rowData,
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
return none();
|
||||
}
|
||||
|
||||
void updateWithBlock(List<GridBlockOrder> blocks) {
|
||||
final rowOrders = blocks.expand((block) => block.rowOrders).toList();
|
||||
_rowNotifier.reset(rowOrders);
|
||||
}
|
||||
}
|
||||
|
||||
class RowsNotifier extends ChangeNotifier {
|
||||
List<GridRow> _rows = [];
|
||||
HashMap<String, Row> _rowDataMap = HashMap();
|
||||
GridRowChangeReason _changeReason = const InitialListState();
|
||||
final GridRow Function(RowOrder) rowBuilder;
|
||||
|
||||
RowsNotifier({
|
||||
required this.rowBuilder,
|
||||
});
|
||||
|
||||
void reset(List<RowOrder> rowOrders) {
|
||||
_rowDataMap = HashMap();
|
||||
final rows = rowOrders.map((rowOrder) => rowBuilder(rowOrder)).toList();
|
||||
_update(rows, const GridRowChangeReason.initial());
|
||||
}
|
||||
|
||||
void deleteRows(List<RowOrder> deletedRows) {
|
||||
if (deletedRows.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<GridRow> newRows = [];
|
||||
final DeletedIndexs deletedIndex = [];
|
||||
final Map<String, RowOrder> deletedRowMap = {for (var e in deletedRows) e.rowId: e};
|
||||
|
||||
_rows.asMap().forEach((index, row) {
|
||||
if (deletedRowMap[row.rowId] == null) {
|
||||
newRows.add(row);
|
||||
} else {
|
||||
deletedIndex.add(DeletedIndex(index: index, row: row));
|
||||
}
|
||||
});
|
||||
|
||||
_update(newRows, GridRowChangeReason.delete(deletedIndex));
|
||||
}
|
||||
|
||||
void insertRows(List<IndexRowOrder> createdRows) {
|
||||
if (createdRows.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
InsertedIndexs insertIndexs = [];
|
||||
final List<GridRow> newRows = clonedRows;
|
||||
for (final createdRow in createdRows) {
|
||||
final insertIndex = InsertedIndex(
|
||||
index: createdRow.index,
|
||||
rowId: createdRow.rowOrder.rowId,
|
||||
);
|
||||
insertIndexs.add(insertIndex);
|
||||
newRows.insert(createdRow.index, (rowBuilder(createdRow.rowOrder)));
|
||||
}
|
||||
_update(newRows, GridRowChangeReason.insert(insertIndexs));
|
||||
}
|
||||
|
||||
void updateRows(List<RowOrder> updatedRows) {
|
||||
if (updatedRows.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final UpdatedIndexs updatedIndexs = UpdatedIndexs();
|
||||
final List<GridRow> newRows = clonedRows;
|
||||
for (final rowOrder in updatedRows) {
|
||||
final index = newRows.indexWhere((row) => row.rowId == rowOrder.rowId);
|
||||
if (index != -1) {
|
||||
// Remove the old row data, the data will be filled if the loadRow method gets called.
|
||||
_rowDataMap.remove(rowOrder.rowId);
|
||||
|
||||
newRows.removeAt(index);
|
||||
newRows.insert(index, rowBuilder(rowOrder));
|
||||
updatedIndexs[rowOrder.rowId] = UpdatedIndex(index: index, rowId: rowOrder.rowId);
|
||||
}
|
||||
}
|
||||
|
||||
_update(newRows, GridRowChangeReason.update(updatedIndexs));
|
||||
}
|
||||
|
||||
void fieldDidChange() {
|
||||
_update(_rows, const GridRowChangeReason.fieldDidChange());
|
||||
}
|
||||
|
||||
void _update(List<GridRow> rows, GridRowChangeReason reason) {
|
||||
_rows = rows;
|
||||
_changeReason = reason;
|
||||
|
||||
_changeReason.map(
|
||||
insert: (_) => notifyListeners(),
|
||||
delete: (_) => notifyListeners(),
|
||||
update: (_) => notifyListeners(),
|
||||
fieldDidChange: (_) => notifyListeners(),
|
||||
initial: (_) {},
|
||||
);
|
||||
}
|
||||
|
||||
set rowData(Row rowData) {
|
||||
rowData.freeze();
|
||||
|
||||
_rowDataMap[rowData.id] = rowData;
|
||||
final index = _rows.indexWhere((row) => row.rowId == rowData.id);
|
||||
if (index != -1) {
|
||||
// update the corresponding row in _rows if they are not the same
|
||||
if (_rows[index].data != rowData) {
|
||||
final row = _rows.removeAt(index).copyWith(data: rowData);
|
||||
_rows.insert(index, row);
|
||||
|
||||
// Calculate the update index
|
||||
final UpdatedIndexs updatedIndexs = UpdatedIndexs();
|
||||
updatedIndexs[row.rowId] = UpdatedIndex(index: index, rowId: row.rowId);
|
||||
_changeReason = GridRowChangeReason.update(updatedIndexs);
|
||||
|
||||
//
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row? rowDataWithId(String rowId) {
|
||||
return _rowDataMap[rowId];
|
||||
}
|
||||
|
||||
List<GridRow> get clonedRows => [..._rows];
|
||||
}
|
||||
|
||||
class RowService {
|
||||
final String gridId;
|
||||
final String rowId;
|
||||
|
||||
RowService({required this.gridId, required this.rowId});
|
||||
|
||||
Future<Either<Row, FlowyError>> createRow() {
|
||||
CreateRowPayload payload = CreateRowPayload.create()
|
||||
..gridId = gridId
|
||||
..startRowId = rowId;
|
||||
|
||||
return GridEventCreateRow(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> moveRow(String rowId, int fromIndex, int toIndex) {
|
||||
final payload = MoveItemPayload.create()
|
||||
..gridId = gridId
|
||||
..itemId = rowId
|
||||
..ty = MoveItemType.MoveRow
|
||||
..fromIndex = fromIndex
|
||||
..toIndex = toIndex;
|
||||
|
||||
return GridEventMoveItem(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Row, FlowyError>> getRow() {
|
||||
final payload = RowIdentifierPayload.create()
|
||||
..gridId = gridId
|
||||
..rowId = rowId;
|
||||
|
||||
return GridEventGetRow(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> deleteRow() {
|
||||
final payload = RowIdentifierPayload.create()
|
||||
..gridId = gridId
|
||||
..rowId = rowId;
|
||||
|
||||
return GridEventDeleteRow(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> duplicateRow() {
|
||||
final payload = RowIdentifierPayload.create()
|
||||
..gridId = gridId
|
||||
..rowId = rowId;
|
||||
|
||||
return GridEventDuplicateRow(payload).send();
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridRow with _$GridRow {
|
||||
const factory GridRow({
|
||||
required String gridId,
|
||||
required String rowId,
|
||||
required List<Field> fields,
|
||||
required double height,
|
||||
Row? data,
|
||||
}) = _GridRow;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridCell with _$GridCell {
|
||||
const factory GridCell({
|
||||
required String gridId,
|
||||
required String rowId,
|
||||
required Field field,
|
||||
Cell? cell,
|
||||
}) = _GridCell;
|
||||
}
|
||||
|
||||
typedef InsertedIndexs = List<InsertedIndex>;
|
||||
typedef DeletedIndexs = List<DeletedIndex>;
|
||||
typedef UpdatedIndexs = LinkedHashMap<String, UpdatedIndex>;
|
||||
|
||||
@freezed
|
||||
class GridRowChangeReason with _$GridRowChangeReason {
|
||||
const factory GridRowChangeReason.insert(InsertedIndexs items) = _Insert;
|
||||
const factory GridRowChangeReason.delete(DeletedIndexs items) = _Delete;
|
||||
const factory GridRowChangeReason.update(UpdatedIndexs indexs) = _Update;
|
||||
const factory GridRowChangeReason.fieldDidChange() = _FieldDidChange;
|
||||
const factory GridRowChangeReason.initial() = InitialListState;
|
||||
}
|
||||
|
||||
class InsertedIndex {
|
||||
final int index;
|
||||
final String rowId;
|
||||
InsertedIndex({
|
||||
required this.index,
|
||||
required this.rowId,
|
||||
});
|
||||
}
|
||||
|
||||
class DeletedIndex {
|
||||
final int index;
|
||||
final GridRow row;
|
||||
DeletedIndex({
|
||||
required this.index,
|
||||
required this.row,
|
||||
});
|
||||
}
|
||||
|
||||
class UpdatedIndex {
|
||||
final int index;
|
||||
final String rowId;
|
||||
UpdatedIndex({
|
||||
required this.index,
|
||||
required this.rowId,
|
||||
});
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/grid_service.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
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';
|
||||
|
||||
part 'property_bloc.freezed.dart';
|
||||
|
||||
class GridPropertyBloc extends Bloc<GridPropertyEvent, GridPropertyState> {
|
||||
final FieldService _service;
|
||||
final GridFieldCache _fieldCache;
|
||||
Function()? _listenFieldCallback;
|
||||
|
||||
GridPropertyBloc({required String gridId, required GridFieldCache fieldCache})
|
||||
: _service = FieldService(gridId: gridId),
|
||||
_fieldCache = fieldCache,
|
||||
super(GridPropertyState.initial(gridId, fieldCache.clonedFields)) {
|
||||
on<GridPropertyEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
initial: (_Initial value) {
|
||||
_startListening();
|
||||
},
|
||||
setFieldVisibility: (_SetFieldVisibility value) async {
|
||||
final result = await _service.updateField(fieldId: value.fieldId, visibility: value.visibility);
|
||||
result.fold(
|
||||
(l) => null,
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
},
|
||||
didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
|
||||
emit(state.copyWith(fields: value.fields));
|
||||
},
|
||||
moveField: (_MoveField value) {
|
||||
//
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_listenFieldCallback != null) {
|
||||
_fieldCache.removeListener(_listenFieldCallback!);
|
||||
}
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_listenFieldCallback = _fieldCache.addListener(
|
||||
onChanged: (fields) => add(GridPropertyEvent.didReceiveFieldUpdate(fields)),
|
||||
listenWhen: () => !isClosed,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridPropertyEvent with _$GridPropertyEvent {
|
||||
const factory GridPropertyEvent.initial() = _Initial;
|
||||
const factory GridPropertyEvent.setFieldVisibility(String fieldId, bool visibility) = _SetFieldVisibility;
|
||||
const factory GridPropertyEvent.didReceiveFieldUpdate(List<Field> fields) = _DidReceiveFieldUpdate;
|
||||
const factory GridPropertyEvent.moveField(int fromIndex, int toIndex) = _MoveField;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridPropertyState with _$GridPropertyState {
|
||||
const factory GridPropertyState({
|
||||
required String gridId,
|
||||
required List<Field> fields,
|
||||
}) = _GridPropertyState;
|
||||
|
||||
factory GridPropertyState.initial(String gridId, List<Field> fields) => GridPropertyState(
|
||||
gridId: gridId,
|
||||
fields: fields,
|
||||
);
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
part 'setting_bloc.freezed.dart';
|
||||
|
||||
class GridSettingBloc extends Bloc<GridSettingEvent, GridSettingState> {
|
||||
final String gridId;
|
||||
GridSettingBloc({required this.gridId}) : super(GridSettingState.initial()) {
|
||||
on<GridSettingEvent>(
|
||||
(event, emit) async {
|
||||
event.map(performAction: (_PerformAction value) {
|
||||
emit(state.copyWith(selectedAction: Some(value.action)));
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridSettingEvent with _$GridSettingEvent {
|
||||
const factory GridSettingEvent.performAction(GridSettingAction action) = _PerformAction;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridSettingState with _$GridSettingState {
|
||||
const factory GridSettingState({
|
||||
required Option<GridSettingAction> selectedAction,
|
||||
}) = _GridSettingState;
|
||||
|
||||
factory GridSettingState.initial() => GridSettingState(
|
||||
selectedAction: none(),
|
||||
);
|
||||
}
|
||||
|
||||
enum GridSettingAction {
|
||||
filter,
|
||||
sortBy,
|
||||
properties,
|
||||
}
|