Merge branch 'main' into drag_drop_docs

This commit is contained in:
Gagan Yadav 2022-04-21 22:12:09 +05:30 committed by GitHub
commit 1946959d01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
578 changed files with 38221 additions and 3315 deletions

View File

@ -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

View File

@ -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 }}

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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"
},

View File

@ -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"

View File

@ -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

View 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

View 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

View 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

View 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

View 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

View 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

View 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.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

View 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

View 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

View File

@ -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

View 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

View 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

View 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.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

View 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

View 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.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

View 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

View 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

View 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

View 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

View 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

View File

Before

Width:  |  Height:  |  Size: 238 B

After

Width:  |  Height:  |  Size: 238 B

View File

Before

Width:  |  Height:  |  Size: 281 B

After

Width:  |  Height:  |  Size: 281 B

View File

Before

Width:  |  Height:  |  Size: 512 B

After

Width:  |  Height:  |  Size: 512 B

View File

Before

Width:  |  Height:  |  Size: 499 B

After

Width:  |  Height:  |  Size: 499 B

View File

@ -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

View 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

View File

@ -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"
}
}
}

View 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ıı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"
}
}
}

View File

@ -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;

View File

@ -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();
}

View 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),
);
}

View File

@ -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>()));
}
}

View File

@ -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 {

View File

@ -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);
// }
}

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -0,0 +1,4 @@
export './auth_service.dart';
export './sign_in_bloc.dart';
export './sign_up_bloc.dart';
export './splash_bloc.dart';

View File

@ -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,

View File

@ -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

View File

@ -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();

View File

@ -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;

View File

@ -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)),

View File

@ -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)),

View File

@ -4,7 +4,7 @@ import 'package:app_flowy/user/domain/auth_state.dart';
import 'package:app_flowy/user/presentation/router.dart';
import 'package: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';

View File

@ -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(

View File

@ -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)),
);

View File

@ -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;

View File

@ -0,0 +1,3 @@
export 'app_bloc.dart';
export 'app_listener.dart';
export 'app_service.dart';

View File

@ -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 {
await UserSettingsService().setAppearanceSettings(setting);
}),
);
_saveOperation?.cancel();
_saveOperation = Timer(const Duration(seconds: 2), () async {
await UserSettingsService().setAppearanceSettings(setting);
});
}
@override

View File

@ -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");

View File

@ -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}) {

View File

@ -0,0 +1,4 @@
export 'doc_bloc.dart';
export 'doc_service.dart';
export 'share_bloc.dart';
export 'share_service.dart';

View File

@ -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';

View File

@ -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) {

View File

@ -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');
}

View File

@ -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';

View File

@ -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;
}
}

View File

@ -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}";
}
}

View File

@ -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";
}

View File

@ -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 ?? "",
);
}

View File

@ -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 ?? "");
}
}

View File

@ -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();
}
}

View File

@ -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: [],
);
}

View File

@ -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,
);
}
}

View File

@ -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,
);
}

View File

@ -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,
);
}

View File

@ -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,
);
}

View File

@ -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(),
);
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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(),
);
}

View File

@ -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(),
);
}

View File

@ -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,
);
}

View File

@ -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,
);
}

View File

@ -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,
);
}

View File

@ -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,
});
}

View 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;
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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';

View File

@ -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,
);
}

View File

@ -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(),
);
}

View File

@ -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(),
);
}

View File

@ -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;
}
}

View File

@ -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,
});
}

View File

@ -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,
);
}

View File

@ -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,
}

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