Merge branch 'main' into #692
32
.github/workflows/ci.yaml
vendored
@ -3,11 +3,11 @@ name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
|
||||
- "main"
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- 'main'
|
||||
- "main"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@ -23,36 +23,37 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
|
||||
- id: rust_toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 'stable-2022-01-20'
|
||||
|
||||
toolchain: "stable-2022-01-20"
|
||||
|
||||
- id: flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: 'stable'
|
||||
channel: "stable"
|
||||
cache: true
|
||||
flutter-version: '3.0.5'
|
||||
flutter-version: "3.0.5"
|
||||
|
||||
- name: Cache Cargo
|
||||
id: cache-cargo
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
with:
|
||||
path: |
|
||||
~/.cargo
|
||||
key: ${{ runner.os }}-cargo-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}
|
||||
|
||||
- name: Cache Rust
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
with:
|
||||
path: |
|
||||
frontend/rust-lib/target
|
||||
shared-lib/target
|
||||
key: ${{ runner.os }}-rust-rust-lib-share-lib-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}
|
||||
key: ${{ runner.os }}-rust-rust-lib-share-lib-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}
|
||||
|
||||
- name: Setup Environment
|
||||
run: |
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "Linux" ]; then
|
||||
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
|
||||
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
|
||||
@ -64,11 +65,16 @@ jobs:
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- name: Deps
|
||||
- if: steps.cache-cargo.outputs.cache-hit != 'true'
|
||||
name: Deps
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo install cargo-make
|
||||
cargo install duckscript_cli
|
||||
|
||||
- name: Cargo make flowy_dev
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo make flowy_dev
|
||||
|
||||
- name: Config Flutter
|
||||
|
35
.github/workflows/dart_lint.yml
vendored
@ -7,14 +7,14 @@ name: Flutter lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
jobs:
|
||||
flutter-analyze:
|
||||
name: flutter analyze
|
||||
runs-on: ubuntu-latest
|
||||
@ -23,16 +23,38 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
- uses: subosito/flutter-action@v1
|
||||
with:
|
||||
flutter-version: '3.0.5'
|
||||
flutter-version: "3.0.5"
|
||||
channel: "stable"
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 'stable-2022-01-20'
|
||||
toolchain: "stable-2022-01-20"
|
||||
|
||||
- name: Rust Deps
|
||||
- name: Cache Cargo
|
||||
id: cache-cargo
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo
|
||||
key: ${{ runner.os }}-cargo-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}
|
||||
|
||||
- name: Cache Rust
|
||||
id: cache-rust-target
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
frontend/rust-lib/target
|
||||
shared-lib/target
|
||||
key: ${{ runner.os }}-rust-rust-lib-share-lib-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}
|
||||
|
||||
- if: steps.cache-cargo.outputs.cache-hit != 'true'
|
||||
name: Rust Deps
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo install cargo-make
|
||||
|
||||
- name: Cargo make flowy dev
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo make flowy_dev
|
||||
|
||||
- name: Flutter Deps
|
||||
@ -53,4 +75,3 @@ jobs:
|
||||
- name: Run Flutter Analyzer
|
||||
working-directory: frontend/app_flowy
|
||||
run: flutter analyze
|
||||
|
||||
|
51
.github/workflows/dart_test.yml
vendored
@ -3,12 +3,12 @@ name: Unit test(Flutter)
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
|
||||
- "main"
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'feat/flowy_editor'
|
||||
- "main"
|
||||
- "feat/flowy_editor"
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@ -18,42 +18,49 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 'stable-2022-01-20'
|
||||
|
||||
toolchain: "stable-2022-01-20"
|
||||
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: 'stable'
|
||||
flutter-version: '3.0.5'
|
||||
channel: "stable"
|
||||
flutter-version: "3.0.5"
|
||||
cache: true
|
||||
|
||||
- name: Cache Cargo
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
with:
|
||||
path: |
|
||||
~/.cargo
|
||||
key: ${{ runner.os }}-cargo-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}
|
||||
|
||||
- name: Cache Rust
|
||||
id: cache-rust-target
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
with:
|
||||
path: |
|
||||
frontend/rust-lib/target
|
||||
shared-lib/target
|
||||
key: ${{ runner.os }}-rust-rust-lib-share-lib-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}
|
||||
key: ${{ runner.os }}-rust-rust-lib-share-lib-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}
|
||||
|
||||
- if: steps.cache-cargo.outputs.cache-hit != 'true'
|
||||
name: Rust Deps
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo install cargo-make
|
||||
|
||||
- name: Cargo make flowy dev
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo make flowy_dev
|
||||
|
||||
- name: Flutter Deps
|
||||
working-directory: frontend/app_flowy
|
||||
run: |
|
||||
flutter config --enable-linux-desktop
|
||||
|
||||
- name: Rust Deps
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo install cargo-make
|
||||
cargo make flowy_dev
|
||||
|
||||
- name: Build FlowySDK
|
||||
working-directory: frontend
|
||||
run: |
|
||||
@ -65,15 +72,9 @@ jobs:
|
||||
flutter packages pub get
|
||||
flutter packages pub run easy_localization:generate -f keys -o locale_keys.g.dart -S assets/translations -s en.json
|
||||
flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||
|
||||
|
||||
- name: Run bloc tests
|
||||
working-directory: frontend/app_flowy
|
||||
run: |
|
||||
flutter pub get
|
||||
flutter test
|
||||
|
||||
- name: Run FlowyEditor tests
|
||||
working-directory: frontend/app_flowy/packages/flowy_editor
|
||||
run: |
|
||||
flutter pub get
|
||||
flutter test
|
36
.github/workflows/flowy_editor_test.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
name: FlowyEditor test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- "main"
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: "3.0.5"
|
||||
cache: true
|
||||
|
||||
- name: Run FlowyEditor tests
|
||||
working-directory: frontend/app_flowy/packages/appflowy_editor
|
||||
run: |
|
||||
flutter pub get
|
||||
flutter test
|
4
frontend/.vscode/launch.json
vendored
@ -29,7 +29,7 @@
|
||||
"program": "./lib/main.dart",
|
||||
"type": "dart",
|
||||
"env": {
|
||||
"RUST_LOG": "debug"
|
||||
"RUST_LOG": "trace"
|
||||
},
|
||||
"cwd": "${workspaceRoot}/app_flowy"
|
||||
},
|
||||
@ -44,7 +44,7 @@
|
||||
"type": "dart",
|
||||
"preLaunchTask": "AF: Clean + Rebuild All",
|
||||
"env": {
|
||||
"RUST_LOG": "info"
|
||||
"RUST_LOG": "trace"
|
||||
},
|
||||
"cwd": "${workspaceRoot}/app_flowy"
|
||||
},
|
||||
|
@ -14,7 +14,7 @@ analyzer:
|
||||
exclude:
|
||||
- "**/*.g.dart"
|
||||
- "**/*.freezed.dart"
|
||||
- "packages/flowy_editor/**"
|
||||
- "packages/appflowy_editor/**"
|
||||
- "packages/editor/**"
|
||||
# - "packages/flowy_infra_ui/**"
|
||||
linter:
|
||||
|
32
frontend/app_flowy/assets/images/emoji/1F42F.svg
Normal file
@ -0,0 +1,32 @@
|
||||
<svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="color">
|
||||
<path fill="#E27022" stroke="none" d="M19.3685,9.2168c0,0-8.31-0.7683-4.7954,9.1741c0,0,2.4911,6.1326,4.7954,7.3748 c0,0-9.3677,6.4041-3.7409,18.5692l0.6476,3.8526c0,0,3.75,5.5625,8.5,5.6875l1.75,1.625c0,0,1.1875,3.5625,2.125,4 c0.9375,0.4375,5.4375,3.0289,5.4375,3.0289l4.3582-0.2763l5.4543-2.5651c0,0,2.4375-2.0625,2.4375-4.6875l1.0021-1.375 c0,0,6.1854-1.2168,8.6229-5.8834l1.9107-7.1166v-4.5268l-0.3757-2.8898l-2.3475-5.8333l-1.5625-1.75c0,0,1.3125-2.2252,2-3.3001 c0.6875-1.0749,2.2857-4.745,2.2857-4.745l1.4018-3.3299l-1.4018-3.5l-3.9951-1.547l-2.3531,0.8595l-5.4035,5.7083 c0,0-5.5911-3.0058-9.1563-2.7633c-3.5652,0.2425-11.0354,2.43-11.0354,2.43L19.3685,9.2168z"/>
|
||||
<path fill="#FFFFFF" stroke="none" d="M38.3377,29.5055c0,0,4.1875,3.7873,4.5,8.1312c0,0,4,3.7631,5.9375-1.9828l2.8125-6.4229 c0,0-1.9316-3.5892-8.125-3.7797L38.3377,29.5055z"/>
|
||||
<path fill="#FFFFFF" stroke="none" d="M33.8377,29.5695c0,0-3.0625,3.7797-3.375,8.1236c0,0-4,3.7631-5.9375-1.9828l-4.125-6.4793 c0,0,2.3691-4.0405,8.5625-4.231L33.8377,29.5695z"/>
|
||||
<path fill="#FFFFFF" stroke="none" d="M52.2127,33.5c0,0.3125,0.125,12-11.0625,13.375c0,0-6.625-2.125-10.625,0.375 c0,0-11.4375-4.3125-10.9375-13.875l-0.1875,5.8125c-1.4367,1.1319-3.6761-4.0312-0.6458-13.0208 c-5.2301,3.4846-5.9625,16.141-2.4792,22.0208c0,0,3.5,5.75,9.3125,5.9375c0,0,3.5,4.4375,10.3125,1.1875 c0,0,5.625,3.625,10.5-1.375c0,0,7.1875-0.7669,9.5625-6.1959c0,0,5.2083-15.2416-2.2083-21.6582 c0,0,3.3333,13.5417-1.2292,13.1667L52.2127,33.5z"/>
|
||||
<path fill="#D0CFCE" stroke="none" d="M26.5252,55.5c0,0,0.8125,4.25,3.5625,4.9167l5.6667,2.25l4.75-0.9167l3.3958-2.0625 c0,0,2.6875-1.5208,2.4375-4.6875c0,0-6.75,2.5-10.1667,0.1667C36.171,55.1667,30.3794,57.3333,26.5252,55.5z"/>
|
||||
<path fill="#E27022" stroke="none" d="M31.5044,47.0833c0,0,3.5,2.75,9.0833,0.0833l-0.25,2.6667l-4.4167,1.8333l-3.9167-1L31.5044,47.0833z"/>
|
||||
</g>
|
||||
<g id="hair"/>
|
||||
<g id="skin"/>
|
||||
<g id="skin-shadow"/>
|
||||
<g id="line">
|
||||
<path fill="#000000" stroke="none" d="M47.3393,34.6182c0,0,0.0607,2.3422-1.2037,3.1596c-1.2644,0.8174-3.3752-0.1996-3.3752-0.1996 s-0.0607-2.3422,1.2037-3.1596S47.3393,34.6182,47.3393,34.6182z"/>
|
||||
<path fill="#000000" stroke="none" d="M25.9305,34.6182c0,0-0.0607,2.3422,1.2037,3.1596c1.2644,0.8174,3.3752-0.1996,3.3752-0.1996s0.0607-2.3422-1.2037-3.1596 S25.9305,34.6182,25.9305,34.6182z"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M24.1981,47.3666c0,0-1.6667,12.7083,11.9583,7.5833v-3.3438"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M21.2398,28.4916c-0.1667,0.3333-4.6667,9.1667,0.9167,16.25"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M22.1549,23.2401c-4.6652,2.7745-12.117,9.7821-5.2485,24.1265"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M27.3231,48.9916c0,0-11.1667,0.75-12.75,6.8333"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M28.9064,52.1582c-0.25,0.1667-10.3333,5.75-8.5833,10.6667"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M29.5766,59.1521c1.9799,2.0495,7.4123,6.3778,13.517,0.1296"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M17.4898,22.3249c-7.9284-9.061-3.0759-11.6581-0.2234-12.8765c0.9417-0.4022,2.0253-0.2705,2.8642,0.3168l5.7994,6.0597 c0,0,9.3237-6.3941,20.14,0l5.7994-6.0597c0.8389-0.5872,1.9225-0.719,2.8642-0.3168c2.8525,1.2184,7.705,3.8155-0.2234,12.8765"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M40.7186,47.7416c0,0,1,2.875-3.4167,3.625h-2.6038c-4.4167-0.75-3.4167-3.625-3.4167-3.625"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M47.8019,47.3666c0,0,1.6667,12.7083-11.9583,7.5833v-3.5833"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M50.7602,28.4916c0.1667,0.3333,4.6667,9.1667-0.9167,16.25"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M49.8832,23.2628c4.6675,2.7899,12.0602,9.7983,5.2104,24.1037"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M44.6769,48.9916c0,0,11.1667,0.75,12.75,6.8333"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M43.0936,52.1582c0.25,0.1667,10.3333,5.75,8.5833,10.6667"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M29.5766,24.1069c0,0,7.0776-3.3333,13.1833,0.4167"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M31.5208,29.2864c0,0,4.9901-3.3333,9.2949,0.4167"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.4 KiB |
24
frontend/app_flowy/assets/images/emoji/1F431.svg
Normal file
@ -0,0 +1,24 @@
|
||||
<svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="color">
|
||||
<path fill="#F4AA41" stroke="none" d="M58.4953,11.21c0,0-10.4076,2.3754-15.5743,6.7088c0,0-9-2.5-13.8333,0.1667 c0,0-9.6549-6.7318-15.6549-6.7318c0,0-5.0326,3.75,0.3216,21.0651c0,0-2.6667,10.6667,1.6667,16.3333s9.8333,9.5,9.8333,9.5 l8.7917,4.0417l3.9583,0.1667l9.9167-4.375c0,0,6.6667-4.8333,8.8333-9.5c2.1667-4.6667,2.1667-13.8333,2.1667-13.8333l-1-3.3333 l2.8333-11.0208L58.4953,11.21z"/>
|
||||
<path fill="#FFFFFF" stroke="none" d="M30.8377,47.3355c0,0-7.625,2.75-1.375,9.25c0,0-0.9739,4.625,3.8034,5.4849h5.1966 c2.0529,0.0031,4.5833-1.0683,3.7404-5.3628c0,0,7.5513-6.3722-1.3654-9.3722l-4.875,2L30.8377,47.3355z"/>
|
||||
</g>
|
||||
<g id="hair"/>
|
||||
<g id="skin"/>
|
||||
<g id="skin-shadow"/>
|
||||
<g id="line">
|
||||
<ellipse cx="45.0854" cy="38.1033" rx="1.6461" ry="2.8119" fill="#000000" stroke="none"/>
|
||||
<ellipse cx="26.8427" cy="38.1033" rx="1.6461" ry="2.8119" fill="#000000" stroke="none"/>
|
||||
<polyline fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="31.9328,47.2287 36.037,50.0204 39.8495,47.2287"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M36.037,50.0204v4.2708c0,0-1.1042,3.6875-5.5417,2.875"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M15.8717,48.4759c-4.8928-7.2535-2.0014-15.8722-2.0014-15.8722s-5.25-14.875-0.4375-21.25c0,0,9.1875,1.5,15.6875,7.375 c4.5946-1.9379,9.1575-2.0128,13.6875-0.1437c6.5-5.875,15.6875-7.375,15.6875-7.375c4.8125,6.375-0.4375,21.25-0.4375,21.25 s2.8914,8.6187-2.0014,15.8722"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M14.7453,15.1037c0,0,12.8125,6.1875,10.0625,11.8125"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M24.8491,50.8753c0,0-9.3615-0.458-13.6525,7.5243"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M35.8911,49.8767v4.2708c0,0,1.1042,3.6875,5.5417,2.875"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M57.1828,14.96c0,0-12.8125,6.1875-10.0625,11.8125"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M47.2048,54.6836c0,0,8.2116,2.2454,8.6795,11.2958"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M48.079,50.7316c0,0,9.3615-0.458,13.6525,7.5243"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M31.3859,60.7598c3.88,1.6845,5.6481,1.8093,9.3021,0"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M25.4446,54.6836c0,0-8.2116,2.2454-8.6795,11.2958"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.2 KiB |
28
frontend/app_flowy/assets/images/emoji/1F435.svg
Normal file
@ -0,0 +1,28 @@
|
||||
<svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="color">
|
||||
<path fill="#3F3F3F" stroke="none" d="M35.8377,9.4188c-0.8333,0.1667-9,0.1667-9,0.1667l-8.75,7.25l-4.9167,10.25l-3,12.4167l1.3333,8.75 l5.75,7.1667c0,0,4.75,4,11.1667,3.75c0,0,20,2.9167,26.5-3.5833l3.3333-3.5833l3.1667-6.3333l0.0833-7.6667l-1.5-7l-2.1667-6.9167 l-4.0833-7.5l-4.9167-4.1667L44.921,9.8355l-4.1667-0.8333L35.8377,9.4188z"/>
|
||||
<path fill="#F4AA41" stroke="none" d="M55.5877,19.5021l2.5-1.8333l3.0833-0.5l1.9167,1.25l0.75,1c0,0,0.9167,3.6667,1,3.9167 c0.0833,0.25,0,6,0,6l-1.25,3.1667l-2.1667,1.9167l-2.3333-5.5l-3.1667-7.25L55.5877,19.5021z"/>
|
||||
<polygon fill="#F4AA41" stroke="none" points="16.671,19.0021 14.0044,17.1688 11.2544,16.5855 8.3377,18.4188 6.671,23.3355 7.171,29.1688 8.3377,31.8355 10.8377,34.6688 12.2544,30.1688 14.421,24.2521"/>
|
||||
<path fill="#F4AA41" stroke="none" d="M46.3377,44.5855l4.0833-2.4167l1.75-4.6667l1.25-7.25l-1.0833-4.75l-2.4167-2.8333l-4.3333-1.8333 l-5.4167,0.1667l-3.5833-0.0833l-7-0.3333l-4.6667,0.3333l-4.25,2.8333l-2,4.5833l0.0833,3.9167c0,0-0.0833,3.5,0.1667,3.9167 c0.25,0.4167,1.5833,4.4167,1.5833,4.4167l1.1667,1.6667l4,1.8333l-1,4c0,0-0.1667,6.5,1.25,8.4167 c1.4167,1.9167,5.75,5.4167,5.75,5.4167l6.4167,0.6667l4.5833-1.6667l3.8334-4.8333c0.8412-2.6884,1.1213-5.4142,1-8.1667 L46.3377,44.5855z"/>
|
||||
</g>
|
||||
<g id="hair"/>
|
||||
<g id="skin"/>
|
||||
<g id="skin-shadow"/>
|
||||
<g id="line">
|
||||
<circle cx="27.6338" cy="29.6771" r="2" fill="#000000" stroke="none"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M23.0393,27.9538c0,0,4.726-6.5434,9.4521,0.1023"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M30.8968,40.1701c0,0,0.6484,2.299,3.537,2.7117"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M27.989,51.7674l1.2915,0.8045c1.2002,0.7477,2.5573,1.1408,3.9382,1.1408h5.6222c1.3232,0,2.6255-0.361,3.7893-1.0503 l1.5111-0.895"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M31.0557,35.5878c-2.6158,1.5082-5.9686,4.8289-5.5798,11.8762"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M26.8178,56.0264l0.2809,0.6695c1.0574,2.5206,3.1675,4.4503,5.7723,5.279l0.7864,0.2502 c2.2541,0.7171,4.6965,0.5509,6.8326-0.4651l0,0c2.0135-0.9577,3.6173-2.6051,4.5206-4.6436l0.4855-1.0957"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M16.0795,18.8447l-1.7688-1.228c-1.6197-1.1245-3.7931-1.0191-5.2965,0.2567l0,0c-0.7659,0.65-1.2866,1.5421-1.4642,2.5308 c-0.6192,3.4457-1.5742,11.5715,2.6707,13.7114"/>
|
||||
<circle cx="43.3662" cy="29.9126" r="2" fill="#000000" stroke="none"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M48.0175,28.1759c0,0-4.726-6.5434-9.4521,0.1023"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M49.9607,41.4195c2.5152-1.7505,2.6995-5.9685,3.3012-10.1803c0.4127-2.8885-0.4716-5.895-3.5959-8.4888 c-3.1321-2.6002-7.3146-2.1486-9.39-1.6993c-1.0224,0.2213-2.0608,0.3145-3.1062,0.2758l-2.7267-0.1011 c-0.842-0.0312-1.6765-0.1564-2.4973-0.3471c-1.9859-0.4613-6.3637-1.0469-9.6117,1.6496 c-3.1243,2.5938-4.0086,5.6002-3.5959,8.4888c0.6017,4.2118,0.786,8.4298,3.3012,10.1803"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M41.1032,40.1701c0,0-0.6484,2.299-3.537,2.7117"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M41.2534,35.9947c2.5625,1.588,5.6444,4.9189,5.2707,11.6914"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M41.2534,35.9947c-0.5708-0.3538-1.1928-0.6531-1.7718-0.8958c-1.2375-0.5188-2.5724-0.7524-3.9139-0.7201l-0.0521,0.0013 c-1.5439,0.0372-3.0699,0.4152-4.4116,1.18c-0.0161,0.0092-0.0322,0.0184-0.0483,0.0277"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M54.4627,54.7601c0,0,12.6763-6.0522,3.9518-28.4623S36.0651,9.7105,36.0651,9.7105S22.3101,3.6656,13.5856,26.0757 s3.9518,28.4623,3.9518,28.4623"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M55.9205,19.0668l1.7688-1.228c1.6197-1.1245,3.7931-1.0191,5.2965,0.2567l0,0c0.7659,0.65,1.2866,1.5421,1.4642,2.5308 c0.6192,3.4457,1.5742,11.5715-2.6707,13.7114"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.9 KiB |
28
frontend/app_flowy/assets/images/emoji/1F43A.svg
Normal file
@ -0,0 +1,28 @@
|
||||
<svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="color">
|
||||
<path fill="#9B9B9A" stroke="none" d="M53.2127,6.2105l-6.25,6c0,0-5.875-3.125-12-3.125s-10.75,3-10.75,3l-6.75-6l-3.25-0.375l-3,3.75l-1.5,7.5 c0,0-0.125,9.75,2.5,11.625s-2,13.25-2,13.25l3.375,6.75l8,5.625l2.75,1.875c0,0,3.3331,7.5833,10.9944,5.5959 c0,0,5.7556,3.4875,10.3806-5.4709l2.5-1.5l6.375-3.625l6-8.125c0,0-3.25-10.75-2.5-14.25s2.875-9.5,2.625-11.25s-2-8.625-2-8.625 S56.8377,4.0855,53.2127,6.2105z"/>
|
||||
<path fill="#3F3F3F" stroke="none" d="M12.421,45.5021c0,0,4.0833-10.25,10.1667-10l7.5833-0.4167l2.25,3.1667c0,0,0.9857,5.682-0.6702,9.5016 c-0.3188,0.7354,0.2502,1.5299,1.0442,1.4201c1.1109-0.1535,2.5407-0.2071,4.1332,0.0797c0.7536,0.1357,1.3819-0.5736,1.13-1.2966 c-0.6854-1.9673-1.4463-5.4314-0.5539-9.7882l2.75-2.9167l3.75,0.1667l3.5,0.5833c0,0,9.5-2,9.9167,10.25l2.6667-3.9167l-2.5-12.25 l1-2.5833l2-7.5l-1.0833-8.9167l-2.75-5.1667L52.671,6.0021l-4.4167,5.1667l-1.6667,0.6667l-5.25-1.8333l-9.25-0.6667 l-8.4167,2.8333l-6-6.8333l-3.5833-0.1667l-3.5,5.6667l-0.8333,6.9167l1.25,6.9167l1.5833,3.6667l-0.75,6.8333l-2,6.5 L12.421,45.5021z"/>
|
||||
<path fill="#3F3F3F" stroke="none" d="M29.489,53.0348c0,0,4.4375-2.5625,11.1875,0.125l0.0625,0.8125l-2.25,1.5l-0.9375,1.625 c0,0-1.3125,1.25-2,1c-0.6875-0.25-3-1.125-3-1.125l-0.75-1.5625l-2.625-1.4375l0.0625-0.875"/>
|
||||
<path fill="#9B9B9A" stroke="none" d="M55.2752,8.7105c0,0,5.25,8.5625,0.75,17.5c0,0-4.4375-6.5-7.9375-6.375 C48.0877,19.8355,55.1502,15.5855,55.2752,8.7105z"/>
|
||||
<path fill="#9B9B9A" stroke="none" d="M14.7227,8.7105c0,0-5.25,8.5625-0.75,17.5c0,0,4.4375-6.5,7.9375-6.375 C21.9102,19.8355,14.8477,15.5855,14.7227,8.7105z"/>
|
||||
<path fill="#9B9B9A" stroke="none" d="M29.1765,62.0759c0.1166,2.4426,0.8751,4.2037,2.6761,4.858h6.5847 c2.1107-0.7139,2.5376-2.4764,2.3018-4.6519L29.1765,62.0759"/>
|
||||
</g>
|
||||
<g id="hair"/>
|
||||
<g id="skin"/>
|
||||
<g id="skin-shadow"/>
|
||||
<g id="line">
|
||||
<circle cx="26.2495" cy="40.2751" r="2" fill="#000000" stroke="none"/>
|
||||
<circle cx="44.2944" cy="40.5106" r="2" fill="#000000" stroke="none"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M29.3116,53.9749l2.375,1.3959c0,0,0.4899,2.9667,3.2712,2.748v3.5625c0,0-3.4688,1.7604-6.3438-0.5312 c-2.2371-1.7832-3.5938-4.6875-3.5938-4.6875"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M21.739,53.3376c-3.8308-1.0774-6.3025-3.0804-7.8464-5.5039L9.871,42.3259c2.7739-7.0508,2.5763-14.1342,2.5763-14.1342 c-4.7083-8.6667-1.625-17.4583-1.625-17.4583c2.5417-8.2917,6.7917-5.0417,6.7917-5.0417c3.0417,5.1667,9.875,8.9167,9.875,8.9167"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M15.614,9.2126c0,0,0.375,7.5,7.3125,10.375c0,0-6.6983,1.679-7.7816,6.3457"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M54.8556,9.2126c0,0-0.375,7.5-7.3125,10.375c0,0,6.6983,1.679,7.7816,6.3457"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M30.9474,40.5106c0,0,2.711-4.3812-1.25-5.6689c-2.7917-0.9075-6.2083,1.0584-6.2083,1.0584"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M35.3321,61.6813c0,0,3.4688,1.7604,6.3438-0.5312c2.2371-1.7832,3.5938-4.6875,3.5938-4.6875"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M40.6873,53.9749l-2.7917,1.3959c0,0-0.1566,2.9667-2.9378,2.748"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M42.8008,14.9502c0,0,6.8333-3.75,9.875-8.9167c0,0,4.25-3.25,6.7917,5.0417c0,0,3.0833,8.7917-1.625,17.4583 c0,0-0.0675,7.3188,2.6299,13.7929l-4.6974,6.7357c-1.5598,2.0223-3.8715,3.6742-7.2242,4.6172"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M39.3425,40.8524c0,0-2.7109-4.3812,1.25-5.6689c2.7917-0.9075,6.2083,1.0584,6.2083,1.0584"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M24.6882,11.4515c0,0,7.9524-5.5773,21.3522,0"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M31.3533,65.8686c2.7191,1.4858,5.2903,1.3535,7.75,0"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.6 KiB |
17
frontend/app_flowy/assets/images/emoji/1F600.svg
Normal file
@ -0,0 +1,17 @@
|
||||
<svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="color">
|
||||
<circle cx="36" cy="36" r="23" fill="#FCEA2B"/>
|
||||
<path fill="#FFFFFF" d="M50.595,41.64c0.012,1.5397-0.2838,3.0662-0.87,4.49c-12.49,3.03-25.43,0.34-27.49-0.13 c-0.5588-1.3852-0.8407-2.8664-0.83-4.36h0.11c0,0,14.8,3.59,28.89,0.07L50.595,41.64z"/>
|
||||
<path fill="#EA5A47" d="M49.7251,46.13c-1.79,4.27-6.35,7.23-13.69,7.23c-7.41,0-12.03-3.03-13.8-7.36 C24.2951,46.47,37.235,49.16,49.7251,46.13z"/>
|
||||
</g>
|
||||
<g id="hair"/>
|
||||
<g id="skin"/>
|
||||
<g id="skin-shadow"/>
|
||||
<g id="line">
|
||||
<circle cx="36" cy="36" r="23" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M50.595,41.64 c0.012,1.5397-0.2838,3.0662-0.87,4.49c-12.49,3.03-25.43,0.34-27.49-0.13c-0.5588-1.3852-0.8407-2.8664-0.83-4.36h0.11 c0,0,14.8,3.59,28.89,0.07L50.595,41.64z"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M49.7251,46.13 c-1.79,4.27-6.35,7.23-13.69,7.23c-7.41,0-12.03-3.03-13.8-7.36C24.2951,46.47,37.235,49.16,49.7251,46.13z"/>
|
||||
<path d="M30,31c0,1.6568-1.3448,3-3,3c-1.6553,0-3-1.3433-3-3c0-1.6552,1.3447-3,3-3C28.6552,28,30,29.3448,30,31"/>
|
||||
<path d="M48,31c0,1.6568-1.3447,3-3,3s-3-1.3433-3-3c0-1.6552,1.3447-3,3-3S48,29.3448,48,31"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
21
frontend/app_flowy/assets/images/emoji/1F984.svg
Normal file
@ -0,0 +1,21 @@
|
||||
<svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="color">
|
||||
<path fill="#FFFFFF" stroke="none" d="M23.7544,12.3618l1.6667,7.1667l-5.3333,5.3333l-8.3333,14.3333l1,4.6667l2.1667,1.3333l4-0.1667 l3.5-3.3333l6.8333-1.8333c0,0,1.3333,1.5,2.1667,3s3.6667,4.1667,3.6667,4.1667l0.5,6l-1.8333,6.1667l-2,2.8333 c0,0,22,9.5,33.1667-7l-0.5-6l-1.8333-5l-3.3333-5.1667l-1-1.5l-0.1667-5.1667l-2.8333-5.3333l-5-3l-2.6667-4.5l-5.1667-4.1667 l-6.5-1.5l-5.6667,1l-4.1667-2.1667L23.7544,12.3618z"/>
|
||||
<path fill="#EA5A47" stroke="none" d="M50.671,23.155l5.2083,4.095c0,0,5.5638,8.2181-0.3258,17.8201c-7.0492,11.4924,0,0,0,0 c-1.6183,3.4754-2.3141,6.7423-1.738,9.7216l-5.3111-4.4167V34.2917L50.671,23.155z"/>
|
||||
<polyline fill="#EA5A47" stroke="none" points="25.8985,19.2712 10.7847,12.0212 15.951,18.1399 21.1747,23.995 25.8985,19.2712"/>
|
||||
<path fill="#92D3F5" stroke="none" d="M29.7367,13.6311l10.7677,0.1362c0,0,9.2377,4.0661,10.5355,11.8161l0.6874,8.9567 c-2.6337,6.5386-3.0562,14.1267,2.0883,20.8336l0,0c0,0-7.1444,1.3215-9.8944-7.1094L42.3377,43.5l0.3258-6.0341l1.4169-5.6426 l-0.2833-4.8929l-2.2761-4.3124l-3.5322-2.8413l-5.792-2.0801L29.7367,13.6311"/>
|
||||
<path fill="#61B2E4" stroke="none" d="M58.4549,36.75c0,0,5.5192,6.4066,6.9982,15.1193c0.1838,1.0826,0.1251,2.193-0.1377,3.2591 c-0.4317,1.7512-0.8179,4.9979,0.1452,7.3825c0.4689,1.1611-0.5621,2.3655-1.7883,2.1115 c-3.7094-0.7686-9.2437-3.6474-10.2567-8.0876c-0.0239-0.1047-0.0368-0.2138-0.0417-0.321l-0.266-5.7459 c-0.0132-0.2857,0.0516-0.5695,0.1875-0.8211l3.692-6.8359c0.0666-0.1233,0.1164-0.2549,0.1482-0.3914L58.4549,36.75"/>
|
||||
</g>
|
||||
<g id="hair"/>
|
||||
<g id="skin"/>
|
||||
<g id="skin-shadow"/>
|
||||
<g id="line">
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M58.4549,37.7826C60.2229,40.1443,65,44.4647,64.5,54.0208"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M32.5,41.8854c0,0,8.4783,6.7823,0,18.7647"/>
|
||||
<polyline fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="24.809,19.1338 10.25,11.75 21.1747,23.995"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M35.1962,30.8696c0.5489,8.3555-9.3225,9.703-11.954,10.3347c-0.3325,0.0798-0.6318,0.25-0.8736,0.4919l-2.2227,2.2227 c-0.3494,0.3494-0.8234,0.5458-1.3176,0.5458h-3.5121c-1.203,0-2.2711-0.7698-2.6515-1.9111l-0.531-1.5931 c-0.258-0.774-0.1649-1.6222,0.2549-2.3218l8.7862-14.6436l4.7238-4.7238l-2.1151-6.9054c0,0,7.8026-0.6987,8.4135,5.3308 c0,0,16.9281,2.4418,10.5531,19.383c0,0-1.625,5.9489,2.375,11.1846"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M30.9167,14.0208c0,0,22.2444-4.0208,19.9583,19.9583"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M49.9187,23.155c0,0,14.7665,6.5865,5.4563,22.22c0,0-5.375,6.5625,0.625,13.6042"/>
|
||||
<circle cx="24.4167" cy="28.9304" r="2" fill="#000000" stroke="none"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.2 KiB |
33
frontend/app_flowy/assets/images/emoji/1F9CC.svg
Normal file
@ -0,0 +1,33 @@
|
||||
<svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="color">
|
||||
<path fill="#e27022" d="M50.63,15.6a1.2,1.2,0,0,1-1.73,1.18c-1.08-.56-2.98-1.24-3.92-.35a.3039.3039,0,0,1-.07.05c-1.52-2.09-4.21-3.79-8.91-3.79a3.7944,3.7944,0,0,1,3.56-4.24,8.4848,8.4848,0,0,0,4.73-2.06,5.0935,5.0935,0,0,1,.65,3.96l.49.01C49.15,10.36,50.4,13.41,50.63,15.6Z"/>
|
||||
<path fill="#b1cc33" d="M36.0022,42.0333c-15.2776,0-10.5548-6.0794-10.5548-12.7628,0-3.5808-.3769-3.4016-.3769-5.9038s.6328-10.681,10.9317-10.681,10.9317,8.1789,10.9317,10.681-.3769,2.323-.3769,5.9038C46.557,35.9539,51.28,42.0333,36.0022,42.0333Z"/>
|
||||
<path fill="#b1cc33" d="M46.9339,23.3667c2.137-2.8369,5.3093-2.0623,8.8-4.36-.548,3.5228-1.6172,12.3056-8.9959,12.787Z"/>
|
||||
<path fill="#b1cc33" d="M25.2622,31.7941c-7.3787-.4814-8.4479-9.2642-8.9959-12.787,3.49,2.2973,6.6628,1.5227,8.8,4.36Z"/>
|
||||
<path fill="#b1cc33" d="M17.0625,57.8177s-2-13,10-13c3.1918,2.1279,5.9264,3.5984,9,3.5922h-.125c3.0736.0062,5.8081-1.4643,9-3.5922,12,0,10,13,10,13l.0076,1H17.0361Z"/>
|
||||
<path fill="#5c9e31" d="M36,58.8164c8.1262-10.7061,8.3828-14.831,8.3828-14.831a1.0008,1.0008,0,0,1,.5547-.168c3.6563,0,6.4844,1.1357,8.4072,3.3769C56.8984,51.3369,55.9668,57.7,55.9258,57.97a.9991.9991,0,0,1-.9863.8467Z"/>
|
||||
<path fill="#a57939" d="M44.9635,57.8182A47.8735,47.8735,0,0,1,51.43,50.9394c4.4058-3.4682,6.7662-3.56,10.948-8.9061,2.0629-2.6374,2.6374-7.2817,0-9.3447S56.024,31.4452,53.5,34.5164c-4.681,5.6962-3.8249,8.713-6.1991,13.1933A102.5608,102.5608,0,0,1,40.4972,57.816v1h4.4654Z"/>
|
||||
<path fill="#fff" d="M31.0551,31.4981h0a2.6068,2.6068,0,0,1,2.6068,2.6068v.651a0,0,0,0,1,0,0H31.0551a0,0,0,0,1,0,0V31.4981A0,0,0,0,1,31.0551,31.4981Z"/>
|
||||
<path transform="translate(79.3987 66.254) rotate(180)" fill="#fff" d="M38.3959,31.4981h2.6068a0,0,0,0,1,0,0v.651a2.6068,2.6068,0,0,1-2.6068,2.6068h0a0,0,0,0,1,0,0V31.4981A0,0,0,0,1,38.3959,31.4981Z"/>
|
||||
</g>
|
||||
<g id="line">
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M54.7288,52.4471a16.7057,16.7057,0,0,1,.2087,5.3706"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.0625,57.8177s-2-13,10-13c3.1918,2.1279,5.9264,3.5984,9,3.5922h-.125c3.0736.0062,5.8081-1.4643,9-3.5922"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M44.287,12.09a5.0267,5.0267,0,0,0,0-5.7006,8.4359,8.4359,0,0,1-4.7251,2.0561,3.7937,3.7937,0,0,0-3.56,4.24"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M46.9339,23.3667c2.137-2.8369,5.3093-2.0623,8.8-4.36-.548,3.5228-1.6172,12.3056-8.9959,12.787"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M25.2622,31.7941c-7.3787-.4814-8.4479-9.2642-8.9959-12.787,3.49,2.2973,6.6628,1.5227,8.8,4.36"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M36.0022,42.0333c-15.2776,0-10.5548-6.0794-10.5548-12.7628,0-3.5808-.3769-3.4016-.3769-5.9038s.6328-10.681,10.9317-10.681,10.9317,8.1789,10.9317,10.681-.3769,2.323-.3769,5.9038C46.557,35.9539,51.28,42.0333,36.0022,42.0333Z"/>
|
||||
<path d="M42.0023,26.4993a2,2,0,1,1-2-2,2,2,0,0,1,2,2"/>
|
||||
<path d="M34.0023,26.4993a2,2,0,1,1-2-2,2,2,0,0,1,2,2"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M38.039,23.0265a5.4257,5.4257,0,0,1,5.0488-1.2713"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M28.97,21.7552a5.4256,5.4256,0,0,1,5.0488,1.2713"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M44.9635,57.8182A47.8735,47.8735,0,0,1,51.43,50.9394c4.4058-3.4682,6.7662-3.56,10.948-8.9061,2.0629-2.6374,2.6374-7.2817,0-9.3447S56.024,31.4452,53.5,34.5164c-4.681,5.6962-3.8249,8.713-6.1991,13.1933A102.5608,102.5608,0,0,1,40.4972,57.816"/>
|
||||
<line x1="50.3728" x2="53.5159" y1="36.0726" y2="37.974" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
<line x1="56.4724" x2="58.3992" y1="45.2117" y2="48.3395" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
<line x1="59.3606" x2="59.0523" y1="29.4325" y2="33.0931" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
<line x1="31.0551" x2="31.0551" y1="32.4956" y2="35.2715" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
<line x1="42.2494" x2="29.8085" y1="35.2715" y2="35.2715" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
<line x1="41.0027" x2="41.0027" y1="32.4956" y2="35.2715" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M47.2467,15.1081a9.3183,9.3183,0,0,1,2.754,1.1523s0-5.21-4.4936-5.21"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.0 KiB |
26
frontend/app_flowy/assets/images/emoji/1F9DB.svg
Normal file
@ -0,0 +1,26 @@
|
||||
<svg id="emoji" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="hair">
|
||||
<path fill="#3F3F3F" d="M26.1,39.2c-4,0-4-6-4-13s4-14,14-14s14,7,14,14s0,13-4,13"/>
|
||||
</g>
|
||||
<g id="skin">
|
||||
<path fill="#FCEA2B" d="M24.9494,31.1c0,9,4.9,14,11,14c5.9,0,11.1-5,11.1-14c0.0246-1.7187-0.3163-3.423-1-5c-3-3-7-8-7-8 c-4,3-7,6-13,7C26.0494,25.1,24.9494,26.1,24.9494,31.1z"/>
|
||||
</g>
|
||||
<g id="color">
|
||||
<path fill="#D22F27" d="M35.65,45.1c-3.4-0.1-8.6-5.7-8.6-5.7h-13.6c1.5916,2.7161,3.0272,5.5207,4.3,8.4 c1.281,3.1916,2.2196,6.5102,2.8,9.9h30.9c0.5804-3.3898,1.519-6.7084,2.8-9.9c1.2728-2.8793,2.7084-5.6839,4.3-8.4h-13.9 C44.75,39.4,39.15,45.1,35.65,45.1z"/>
|
||||
<path fill="#3F3F3F" d="M17,61v-3.8c0-5,5-9,10-9c6,5,12,5,18,0c5,0,10,4,10,9V61H17z"/>
|
||||
<path d="M31.45,61h9c3.6-2.9,5.6-13.5,5.6-13.5c-5.7672,5-14.3328,5-20.1,0C25.95,47.4,27.95,58,31.45,61z"/>
|
||||
</g>
|
||||
<g id="line">
|
||||
<path fill="none" stroke="#000000" stroke-linejoin="round" stroke-width="2" d="M26.5,39.4H13.4 c1.6694,2.6727,3.1079,5.4829,4.3,8.4c0.5072,1.2402,0.9413,2.509,1.3,3.8"/>
|
||||
<path fill="none" stroke="#000000" stroke-linejoin="round" stroke-width="2" d="M53,51.4c0.4-1.2,0.8-2.4,1.3-3.6 c1.1713-2.9266,2.6108-5.7385,4.3-8.4H44.7"/>
|
||||
<path d="M42,30.1c0,1.1046-0.8954,2-2,2s-2-0.8954-2-2s0.8954-2,2-2C41.1032,28.1032,41.9968,28.9968,42,30.1"/>
|
||||
<path d="M34,30.1c0,1.1046-0.8954,2-2,2c-1.1046,0-2-0.8954-2-2s0.8954-2,2-2C33.1032,28.1032,33.9968,28.9968,34,30.1"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M33,39 c1.875-1,4.125-1,6,0"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" d="M38,38.5 c0.2408,0.0816,0.4748,0.1819,0.7,0.3"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17,60v-3c0-5,5-9,10-9 c6,5,12,5,18,0c5,0,10,4,10,9v3"/>
|
||||
<polygon stroke="#000000" stroke-linecap="round" stroke-linejoin="round" points="34,38.7 34.3,41.1 35.1,38.8 35,38.7"/>
|
||||
<polygon stroke="#000000" stroke-linecap="round" stroke-linejoin="round" points="37.9,38.7 37.7,41.1 36.9,38.8 36.8,38.7"/>
|
||||
<path fill="none" stroke="#000000" stroke-linejoin="round" stroke-width="2" d="M24.9494,31.1c0,9,4.9,14,11,14 c5.9,0,11.1-5,11.1-14c0.0246-1.7187-0.3163-3.423-1-5c-3-3-7-8-7-8c-4,3-7,6-13,7C26.0494,25.1,24.9494,26.1,24.9494,31.1z"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M26,39.1c-4,0-4-6-4-13 s4-14,14-14s14,7,14,14s0,13-4,13"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
@ -0,0 +1,39 @@
|
||||
<svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="color">
|
||||
<path fill="#92d3f5" d="M54.9224,60.9315s2-14.0268-10-14.0268c-3.1919,2.1279-5.9264,3.5984-9,3.5921h.125c-3.0736.0063-5.8082-1.4642-9-3.5921-12,0-10,14.0268-10,14.0268Z"/>
|
||||
<path fill="#61b2e4" d="M45.3084,46.9047a18.0129,18.0129,0,0,1-8.9375,3.5885c13.0625.4115,12.9775,6.395,13.74,10.395h5.1129S57.3084,46.9047,45.3084,46.9047Z"/>
|
||||
<polyline fill="#6a462f" points="18.056 52.064 18.056 42.499 25.141 42.499 25.141 46.063"/>
|
||||
<path fill="#a57939" d="M47.9673,60.9583c-18.4989-5.1358-20.92-14.0536-20.92-14.0536s-4.2.2046-5.6776,1.3537c3.1163,6.4287,5.9346,9.5505,14.1044,12.6807"/>
|
||||
</g>
|
||||
<g id="skin">
|
||||
<path fill="#fcea2b" d="M38.1326,20.5319a9.3955,9.3955,0,0,1-2.18-5.054c-.028,0-.0549-.0052-.0829-.0052a8.2719,8.2719,0,0,1-1.98,5.0591,10.8766,10.8766,0,0,1-8.7937,4.7087,17.3993,17.3993,0,0,0-.5656,4.4052c0,7.8277,5.0765,14.1732,11.3386,14.1732S47.208,37.4734,47.208,29.6457a17.3453,17.3453,0,0,0-.6258-4.6109A9.7529,9.7529,0,0,1,38.1326,20.5319Z"/>
|
||||
<path fill="#fcea2b" d="M46.5844,24.451l11.45-1.6359S51.7663,32.6639,47.54,31.614"/>
|
||||
<path fill="#fcea2b" d="M25.4747,24.4322,14.1572,22.8151s6.1889,9.7351,10.3663,8.6973"/>
|
||||
</g>
|
||||
<g id="hair">
|
||||
<path fill="#f4aa41" d="M35.7974,11.7565A15.35,15.35,0,0,0,20.661,24.6021c8.8261,3.0574,13.1564-3.9388,13.1564-3.9388a8.2716,8.2716,0,0,0,1.98-5.0592H35.88a9.3969,9.3969,0,0,0,2.18,5.0592s3.413,6.46,12.8727,3.93A15.3509,15.3509,0,0,0,35.7974,11.7565Z"/>
|
||||
<path fill="#f4aa41" d="M37.48,44.3806l-5.1958-.6448-4.21-2.6168-3.4133-6.106-.3793-2.8445-2.3514-.4171L19.02,29.4878V42.0409l6.159.3305v4.5958l1.9062-.19c3.17,2.1133,5.8895,3.5747,8.9375,3.5885,3.048-.0138,5.7675-1.4752,8.9375-3.5885a9.6884,9.6884,0,0,1,6.5536,2.1348,12.86,12.86,0,0,0,.6612-4.1082c0-16.6561-.27-14.3257-.27-14.3257l-2.3265,1.4249-2.2.2654-1.29,4.8924-3.6029,4.9683Z"/>
|
||||
</g>
|
||||
<g id="line">
|
||||
<path d="M42.0163,28.5884a2,2,0,1,1-2-2,2.0007,2.0007,0,0,1,2,2"/>
|
||||
<path d="M34.0163,28.5884a2,2,0,1,1-2-2,2.0007,2.0007,0,0,1,2,2"/>
|
||||
<path d="M36.0162,38.5908a7.6528,7.6528,0,0,1-3.4473-.8579,1,1,0,0,1,.8945-1.7891,5.3774,5.3774,0,0,0,5.1055,0,1,1,0,1,1,.8945,1.7891A7.6524,7.6524,0,0,1,36.0162,38.5908Z"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M50.9636,24.15C46.49,10.5289,36.0162,11.9284,36.0162,11.9284S25.663,10.545,21.147,23.9148"/>
|
||||
<circle cx="36.0162" cy="22.1497" r="1.5"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M25.4613,24.7355,14.1438,23.1184s6.1889,9.7351,10.3663,8.6973"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M36.0162,15.776a20.4581,20.4581,0,0,1-3.08,5.0592"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M46.571,24.7543l11.45-1.6359s-6.2678,9.8488-10.494,8.7989"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M36.0162,15.776a20.4558,20.4558,0,0,0,3.08,5.0592"/>
|
||||
<line x1="51.2159" x2="51.2159" y1="31.033" y2="44.5357" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
<polyline fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" points="18.088 52.108 18.088 42.543 25.173 42.543 25.173 46.108"/>
|
||||
<line x1="20.0217" x2="20.0217" y1="42.5045" y2="37.7613" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
<line x1="23.3932" x2="23.3932" y1="42.5433" y2="39.102" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M54.9537,59.9492s2-13-10-13c-3.1919,2.1279-5.9264,3.5984-9,3.5921h.125c-3.0736.0063-5.8082-1.4642-9-3.5921-12,0-10,13-10,13"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M26.4391,24.5466l-.9825.2293a17.29,17.29,0,0,0-.779,5.1733c0,7.8276,5.0765,14.1732,11.3386,14.1732s11.3386-6.3456,11.3386-14.1732a17.29,17.29,0,0,0-.7791-5.1733l-.8123-.3239"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M46.571,24.7543l11.45-1.6359s-6.2678,9.8488-10.494,8.7989"/>
|
||||
<line x1="19.9468" x2="19.9468" y1="30.333" y2="33.5011" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M47.8955,60.0059C29.3966,54.87,27.0787,46.9492,27.0787,46.9492s-4.2.2046-5.6776,1.3537c3.1163,6.4287,5.69,8.6387,13.8595,11.7689"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M46.7244,21.2945c-2.6425-.6931-3.2409,2.473-6.127,1.7854"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M31.3867,23.08c-2.886.6876-2.8758-2.7625-5.5183-2.0694"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.1 KiB |
@ -0,0 +1,28 @@
|
||||
<svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="color">
|
||||
<path fill="#92d3f5" d="M45.5317,50.6166l.797,2.48-1.1339,4.9948L40.0135,63.766A23.9672,23.9672,0,0,0,54.951,59.6536s2-13-10-13c-3.1918,2.1279-5.9264,3.5984-9,3.5921h.125c-3.0736.0063-5.8081-1.4642-9-3.5921-12,0-10,13-10,13,9.9776,6.0208,14.368,3.708,28.1188-1.5619l.6171-3.5208-24.0677,1.35,5.3094-.9076"/>
|
||||
<path fill="#92d3f5" d="M54.9358,59.6536s2-13-10-13c-3.1919,2.1279-5.9264,3.5984-9,3.5921h.125c-3.0736.0063-5.8081-1.4642-9-3.5921-12,0-10,13-10,13"/>
|
||||
<path fill="#61b2e4" d="M45.2181,46.6536a18.0129,18.0129,0,0,1-8.9375,3.5885c13.0625.4115,13.0625,5.4115,13.8247,9.4115h5.1128S57.2181,46.6536,45.2181,46.6536Z"/>
|
||||
<path fill="#61b2e4" d="M55.2181,59.6536S43.4619,68.68,34.4813,62.6168l10.7135-4.5251,1.1339-4.9948S55.3057,50.6328,55.2181,59.6536Z"/>
|
||||
<ellipse cx="36.0134" cy="30.1536" rx="11.3386" ry="14.1732" fill="#92d3f5"/>
|
||||
<path fill="#fff" d="M49.1847,14.728c-13.1713,4.1165-24.51,13.5184-24.51,13.5184s-3.96-5.3394-2.2369-10.7265c0,0,2.2369-8.0773,13.036-8.0773,7.7035,0,11.5948,3.1694,13.7108,5.2854C55.331,23.4529,47.05,28.3322,47.05,28.3322a126.1662,126.1662,0,0,1-9.655-8.3413"/>
|
||||
<path fill="#3f3f3f" d="M36.0422,46.9384c5.0174-.1253,9.5065-6.0464,10.3-10.8959-3.2477,5.68-8.6155,4.61-8.6155,4.61L36.02,38.0191l-1.7063,2.6338s-5.3678,1.0695-8.6155-4.61c.7933,4.85,5.2824,10.7706,10.3,10.8959"/>
|
||||
<path fill="#d0cfce" d="M49.1847,14.728C55.331,23.4529,47.05,28.3322,47.05,28.3322a126.1662,126.1662,0,0,1-9.655-8.3413"/>
|
||||
<path fill="#fcea2b" d="M36.0135,25.82h0a2,2,0,0,1-2-2v-4a2,2,0,0,1,2-2h0a2,2,0,0,1,2,2v4A2,2,0,0,1,36.0135,25.82Z"/>
|
||||
<polygon fill="#fcea2b" points="45.195 58.092 40.783 59.923 40.783 54.678 45.812 54.678 45.195 58.092"/>
|
||||
</g>
|
||||
<g id="line">
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M40.0135,63.766A23.9672,23.9672,0,0,0,54.951,59.6536s2-13-10-13c-3.1918,2.1279-5.9264,3.5984-9,3.5921h.125c-3.0736.0063-5.8081-1.4642-9-3.5921-12,0-10,13-10,13,9.9776,6.0208,14.368,3.708,28.1188-1.5619l.6171-3.5208-24.0677,1.35"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M45.5317,50.6166l.797,2.48-1.1339,4.9948"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M25.7159,36.0725a17.002,17.002,0,0,1-1.04-5.92,18.7051,18.7051,0,0,1,.11-2"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M47.2459,28.2025a17.9177,17.9177,0,0,1,.11,1.95,17.0185,17.0185,0,0,1-1.06,5.97"/>
|
||||
<path d="M42.0135,28.7928a2,2,0,1,1-2-2,2.0007,2.0007,0,0,1,2,2"/>
|
||||
<path d="M34.0135,28.7928a2,2,0,1,1-2-2,2.0007,2.0007,0,0,1,2,2"/>
|
||||
<path d="M36.0134,38.7952a7.6528,7.6528,0,0,1-3.4473-.8579,1,1,0,0,1,.8946-1.7891,5.3772,5.3772,0,0,0,5.1054,0,1,1,0,0,1,.8946,1.7891A7.6528,7.6528,0,0,1,36.0134,38.7952Z"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M36.0422,46.9384c5.0174-.1253,9.5065-6.0464,10.3-10.8959-3.2477,5.68-8.6155,4.61-8.6155,4.61L36.02,38.0191l-1.7063,2.6338s-5.3678,1.0695-8.6155-4.61c.7933,4.85,5.2824,10.7706,10.3,10.8959"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M34.0159,21.7725c-1.28.77-2.47,1.53-3.53,2.24-3.13,2.09-5.21,3.75-5.7,4.14-.07.06-.11.09-.11.09s-3.96-5.34-2.24-10.72c0,0,2.24-8.08,13.04-8.08,7.7,0,11.59,3.17,13.71,5.29,5.58,7.91-.72,12.65-1.94,13.47-.13.09-.2.13-.2.13s-4.41-3.38-9.03-7.75"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M37.9859,19.5125a66.4314,66.4314,0,0,1,11.2-4.78"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M36.0135,25.82h0a2,2,0,0,1-2-2v-4a2,2,0,0,1,2-2h0a2,2,0,0,1,2,2v4A2,2,0,0,1,36.0135,25.82Z"/>
|
||||
<line x1="40.7828" x2="40.7828" y1="54.9709" y2="59.7193" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.0 KiB |
28
frontend/app_flowy/assets/images/emoji/1F9DF.svg
Normal file
@ -0,0 +1,28 @@
|
||||
<svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="color">
|
||||
<circle cx="36.9435" cy="28.0392" r="10" fill="#e67a94"/>
|
||||
<path fill="#b1cc33" d="M35.1773,17.1567c-5.8765.5287-10.5159,6.6407-10.5159,14.1212,0,7.8277,5.0764,14.1733,11.3386,14.1733s11.3387-6.3456,11.3387-14.1733a17.2594,17.2594,0,0,0-.7674-5.0941,9.9175,9.9175,0,0,1-11.394-9.0271Z"/>
|
||||
<path fill="#b1cc33" d="M54.9375,60.9094s2-12.6032-10-12.6032c-3.1918,2.128-5.9264,3.5985-9,3.5922h.125c-3.0736.0063-5.8081-1.4642-9-3.5922-12,0-10,12.6032-10,12.6032"/>
|
||||
<path fill="#d0cfce" d="M46.354,48.2762l-.9075.8851a15.6367,15.6367,0,0,1-5.3451,5.81l-2.0029,5.7447-2-4.5853c-3.337.02-6.674-2.2824-9.3481-6.9693L26.2,48.25C15.27,48.9272,17.1609,60.937,17.1609,60.937l3.1531-.007,2.0839-4.7825,1.5287,4.8108,31.18-.05"/>
|
||||
<path fill="#fcea2b" d="M32,38.2987s8-3.11,8,0C40,44.5411,32,38.2987,32,38.2987Z"/>
|
||||
<path fill="#9b9b9a" d="M42.5077,52.7252c6.4062,1.661,6.5337,5.0343,7.1339,8.1842h5.1128s1.8925-11.904-9.0495-12.5747q-.46-.0282-.9505-.0285"/>
|
||||
<path fill="#5c9e31" d="M30,29.9325v2.041a2.1088,2.1088,0,0,0,1.7064,2.1339A2.0016,2.0016,0,0,0,34,32.1283V29.9325a.0571.0571,0,0,0-.0571-.0571H30.0573A.057.057,0,0,0,30,29.9325Z"/>
|
||||
<path fill="#5c9e31" d="M38,29.8754v2.2529a2,2,0,0,0,4,0V29.8754Z"/>
|
||||
<path fill="#fff" d="M42,29.9171a2,2,0,1,1-2-2,2.0007,2.0007,0,0,1,2,2"/>
|
||||
<path fill="#fff" d="M34,29.9171a2,2,0,1,1-2-2,2.0007,2.0007,0,0,1,2,2"/>
|
||||
</g>
|
||||
<g id="line">
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M35.1773,17.1567c-5.8765.5287-10.5159,6.6407-10.5159,14.1212,0,7.8277,5.0764,14.1733,11.3386,14.1733s11.3387-6.3456,11.3387-14.1733a17.2594,17.2594,0,0,0-.7674-5.0941,9.9175,9.9175,0,0,1-11.394-9.0271Z"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M45.3836,25.2642a2,2,0,0,0-1.3286-2.18,1.9753,1.9753,0,0,0,.3775-.8915,1.9931,1.9931,0,0,0-2.79-2.1141,1.9923,1.9923,0,0,0-3.3909-1.3058,1.9922,1.9922,0,0,0-2.7713-.4814"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M35.1773,17.1567q.0408.48.1262.9464a9.9978,9.9978,0,0,0,9.4213,8.1874q.2037.0082.4093.0082a10.0162,10.0162,0,0,0,1.4372-.1149"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M32,38.2987s8-3.11,8,0C40,44.5411,32,38.2987,32,38.2987Z"/>
|
||||
<line x1="39.4332" x2="35.134" y1="40.0153" y2="40.0153" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
<line x1="36.9435" x2="36.9435" y1="40.0153" y2="37.0896" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/>
|
||||
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M26.6519,48.9249c2.6741,4.6869,6.0111,6.99,9.3481,6.9693l2,4.0183,2.0029-5.1777a15.6376,15.6376,0,0,0,5.3451-5.81"/>
|
||||
<line x1="43.5496" x2="42.7939" y1="57.2401" y2="59.9029" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
<polyline fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" points="20.216 59.871 22.3 56.308 23.828 59.871"/>
|
||||
<path d="M55,60.9171a1,1,0,0,1-1-1v-3c0-4.4517-4.4961-7.81-8.6518-7.9922-6.2051,5.0117-12.4912,5.0117-18.6963,0C22.4961,49.1066,18,52.4654,18,56.9171v3a1,1,0,1,1-2,0v-3c0-5.3247,5.14-9.9976,11-10h0a.9994.9994,0,0,1,.64.2319c5.625,4.6875,11.0947,4.6875,16.72,0a.9994.9994,0,0,1,.64-.2319h.0005C50.86,46.92,56,51.5924,56,56.9171v3A1,1,0,0,1,55,60.9171Z"/>
|
||||
<path fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="1.5" d="M42,29.9171a2,2,0,1,1-2-2,2.0007,2.0007,0,0,1,2,2"/>
|
||||
<path fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="1.5" d="M34,29.9171a2,2,0,1,1-2-2,2.0007,2.0007,0,0,1,2,2"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.9 KiB |
@ -16,7 +16,7 @@ class BlankPluginBuilder extends PluginBuilder {
|
||||
String get menuName => "Blank";
|
||||
|
||||
@override
|
||||
PluginType get pluginType => DefaultPlugin.blank.type();
|
||||
PluginType get pluginType => PluginType.blank;
|
||||
}
|
||||
|
||||
class BlankPluginConfig implements PluginConfig {
|
||||
|
@ -0,0 +1,290 @@
|
||||
import 'dart:async';
|
||||
import 'package:app_flowy/plugins/grid/application/block/block_cache.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_service.dart';
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'board_data_controller.dart';
|
||||
import 'group_controller.dart';
|
||||
|
||||
part 'board_bloc.freezed.dart';
|
||||
|
||||
class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
final BoardDataController _dataController;
|
||||
late final AFBoardDataController afBoardDataController;
|
||||
final MoveRowFFIService _rowService;
|
||||
Map<String, GroupController> groupControllers = {};
|
||||
|
||||
GridFieldCache get fieldCache => _dataController.fieldCache;
|
||||
String get gridId => _dataController.gridId;
|
||||
|
||||
BoardBloc({required ViewPB view})
|
||||
: _rowService = MoveRowFFIService(gridId: view.id),
|
||||
_dataController = BoardDataController(view: view),
|
||||
super(BoardState.initial(view.id)) {
|
||||
afBoardDataController = AFBoardDataController(
|
||||
onMoveColumn: (
|
||||
fromIndex,
|
||||
toIndex,
|
||||
) {},
|
||||
onMoveColumnItem: (
|
||||
columnId,
|
||||
fromIndex,
|
||||
toIndex,
|
||||
) {
|
||||
final fromRow = groupControllers[columnId]?.rowAtIndex(fromIndex);
|
||||
final toRow = groupControllers[columnId]?.rowAtIndex(toIndex);
|
||||
_moveRow(fromRow, toRow);
|
||||
},
|
||||
onMoveColumnItemToColumn: (
|
||||
fromColumnId,
|
||||
fromIndex,
|
||||
toColumnId,
|
||||
toIndex,
|
||||
) {
|
||||
final fromRow = groupControllers[fromColumnId]?.rowAtIndex(fromIndex);
|
||||
final toRow = groupControllers[toColumnId]?.rowAtIndex(toIndex);
|
||||
_moveRow(fromRow, toRow);
|
||||
},
|
||||
);
|
||||
|
||||
on<BoardEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
_startListening();
|
||||
await _loadGrid(emit);
|
||||
},
|
||||
createRow: (groupId) async {
|
||||
final result = await _dataController.createBoardCard(groupId);
|
||||
result.fold(
|
||||
(rowPB) {
|
||||
emit(state.copyWith(editingRow: some(rowPB)));
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
},
|
||||
endEditRow: (rowId) {
|
||||
assert(state.editingRow.isSome());
|
||||
state.editingRow.fold(() => null, (row) {
|
||||
assert(row.id == rowId);
|
||||
emit(state.copyWith(editingRow: none()));
|
||||
});
|
||||
},
|
||||
didReceiveGridUpdate: (GridPB grid) {
|
||||
emit(state.copyWith(grid: Some(grid)));
|
||||
},
|
||||
didReceiveRows: (List<RowInfo> rowInfos) {
|
||||
emit(state.copyWith(rowInfos: rowInfos));
|
||||
},
|
||||
didReceiveError: (FlowyError error) {
|
||||
emit(state.copyWith(noneOrError: some(error)));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _moveRow(RowPB? fromRow, RowPB? toRow) {
|
||||
if (fromRow != null && toRow != null) {
|
||||
_rowService
|
||||
.moveRow(
|
||||
fromRowId: fromRow.id,
|
||||
toRowId: toRow.id,
|
||||
)
|
||||
.then((result) {
|
||||
result.fold((l) => null, (r) => add(BoardEvent.didReceiveError(r)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _dataController.dispose();
|
||||
for (final controller in groupControllers.values) {
|
||||
controller.dispose();
|
||||
}
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void initializeGroups(List<GroupPB> groups) {
|
||||
for (final group in groups) {
|
||||
final delegate = GroupControllerDelegateImpl(afBoardDataController);
|
||||
final controller = GroupController(
|
||||
gridId: state.gridId,
|
||||
group: group,
|
||||
delegate: delegate,
|
||||
);
|
||||
controller.startListening();
|
||||
groupControllers[controller.group.groupId] = (controller);
|
||||
}
|
||||
}
|
||||
|
||||
GridRowCache? getRowCache(String blockId) {
|
||||
final GridBlockCache? blockCache = _dataController.blocks[blockId];
|
||||
return blockCache?.rowCache;
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_dataController.addListener(
|
||||
onGridChanged: (grid) {
|
||||
if (!isClosed) {
|
||||
add(BoardEvent.didReceiveGridUpdate(grid));
|
||||
}
|
||||
},
|
||||
didLoadGroups: (groups) {
|
||||
List<AFBoardColumnData> columns = groups.map((group) {
|
||||
return AFBoardColumnData(
|
||||
id: group.groupId,
|
||||
desc: group.desc,
|
||||
items: _buildRows(group.rows),
|
||||
customData: group,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
afBoardDataController.addColumns(columns);
|
||||
initializeGroups(groups);
|
||||
},
|
||||
onRowsChanged: (List<RowInfo> rowInfos, RowsChangedReason reason) {
|
||||
add(BoardEvent.didReceiveRows(rowInfos));
|
||||
},
|
||||
onError: (err) {
|
||||
Log.error(err);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<AFColumnItem> _buildRows(List<RowPB> rows) {
|
||||
final items = rows.map((row) {
|
||||
return BoardColumnItem(row: row);
|
||||
}).toList();
|
||||
|
||||
return <AFColumnItem>[...items];
|
||||
}
|
||||
|
||||
Future<void> _loadGrid(Emitter<BoardState> emit) async {
|
||||
final result = await _dataController.loadData();
|
||||
result.fold(
|
||||
(grid) => emit(
|
||||
state.copyWith(loadingState: GridLoadingState.finish(left(unit))),
|
||||
),
|
||||
(err) => emit(
|
||||
state.copyWith(loadingState: GridLoadingState.finish(right(err))),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardEvent with _$BoardEvent {
|
||||
const factory BoardEvent.initial() = InitialGrid;
|
||||
const factory BoardEvent.createRow(String groupId) = _CreateRow;
|
||||
const factory BoardEvent.endEditRow(String rowId) = _EndEditRow;
|
||||
const factory BoardEvent.didReceiveError(FlowyError error) = _DidReceiveError;
|
||||
const factory BoardEvent.didReceiveRows(List<RowInfo> rowInfos) =
|
||||
_DidReceiveRows;
|
||||
const factory BoardEvent.didReceiveGridUpdate(
|
||||
GridPB grid,
|
||||
) = _DidReceiveGridUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardState with _$BoardState {
|
||||
const factory BoardState({
|
||||
required String gridId,
|
||||
required Option<GridPB> grid,
|
||||
required Option<RowPB> editingRow,
|
||||
required List<RowInfo> rowInfos,
|
||||
required GridLoadingState loadingState,
|
||||
required Option<FlowyError> noneOrError,
|
||||
}) = _BoardState;
|
||||
|
||||
factory BoardState.initial(String gridId) => BoardState(
|
||||
rowInfos: [],
|
||||
grid: none(),
|
||||
gridId: gridId,
|
||||
editingRow: none(),
|
||||
noneOrError: none(),
|
||||
loadingState: const _Loading(),
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridLoadingState with _$GridLoadingState {
|
||||
const factory GridLoadingState.loading() = _Loading;
|
||||
const factory GridLoadingState.finish(
|
||||
Either<Unit, FlowyError> successOrFail) = _Finish;
|
||||
}
|
||||
|
||||
class GridFieldEquatable extends Equatable {
|
||||
final UnmodifiableListView<FieldPB> _fields;
|
||||
const GridFieldEquatable(
|
||||
UnmodifiableListView<FieldPB> fields,
|
||||
) : _fields = fields;
|
||||
|
||||
@override
|
||||
List<Object?> get props {
|
||||
if (_fields.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
_fields.length,
|
||||
_fields
|
||||
.map((field) => field.width)
|
||||
.reduce((value, element) => value + element),
|
||||
];
|
||||
}
|
||||
|
||||
UnmodifiableListView<FieldPB> get value => UnmodifiableListView(_fields);
|
||||
}
|
||||
|
||||
class BoardColumnItem extends AFColumnItem {
|
||||
final RowPB row;
|
||||
|
||||
BoardColumnItem({required this.row});
|
||||
|
||||
@override
|
||||
String get id => row.id;
|
||||
}
|
||||
|
||||
class CreateCardItem extends AFColumnItem {
|
||||
@override
|
||||
String get id => '$CreateCardItem';
|
||||
}
|
||||
|
||||
class GroupControllerDelegateImpl extends GroupControllerDelegate {
|
||||
final AFBoardDataController controller;
|
||||
|
||||
GroupControllerDelegateImpl(this.controller);
|
||||
|
||||
@override
|
||||
void insertRow(String groupId, RowPB row, int? index) {
|
||||
final item = BoardColumnItem(row: row);
|
||||
if (index != null) {
|
||||
controller.insertColumnItem(groupId, index, item);
|
||||
} else {
|
||||
controller.addColumnItem(groupId, item);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void removeRow(String groupId, String rowId) {
|
||||
controller.removeColumnItem(groupId, rowId);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRow(String groupId, RowPB row) {
|
||||
//
|
||||
}
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:app_flowy/plugins/grid/application/block/block_cache.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'dart:async';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
|
||||
|
||||
typedef OnFieldsChanged = void Function(UnmodifiableListView<FieldPB>);
|
||||
typedef OnGridChanged = void Function(GridPB);
|
||||
typedef DidLoadGroups = void Function(List<GroupPB>);
|
||||
typedef OnRowsChanged = void Function(
|
||||
List<RowInfo>,
|
||||
RowsChangedReason,
|
||||
);
|
||||
typedef OnError = void Function(FlowyError);
|
||||
|
||||
class BoardDataController {
|
||||
final String gridId;
|
||||
final GridFFIService _gridFFIService;
|
||||
final GridFieldCache fieldCache;
|
||||
|
||||
// key: the block id
|
||||
final LinkedHashMap<String, GridBlockCache> _blocks;
|
||||
LinkedHashMap<String, GridBlockCache> get blocks => _blocks;
|
||||
|
||||
OnFieldsChanged? _onFieldsChanged;
|
||||
OnGridChanged? _onGridChanged;
|
||||
DidLoadGroups? _didLoadGroup;
|
||||
OnRowsChanged? _onRowsChanged;
|
||||
OnError? _onError;
|
||||
|
||||
List<RowInfo> get rowInfos {
|
||||
final List<RowInfo> rows = [];
|
||||
for (var block in _blocks.values) {
|
||||
rows.addAll(block.rows);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
BoardDataController({required ViewPB view})
|
||||
: gridId = view.id,
|
||||
_blocks = LinkedHashMap.new(),
|
||||
_gridFFIService = GridFFIService(gridId: view.id),
|
||||
fieldCache = GridFieldCache(gridId: view.id);
|
||||
|
||||
void addListener({
|
||||
OnGridChanged? onGridChanged,
|
||||
OnFieldsChanged? onFieldsChanged,
|
||||
DidLoadGroups? didLoadGroups,
|
||||
OnRowsChanged? onRowsChanged,
|
||||
OnError? onError,
|
||||
}) {
|
||||
_onGridChanged = onGridChanged;
|
||||
_onFieldsChanged = onFieldsChanged;
|
||||
_didLoadGroup = didLoadGroups;
|
||||
_onRowsChanged = onRowsChanged;
|
||||
_onError = onError;
|
||||
|
||||
fieldCache.addListener(onFields: (fields) {
|
||||
_onFieldsChanged?.call(UnmodifiableListView(fields));
|
||||
});
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> loadData() async {
|
||||
final result = await _gridFFIService.loadGrid();
|
||||
return Future(
|
||||
() => result.fold(
|
||||
(grid) async {
|
||||
_onGridChanged?.call(grid);
|
||||
|
||||
return await _loadFields(grid).then((result) {
|
||||
return result.fold(
|
||||
(l) {
|
||||
_loadGroups(grid.blocks);
|
||||
return left(l);
|
||||
},
|
||||
(err) => right(err),
|
||||
);
|
||||
});
|
||||
},
|
||||
(err) => right(err),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Either<RowPB, FlowyError>> createBoardCard(String groupId) {
|
||||
return _gridFFIService.createBoardCard(groupId);
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
await _gridFFIService.closeGrid();
|
||||
await fieldCache.dispose();
|
||||
|
||||
for (final blockCache in _blocks.values) {
|
||||
blockCache.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> _loadFields(GridPB grid) async {
|
||||
final result = await _gridFFIService.getFields(fieldIds: grid.fields);
|
||||
return Future(
|
||||
() => result.fold(
|
||||
(fields) {
|
||||
fieldCache.fields = fields.items;
|
||||
_onFieldsChanged?.call(UnmodifiableListView(fieldCache.fields));
|
||||
return left(unit);
|
||||
},
|
||||
(err) => right(err),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _loadGroups(List<BlockPB> blocks) async {
|
||||
for (final block in blocks) {
|
||||
final cache = GridBlockCache(
|
||||
gridId: gridId,
|
||||
block: block,
|
||||
fieldCache: fieldCache,
|
||||
);
|
||||
|
||||
cache.addListener(onRowsChanged: (reason) {
|
||||
_onRowsChanged?.call(rowInfos, reason);
|
||||
});
|
||||
_blocks[block.id] = cache;
|
||||
}
|
||||
|
||||
final result = await _gridFFIService.loadGroups();
|
||||
return Future(
|
||||
() => result.fold(
|
||||
(groups) {
|
||||
_didLoadGroup?.call(groups.items);
|
||||
},
|
||||
(err) => _onError?.call(err),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
part 'board_checkbox_cell_bloc.freezed.dart';
|
||||
|
||||
class BoardCheckboxCellBloc
|
||||
extends Bloc<BoardCheckboxCellEvent, BoardCheckboxCellState> {
|
||||
final GridCheckboxCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
BoardCheckboxCellBloc({
|
||||
required this.cellController,
|
||||
}) : super(BoardCheckboxCellState.initial(cellController)) {
|
||||
on<BoardCheckboxCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
_startListening();
|
||||
},
|
||||
didReceiveCellUpdate: (cellData) {
|
||||
emit(state.copyWith(isSelected: _isSelected(cellData)));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cellContent) {
|
||||
if (!isClosed) {
|
||||
add(BoardCheckboxCellEvent.didReceiveCellUpdate(cellContent ?? ""));
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardCheckboxCellEvent with _$BoardCheckboxCellEvent {
|
||||
const factory BoardCheckboxCellEvent.initial() = _InitialCell;
|
||||
const factory BoardCheckboxCellEvent.didReceiveCellUpdate(
|
||||
String cellContent) = _DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardCheckboxCellState with _$BoardCheckboxCellState {
|
||||
const factory BoardCheckboxCellState({
|
||||
required bool isSelected,
|
||||
}) = _CheckboxCellState;
|
||||
|
||||
factory BoardCheckboxCellState.initial(GridCellController context) {
|
||||
return BoardCheckboxCellState(
|
||||
isSelected: _isSelected(context.getCellData()));
|
||||
}
|
||||
}
|
||||
|
||||
bool _isSelected(String? cellData) {
|
||||
return cellData == "Yes";
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
part 'board_date_cell_bloc.freezed.dart';
|
||||
|
||||
class BoardDateCellBloc extends Bloc<BoardDateCellEvent, BoardDateCellState> {
|
||||
final GridDateCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
BoardDateCellBloc({required this.cellController})
|
||||
: super(BoardDateCellState.initial(cellController)) {
|
||||
on<BoardDateCellEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
initial: () => _startListening(),
|
||||
didReceiveCellUpdate: (DateCellDataPB? cellData) {
|
||||
emit(state.copyWith(
|
||||
data: cellData, dateStr: _dateStrFromCellData(cellData)));
|
||||
},
|
||||
didReceiveFieldUpdate: (FieldPB value) =>
|
||||
emit(state.copyWith(field: value)),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((data) {
|
||||
if (!isClosed) {
|
||||
add(BoardDateCellEvent.didReceiveCellUpdate(data));
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardDateCellEvent with _$BoardDateCellEvent {
|
||||
const factory BoardDateCellEvent.initial() = _InitialCell;
|
||||
const factory BoardDateCellEvent.didReceiveCellUpdate(DateCellDataPB? data) =
|
||||
_DidReceiveCellUpdate;
|
||||
const factory BoardDateCellEvent.didReceiveFieldUpdate(FieldPB field) =
|
||||
_DidReceiveFieldUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardDateCellState with _$BoardDateCellState {
|
||||
const factory BoardDateCellState({
|
||||
required DateCellDataPB? data,
|
||||
required String dateStr,
|
||||
required FieldPB field,
|
||||
}) = _BoardDateCellState;
|
||||
|
||||
factory BoardDateCellState.initial(GridDateCellController context) {
|
||||
final cellData = context.getCellData();
|
||||
|
||||
return BoardDateCellState(
|
||||
field: context.field,
|
||||
data: cellData,
|
||||
dateStr: _dateStrFromCellData(cellData),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _dateStrFromCellData(DateCellDataPB? cellData) {
|
||||
String dateStr = "";
|
||||
if (cellData != null) {
|
||||
dateStr = cellData.date + " " + cellData.time;
|
||||
}
|
||||
return dateStr;
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
part 'board_number_cell_bloc.freezed.dart';
|
||||
|
||||
class BoardNumberCellBloc
|
||||
extends Bloc<BoardNumberCellEvent, BoardNumberCellState> {
|
||||
final GridNumberCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
BoardNumberCellBloc({
|
||||
required this.cellController,
|
||||
}) : super(BoardNumberCellState.initial(cellController)) {
|
||||
on<BoardNumberCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
_startListening();
|
||||
},
|
||||
didReceiveCellUpdate: (content) {
|
||||
emit(state.copyWith(content: content));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cellContent) {
|
||||
if (!isClosed) {
|
||||
add(BoardNumberCellEvent.didReceiveCellUpdate(cellContent ?? ""));
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardNumberCellEvent with _$BoardNumberCellEvent {
|
||||
const factory BoardNumberCellEvent.initial() = _InitialCell;
|
||||
const factory BoardNumberCellEvent.didReceiveCellUpdate(String cellContent) =
|
||||
_DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardNumberCellState with _$BoardNumberCellState {
|
||||
const factory BoardNumberCellState({
|
||||
required String content,
|
||||
}) = _BoardNumberCellState;
|
||||
|
||||
factory BoardNumberCellState.initial(GridCellController context) =>
|
||||
BoardNumberCellState(
|
||||
content: context.getCellData() ?? "",
|
||||
);
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
import 'dart:async';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
|
||||
part 'board_select_option_cell_bloc.freezed.dart';
|
||||
|
||||
class BoardSelectOptionCellBloc
|
||||
extends Bloc<BoardSelectOptionCellEvent, BoardSelectOptionCellState> {
|
||||
final GridSelectOptionCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
BoardSelectOptionCellBloc({
|
||||
required this.cellController,
|
||||
}) : super(BoardSelectOptionCellState.initial(cellController)) {
|
||||
on<BoardSelectOptionCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
_startListening();
|
||||
},
|
||||
didReceiveOptions: (List<SelectOptionPB> selectedOptions) {
|
||||
emit(state.copyWith(selectedOptions: selectedOptions));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((selectOptionContext) {
|
||||
if (!isClosed) {
|
||||
add(BoardSelectOptionCellEvent.didReceiveOptions(
|
||||
selectOptionContext?.selectOptions ?? [],
|
||||
));
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardSelectOptionCellEvent with _$BoardSelectOptionCellEvent {
|
||||
const factory BoardSelectOptionCellEvent.initial() = _InitialCell;
|
||||
const factory BoardSelectOptionCellEvent.didReceiveOptions(
|
||||
List<SelectOptionPB> selectedOptions,
|
||||
) = _DidReceiveOptions;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardSelectOptionCellState with _$BoardSelectOptionCellState {
|
||||
const factory BoardSelectOptionCellState({
|
||||
required List<SelectOptionPB> selectedOptions,
|
||||
}) = _BoardSelectOptionCellState;
|
||||
|
||||
factory BoardSelectOptionCellState.initial(
|
||||
GridSelectOptionCellController context) {
|
||||
final data = context.getCellData();
|
||||
|
||||
return BoardSelectOptionCellState(
|
||||
selectedOptions: data?.selectOptions ?? [],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
part 'board_text_cell_bloc.freezed.dart';
|
||||
|
||||
class BoardTextCellBloc extends Bloc<BoardTextCellEvent, BoardTextCellState> {
|
||||
final GridCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
BoardTextCellBloc({
|
||||
required this.cellController,
|
||||
}) : super(BoardTextCellState.initial(cellController)) {
|
||||
on<BoardTextCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
_startListening();
|
||||
},
|
||||
didReceiveCellUpdate: (content) {
|
||||
emit(state.copyWith(content: content));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cellContent) {
|
||||
if (!isClosed) {
|
||||
add(BoardTextCellEvent.didReceiveCellUpdate(cellContent ?? ""));
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardTextCellEvent with _$BoardTextCellEvent {
|
||||
const factory BoardTextCellEvent.initial() = _InitialCell;
|
||||
const factory BoardTextCellEvent.didReceiveCellUpdate(String cellContent) =
|
||||
_DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardTextCellState with _$BoardTextCellState {
|
||||
const factory BoardTextCellState({
|
||||
required String content,
|
||||
}) = _BoardTextCellState;
|
||||
|
||||
factory BoardTextCellState.initial(GridCellController context) =>
|
||||
BoardTextCellState(
|
||||
content: context.getCellData() ?? "",
|
||||
);
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option_entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
part 'board_url_cell_bloc.freezed.dart';
|
||||
|
||||
class BoardURLCellBloc extends Bloc<BoardURLCellEvent, BoardURLCellState> {
|
||||
final GridURLCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
BoardURLCellBloc({
|
||||
required this.cellController,
|
||||
}) : super(BoardURLCellState.initial(cellController)) {
|
||||
on<BoardURLCellEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
initial: () {
|
||||
_startListening();
|
||||
},
|
||||
didReceiveCellUpdate: (cellData) {
|
||||
emit(state.copyWith(
|
||||
content: cellData?.content ?? "",
|
||||
url: cellData?.url ?? "",
|
||||
));
|
||||
},
|
||||
updateURL: (String url) {
|
||||
cellController.saveCellData(url, deduplicate: true);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cellData) {
|
||||
if (!isClosed) {
|
||||
add(BoardURLCellEvent.didReceiveCellUpdate(cellData));
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardURLCellEvent with _$BoardURLCellEvent {
|
||||
const factory BoardURLCellEvent.initial() = _InitialCell;
|
||||
const factory BoardURLCellEvent.updateURL(String url) = _UpdateURL;
|
||||
const factory BoardURLCellEvent.didReceiveCellUpdate(URLCellDataPB? cell) =
|
||||
_DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardURLCellState with _$BoardURLCellState {
|
||||
const factory BoardURLCellState({
|
||||
required String content,
|
||||
required String url,
|
||||
}) = _BoardURLCellState;
|
||||
|
||||
factory BoardURLCellState.initial(GridURLCellController context) {
|
||||
final cellData = context.getCellData();
|
||||
return BoardURLCellState(
|
||||
content: cellData?.content ?? "",
|
||||
url: cellData?.url ?? "",
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
import 'dart:collection';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_service.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'card_data_controller.dart';
|
||||
|
||||
part 'card_bloc.freezed.dart';
|
||||
|
||||
class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
|
||||
final RowFFIService _rowService;
|
||||
final CardDataController _dataController;
|
||||
|
||||
BoardCardBloc({
|
||||
required String gridId,
|
||||
required CardDataController dataController,
|
||||
}) : _rowService = RowFFIService(
|
||||
gridId: gridId,
|
||||
blockId: dataController.rowPB.blockId,
|
||||
),
|
||||
_dataController = dataController,
|
||||
super(BoardCardState.initial(
|
||||
dataController.rowPB, dataController.loadData())) {
|
||||
on<BoardCardEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
initial: (_InitialRow value) async {
|
||||
await _startListening();
|
||||
},
|
||||
didReceiveCells: (_DidReceiveCells value) async {
|
||||
final cells = value.gridCellMap.values
|
||||
.map((e) => GridCellEquatable(e.field))
|
||||
.toList();
|
||||
emit(state.copyWith(
|
||||
gridCellMap: value.gridCellMap,
|
||||
cells: UnmodifiableListView(cells),
|
||||
changeReason: value.reason,
|
||||
));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
_dataController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
RowInfo rowInfo() {
|
||||
return RowInfo(
|
||||
gridId: _rowService.gridId,
|
||||
fields: UnmodifiableListView(
|
||||
state.cells.map((cell) => cell._field).toList(),
|
||||
),
|
||||
rowPB: state.rowPB,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _startListening() async {
|
||||
_dataController.addListener(
|
||||
onRowChanged: (cells, reason) {
|
||||
if (!isClosed) {
|
||||
add(BoardCardEvent.didReceiveCells(cells, reason));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardCardEvent with _$BoardCardEvent {
|
||||
const factory BoardCardEvent.initial() = _InitialRow;
|
||||
const factory BoardCardEvent.didReceiveCells(
|
||||
GridCellMap gridCellMap, RowsChangedReason reason) = _DidReceiveCells;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardCardState with _$BoardCardState {
|
||||
const factory BoardCardState({
|
||||
required RowPB rowPB,
|
||||
required GridCellMap gridCellMap,
|
||||
required UnmodifiableListView<GridCellEquatable> cells,
|
||||
RowsChangedReason? changeReason,
|
||||
}) = _BoardCardState;
|
||||
|
||||
factory BoardCardState.initial(RowPB rowPB, GridCellMap cellDataMap) =>
|
||||
BoardCardState(
|
||||
rowPB: rowPB,
|
||||
gridCellMap: cellDataMap,
|
||||
cells: UnmodifiableListView(
|
||||
cellDataMap.values.map((e) => GridCellEquatable(e.field)).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class GridCellEquatable extends Equatable {
|
||||
final FieldPB _field;
|
||||
|
||||
const GridCellEquatable(FieldPB field) : _field = field;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
_field.id,
|
||||
_field.fieldType,
|
||||
_field.visibility,
|
||||
_field.width,
|
||||
];
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
import 'package:app_flowy/plugins/board/presentation/card/card_cell_builder.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_field_notifier.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
typedef OnCardChanged = void Function(GridCellMap, RowsChangedReason);
|
||||
|
||||
class CardDataController extends BoardCellBuilderDelegate {
|
||||
final RowPB rowPB;
|
||||
final GridFieldCache _fieldCache;
|
||||
final GridRowCache _rowCache;
|
||||
final List<VoidCallback> _onCardChangedListeners = [];
|
||||
|
||||
CardDataController({
|
||||
required this.rowPB,
|
||||
required GridFieldCache fieldCache,
|
||||
required GridRowCache rowCache,
|
||||
}) : _fieldCache = fieldCache,
|
||||
_rowCache = rowCache;
|
||||
|
||||
GridCellMap loadData() {
|
||||
return _rowCache.loadGridCells(rowPB.id);
|
||||
}
|
||||
|
||||
void addListener({OnCardChanged? onRowChanged}) {
|
||||
_onCardChangedListeners.add(_rowCache.addListener(
|
||||
rowId: rowPB.id,
|
||||
onCellUpdated: onRowChanged,
|
||||
));
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
for (final fn in _onCardChangedListeners) {
|
||||
_rowCache.removeRowListener(fn);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
GridCellFieldNotifier buildFieldNotifier() {
|
||||
return GridCellFieldNotifier(
|
||||
notifier: GridCellFieldNotifierImpl(_fieldCache));
|
||||
}
|
||||
|
||||
@override
|
||||
GridCellCache get cellCache => _rowCache.cellCache;
|
||||
}
|
20
frontend/app_flowy/lib/plugins/board/application/group.dart
Normal file
@ -0,0 +1,20 @@
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
|
||||
class BoardGroupService {
|
||||
final String gridId;
|
||||
FieldPB? groupField;
|
||||
|
||||
BoardGroupService(this.gridId);
|
||||
|
||||
void setGroupField(FieldPB field) {
|
||||
groupField = field;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class CanBeGroupField {
|
||||
String get groupContent;
|
||||
}
|
||||
|
||||
// class SingleSelectGroup extends CanBeGroupField {
|
||||
// final SingleSelectTypeOptionContext typeOptionContext;
|
||||
// }
|
@ -0,0 +1,63 @@
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
|
||||
|
||||
import 'group_listener.dart';
|
||||
|
||||
typedef OnGroupError = void Function(FlowyError);
|
||||
|
||||
abstract class GroupControllerDelegate {
|
||||
void removeRow(String groupId, String rowId);
|
||||
void insertRow(String groupId, RowPB row, int? index);
|
||||
void updateRow(String groupId, RowPB row);
|
||||
}
|
||||
|
||||
class GroupController {
|
||||
final GroupPB group;
|
||||
final GroupListener _listener;
|
||||
final GroupControllerDelegate delegate;
|
||||
|
||||
GroupController({
|
||||
required String gridId,
|
||||
required this.group,
|
||||
required this.delegate,
|
||||
}) : _listener = GroupListener(group);
|
||||
|
||||
RowPB? rowAtIndex(int index) {
|
||||
if (index < group.rows.length) {
|
||||
return group.rows[index];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void startListening() {
|
||||
_listener.start(onGroupChanged: (result) {
|
||||
result.fold(
|
||||
(GroupRowsChangesetPB changeset) {
|
||||
for (final insertedRow in changeset.insertedRows) {
|
||||
final index = insertedRow.hasIndex() ? insertedRow.index : null;
|
||||
delegate.insertRow(
|
||||
group.groupId,
|
||||
insertedRow.row,
|
||||
index,
|
||||
);
|
||||
}
|
||||
|
||||
for (final deletedRow in changeset.deletedRows) {
|
||||
delegate.removeRow(group.groupId, deletedRow);
|
||||
}
|
||||
|
||||
for (final updatedRow in changeset.updatedRows) {
|
||||
delegate.updateRow(group.groupId, updatedRow);
|
||||
}
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
_listener.stop();
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:app_flowy/core/grid_notification.dart';
|
||||
import 'package:flowy_infra/notifier.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/group_changeset.pb.dart';
|
||||
|
||||
typedef UpdateGroupNotifiedValue = Either<GroupRowsChangesetPB, FlowyError>;
|
||||
|
||||
class GroupListener {
|
||||
final GroupPB group;
|
||||
PublishNotifier<UpdateGroupNotifiedValue>? _groupNotifier = PublishNotifier();
|
||||
GridNotificationListener? _listener;
|
||||
GroupListener(this.group);
|
||||
|
||||
void start({
|
||||
required void Function(UpdateGroupNotifiedValue) onGroupChanged,
|
||||
}) {
|
||||
_groupNotifier?.addPublishListener(onGroupChanged);
|
||||
_listener = GridNotificationListener(
|
||||
objectId: group.groupId,
|
||||
handler: _handler,
|
||||
);
|
||||
}
|
||||
|
||||
void _handler(
|
||||
GridNotification ty,
|
||||
Either<Uint8List, FlowyError> result,
|
||||
) {
|
||||
switch (ty) {
|
||||
case GridNotification.DidUpdateGroup:
|
||||
result.fold(
|
||||
(payload) => _groupNotifier?.value =
|
||||
left(GroupRowsChangesetPB.fromBuffer(payload)),
|
||||
(error) => _groupNotifier?.value = right(error),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> stop() async {
|
||||
await _listener?.stop();
|
||||
_groupNotifier?.dispose();
|
||||
_groupNotifier = null;
|
||||
}
|
||||
}
|
@ -20,15 +20,18 @@ class BoardPluginBuilder implements PluginBuilder {
|
||||
String get menuName => "Board";
|
||||
|
||||
@override
|
||||
PluginType get pluginType => DefaultPlugin.board.type();
|
||||
PluginType get pluginType => PluginType.board;
|
||||
|
||||
@override
|
||||
ViewDataType get dataType => ViewDataType.Grid;
|
||||
ViewDataTypePB get dataType => ViewDataTypePB.Database;
|
||||
|
||||
@override
|
||||
ViewLayoutTypePB? get subDataType => ViewLayoutTypePB.Board;
|
||||
}
|
||||
|
||||
class BoardPluginConfig implements PluginConfig {
|
||||
@override
|
||||
bool get creatable => true;
|
||||
bool get creatable => false;
|
||||
}
|
||||
|
||||
class BoardPlugin extends Plugin {
|
||||
|
@ -1,17 +1,174 @@
|
||||
// ignore_for_file: unused_field
|
||||
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:app_flowy/plugins/board/application/card/card_data_controller.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/cell_builder.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/row/row_detail.dart';
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../grid/application/row/row_cache.dart';
|
||||
import '../application/board_bloc.dart';
|
||||
import 'card/card.dart';
|
||||
import 'card/card_cell_builder.dart';
|
||||
|
||||
class BoardPage extends StatelessWidget {
|
||||
final ViewPB _view;
|
||||
|
||||
const BoardPage({required ViewPB view, Key? key})
|
||||
: _view = view,
|
||||
super(key: key);
|
||||
final ViewPB view;
|
||||
BoardPage({required this.view, Key? key}) : super(key: ValueKey(view.id));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container();
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
BoardBloc(view: view)..add(const BoardEvent.initial()),
|
||||
child: BlocBuilder<BoardBloc, BoardState>(
|
||||
builder: (context, state) {
|
||||
return state.loadingState.map(
|
||||
loading: (_) =>
|
||||
const Center(child: CircularProgressIndicator.adaptive()),
|
||||
finish: (result) {
|
||||
return result.successOrFail.fold(
|
||||
(_) => BoardContent(),
|
||||
(err) => FlowyErrorPage(err.toString()),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BoardContent extends StatelessWidget {
|
||||
final config = AFBoardConfig(
|
||||
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
||||
);
|
||||
|
||||
BoardContent({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<BoardBloc, BoardState>(
|
||||
builder: (context, state) {
|
||||
return Container(
|
||||
color: Colors.white,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
|
||||
child: AFBoard(
|
||||
// key: UniqueKey(),
|
||||
scrollController: ScrollController(),
|
||||
dataController: context.read<BoardBloc>().afBoardDataController,
|
||||
headerBuilder: _buildHeader,
|
||||
footBuilder: _buildFooter,
|
||||
cardBuilder: (_, data) => _buildCard(context, data),
|
||||
columnConstraints: const BoxConstraints.tightFor(width: 240),
|
||||
config: AFBoardConfig(
|
||||
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader(BuildContext context, AFBoardColumnData columnData) {
|
||||
return AppFlowyColumnHeader(
|
||||
icon: const Icon(Icons.lightbulb_circle),
|
||||
title: Text(columnData.desc),
|
||||
addIcon: const Icon(Icons.add, size: 20),
|
||||
moreIcon: const Icon(Icons.more_horiz, size: 20),
|
||||
height: 50,
|
||||
margin: config.columnItemPadding,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFooter(BuildContext context, AFBoardColumnData columnData) {
|
||||
return AppFlowyColumnFooter(
|
||||
icon: const Icon(Icons.add, size: 20),
|
||||
title: const Text('New'),
|
||||
height: 50,
|
||||
margin: config.columnItemPadding,
|
||||
onAddButtonClick: () {
|
||||
context.read<BoardBloc>().add(BoardEvent.createRow(columnData.id));
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildCard(BuildContext context, AFColumnItem item) {
|
||||
final rowPB = (item as BoardColumnItem).row;
|
||||
final rowCache = context.read<BoardBloc>().getRowCache(rowPB.blockId);
|
||||
|
||||
/// Return placeholder widget if the rowCache is null.
|
||||
if (rowCache == null) return SizedBox(key: ObjectKey(item));
|
||||
|
||||
final fieldCache = context.read<BoardBloc>().fieldCache;
|
||||
final gridId = context.read<BoardBloc>().gridId;
|
||||
final cardController = CardDataController(
|
||||
fieldCache: fieldCache,
|
||||
rowCache: rowCache,
|
||||
rowPB: rowPB,
|
||||
);
|
||||
|
||||
final cellBuilder = BoardCellBuilder(cardController);
|
||||
final isEditing = context.read<BoardBloc>().state.editingRow.fold(
|
||||
() => false,
|
||||
(editingRow) => editingRow.id == rowPB.id,
|
||||
);
|
||||
|
||||
return AppFlowyColumnItemCard(
|
||||
key: ObjectKey(item),
|
||||
child: BoardCard(
|
||||
gridId: gridId,
|
||||
isEditing: isEditing,
|
||||
cellBuilder: cellBuilder,
|
||||
dataController: cardController,
|
||||
onEditEditing: (rowId) {
|
||||
context.read<BoardBloc>().add(BoardEvent.endEditRow(rowId));
|
||||
},
|
||||
openCard: (context) => _openCard(
|
||||
gridId,
|
||||
fieldCache,
|
||||
rowPB,
|
||||
rowCache,
|
||||
context,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _openCard(String gridId, GridFieldCache fieldCache, RowPB rowPB,
|
||||
GridRowCache rowCache, BuildContext context) {
|
||||
final rowInfo = RowInfo(
|
||||
gridId: gridId,
|
||||
fields: UnmodifiableListView(fieldCache.fields),
|
||||
rowPB: rowPB,
|
||||
);
|
||||
|
||||
final dataController = GridRowDataController(
|
||||
rowInfo: rowInfo,
|
||||
fieldCache: fieldCache,
|
||||
rowCache: rowCache,
|
||||
);
|
||||
|
||||
RowDetailPage(
|
||||
cellBuilder: GridCellBuilder(delegate: dataController),
|
||||
dataController: dataController,
|
||||
).show(context);
|
||||
}
|
||||
}
|
||||
|
||||
extension HexColor on Color {
|
||||
static Color fromHex(String hexString) {
|
||||
final buffer = StringBuffer();
|
||||
if (hexString.length == 6 || hexString.length == 7) buffer.write('ff');
|
||||
buffer.write(hexString.replaceFirst('#', ''));
|
||||
return Color(int.parse(buffer.toString(), radix: 16));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,59 @@
|
||||
import 'package:app_flowy/plugins/board/application/card/board_checkbox_cell_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class BoardCheckboxCell extends StatefulWidget {
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
|
||||
const BoardCheckboxCell({
|
||||
required this.cellControllerBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardCheckboxCell> createState() => _BoardCheckboxCellState();
|
||||
}
|
||||
|
||||
class _BoardCheckboxCellState extends State<BoardCheckboxCell> {
|
||||
late BoardCheckboxCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridCheckboxCellController;
|
||||
_cellBloc = BoardCheckboxCellBloc(cellController: cellController);
|
||||
_cellBloc.add(const BoardCheckboxCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardCheckboxCellBloc, BoardCheckboxCellState>(
|
||||
builder: (context, state) {
|
||||
final icon = state.isSelected
|
||||
? svgWidget('editor/editor_check')
|
||||
: svgWidget('editor/editor_uncheck');
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyIconButton(
|
||||
iconPadding: EdgeInsets.zero,
|
||||
icon: icon,
|
||||
width: 20,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
import 'package:app_flowy/plugins/board/application/card/board_date_cell_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class BoardDateCell extends StatefulWidget {
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
|
||||
const BoardDateCell({
|
||||
required this.cellControllerBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardDateCell> createState() => _BoardDateCellState();
|
||||
}
|
||||
|
||||
class _BoardDateCellState extends State<BoardDateCell> {
|
||||
late BoardDateCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridDateCellController;
|
||||
|
||||
_cellBloc = BoardDateCellBloc(cellController: cellController)
|
||||
..add(const BoardDateCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardDateCellBloc, BoardDateCellState>(
|
||||
builder: (context, state) {
|
||||
if (state.dateStr.isEmpty) {
|
||||
return const SizedBox();
|
||||
} else {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyText.regular(
|
||||
state.dateStr,
|
||||
fontSize: 14,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
import 'package:app_flowy/plugins/board/application/card/board_number_cell_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class BoardNumberCell extends StatefulWidget {
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
|
||||
const BoardNumberCell({
|
||||
required this.cellControllerBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardNumberCell> createState() => _BoardNumberCellState();
|
||||
}
|
||||
|
||||
class _BoardNumberCellState extends State<BoardNumberCell> {
|
||||
late BoardNumberCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridNumberCellController;
|
||||
|
||||
_cellBloc = BoardNumberCellBloc(cellController: cellController)
|
||||
..add(const BoardNumberCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardNumberCellBloc, BoardNumberCellState>(
|
||||
builder: (context, state) {
|
||||
if (state.content.isEmpty) {
|
||||
return const SizedBox();
|
||||
} else {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyText.regular(
|
||||
state.content,
|
||||
fontSize: 14,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
import 'package:app_flowy/plugins/board/application/card/board_select_option_cell_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class BoardSelectOptionCell extends StatefulWidget {
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
|
||||
const BoardSelectOptionCell({
|
||||
required this.cellControllerBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardSelectOptionCell> createState() => _BoardSelectOptionCellState();
|
||||
}
|
||||
|
||||
class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
|
||||
late BoardSelectOptionCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridSelectOptionCellController;
|
||||
_cellBloc = BoardSelectOptionCellBloc(cellController: cellController)
|
||||
..add(const BoardSelectOptionCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardSelectOptionCellBloc, BoardSelectOptionCellState>(
|
||||
builder: (context, state) {
|
||||
final children = state.selectedOptions
|
||||
.map((option) => SelectOptionTag.fromOption(
|
||||
context: context,
|
||||
option: option,
|
||||
))
|
||||
.toList();
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: AbsorbPointer(
|
||||
child: Wrap(
|
||||
children: children,
|
||||
spacing: 4,
|
||||
runSpacing: 2,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
import 'package:app_flowy/plugins/board/application/card/board_text_cell_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class BoardTextCell extends StatefulWidget {
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
const BoardTextCell({required this.cellControllerBuilder, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardTextCell> createState() => _BoardTextCellState();
|
||||
}
|
||||
|
||||
class _BoardTextCellState extends State<BoardTextCell> {
|
||||
late BoardTextCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridCellController;
|
||||
|
||||
_cellBloc = BoardTextCellBloc(cellController: cellController)
|
||||
..add(const BoardTextCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardTextCellBloc, BoardTextCellState>(
|
||||
builder: (context, state) {
|
||||
if (state.content.isEmpty) {
|
||||
return const SizedBox();
|
||||
} else {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints.loose(
|
||||
const Size(double.infinity, 100),
|
||||
),
|
||||
child: FlowyText.regular(
|
||||
state.content,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
import 'package:app_flowy/plugins/board/application/card/board_url_cell_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class BoardUrlCell extends StatefulWidget {
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
|
||||
const BoardUrlCell({
|
||||
required this.cellControllerBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardUrlCell> createState() => _BoardUrlCellState();
|
||||
}
|
||||
|
||||
class _BoardUrlCellState extends State<BoardUrlCell> {
|
||||
late BoardURLCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridURLCellController;
|
||||
_cellBloc = BoardURLCellBloc(cellController: cellController);
|
||||
_cellBloc.add(const BoardURLCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardURLCellBloc, BoardURLCellState>(
|
||||
builder: (context, state) {
|
||||
if (state.content.isEmpty) {
|
||||
return const SizedBox();
|
||||
} else {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: RichText(
|
||||
textAlign: TextAlign.left,
|
||||
text: TextSpan(
|
||||
text: state.content,
|
||||
style: TextStyle(
|
||||
color: theme.main2,
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
import 'package:app_flowy/plugins/board/application/card/card_bloc.dart';
|
||||
import 'package:app_flowy/plugins/board/application/card/card_data_controller.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/row/row_action_sheet.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'card_cell_builder.dart';
|
||||
import 'card_container.dart';
|
||||
|
||||
typedef OnEndEditing = void Function(String rowId);
|
||||
|
||||
class BoardCard extends StatefulWidget {
|
||||
final String gridId;
|
||||
final bool isEditing;
|
||||
final CardDataController dataController;
|
||||
final BoardCellBuilder cellBuilder;
|
||||
final OnEndEditing onEditEditing;
|
||||
final void Function(BuildContext) openCard;
|
||||
|
||||
const BoardCard({
|
||||
required this.gridId,
|
||||
required this.isEditing,
|
||||
required this.dataController,
|
||||
required this.cellBuilder,
|
||||
required this.onEditEditing,
|
||||
required this.openCard,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardCard> createState() => _BoardCardState();
|
||||
}
|
||||
|
||||
class _BoardCardState extends State<BoardCard> {
|
||||
late BoardCardBloc _cardBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_cardBloc = BoardCardBloc(
|
||||
gridId: widget.gridId,
|
||||
dataController: widget.dataController,
|
||||
);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cardBloc,
|
||||
child: BlocBuilder<BoardCardBloc, BoardCardState>(
|
||||
builder: (context, state) {
|
||||
return BoardCardContainer(
|
||||
accessoryBuilder: (context) {
|
||||
return [const _CardMoreOption()];
|
||||
},
|
||||
onTap: (context) {
|
||||
widget.openCard(context);
|
||||
},
|
||||
child: Column(
|
||||
children: _makeCells(context, state.gridCellMap),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _makeCells(BuildContext context, GridCellMap cellMap) {
|
||||
return cellMap.values.map(
|
||||
(cellId) {
|
||||
final child = widget.cellBuilder.buildCell(cellId);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
).toList();
|
||||
}
|
||||
}
|
||||
|
||||
class _CardMoreOption extends StatelessWidget with CardAccessory {
|
||||
const _CardMoreOption({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return svgWidget('home/details', color: context.read<AppTheme>().iconColor);
|
||||
}
|
||||
|
||||
@override
|
||||
void onTap(BuildContext context) {
|
||||
GridRowActionSheet(
|
||||
rowData: context.read<BoardCardBloc>().rowInfo(),
|
||||
).show(context, direction: AnchorDirection.bottomWithCenterAligned);
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'board_checkbox_cell.dart';
|
||||
import 'board_date_cell.dart';
|
||||
import 'board_number_cell.dart';
|
||||
import 'board_select_option_cell.dart';
|
||||
import 'board_text_cell.dart';
|
||||
import 'board_url_cell.dart';
|
||||
|
||||
abstract class BoardCellBuilderDelegate
|
||||
extends GridCellControllerBuilderDelegate {
|
||||
GridCellCache get cellCache;
|
||||
}
|
||||
|
||||
class BoardCellBuilder {
|
||||
final BoardCellBuilderDelegate delegate;
|
||||
|
||||
BoardCellBuilder(this.delegate);
|
||||
|
||||
Widget buildCell(GridCellIdentifier cellId) {
|
||||
final cellControllerBuilder = GridCellControllerBuilder(
|
||||
delegate: delegate,
|
||||
cellId: cellId,
|
||||
cellCache: delegate.cellCache,
|
||||
);
|
||||
|
||||
final key = cellId.key();
|
||||
switch (cellId.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
return BoardCheckboxCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.DateTime:
|
||||
return BoardDateCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.SingleSelect:
|
||||
return BoardSelectOptionCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.MultiSelect:
|
||||
return BoardSelectOptionCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.Number:
|
||||
return BoardNumberCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.RichText:
|
||||
return BoardTextCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.URL:
|
||||
return BoardUrlCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
}
|
||||
throw UnimplementedError;
|
||||
}
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
class BoardCardContainer extends StatelessWidget {
|
||||
final Widget child;
|
||||
final CardAccessoryBuilder? accessoryBuilder;
|
||||
final void Function(BuildContext) onTap;
|
||||
const BoardCardContainer({
|
||||
required this.child,
|
||||
required this.onTap,
|
||||
this.accessoryBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider(
|
||||
create: (_) => _CardContainerNotifier(),
|
||||
child: Consumer<_CardContainerNotifier>(
|
||||
builder: (context, notifier, _) {
|
||||
Widget container = Center(child: child);
|
||||
if (accessoryBuilder != null) {
|
||||
final accessories = accessoryBuilder!(context);
|
||||
if (accessories.isNotEmpty) {
|
||||
container = _CardEnterRegion(
|
||||
child: container,
|
||||
accessories: accessories,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => onTap(context),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(minHeight: 30),
|
||||
child: container,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class CardAccessory implements Widget {
|
||||
void onTap(BuildContext context);
|
||||
}
|
||||
|
||||
typedef CardAccessoryBuilder = List<CardAccessory> Function(
|
||||
BuildContext buildContext,
|
||||
);
|
||||
|
||||
class CardAccessoryContainer extends StatelessWidget {
|
||||
final List<CardAccessory> accessories;
|
||||
const CardAccessoryContainer({required this.accessories, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.read<AppTheme>();
|
||||
final children = accessories.map((accessory) {
|
||||
final hover = FlowyHover(
|
||||
style: HoverStyle(
|
||||
hoverColor: theme.hover,
|
||||
backgroundColor: theme.surface,
|
||||
),
|
||||
builder: (_, onHover) => Container(
|
||||
width: 26,
|
||||
height: 26,
|
||||
padding: const EdgeInsets.all(3),
|
||||
child: accessory,
|
||||
),
|
||||
);
|
||||
return GestureDetector(
|
||||
child: hover,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => accessory.onTap(context),
|
||||
);
|
||||
}).toList();
|
||||
|
||||
return Wrap(children: children, spacing: 6);
|
||||
}
|
||||
}
|
||||
|
||||
class _CardEnterRegion extends StatelessWidget {
|
||||
final Widget child;
|
||||
final List<CardAccessory> accessories;
|
||||
const _CardEnterRegion(
|
||||
{required this.child, required this.accessories, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<_CardContainerNotifier, bool>(
|
||||
selector: (context, notifier) => notifier.onEnter,
|
||||
builder: (context, onEnter, _) {
|
||||
List<Widget> children = [child];
|
||||
if (onEnter) {
|
||||
children.add(CardAccessoryContainer(accessories: accessories)
|
||||
.positioned(right: 0));
|
||||
}
|
||||
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
onEnter: (p) =>
|
||||
Provider.of<_CardContainerNotifier>(context, listen: false)
|
||||
.onEnter = true,
|
||||
onExit: (p) =>
|
||||
Provider.of<_CardContainerNotifier>(context, listen: false)
|
||||
.onEnter = false,
|
||||
child: IntrinsicHeight(
|
||||
child: Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
fit: StackFit.expand,
|
||||
children: children,
|
||||
)),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CardContainerNotifier extends ChangeNotifier {
|
||||
bool _onEnter = false;
|
||||
|
||||
_CardContainerNotifier();
|
||||
|
||||
set onEnter(bool value) {
|
||||
if (_onEnter != value) {
|
||||
_onEnter = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
bool get onEnter => _onEnter;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
library docuemnt_plugin;
|
||||
library document_plugin;
|
||||
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:app_flowy/startup/plugin/plugin.dart';
|
||||
@ -42,10 +42,10 @@ class DocumentPluginBuilder extends PluginBuilder {
|
||||
String get menuName => LocaleKeys.document_menuName.tr();
|
||||
|
||||
@override
|
||||
PluginType get pluginType => DefaultPlugin.quill.type();
|
||||
PluginType get pluginType => PluginType.editor;
|
||||
|
||||
@override
|
||||
ViewDataType get dataType => ViewDataType.TextBlock;
|
||||
ViewDataTypePB get dataType => ViewDataTypePB.Text;
|
||||
}
|
||||
|
||||
class DocumentPlugin implements Plugin {
|
||||
|
@ -11,7 +11,9 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
class DocumentBanner extends StatelessWidget {
|
||||
final void Function() onRestore;
|
||||
final void Function() onDelete;
|
||||
const DocumentBanner({required this.onRestore, required this.onDelete, Key? key}) : super(key: key);
|
||||
const DocumentBanner(
|
||||
{required this.onRestore, required this.onDelete, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -26,7 +28,8 @@ class DocumentBanner extends StatelessWidget {
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Row(
|
||||
children: [
|
||||
FlowyText.medium(LocaleKeys.deletePagePrompt_text.tr(), color: Colors.white),
|
||||
FlowyText.medium(LocaleKeys.deletePagePrompt_text.tr(),
|
||||
color: Colors.white),
|
||||
const HSpace(20),
|
||||
BaseStyledButton(
|
||||
minWidth: 160,
|
||||
@ -37,7 +40,10 @@ class DocumentBanner extends StatelessWidget {
|
||||
downColor: theme.main1,
|
||||
outlineColor: Colors.white,
|
||||
borderRadius: Corners.s8Border,
|
||||
child: FlowyText.medium(LocaleKeys.deletePagePrompt_restore.tr(), color: Colors.white, fontSize: 14),
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.deletePagePrompt_restore.tr(),
|
||||
color: Colors.white,
|
||||
fontSize: 14),
|
||||
onPressed: onRestore),
|
||||
const HSpace(20),
|
||||
BaseStyledButton(
|
||||
@ -49,8 +55,10 @@ class DocumentBanner extends StatelessWidget {
|
||||
downColor: theme.main1,
|
||||
outlineColor: Colors.white,
|
||||
borderRadius: Corners.s8Border,
|
||||
child: FlowyText.medium(LocaleKeys.deletePagePrompt_deletePermanent.tr(),
|
||||
color: Colors.white, fontSize: 14),
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.deletePagePrompt_deletePermanent.tr(),
|
||||
color: Colors.white,
|
||||
fontSize: 14),
|
||||
onPressed: onDelete),
|
||||
],
|
||||
),
|
||||
|
@ -1,19 +1,19 @@
|
||||
import 'dart:async';
|
||||
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_service.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
|
||||
|
||||
import '../field/field_cache.dart';
|
||||
import '../row/row_cache.dart';
|
||||
import 'block_listener.dart';
|
||||
|
||||
/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid for more information
|
||||
class GridBlockCache {
|
||||
final String gridId;
|
||||
final GridBlockPB block;
|
||||
final BlockPB block;
|
||||
late GridRowCache _rowCache;
|
||||
late GridBlockListener _listener;
|
||||
|
||||
List<GridRowInfo> get rows => _rowCache.rows;
|
||||
List<RowInfo> get rows => _rowCache.rows;
|
||||
GridRowCache get rowCache => _rowCache;
|
||||
|
||||
GridBlockCache({
|
||||
@ -24,7 +24,7 @@ class GridBlockCache {
|
||||
_rowCache = GridRowCache(
|
||||
gridId: gridId,
|
||||
block: block,
|
||||
notifier: GridRowCacheFieldNotifierImpl(fieldCache),
|
||||
notifier: GridRowFieldNotifierImpl(fieldCache),
|
||||
);
|
||||
|
||||
_listener = GridBlockListener(blockId: block.id);
|
||||
@ -42,7 +42,7 @@ class GridBlockCache {
|
||||
}
|
||||
|
||||
void addListener({
|
||||
required void Function(GridRowChangeReason) onChangeReason,
|
||||
required void Function(RowsChangedReason) onRowsChanged,
|
||||
bool Function()? listenWhen,
|
||||
}) {
|
||||
_rowCache.onRowsChanged((reason) {
|
||||
@ -50,7 +50,7 @@ class GridBlockCache {
|
||||
return;
|
||||
}
|
||||
|
||||
onChangeReason(reason);
|
||||
onRowsChanged(reason);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -3,19 +3,22 @@ import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'cell_service.dart';
|
||||
|
||||
abstract class GridFieldChangedNotifier {
|
||||
void onFieldChanged(void Function(GridFieldPB) callback);
|
||||
void dispose();
|
||||
abstract class IGridCellFieldNotifier {
|
||||
void onCellFieldChanged(void Function(FieldPB) callback);
|
||||
void onCellDispose();
|
||||
}
|
||||
|
||||
/// GridPB's cell helper wrapper that enables each cell will get notified when the corresponding field was changed.
|
||||
/// You Register an onFieldChanged callback to listen to the cell changes, and unregister if you don't want to listen.
|
||||
class GridCellFieldNotifier {
|
||||
/// fieldId: {objectId: callback}
|
||||
final Map<String, Map<String, List<VoidCallback>>> _fieldListenerByFieldId = {};
|
||||
final IGridCellFieldNotifier notifier;
|
||||
|
||||
GridCellFieldNotifier({required GridFieldChangedNotifier notifier}) {
|
||||
notifier.onFieldChanged(
|
||||
/// fieldId: {objectId: callback}
|
||||
final Map<String, Map<String, List<VoidCallback>>> _fieldListenerByFieldId =
|
||||
{};
|
||||
|
||||
GridCellFieldNotifier({required this.notifier}) {
|
||||
notifier.onCellFieldChanged(
|
||||
(field) {
|
||||
final map = _fieldListenerByFieldId[field.id];
|
||||
if (map != null) {
|
||||
@ -55,6 +58,7 @@ class GridCellFieldNotifier {
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
notifier.onCellDispose();
|
||||
_fieldListenerByFieldId.clear();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
@ -18,7 +16,8 @@ import 'package:app_flowy/plugins/grid/application/cell/cell_listener.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
|
||||
import 'dart:convert' show utf8;
|
||||
|
||||
import '../../field/type_option/type_option_service.dart';
|
||||
import '../../field/field_cache.dart';
|
||||
import '../../field/type_option/type_option_context.dart';
|
||||
import 'cell_field_notifier.dart';
|
||||
part 'cell_service.freezed.dart';
|
||||
part 'cell_data_loader.dart';
|
||||
@ -61,7 +60,7 @@ class GridCellIdentifier with _$GridCellIdentifier {
|
||||
const factory GridCellIdentifier({
|
||||
required String gridId,
|
||||
required String rowId,
|
||||
required GridFieldPB field,
|
||||
required FieldPB field,
|
||||
}) = _GridCellIdentifier;
|
||||
|
||||
// ignore: unused_element
|
||||
|
@ -1,29 +1,32 @@
|
||||
part of 'cell_service.dart';
|
||||
|
||||
typedef GridCellController = IGridCellController<String, String>;
|
||||
typedef GridCheckboxCellController = IGridCellController<String, String>;
|
||||
typedef GridNumberCellController = IGridCellController<String, String>;
|
||||
typedef GridSelectOptionCellController
|
||||
= IGridCellController<SelectOptionCellDataPB, String>;
|
||||
typedef GridDateCellController
|
||||
= IGridCellController<DateCellDataPB, CalendarData>;
|
||||
typedef GridURLCellController = IGridCellController<URLCellDataPB, String>;
|
||||
|
||||
abstract class GridCellControllerBuilderDelegate {
|
||||
GridCellFieldNotifier buildFieldNotifier();
|
||||
}
|
||||
|
||||
class GridCellControllerBuilder {
|
||||
final GridCellIdentifier _cellId;
|
||||
final GridCellCache _cellCache;
|
||||
final GridFieldCache _fieldCache;
|
||||
final GridCellControllerBuilderDelegate delegate;
|
||||
|
||||
GridCellControllerBuilder({
|
||||
required this.delegate,
|
||||
required GridCellIdentifier cellId,
|
||||
required GridCellCache cellCache,
|
||||
required GridFieldCache fieldCache,
|
||||
}) : _cellCache = cellCache,
|
||||
_fieldCache = fieldCache,
|
||||
_cellId = cellId;
|
||||
|
||||
IGridCellController build() {
|
||||
final cellFieldNotifier = GridCellFieldNotifier(
|
||||
notifier: _GridFieldChangedNotifierImpl(_fieldCache));
|
||||
|
||||
final cellFieldNotifier = delegate.buildFieldNotifier();
|
||||
switch (_cellId.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
final cellDataLoader = GridCellDataLoader(
|
||||
@ -57,7 +60,7 @@ class GridCellControllerBuilder {
|
||||
parser: StringCellDataParser(),
|
||||
reloadOnFieldChanged: true,
|
||||
);
|
||||
return GridCellController(
|
||||
return GridNumberCellController(
|
||||
cellId: _cellId,
|
||||
cellCache: _cellCache,
|
||||
cellDataLoader: cellDataLoader,
|
||||
@ -126,7 +129,7 @@ class IGridCellController<T, D> extends Equatable {
|
||||
final GridCellDataLoader<T> _cellDataLoader;
|
||||
final IGridCellDataPersistence<D> _cellDataPersistence;
|
||||
|
||||
late final CellListener _cellListener;
|
||||
CellListener? _cellListener;
|
||||
ValueNotifier<T?>? _cellDataNotifier;
|
||||
|
||||
bool isListening = false;
|
||||
@ -165,7 +168,7 @@ class IGridCellController<T, D> extends Equatable {
|
||||
|
||||
String get fieldId => cellId.field.id;
|
||||
|
||||
GridFieldPB get field => cellId.field;
|
||||
FieldPB get field => cellId.field;
|
||||
|
||||
FieldType get fieldType => cellId.field.fieldType;
|
||||
|
||||
@ -185,7 +188,7 @@ class IGridCellController<T, D> extends Equatable {
|
||||
/// For example:
|
||||
/// user input: 12
|
||||
/// cell display: $12
|
||||
_cellListener.start(onCellChanged: (result) {
|
||||
_cellListener?.start(onCellChanged: (result) {
|
||||
result.fold(
|
||||
(_) => _loadData(),
|
||||
(err) => Log.error(err),
|
||||
@ -239,14 +242,14 @@ class IGridCellController<T, D> extends Equatable {
|
||||
.getFieldTypeOptionData(fieldType: fieldType)
|
||||
.then((result) {
|
||||
return result.fold(
|
||||
(data) => parser.fromBuffer(data.typeOptionData),
|
||||
(data) => left(parser.fromBuffer(data.typeOptionData)),
|
||||
(err) => right(err),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Save the cell data to disk
|
||||
/// You can set [dedeplicate] to true (default is false) to reduce the save operation.
|
||||
/// You can set [deduplicate] to true (default is false) to reduce the save operation.
|
||||
/// It's useful when you call this method when user editing the [TextField].
|
||||
/// The default debounce interval is 300 milliseconds.
|
||||
void saveCellData(D data,
|
||||
@ -288,13 +291,14 @@ class IGridCellController<T, D> extends Equatable {
|
||||
return;
|
||||
}
|
||||
_isDispose = true;
|
||||
_cellListener.stop();
|
||||
_cellListener?.stop();
|
||||
_loadDataOperation?.cancel();
|
||||
_saveDataOperation?.cancel();
|
||||
_cellDataNotifier = null;
|
||||
|
||||
if (_onFieldChangedFn != null) {
|
||||
_fieldNotifier.unregister(_cacheKey, _onFieldChangedFn!);
|
||||
_fieldNotifier.dispose();
|
||||
_onFieldChangedFn = null;
|
||||
}
|
||||
}
|
||||
@ -304,14 +308,14 @@ class IGridCellController<T, D> extends Equatable {
|
||||
[_cellsCache.get(_cacheKey) ?? "", cellId.rowId + cellId.field.id];
|
||||
}
|
||||
|
||||
class _GridFieldChangedNotifierImpl extends GridFieldChangedNotifier {
|
||||
class GridCellFieldNotifierImpl extends IGridCellFieldNotifier {
|
||||
final GridFieldCache _cache;
|
||||
FieldChangesetCallback? _onChangesetFn;
|
||||
|
||||
_GridFieldChangedNotifierImpl(GridFieldCache cache) : _cache = cache;
|
||||
GridCellFieldNotifierImpl(GridFieldCache cache) : _cache = cache;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
void onCellDispose() {
|
||||
if (_onChangesetFn != null) {
|
||||
_cache.removeListener(onChangesetListener: _onChangesetFn!);
|
||||
_onChangesetFn = null;
|
||||
@ -319,8 +323,8 @@ class _GridFieldChangedNotifierImpl extends GridFieldChangedNotifier {
|
||||
}
|
||||
|
||||
@override
|
||||
void onFieldChanged(void Function(GridFieldPB p1) callback) {
|
||||
_onChangesetFn = (GridFieldChangesetPB changeset) {
|
||||
void onCellFieldChanged(void Function(FieldPB p1) callback) {
|
||||
_onChangesetFn = (FieldChangesetPB changeset) {
|
||||
for (final updatedField in changeset.updatedFields) {
|
||||
callback(updatedField);
|
||||
}
|
||||
|
@ -6,13 +6,13 @@ import 'cell_service/cell_service.dart';
|
||||
part 'checkbox_cell_bloc.freezed.dart';
|
||||
|
||||
class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
|
||||
final GridCellController cellContext;
|
||||
final GridCheckboxCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
CheckboxCellBloc({
|
||||
required CellService service,
|
||||
required this.cellContext,
|
||||
}) : super(CheckboxCellState.initial(cellContext)) {
|
||||
required this.cellController,
|
||||
}) : super(CheckboxCellState.initial(cellController)) {
|
||||
on<CheckboxCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
@ -33,16 +33,17 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellContext.removeListener(_onCellChangedFn!);
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
|
||||
cellContext.dispose();
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellContext.startListening(onCellChanged: ((cellData) {
|
||||
_onCellChangedFn =
|
||||
cellController.startListening(onCellChanged: ((cellData) {
|
||||
if (!isClosed) {
|
||||
add(CheckboxCellEvent.didReceiveCellUpdate(cellData));
|
||||
}
|
||||
@ -50,7 +51,7 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
|
||||
}
|
||||
|
||||
void _updateCellData() {
|
||||
cellContext.saveCellData(!state.isSelected ? "Yes" : "No");
|
||||
cellController.saveCellData(!state.isSelected ? "Yes" : "No");
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,7 +59,8 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
|
||||
class CheckboxCellEvent with _$CheckboxCellEvent {
|
||||
const factory CheckboxCellEvent.initial() = _Initial;
|
||||
const factory CheckboxCellEvent.select() = _Selected;
|
||||
const factory CheckboxCellEvent.didReceiveCellUpdate(String? cellData) = _DidReceiveCellUpdate;
|
||||
const factory CheckboxCellEvent.didReceiveCellUpdate(String? cellData) =
|
||||
_DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
@ -18,14 +18,14 @@ import 'package:fixnum/fixnum.dart' as $fixnum;
|
||||
part 'date_cal_bloc.freezed.dart';
|
||||
|
||||
class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
||||
final GridDateCellController cellContext;
|
||||
final GridDateCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
DateCalBloc({
|
||||
required DateTypeOption dateTypeOption,
|
||||
required DateTypeOptionPB dateTypeOptionPB,
|
||||
required DateCellDataPB? cellData,
|
||||
required this.cellContext,
|
||||
}) : super(DateCalState.initial(dateTypeOption, cellData)) {
|
||||
required this.cellController,
|
||||
}) : super(DateCalState.initial(dateTypeOptionPB, cellData)) {
|
||||
on<DateCalEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
@ -102,7 +102,7 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
||||
}
|
||||
}
|
||||
|
||||
cellContext.saveCellData(newCalData, resultCallback: (result) {
|
||||
cellController.saveCellData(newCalData, resultCallback: (result) {
|
||||
result.fold(
|
||||
() => updateCalData(Some(newCalData), none()),
|
||||
(err) {
|
||||
@ -120,7 +120,7 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
||||
|
||||
String timeFormatPrompt(FlowyError error) {
|
||||
String msg = LocaleKeys.grid_field_invalidTimeFormat.tr() + ". ";
|
||||
switch (state.dateTypeOption.timeFormat) {
|
||||
switch (state.dateTypeOptionPB.timeFormat) {
|
||||
case TimeFormat.TwelveHour:
|
||||
msg = msg + "e.g. 01: 00 AM";
|
||||
break;
|
||||
@ -136,15 +136,15 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellContext.removeListener(_onCellChangedFn!);
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellContext.dispose();
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellContext.startListening(
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cell) {
|
||||
if (!isClosed) {
|
||||
add(DateCalEvent.didReceiveCellUpdate(cell));
|
||||
@ -159,8 +159,8 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
||||
TimeFormat? timeFormat,
|
||||
bool? includeTime,
|
||||
}) async {
|
||||
state.dateTypeOption.freeze();
|
||||
final newDateTypeOption = state.dateTypeOption.rebuild((typeOption) {
|
||||
state.dateTypeOptionPB.freeze();
|
||||
final newDateTypeOption = state.dateTypeOptionPB.rebuild((typeOption) {
|
||||
if (dateFormat != null) {
|
||||
typeOption.dateFormat = dateFormat;
|
||||
}
|
||||
@ -175,14 +175,14 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
||||
});
|
||||
|
||||
final result = await FieldService.updateFieldTypeOption(
|
||||
gridId: cellContext.gridId,
|
||||
fieldId: cellContext.field.id,
|
||||
gridId: cellController.gridId,
|
||||
fieldId: cellController.field.id,
|
||||
typeOptionData: newDateTypeOption.writeToBuffer(),
|
||||
);
|
||||
|
||||
result.fold(
|
||||
(l) => emit(state.copyWith(
|
||||
dateTypeOption: newDateTypeOption,
|
||||
dateTypeOptionPB: newDateTypeOption,
|
||||
timeHintText: _timeHintText(newDateTypeOption))),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
@ -210,7 +210,7 @@ class DateCalEvent with _$DateCalEvent {
|
||||
@freezed
|
||||
class DateCalState with _$DateCalState {
|
||||
const factory DateCalState({
|
||||
required DateTypeOption dateTypeOption,
|
||||
required DateTypeOptionPB dateTypeOptionPB,
|
||||
required CalendarFormat format,
|
||||
required DateTime focusedDay,
|
||||
required Option<String> timeFormatError,
|
||||
@ -220,24 +220,24 @@ class DateCalState with _$DateCalState {
|
||||
}) = _DateCalState;
|
||||
|
||||
factory DateCalState.initial(
|
||||
DateTypeOption dateTypeOption,
|
||||
DateTypeOptionPB dateTypeOptionPB,
|
||||
DateCellDataPB? cellData,
|
||||
) {
|
||||
Option<CalendarData> calData = calDataFromCellData(cellData);
|
||||
final time = calData.foldRight("", (dateData, previous) => dateData.time);
|
||||
return DateCalState(
|
||||
dateTypeOption: dateTypeOption,
|
||||
dateTypeOptionPB: dateTypeOptionPB,
|
||||
format: CalendarFormat.month,
|
||||
focusedDay: DateTime.now(),
|
||||
time: time,
|
||||
calData: calData,
|
||||
timeFormatError: none(),
|
||||
timeHintText: _timeHintText(dateTypeOption),
|
||||
timeHintText: _timeHintText(dateTypeOptionPB),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _timeHintText(DateTypeOption typeOption) {
|
||||
String _timeHintText(DateTypeOptionPB typeOption) {
|
||||
switch (typeOption.timeFormat) {
|
||||
case TimeFormat.TwelveHour:
|
||||
return LocaleKeys.document_date_timeHintTextInTwelveHour.tr();
|
||||
|
@ -7,18 +7,21 @@ import 'cell_service/cell_service.dart';
|
||||
part 'date_cell_bloc.freezed.dart';
|
||||
|
||||
class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
|
||||
final GridDateCellController cellContext;
|
||||
final GridDateCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
DateCellBloc({required this.cellContext}) : super(DateCellState.initial(cellContext)) {
|
||||
DateCellBloc({required this.cellController})
|
||||
: super(DateCellState.initial(cellController)) {
|
||||
on<DateCellEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
initial: () => _startListening(),
|
||||
didReceiveCellUpdate: (DateCellDataPB? cellData) {
|
||||
emit(state.copyWith(data: cellData, dateStr: _dateStrFromCellData(cellData)));
|
||||
emit(state.copyWith(
|
||||
data: cellData, dateStr: _dateStrFromCellData(cellData)));
|
||||
},
|
||||
didReceiveFieldUpdate: (GridFieldPB value) => emit(state.copyWith(field: value)),
|
||||
didReceiveFieldUpdate: (FieldPB value) =>
|
||||
emit(state.copyWith(field: value)),
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -27,15 +30,15 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellContext.removeListener(_onCellChangedFn!);
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellContext.dispose();
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellContext.startListening(
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((data) {
|
||||
if (!isClosed) {
|
||||
add(DateCellEvent.didReceiveCellUpdate(data));
|
||||
@ -48,8 +51,10 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
|
||||
@freezed
|
||||
class DateCellEvent with _$DateCellEvent {
|
||||
const factory DateCellEvent.initial() = _InitialCell;
|
||||
const factory DateCellEvent.didReceiveCellUpdate(DateCellDataPB? data) = _DidReceiveCellUpdate;
|
||||
const factory DateCellEvent.didReceiveFieldUpdate(GridFieldPB field) = _DidReceiveFieldUpdate;
|
||||
const factory DateCellEvent.didReceiveCellUpdate(DateCellDataPB? data) =
|
||||
_DidReceiveCellUpdate;
|
||||
const factory DateCellEvent.didReceiveFieldUpdate(FieldPB field) =
|
||||
_DidReceiveFieldUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
@ -57,7 +62,7 @@ class DateCellState with _$DateCellState {
|
||||
const factory DateCellState({
|
||||
required DateCellDataPB? data,
|
||||
required String dateStr,
|
||||
required GridFieldPB field,
|
||||
required FieldPB field,
|
||||
}) = _DateCellState;
|
||||
|
||||
factory DateCellState.initial(GridDateCellController context) {
|
||||
|
@ -8,12 +8,12 @@ import 'cell_service/cell_service.dart';
|
||||
part 'number_cell_bloc.freezed.dart';
|
||||
|
||||
class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
|
||||
final GridCellController cellContext;
|
||||
final GridNumberCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
NumberCellBloc({
|
||||
required this.cellContext,
|
||||
}) : super(NumberCellState.initial(cellContext)) {
|
||||
required this.cellController,
|
||||
}) : super(NumberCellState.initial(cellController)) {
|
||||
on<NumberCellEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
@ -24,11 +24,13 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
|
||||
emit(state.copyWith(content: content));
|
||||
},
|
||||
updateCell: (text) {
|
||||
cellContext.saveCellData(text, resultCallback: (result) {
|
||||
cellController.saveCellData(text, resultCallback: (result) {
|
||||
result.fold(
|
||||
() => null,
|
||||
(err) {
|
||||
if (!isClosed) add(NumberCellEvent.didReceiveCellUpdate(right(err)));
|
||||
if (!isClosed) {
|
||||
add(NumberCellEvent.didReceiveCellUpdate(right(err)));
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
@ -41,15 +43,15 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellContext.removeListener(_onCellChangedFn!);
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellContext.dispose();
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellContext.startListening(
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cellContent) {
|
||||
if (!isClosed) {
|
||||
add(NumberCellEvent.didReceiveCellUpdate(left(cellContent ?? "")));
|
||||
@ -63,7 +65,8 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
|
||||
class NumberCellEvent with _$NumberCellEvent {
|
||||
const factory NumberCellEvent.initial() = _Initial;
|
||||
const factory NumberCellEvent.updateCell(String text) = _UpdateCell;
|
||||
const factory NumberCellEvent.didReceiveCellUpdate(Either<String, FlowyError> cellContent) = _DidReceiveCellUpdate;
|
||||
const factory NumberCellEvent.didReceiveCellUpdate(
|
||||
Either<String, FlowyError> cellContent) = _DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
@ -8,12 +8,12 @@ part 'select_option_cell_bloc.freezed.dart';
|
||||
|
||||
class SelectOptionCellBloc
|
||||
extends Bloc<SelectOptionCellEvent, SelectOptionCellState> {
|
||||
final GridSelectOptionCellController cellContext;
|
||||
final GridSelectOptionCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
SelectOptionCellBloc({
|
||||
required this.cellContext,
|
||||
}) : super(SelectOptionCellState.initial(cellContext)) {
|
||||
required this.cellController,
|
||||
}) : super(SelectOptionCellState.initial(cellController)) {
|
||||
on<SelectOptionCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
@ -33,15 +33,15 @@ class SelectOptionCellBloc
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellContext.removeListener(_onCellChangedFn!);
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellContext.dispose();
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellContext.startListening(
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((selectOptionContext) {
|
||||
if (!isClosed) {
|
||||
add(SelectOptionCellEvent.didReceiveOptions(
|
||||
|
@ -111,7 +111,7 @@ class SelectOptionCellEditorBloc
|
||||
void _loadOptions() {
|
||||
_delayOperation?.cancel();
|
||||
_delayOperation = Timer(const Duration(milliseconds: 10), () {
|
||||
_selectOptionService.getOpitonContext().then((result) {
|
||||
_selectOptionService.getOptionContext().then((result) {
|
||||
if (isClosed) {
|
||||
return;
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ class SelectOptionService {
|
||||
String get rowId => cellId.rowId;
|
||||
|
||||
Future<Either<Unit, FlowyError>> create({required String name}) {
|
||||
return TypeOptionService(gridId: gridId, fieldId: fieldId)
|
||||
return TypeOptionFFIService(gridId: gridId, fieldId: fieldId)
|
||||
.newOption(name: name)
|
||||
.then(
|
||||
(result) {
|
||||
@ -55,7 +55,7 @@ class SelectOptionService {
|
||||
return GridEventUpdateSelectOption(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<SelectOptionCellDataPB, FlowyError>> getOpitonContext() {
|
||||
Future<Either<SelectOptionCellDataPB, FlowyError>> getOptionContext() {
|
||||
final payload = GridCellIdPB.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId
|
||||
|
@ -6,11 +6,11 @@ import 'cell_service/cell_service.dart';
|
||||
part 'text_cell_bloc.freezed.dart';
|
||||
|
||||
class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
|
||||
final GridCellController cellContext;
|
||||
final GridCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
TextCellBloc({
|
||||
required this.cellContext,
|
||||
}) : super(TextCellState.initial(cellContext)) {
|
||||
required this.cellController,
|
||||
}) : super(TextCellState.initial(cellController)) {
|
||||
on<TextCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
@ -18,7 +18,7 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
|
||||
_startListening();
|
||||
},
|
||||
updateText: (text) {
|
||||
cellContext.saveCellData(text);
|
||||
cellController.saveCellData(text);
|
||||
emit(state.copyWith(content: text));
|
||||
},
|
||||
didReceiveCellUpdate: (content) {
|
||||
@ -32,15 +32,15 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellContext.removeListener(_onCellChangedFn!);
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellContext.dispose();
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellContext.startListening(
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cellContent) {
|
||||
if (!isClosed) {
|
||||
add(TextCellEvent.didReceiveCellUpdate(cellContent ?? ""));
|
||||
@ -53,7 +53,8 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
|
||||
@freezed
|
||||
class TextCellEvent with _$TextCellEvent {
|
||||
const factory TextCellEvent.initial() = _InitialCell;
|
||||
const factory TextCellEvent.didReceiveCellUpdate(String cellContent) = _DidReceiveCellUpdate;
|
||||
const factory TextCellEvent.didReceiveCellUpdate(String cellContent) =
|
||||
_DidReceiveCellUpdate;
|
||||
const factory TextCellEvent.updateText(String text) = _UpdateText;
|
||||
}
|
||||
|
||||
|
@ -7,11 +7,11 @@ import 'cell_service/cell_service.dart';
|
||||
part 'url_cell_bloc.freezed.dart';
|
||||
|
||||
class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
|
||||
final GridURLCellController cellContext;
|
||||
final GridURLCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
URLCellBloc({
|
||||
required this.cellContext,
|
||||
}) : super(URLCellState.initial(cellContext)) {
|
||||
required this.cellController,
|
||||
}) : super(URLCellState.initial(cellController)) {
|
||||
on<URLCellEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
@ -25,7 +25,7 @@ class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
|
||||
));
|
||||
},
|
||||
updateURL: (String url) {
|
||||
cellContext.saveCellData(url, deduplicate: true);
|
||||
cellController.saveCellData(url, deduplicate: true);
|
||||
},
|
||||
);
|
||||
},
|
||||
@ -35,15 +35,15 @@ class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellContext.removeListener(_onCellChangedFn!);
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellContext.dispose();
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellContext.startListening(
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cellData) {
|
||||
if (!isClosed) {
|
||||
add(URLCellEvent.didReceiveCellUpdate(cellData));
|
||||
@ -57,7 +57,8 @@ class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
|
||||
class URLCellEvent with _$URLCellEvent {
|
||||
const factory URLCellEvent.initial() = _InitialCell;
|
||||
const factory URLCellEvent.updateURL(String url) = _UpdateURL;
|
||||
const factory URLCellEvent.didReceiveCellUpdate(URLCellDataPB? cell) = _DidReceiveCellUpdate;
|
||||
const factory URLCellEvent.didReceiveCellUpdate(URLCellDataPB? cell) =
|
||||
_DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
@ -7,11 +7,11 @@ import 'cell_service/cell_service.dart';
|
||||
part 'url_cell_editor_bloc.freezed.dart';
|
||||
|
||||
class URLCellEditorBloc extends Bloc<URLCellEditorEvent, URLCellEditorState> {
|
||||
final GridURLCellController cellContext;
|
||||
final GridURLCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
URLCellEditorBloc({
|
||||
required this.cellContext,
|
||||
}) : super(URLCellEditorState.initial(cellContext)) {
|
||||
required this.cellController,
|
||||
}) : super(URLCellEditorState.initial(cellController)) {
|
||||
on<URLCellEditorEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
@ -19,7 +19,7 @@ class URLCellEditorBloc extends Bloc<URLCellEditorEvent, URLCellEditorState> {
|
||||
_startListening();
|
||||
},
|
||||
updateText: (text) {
|
||||
cellContext.saveCellData(text, deduplicate: true);
|
||||
cellController.saveCellData(text, deduplicate: true);
|
||||
emit(state.copyWith(content: text));
|
||||
},
|
||||
didReceiveCellUpdate: (cellData) {
|
||||
@ -33,15 +33,15 @@ class URLCellEditorBloc extends Bloc<URLCellEditorEvent, URLCellEditorState> {
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellContext.removeListener(_onCellChangedFn!);
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellContext.dispose();
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellContext.startListening(
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cellData) {
|
||||
if (!isClosed) {
|
||||
add(URLCellEditorEvent.didReceiveCellUpdate(cellData));
|
||||
@ -54,7 +54,8 @@ class URLCellEditorBloc extends Bloc<URLCellEditorEvent, URLCellEditorState> {
|
||||
@freezed
|
||||
class URLCellEditorEvent with _$URLCellEditorEvent {
|
||||
const factory URLCellEditorEvent.initial() = _InitialCell;
|
||||
const factory URLCellEditorEvent.didReceiveCellUpdate(URLCellDataPB? cell) = _DidReceiveCellUpdate;
|
||||
const factory URLCellEditorEvent.didReceiveCellUpdate(URLCellDataPB? cell) =
|
||||
_DidReceiveCellUpdate;
|
||||
const factory URLCellEditorEvent.updateText(String text) = _UpdateText;
|
||||
}
|
||||
|
||||
|
@ -7,11 +7,13 @@ import 'field_service.dart';
|
||||
|
||||
part 'field_action_sheet_bloc.freezed.dart';
|
||||
|
||||
class FieldActionSheetBloc extends Bloc<FieldActionSheetEvent, FieldActionSheetState> {
|
||||
class FieldActionSheetBloc
|
||||
extends Bloc<FieldActionSheetEvent, FieldActionSheetState> {
|
||||
final FieldService fieldService;
|
||||
|
||||
FieldActionSheetBloc({required GridFieldPB field, required this.fieldService})
|
||||
: super(FieldActionSheetState.initial(FieldTypeOptionDataPB.create()..field_2 = field)) {
|
||||
FieldActionSheetBloc({required FieldPB field, required this.fieldService})
|
||||
: super(FieldActionSheetState.initial(
|
||||
FieldTypeOptionDataPB.create()..field_2 = field)) {
|
||||
on<FieldActionSheetEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
@ -57,7 +59,8 @@ class FieldActionSheetBloc extends Bloc<FieldActionSheetEvent, FieldActionSheetS
|
||||
|
||||
@freezed
|
||||
class FieldActionSheetEvent with _$FieldActionSheetEvent {
|
||||
const factory FieldActionSheetEvent.updateFieldName(String name) = _UpdateFieldName;
|
||||
const factory FieldActionSheetEvent.updateFieldName(String name) =
|
||||
_UpdateFieldName;
|
||||
const factory FieldActionSheetEvent.hideField() = _HideField;
|
||||
const factory FieldActionSheetEvent.duplicateField() = _DuplicateField;
|
||||
const factory FieldActionSheetEvent.deleteField() = _DeleteField;
|
||||
@ -72,7 +75,8 @@ class FieldActionSheetState with _$FieldActionSheetState {
|
||||
required String fieldName,
|
||||
}) = _FieldActionSheetState;
|
||||
|
||||
factory FieldActionSheetState.initial(FieldTypeOptionDataPB data) => FieldActionSheetState(
|
||||
factory FieldActionSheetState.initial(FieldTypeOptionDataPB data) =>
|
||||
FieldActionSheetState(
|
||||
fieldTypeOptionData: data,
|
||||
errorText: '',
|
||||
fieldName: data.field_2.name,
|
||||
|
@ -0,0 +1,192 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:app_flowy/plugins/grid/application/field/grid_listener.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../row/row_cache.dart';
|
||||
|
||||
class FieldsNotifier extends ChangeNotifier {
|
||||
List<FieldPB> _fields = [];
|
||||
|
||||
set fields(List<FieldPB> fields) {
|
||||
_fields = fields;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
List<FieldPB> get fields => _fields;
|
||||
}
|
||||
|
||||
typedef FieldChangesetCallback = void Function(FieldChangesetPB);
|
||||
typedef FieldsCallback = void Function(List<FieldPB>);
|
||||
|
||||
class GridFieldCache {
|
||||
final String gridId;
|
||||
final GridFieldsListener _fieldListener;
|
||||
FieldsNotifier? _fieldNotifier = FieldsNotifier();
|
||||
final Map<FieldsCallback, VoidCallback> _fieldsCallbackMap = {};
|
||||
final Map<FieldChangesetCallback, FieldChangesetCallback>
|
||||
_changesetCallbackMap = {};
|
||||
|
||||
GridFieldCache({required this.gridId})
|
||||
: _fieldListener = GridFieldsListener(gridId: gridId) {
|
||||
_fieldListener.start(onFieldsChanged: (result) {
|
||||
result.fold(
|
||||
(changeset) {
|
||||
_deleteFields(changeset.deletedFields);
|
||||
_insertFields(changeset.insertedFields);
|
||||
_updateFields(changeset.updatedFields);
|
||||
for (final listener in _changesetCallbackMap.values) {
|
||||
listener(changeset);
|
||||
}
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
await _fieldListener.stop();
|
||||
_fieldNotifier?.dispose();
|
||||
_fieldNotifier = null;
|
||||
}
|
||||
|
||||
UnmodifiableListView<FieldPB> get unmodifiableFields =>
|
||||
UnmodifiableListView(_fieldNotifier?.fields ?? []);
|
||||
|
||||
List<FieldPB> get fields => [..._fieldNotifier?.fields ?? []];
|
||||
|
||||
set fields(List<FieldPB> fields) {
|
||||
_fieldNotifier?.fields = [...fields];
|
||||
}
|
||||
|
||||
void addListener({
|
||||
FieldsCallback? onFields,
|
||||
FieldChangesetCallback? onChangeset,
|
||||
bool Function()? listenWhen,
|
||||
}) {
|
||||
if (onChangeset != null) {
|
||||
fn(c) {
|
||||
if (listenWhen != null && listenWhen() == false) {
|
||||
return;
|
||||
}
|
||||
onChangeset(c);
|
||||
}
|
||||
|
||||
_changesetCallbackMap[onChangeset] = fn;
|
||||
}
|
||||
|
||||
if (onFields != null) {
|
||||
fn() {
|
||||
if (listenWhen != null && listenWhen() == false) {
|
||||
return;
|
||||
}
|
||||
onFields(fields);
|
||||
}
|
||||
|
||||
_fieldsCallbackMap[onFields] = fn;
|
||||
_fieldNotifier?.addListener(fn);
|
||||
}
|
||||
}
|
||||
|
||||
void removeListener({
|
||||
FieldsCallback? onFieldsListener,
|
||||
FieldChangesetCallback? onChangesetListener,
|
||||
}) {
|
||||
if (onFieldsListener != null) {
|
||||
final fn = _fieldsCallbackMap.remove(onFieldsListener);
|
||||
if (fn != null) {
|
||||
_fieldNotifier?.removeListener(fn);
|
||||
}
|
||||
}
|
||||
|
||||
if (onChangesetListener != null) {
|
||||
_changesetCallbackMap.remove(onChangesetListener);
|
||||
}
|
||||
}
|
||||
|
||||
void _deleteFields(List<FieldIdPB> deletedFields) {
|
||||
if (deletedFields.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final List<FieldPB> newFields = fields;
|
||||
final Map<String, FieldIdPB> deletedFieldMap = {
|
||||
for (var fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder
|
||||
};
|
||||
|
||||
newFields.retainWhere((field) => (deletedFieldMap[field.id] == null));
|
||||
_fieldNotifier?.fields = newFields;
|
||||
}
|
||||
|
||||
void _insertFields(List<IndexFieldPB> insertedFields) {
|
||||
if (insertedFields.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final List<FieldPB> newFields = fields;
|
||||
for (final indexField in insertedFields) {
|
||||
if (newFields.length > indexField.index) {
|
||||
newFields.insert(indexField.index, indexField.field_1);
|
||||
} else {
|
||||
newFields.add(indexField.field_1);
|
||||
}
|
||||
}
|
||||
_fieldNotifier?.fields = newFields;
|
||||
}
|
||||
|
||||
void _updateFields(List<FieldPB> updatedFields) {
|
||||
if (updatedFields.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final List<FieldPB> newFields = fields;
|
||||
for (final updatedField in updatedFields) {
|
||||
final index =
|
||||
newFields.indexWhere((field) => field.id == updatedField.id);
|
||||
if (index != -1) {
|
||||
newFields.removeAt(index);
|
||||
newFields.insert(index, updatedField);
|
||||
}
|
||||
}
|
||||
_fieldNotifier?.fields = newFields;
|
||||
}
|
||||
}
|
||||
|
||||
class GridRowFieldNotifierImpl extends IGridRowFieldNotifier {
|
||||
final GridFieldCache _cache;
|
||||
FieldChangesetCallback? _onChangesetFn;
|
||||
FieldsCallback? _onFieldFn;
|
||||
GridRowFieldNotifierImpl(GridFieldCache cache) : _cache = cache;
|
||||
|
||||
@override
|
||||
UnmodifiableListView<FieldPB> get fields => _cache.unmodifiableFields;
|
||||
|
||||
@override
|
||||
void onRowFieldsChanged(VoidCallback callback) {
|
||||
_onFieldFn = (_) => callback();
|
||||
_cache.addListener(onFields: _onFieldFn);
|
||||
}
|
||||
|
||||
@override
|
||||
void onRowFieldChanged(void Function(FieldPB) callback) {
|
||||
_onChangesetFn = (FieldChangesetPB changeset) {
|
||||
for (final updatedField in changeset.updatedFields) {
|
||||
callback(updatedField);
|
||||
}
|
||||
};
|
||||
|
||||
_cache.addListener(onChangeset: _onChangesetFn);
|
||||
}
|
||||
|
||||
@override
|
||||
void onRowDispose() {
|
||||
if (_onFieldFn != null) {
|
||||
_cache.removeListener(onFieldsListener: _onFieldFn!);
|
||||
_onFieldFn = null;
|
||||
}
|
||||
|
||||
if (_onChangesetFn != null) {
|
||||
_cache.removeListener(onChangesetListener: _onChangesetFn!);
|
||||
_onChangesetFn = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -63,7 +63,7 @@ class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
|
||||
@freezed
|
||||
class FieldCellEvent with _$FieldCellEvent {
|
||||
const factory FieldCellEvent.initial() = _InitialCell;
|
||||
const factory FieldCellEvent.didReceiveFieldUpdate(GridFieldPB field) =
|
||||
const factory FieldCellEvent.didReceiveFieldUpdate(FieldPB field) =
|
||||
_DidReceiveFieldUpdate;
|
||||
const factory FieldCellEvent.startUpdateWidth(double offset) =
|
||||
_StartUpdateWidth;
|
||||
@ -74,7 +74,7 @@ class FieldCellEvent with _$FieldCellEvent {
|
||||
class FieldCellState with _$FieldCellState {
|
||||
const factory FieldCellState({
|
||||
required String gridId,
|
||||
required GridFieldPB field,
|
||||
required FieldPB field,
|
||||
required double width,
|
||||
}) = _FieldCellState;
|
||||
|
||||
|
@ -1,9 +1,12 @@
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'field_service.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'type_option/type_option_context.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import 'type_option/type_option_data_controller.dart';
|
||||
|
||||
part 'field_editor_bloc.freezed.dart';
|
||||
|
||||
class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
||||
@ -13,7 +16,8 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
||||
required String gridId,
|
||||
required String fieldName,
|
||||
required IFieldTypeOptionLoader loader,
|
||||
}) : dataController = TypeOptionDataController(gridId: gridId, loader: loader),
|
||||
}) : dataController =
|
||||
TypeOptionDataController(gridId: gridId, loader: loader),
|
||||
super(FieldEditorState.initial(gridId, fieldName)) {
|
||||
on<FieldEditorEvent>(
|
||||
(event, emit) async {
|
||||
@ -24,13 +28,13 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
||||
add(FieldEditorEvent.didReceiveFieldChanged(field));
|
||||
}
|
||||
});
|
||||
await dataController.loadData();
|
||||
await dataController.loadTypeOptionData();
|
||||
},
|
||||
updateName: (name) {
|
||||
dataController.fieldName = name;
|
||||
emit(state.copyWith(name: name));
|
||||
},
|
||||
didReceiveFieldChanged: (GridFieldPB field) {
|
||||
didReceiveFieldChanged: (FieldPB field) {
|
||||
emit(state.copyWith(field: Some(field)));
|
||||
},
|
||||
);
|
||||
@ -48,7 +52,8 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
||||
class FieldEditorEvent with _$FieldEditorEvent {
|
||||
const factory FieldEditorEvent.initial() = _InitialField;
|
||||
const factory FieldEditorEvent.updateName(String name) = _UpdateName;
|
||||
const factory FieldEditorEvent.didReceiveFieldChanged(GridFieldPB field) = _DidReceiveFieldChanged;
|
||||
const factory FieldEditorEvent.didReceiveFieldChanged(FieldPB field) =
|
||||
_DidReceiveFieldChanged;
|
||||
}
|
||||
|
||||
@freezed
|
||||
@ -57,7 +62,7 @@ class FieldEditorState with _$FieldEditorState {
|
||||
required String gridId,
|
||||
required String errorText,
|
||||
required String name,
|
||||
required Option<GridFieldPB> field,
|
||||
required Option<FieldPB> field,
|
||||
}) = _FieldEditorState;
|
||||
|
||||
factory FieldEditorState.initial(
|
||||
|
@ -7,16 +7,18 @@ import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
|
||||
typedef UpdateFieldNotifiedValue = Either<GridFieldPB, FlowyError>;
|
||||
typedef UpdateFieldNotifiedValue = Either<FieldPB, FlowyError>;
|
||||
|
||||
class SingleFieldListener {
|
||||
final String fieldId;
|
||||
PublishNotifier<UpdateFieldNotifiedValue>? _updateFieldNotifier = PublishNotifier();
|
||||
PublishNotifier<UpdateFieldNotifiedValue>? _updateFieldNotifier =
|
||||
PublishNotifier();
|
||||
GridNotificationListener? _listener;
|
||||
|
||||
SingleFieldListener({required this.fieldId});
|
||||
|
||||
void start({required void Function(UpdateFieldNotifiedValue) onFieldChanged}) {
|
||||
void start(
|
||||
{required void Function(UpdateFieldNotifiedValue) onFieldChanged}) {
|
||||
_updateFieldNotifier?.addPublishListener(onFieldChanged);
|
||||
_listener = GridNotificationListener(
|
||||
objectId: fieldId,
|
||||
@ -31,7 +33,8 @@ class SingleFieldListener {
|
||||
switch (ty) {
|
||||
case GridNotification.DidUpdateField:
|
||||
result.fold(
|
||||
(payload) => _updateFieldNotifier?.value = left(GridFieldPB.fromBuffer(payload)),
|
||||
(payload) =>
|
||||
_updateFieldNotifier?.value = left(FieldPB.fromBuffer(payload)),
|
||||
(error) => _updateFieldNotifier?.value = right(error),
|
||||
);
|
||||
break;
|
||||
|
@ -1,13 +1,10 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_infra/notifier.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/field_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
part 'field_service.freezed.dart';
|
||||
|
||||
/// FieldService consists of lots of event functions. We define the events in the backend(Rust),
|
||||
@ -21,14 +18,13 @@ class FieldService {
|
||||
FieldService({required this.gridId, required this.fieldId});
|
||||
|
||||
Future<Either<Unit, FlowyError>> moveField(int fromIndex, int toIndex) {
|
||||
final payload = MoveItemPayloadPB.create()
|
||||
final payload = MoveFieldPayloadPB.create()
|
||||
..gridId = gridId
|
||||
..itemId = fieldId
|
||||
..ty = MoveItemTypePB.MoveField
|
||||
..fieldId = fieldId
|
||||
..fromIndex = fromIndex
|
||||
..toIndex = toIndex;
|
||||
|
||||
return GridEventMoveItem(payload).send();
|
||||
return GridEventMoveField(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> updateField({
|
||||
@ -73,7 +69,7 @@ class FieldService {
|
||||
// Create the field if it does not exist. Otherwise, update the field.
|
||||
static Future<Either<Unit, FlowyError>> insertField({
|
||||
required String gridId,
|
||||
required GridFieldPB field,
|
||||
required FieldPB field,
|
||||
List<int>? typeOptionData,
|
||||
String? startFieldId,
|
||||
}) {
|
||||
@ -121,7 +117,7 @@ class FieldService {
|
||||
Future<Either<FieldTypeOptionDataPB, FlowyError>> getFieldTypeOptionData({
|
||||
required FieldType fieldType,
|
||||
}) {
|
||||
final payload = GridFieldTypeOptionIdPB.create()
|
||||
final payload = FieldTypeOptionIdPB.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId
|
||||
..fieldType = fieldType;
|
||||
@ -138,158 +134,6 @@ class FieldService {
|
||||
class GridFieldCellContext with _$GridFieldCellContext {
|
||||
const factory GridFieldCellContext({
|
||||
required String gridId,
|
||||
required GridFieldPB field,
|
||||
required FieldPB field,
|
||||
}) = _GridFieldCellContext;
|
||||
}
|
||||
|
||||
abstract class IFieldTypeOptionLoader {
|
||||
String get gridId;
|
||||
Future<Either<FieldTypeOptionDataPB, FlowyError>> load();
|
||||
|
||||
Future<Either<FieldTypeOptionDataPB, FlowyError>> switchToField(String fieldId, FieldType fieldType) {
|
||||
final payload = EditFieldPayloadPB.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId
|
||||
..fieldType = fieldType;
|
||||
|
||||
return GridEventSwitchToField(payload).send();
|
||||
}
|
||||
}
|
||||
|
||||
class NewFieldTypeOptionLoader extends IFieldTypeOptionLoader {
|
||||
@override
|
||||
final String gridId;
|
||||
NewFieldTypeOptionLoader({
|
||||
required this.gridId,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<Either<FieldTypeOptionDataPB, FlowyError>> load() {
|
||||
final payload = CreateFieldPayloadPB.create()
|
||||
..gridId = gridId
|
||||
..fieldType = FieldType.RichText;
|
||||
|
||||
return GridEventCreateFieldTypeOption(payload).send();
|
||||
}
|
||||
}
|
||||
|
||||
class FieldTypeOptionLoader extends IFieldTypeOptionLoader {
|
||||
@override
|
||||
final String gridId;
|
||||
final GridFieldPB field;
|
||||
|
||||
FieldTypeOptionLoader({
|
||||
required this.gridId,
|
||||
required this.field,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<Either<FieldTypeOptionDataPB, FlowyError>> load() {
|
||||
final payload = GridFieldTypeOptionIdPB.create()
|
||||
..gridId = gridId
|
||||
..fieldId = field.id
|
||||
..fieldType = field.fieldType;
|
||||
|
||||
return GridEventGetFieldTypeOption(payload).send();
|
||||
}
|
||||
}
|
||||
|
||||
class TypeOptionDataController {
|
||||
final String gridId;
|
||||
final IFieldTypeOptionLoader _loader;
|
||||
|
||||
late FieldTypeOptionDataPB _data;
|
||||
final PublishNotifier<GridFieldPB> _fieldNotifier = PublishNotifier();
|
||||
|
||||
TypeOptionDataController({
|
||||
required this.gridId,
|
||||
required IFieldTypeOptionLoader loader,
|
||||
}) : _loader = loader;
|
||||
|
||||
Future<Either<Unit, FlowyError>> loadData() async {
|
||||
final result = await _loader.load();
|
||||
return result.fold(
|
||||
(data) {
|
||||
data.freeze();
|
||||
_data = data;
|
||||
_fieldNotifier.value = data.field_2;
|
||||
return left(unit);
|
||||
},
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return right(err);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
GridFieldPB get field => _data.field_2;
|
||||
|
||||
set field(GridFieldPB field) {
|
||||
_updateData(newField: field);
|
||||
}
|
||||
|
||||
List<int> get typeOptionData => _data.typeOptionData;
|
||||
|
||||
set fieldName(String name) {
|
||||
_updateData(newName: name);
|
||||
}
|
||||
|
||||
set typeOptionData(List<int> typeOptionData) {
|
||||
_updateData(newTypeOptionData: typeOptionData);
|
||||
}
|
||||
|
||||
void _updateData({String? newName, GridFieldPB? newField, List<int>? newTypeOptionData}) {
|
||||
_data = _data.rebuild((rebuildData) {
|
||||
if (newName != null) {
|
||||
rebuildData.field_2 = rebuildData.field_2.rebuild((rebuildField) {
|
||||
rebuildField.name = newName;
|
||||
});
|
||||
}
|
||||
|
||||
if (newField != null) {
|
||||
rebuildData.field_2 = newField;
|
||||
}
|
||||
|
||||
if (newTypeOptionData != null) {
|
||||
rebuildData.typeOptionData = newTypeOptionData;
|
||||
}
|
||||
});
|
||||
|
||||
_fieldNotifier.value = _data.field_2;
|
||||
|
||||
FieldService.insertField(
|
||||
gridId: gridId,
|
||||
field: field,
|
||||
typeOptionData: typeOptionData,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> switchToField(FieldType newFieldType) {
|
||||
return _loader.switchToField(field.id, newFieldType).then((result) {
|
||||
return result.fold(
|
||||
(fieldTypeOptionData) {
|
||||
_updateData(
|
||||
newField: fieldTypeOptionData.field_2,
|
||||
newTypeOptionData: fieldTypeOptionData.typeOptionData,
|
||||
);
|
||||
},
|
||||
(err) {
|
||||
Log.error(err);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void Function() addFieldListener(void Function(GridFieldPB) callback) {
|
||||
listener() {
|
||||
callback(field);
|
||||
}
|
||||
|
||||
_fieldNotifier.addListener(listener);
|
||||
return listener;
|
||||
}
|
||||
|
||||
void removeFieldListener(void Function() listener) {
|
||||
_fieldNotifier.removeListener(listener);
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,11 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'field_service.dart';
|
||||
|
||||
import 'type_option/type_option_data_controller.dart';
|
||||
part 'field_type_option_edit_bloc.freezed.dart';
|
||||
|
||||
class FieldTypeOptionEditBloc extends Bloc<FieldTypeOptionEditEvent, FieldTypeOptionEditState> {
|
||||
class FieldTypeOptionEditBloc
|
||||
extends Bloc<FieldTypeOptionEditEvent, FieldTypeOptionEditState> {
|
||||
final TypeOptionDataController _dataController;
|
||||
void Function()? _fieldListenFn;
|
||||
|
||||
@ -42,16 +42,19 @@ class FieldTypeOptionEditBloc extends Bloc<FieldTypeOptionEditEvent, FieldTypeOp
|
||||
@freezed
|
||||
class FieldTypeOptionEditEvent with _$FieldTypeOptionEditEvent {
|
||||
const factory FieldTypeOptionEditEvent.initial() = _Initial;
|
||||
const factory FieldTypeOptionEditEvent.didReceiveFieldUpdated(GridFieldPB field) = _DidReceiveFieldUpdated;
|
||||
const factory FieldTypeOptionEditEvent.didReceiveFieldUpdated(FieldPB field) =
|
||||
_DidReceiveFieldUpdated;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class FieldTypeOptionEditState with _$FieldTypeOptionEditState {
|
||||
const factory FieldTypeOptionEditState({
|
||||
required GridFieldPB field,
|
||||
required FieldPB field,
|
||||
}) = _FieldTypeOptionEditState;
|
||||
|
||||
factory FieldTypeOptionEditState.initial(TypeOptionDataController fieldContext) => FieldTypeOptionEditState(
|
||||
factory FieldTypeOptionEditState.initial(
|
||||
TypeOptionDataController fieldContext) =>
|
||||
FieldTypeOptionEditState(
|
||||
field: fieldContext.field,
|
||||
);
|
||||
}
|
||||
|
@ -7,15 +7,17 @@ import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
|
||||
typedef UpdateFieldNotifiedValue = Either<GridFieldChangesetPB, FlowyError>;
|
||||
typedef UpdateFieldNotifiedValue = Either<FieldChangesetPB, FlowyError>;
|
||||
|
||||
class GridFieldsListener {
|
||||
final String gridId;
|
||||
PublishNotifier<UpdateFieldNotifiedValue>? updateFieldsNotifier = PublishNotifier();
|
||||
PublishNotifier<UpdateFieldNotifiedValue>? updateFieldsNotifier =
|
||||
PublishNotifier();
|
||||
GridNotificationListener? _listener;
|
||||
GridFieldsListener({required this.gridId});
|
||||
|
||||
void start({required void Function(UpdateFieldNotifiedValue) onFieldsChanged}) {
|
||||
void start(
|
||||
{required void Function(UpdateFieldNotifiedValue) onFieldsChanged}) {
|
||||
updateFieldsNotifier?.addPublishListener(onFieldsChanged);
|
||||
_listener = GridNotificationListener(
|
||||
objectId: gridId,
|
||||
@ -27,7 +29,8 @@ class GridFieldsListener {
|
||||
switch (ty) {
|
||||
case GridNotification.DidUpdateGridField:
|
||||
result.fold(
|
||||
(payload) => updateFieldsNotifier?.value = left(GridFieldChangesetPB.fromBuffer(payload)),
|
||||
(payload) => updateFieldsNotifier?.value =
|
||||
left(FieldChangesetPB.fromBuffer(payload)),
|
||||
(error) => updateFieldsNotifier?.value = right(error),
|
||||
);
|
||||
break;
|
||||
|
@ -1,21 +1,13 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_service.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.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_context.dart';
|
||||
part 'date_bloc.freezed.dart';
|
||||
|
||||
typedef DateTypeOptionContext = TypeOptionWidgetContext<DateTypeOption>;
|
||||
|
||||
class DateTypeOptionDataParser extends TypeOptionDataParser<DateTypeOption> {
|
||||
@override
|
||||
DateTypeOption fromBuffer(List<int> buffer) {
|
||||
return DateTypeOption.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
class DateTypeOptionBloc
|
||||
extends Bloc<DateTypeOptionEvent, DateTypeOptionState> {
|
||||
DateTypeOptionBloc({required DateTypeOptionContext typeOptionContext})
|
||||
@ -40,7 +32,7 @@ class DateTypeOptionBloc
|
||||
);
|
||||
}
|
||||
|
||||
DateTypeOption _updateTypeOption({
|
||||
DateTypeOptionPB _updateTypeOption({
|
||||
DateFormat? dateFormat,
|
||||
TimeFormat? timeFormat,
|
||||
bool? includeTime,
|
||||
@ -80,9 +72,9 @@ class DateTypeOptionEvent with _$DateTypeOptionEvent {
|
||||
@freezed
|
||||
class DateTypeOptionState with _$DateTypeOptionState {
|
||||
const factory DateTypeOptionState({
|
||||
required DateTypeOption typeOption,
|
||||
required DateTypeOptionPB typeOption,
|
||||
}) = _DateTypeOptionState;
|
||||
|
||||
factory DateTypeOptionState.initial(DateTypeOption typeOption) =>
|
||||
factory DateTypeOptionState.initial(DateTypeOptionPB typeOption) =>
|
||||
DateTypeOptionState(typeOption: typeOption);
|
||||
}
|
||||
|
@ -1,25 +1,32 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/multi_select_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
|
||||
import 'dart:async';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
import 'select_option_type_option_bloc.dart';
|
||||
import 'type_option_context.dart';
|
||||
import 'type_option_service.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
class MultiSelectTypeOptionContext
|
||||
extends TypeOptionWidgetContext<MultiSelectTypeOption>
|
||||
with SelectOptionTypeOptionAction {
|
||||
final TypeOptionService service;
|
||||
class MultiSelectAction with ISelectOptionAction {
|
||||
final String gridId;
|
||||
final String fieldId;
|
||||
final TypeOptionFFIService service;
|
||||
final MultiSelectTypeOptionContext typeOptionContext;
|
||||
|
||||
MultiSelectTypeOptionContext({
|
||||
required MultiSelectTypeOptionWidgetDataParser dataBuilder,
|
||||
required TypeOptionDataController dataController,
|
||||
}) : service = TypeOptionService(
|
||||
gridId: dataController.gridId,
|
||||
fieldId: dataController.field.id,
|
||||
),
|
||||
super(dataParser: dataBuilder, dataController: dataController);
|
||||
MultiSelectAction({
|
||||
required this.gridId,
|
||||
required this.fieldId,
|
||||
required this.typeOptionContext,
|
||||
}) : service = TypeOptionFFIService(
|
||||
gridId: gridId,
|
||||
fieldId: fieldId,
|
||||
);
|
||||
|
||||
MultiSelectTypeOptionPB get typeOption => typeOptionContext.typeOption;
|
||||
|
||||
set typeOption(MultiSelectTypeOptionPB newTypeOption) {
|
||||
typeOptionContext.typeOption = newTypeOption;
|
||||
}
|
||||
|
||||
@override
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get deleteOption {
|
||||
@ -59,7 +66,7 @@ class MultiSelectTypeOptionContext
|
||||
}
|
||||
|
||||
@override
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get udpateOption {
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get updateOption {
|
||||
return (SelectOptionPB option) {
|
||||
typeOption.freeze();
|
||||
typeOption = typeOption.rebuild((typeOption) {
|
||||
@ -73,11 +80,3 @@ class MultiSelectTypeOptionContext
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class MultiSelectTypeOptionWidgetDataParser
|
||||
extends TypeOptionDataParser<MultiSelectTypeOption> {
|
||||
@override
|
||||
MultiSelectTypeOption fromBuffer(List<int> buffer) {
|
||||
return MultiSelectTypeOption.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,13 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_service.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/format.pbenum.dart';
|
||||
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';
|
||||
import 'type_option_context.dart';
|
||||
|
||||
part 'number_bloc.freezed.dart';
|
||||
|
||||
typedef NumberTypeOptionContext = TypeOptionWidgetContext<NumberTypeOption>;
|
||||
|
||||
class NumberTypeOptionWidgetDataParser
|
||||
extends TypeOptionDataParser<NumberTypeOption> {
|
||||
@override
|
||||
NumberTypeOption fromBuffer(List<int> buffer) {
|
||||
return NumberTypeOption.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
class NumberTypeOptionBloc
|
||||
extends Bloc<NumberTypeOptionEvent, NumberTypeOptionState> {
|
||||
NumberTypeOptionBloc({required NumberTypeOptionContext typeOptionContext})
|
||||
@ -33,7 +23,7 @@ class NumberTypeOptionBloc
|
||||
);
|
||||
}
|
||||
|
||||
NumberTypeOption _updateNumberFormat(NumberFormat format) {
|
||||
NumberTypeOptionPB _updateNumberFormat(NumberFormat format) {
|
||||
state.typeOption.freeze();
|
||||
return state.typeOption.rebuild((typeOption) {
|
||||
typeOption.format = format;
|
||||
@ -55,10 +45,10 @@ class NumberTypeOptionEvent with _$NumberTypeOptionEvent {
|
||||
@freezed
|
||||
class NumberTypeOptionState with _$NumberTypeOptionState {
|
||||
const factory NumberTypeOptionState({
|
||||
required NumberTypeOption typeOption,
|
||||
required NumberTypeOptionPB typeOption,
|
||||
}) = _NumberTypeOptionState;
|
||||
|
||||
factory NumberTypeOptionState.initial(NumberTypeOption typeOption) =>
|
||||
factory NumberTypeOptionState.initial(NumberTypeOptionPB typeOption) =>
|
||||
NumberTypeOptionState(
|
||||
typeOption: typeOption,
|
||||
);
|
||||
|
@ -5,16 +5,17 @@ import 'dart:async';
|
||||
import 'package:dartz/dartz.dart';
|
||||
part 'select_option_type_option_bloc.freezed.dart';
|
||||
|
||||
abstract class SelectOptionTypeOptionAction {
|
||||
abstract class ISelectOptionAction {
|
||||
Future<List<SelectOptionPB>> Function(String) get insertOption;
|
||||
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get deleteOption;
|
||||
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get udpateOption;
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get updateOption;
|
||||
}
|
||||
|
||||
class SelectOptionTypeOptionBloc extends Bloc<SelectOptionTypeOptionEvent, SelectOptionTypeOptionState> {
|
||||
final SelectOptionTypeOptionAction typeOptionAction;
|
||||
class SelectOptionTypeOptionBloc
|
||||
extends Bloc<SelectOptionTypeOptionEvent, SelectOptionTypeOptionState> {
|
||||
final ISelectOptionAction typeOptionAction;
|
||||
|
||||
SelectOptionTypeOptionBloc({
|
||||
required List<SelectOptionPB> options,
|
||||
@ -24,7 +25,8 @@ class SelectOptionTypeOptionBloc extends Bloc<SelectOptionTypeOptionEvent, Selec
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
createOption: (optionName) async {
|
||||
final List<SelectOptionPB> options = await typeOptionAction.insertOption(optionName);
|
||||
final List<SelectOptionPB> options =
|
||||
await typeOptionAction.insertOption(optionName);
|
||||
emit(state.copyWith(options: options));
|
||||
},
|
||||
addingOption: () {
|
||||
@ -34,11 +36,13 @@ class SelectOptionTypeOptionBloc extends Bloc<SelectOptionTypeOptionEvent, Selec
|
||||
emit(state.copyWith(isEditingOption: false, newOptionName: none()));
|
||||
},
|
||||
updateOption: (option) {
|
||||
final List<SelectOptionPB> options = typeOptionAction.udpateOption(option);
|
||||
final List<SelectOptionPB> options =
|
||||
typeOptionAction.updateOption(option);
|
||||
emit(state.copyWith(options: options));
|
||||
},
|
||||
deleteOption: (option) {
|
||||
final List<SelectOptionPB> options = typeOptionAction.deleteOption(option);
|
||||
final List<SelectOptionPB> options =
|
||||
typeOptionAction.deleteOption(option);
|
||||
emit(state.copyWith(options: options));
|
||||
},
|
||||
);
|
||||
@ -54,11 +58,15 @@ class SelectOptionTypeOptionBloc extends Bloc<SelectOptionTypeOptionEvent, Selec
|
||||
|
||||
@freezed
|
||||
class SelectOptionTypeOptionEvent with _$SelectOptionTypeOptionEvent {
|
||||
const factory SelectOptionTypeOptionEvent.createOption(String optionName) = _CreateOption;
|
||||
const factory SelectOptionTypeOptionEvent.createOption(String optionName) =
|
||||
_CreateOption;
|
||||
const factory SelectOptionTypeOptionEvent.addingOption() = _AddingOption;
|
||||
const factory SelectOptionTypeOptionEvent.endAddingOption() = _EndAddingOption;
|
||||
const factory SelectOptionTypeOptionEvent.updateOption(SelectOptionPB option) = _UpdateOption;
|
||||
const factory SelectOptionTypeOptionEvent.deleteOption(SelectOptionPB option) = _DeleteOption;
|
||||
const factory SelectOptionTypeOptionEvent.endAddingOption() =
|
||||
_EndAddingOption;
|
||||
const factory SelectOptionTypeOptionEvent.updateOption(
|
||||
SelectOptionPB option) = _UpdateOption;
|
||||
const factory SelectOptionTypeOptionEvent.deleteOption(
|
||||
SelectOptionPB option) = _DeleteOption;
|
||||
}
|
||||
|
||||
@freezed
|
||||
@ -67,9 +75,10 @@ class SelectOptionTypeOptionState with _$SelectOptionTypeOptionState {
|
||||
required List<SelectOptionPB> options,
|
||||
required bool isEditingOption,
|
||||
required Option<String> newOptionName,
|
||||
}) = _SelectOptionTyepOptionState;
|
||||
}) = _SelectOptionTypeOptionState;
|
||||
|
||||
factory SelectOptionTypeOptionState.initial(List<SelectOptionPB> options) => SelectOptionTypeOptionState(
|
||||
factory SelectOptionTypeOptionState.initial(List<SelectOptionPB> options) =>
|
||||
SelectOptionTypeOptionState(
|
||||
options: options,
|
||||
isEditingOption: false,
|
||||
newOptionName: none(),
|
||||
|
@ -1,25 +1,29 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/single_select_type_option.pb.dart';
|
||||
import 'dart:async';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
import 'select_option_type_option_bloc.dart';
|
||||
import 'type_option_context.dart';
|
||||
import 'type_option_service.dart';
|
||||
|
||||
class SingleSelectTypeOptionContext
|
||||
extends TypeOptionWidgetContext<SingleSelectTypeOptionPB>
|
||||
with SelectOptionTypeOptionAction {
|
||||
final TypeOptionService service;
|
||||
class SingleSelectAction with ISelectOptionAction {
|
||||
final String gridId;
|
||||
final String fieldId;
|
||||
final SingleSelectTypeOptionContext typeOptionContext;
|
||||
final TypeOptionFFIService service;
|
||||
|
||||
SingleSelectTypeOptionContext({
|
||||
required SingleSelectTypeOptionWidgetDataParser dataBuilder,
|
||||
required TypeOptionDataController fieldContext,
|
||||
}) : service = TypeOptionService(
|
||||
gridId: fieldContext.gridId,
|
||||
fieldId: fieldContext.field.id,
|
||||
),
|
||||
super(dataParser: dataBuilder, dataController: fieldContext);
|
||||
SingleSelectAction({
|
||||
required this.gridId,
|
||||
required this.fieldId,
|
||||
required this.typeOptionContext,
|
||||
}) : service = TypeOptionFFIService(gridId: gridId, fieldId: fieldId);
|
||||
|
||||
SingleSelectTypeOptionPB get typeOption => typeOptionContext.typeOption;
|
||||
|
||||
set typeOption(SingleSelectTypeOptionPB newTypeOption) {
|
||||
typeOptionContext.typeOption = newTypeOption;
|
||||
}
|
||||
|
||||
@override
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get deleteOption {
|
||||
@ -59,7 +63,7 @@ class SingleSelectTypeOptionContext
|
||||
}
|
||||
|
||||
@override
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get udpateOption {
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get updateOption {
|
||||
return (SelectOptionPB option) {
|
||||
typeOption.freeze();
|
||||
typeOption = typeOption.rebuild((typeOption) {
|
||||
@ -73,11 +77,3 @@ class SingleSelectTypeOptionContext
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class SingleSelectTypeOptionWidgetDataParser
|
||||
extends TypeOptionDataParser<SingleSelectTypeOptionPB> {
|
||||
@override
|
||||
SingleSelectTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return SingleSelectTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,195 @@
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/multi_select_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/single_select_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/text_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
import 'type_option_data_controller.dart';
|
||||
|
||||
abstract class TypeOptionDataParser<T> {
|
||||
T fromBuffer(List<int> buffer);
|
||||
}
|
||||
|
||||
// Number
|
||||
typedef NumberTypeOptionContext = TypeOptionContext<NumberTypeOptionPB>;
|
||||
|
||||
class NumberTypeOptionWidgetDataParser
|
||||
extends TypeOptionDataParser<NumberTypeOptionPB> {
|
||||
@override
|
||||
NumberTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return NumberTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// RichText
|
||||
typedef RichTextTypeOptionContext = TypeOptionContext<RichTextTypeOptionPB>;
|
||||
|
||||
class RichTextTypeOptionWidgetDataParser
|
||||
extends TypeOptionDataParser<RichTextTypeOptionPB> {
|
||||
@override
|
||||
RichTextTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return RichTextTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// Checkbox
|
||||
typedef CheckboxTypeOptionContext = TypeOptionContext<CheckboxTypeOptionPB>;
|
||||
|
||||
class CheckboxTypeOptionWidgetDataParser
|
||||
extends TypeOptionDataParser<CheckboxTypeOptionPB> {
|
||||
@override
|
||||
CheckboxTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return CheckboxTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// URL
|
||||
typedef URLTypeOptionContext = TypeOptionContext<URLTypeOptionPB>;
|
||||
|
||||
class URLTypeOptionWidgetDataParser
|
||||
extends TypeOptionDataParser<URLTypeOptionPB> {
|
||||
@override
|
||||
URLTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return URLTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// Date
|
||||
typedef DateTypeOptionContext = TypeOptionContext<DateTypeOptionPB>;
|
||||
|
||||
class DateTypeOptionDataParser extends TypeOptionDataParser<DateTypeOptionPB> {
|
||||
@override
|
||||
DateTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return DateTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// SingleSelect
|
||||
typedef SingleSelectTypeOptionContext
|
||||
= TypeOptionContext<SingleSelectTypeOptionPB>;
|
||||
|
||||
class SingleSelectTypeOptionWidgetDataParser
|
||||
extends TypeOptionDataParser<SingleSelectTypeOptionPB> {
|
||||
@override
|
||||
SingleSelectTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return SingleSelectTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-select
|
||||
typedef MultiSelectTypeOptionContext
|
||||
= TypeOptionContext<MultiSelectTypeOptionPB>;
|
||||
|
||||
class MultiSelectTypeOptionWidgetDataParser
|
||||
extends TypeOptionDataParser<MultiSelectTypeOptionPB> {
|
||||
@override
|
||||
MultiSelectTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return MultiSelectTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
class TypeOptionContext<T extends GeneratedMessage> {
|
||||
T? _typeOptionObject;
|
||||
final TypeOptionDataParser<T> dataParser;
|
||||
final TypeOptionDataController _dataController;
|
||||
|
||||
TypeOptionContext({
|
||||
required this.dataParser,
|
||||
required TypeOptionDataController dataController,
|
||||
}) : _dataController = dataController;
|
||||
|
||||
String get gridId => _dataController.gridId;
|
||||
|
||||
String get fieldId => _dataController.field.id;
|
||||
|
||||
Future<void> loadTypeOptionData({
|
||||
required void Function(T) onCompleted,
|
||||
required void Function(FlowyError) onError,
|
||||
}) async {
|
||||
await _dataController.loadTypeOptionData().then((result) {
|
||||
result.fold((l) => null, (err) => onError(err));
|
||||
});
|
||||
|
||||
onCompleted(typeOption);
|
||||
}
|
||||
|
||||
T get typeOption {
|
||||
if (_typeOptionObject != null) {
|
||||
return _typeOptionObject!;
|
||||
}
|
||||
|
||||
final T object = _dataController.getTypeOption(dataParser);
|
||||
_typeOptionObject = object;
|
||||
return object;
|
||||
}
|
||||
|
||||
set typeOption(T typeOption) {
|
||||
_dataController.typeOptionData = typeOption.writeToBuffer();
|
||||
_typeOptionObject = typeOption;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TypeOptionFieldDelegate {
|
||||
void onFieldChanged(void Function(String) callback);
|
||||
void dispose();
|
||||
}
|
||||
|
||||
abstract class IFieldTypeOptionLoader {
|
||||
String get gridId;
|
||||
Future<Either<FieldTypeOptionDataPB, FlowyError>> load();
|
||||
|
||||
Future<Either<FieldTypeOptionDataPB, FlowyError>> switchToField(
|
||||
String fieldId, FieldType fieldType) {
|
||||
final payload = EditFieldPayloadPB.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId
|
||||
..fieldType = fieldType;
|
||||
|
||||
return GridEventSwitchToField(payload).send();
|
||||
}
|
||||
}
|
||||
|
||||
class NewFieldTypeOptionLoader extends IFieldTypeOptionLoader {
|
||||
@override
|
||||
final String gridId;
|
||||
NewFieldTypeOptionLoader({
|
||||
required this.gridId,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<Either<FieldTypeOptionDataPB, FlowyError>> load() {
|
||||
final payload = CreateFieldPayloadPB.create()
|
||||
..gridId = gridId
|
||||
..fieldType = FieldType.RichText;
|
||||
|
||||
return GridEventCreateFieldTypeOption(payload).send();
|
||||
}
|
||||
}
|
||||
|
||||
class FieldTypeOptionLoader extends IFieldTypeOptionLoader {
|
||||
@override
|
||||
final String gridId;
|
||||
final FieldPB field;
|
||||
|
||||
FieldTypeOptionLoader({
|
||||
required this.gridId,
|
||||
required this.field,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<Either<FieldTypeOptionDataPB, FlowyError>> load() {
|
||||
final payload = FieldTypeOptionIdPB.create()
|
||||
..gridId = gridId
|
||||
..fieldId = field.id
|
||||
..fieldType = field.fieldType;
|
||||
|
||||
return GridEventGetFieldTypeOption(payload).send();
|
||||
}
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
import 'package:flowy_infra/notifier.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
|
||||
import 'type_option_context.dart';
|
||||
|
||||
class TypeOptionDataController {
|
||||
final String gridId;
|
||||
final IFieldTypeOptionLoader loader;
|
||||
late FieldTypeOptionDataPB _data;
|
||||
final PublishNotifier<FieldPB> _fieldNotifier = PublishNotifier();
|
||||
|
||||
TypeOptionDataController({
|
||||
required this.gridId,
|
||||
required this.loader,
|
||||
FieldPB? field,
|
||||
}) {
|
||||
if (field != null) {
|
||||
_data = FieldTypeOptionDataPB.create()
|
||||
..gridId = gridId
|
||||
..field_2 = field;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> loadTypeOptionData() async {
|
||||
final result = await loader.load();
|
||||
return result.fold(
|
||||
(data) {
|
||||
data.freeze();
|
||||
_data = data;
|
||||
_fieldNotifier.value = data.field_2;
|
||||
return left(unit);
|
||||
},
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return right(err);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
FieldPB get field {
|
||||
return _data.field_2;
|
||||
}
|
||||
|
||||
set field(FieldPB field) {
|
||||
_updateData(newField: field);
|
||||
}
|
||||
|
||||
T getTypeOption<T>(TypeOptionDataParser<T> parser) {
|
||||
return parser.fromBuffer(_data.typeOptionData);
|
||||
}
|
||||
|
||||
set fieldName(String name) {
|
||||
_updateData(newName: name);
|
||||
}
|
||||
|
||||
set typeOptionData(List<int> typeOptionData) {
|
||||
_updateData(newTypeOptionData: typeOptionData);
|
||||
}
|
||||
|
||||
void _updateData({
|
||||
String? newName,
|
||||
FieldPB? newField,
|
||||
List<int>? newTypeOptionData,
|
||||
}) {
|
||||
_data = _data.rebuild((rebuildData) {
|
||||
if (newName != null) {
|
||||
rebuildData.field_2 = rebuildData.field_2.rebuild((rebuildField) {
|
||||
rebuildField.name = newName;
|
||||
});
|
||||
}
|
||||
|
||||
if (newField != null) {
|
||||
rebuildData.field_2 = newField;
|
||||
}
|
||||
|
||||
if (newTypeOptionData != null) {
|
||||
rebuildData.typeOptionData = newTypeOptionData;
|
||||
}
|
||||
});
|
||||
|
||||
_fieldNotifier.value = _data.field_2;
|
||||
|
||||
FieldService.insertField(
|
||||
gridId: gridId,
|
||||
field: field,
|
||||
typeOptionData: _data.typeOptionData,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> switchToField(FieldType newFieldType) {
|
||||
return loader.switchToField(field.id, newFieldType).then((result) {
|
||||
return result.fold(
|
||||
(fieldTypeOptionData) {
|
||||
_updateData(
|
||||
newField: fieldTypeOptionData.field_2,
|
||||
newTypeOptionData: fieldTypeOptionData.typeOptionData,
|
||||
);
|
||||
},
|
||||
(err) {
|
||||
Log.error(err);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void Function() addFieldListener(void Function(FieldPB) callback) {
|
||||
listener() {
|
||||
callback(field);
|
||||
}
|
||||
|
||||
_fieldNotifier.addListener(listener);
|
||||
return listener;
|
||||
}
|
||||
|
||||
void removeFieldListener(void Function() listener) {
|
||||
_fieldNotifier.removeListener(listener);
|
||||
}
|
||||
}
|
@ -1,19 +1,14 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
class TypeOptionService {
|
||||
class TypeOptionFFIService {
|
||||
final String gridId;
|
||||
final String fieldId;
|
||||
|
||||
TypeOptionService({
|
||||
TypeOptionFFIService({
|
||||
required this.gridId,
|
||||
required this.fieldId,
|
||||
});
|
||||
@ -29,79 +24,3 @@ class TypeOptionService {
|
||||
return GridEventNewSelectOption(payload).send();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TypeOptionDataParser<T> {
|
||||
T fromBuffer(List<int> buffer);
|
||||
}
|
||||
|
||||
class TypeOptionWidgetContext<T extends GeneratedMessage> {
|
||||
T? _typeOptionObject;
|
||||
final TypeOptionDataController _dataController;
|
||||
final TypeOptionDataParser<T> dataParser;
|
||||
|
||||
TypeOptionWidgetContext({
|
||||
required this.dataParser,
|
||||
required TypeOptionDataController dataController,
|
||||
}) : _dataController = dataController;
|
||||
|
||||
String get gridId => _dataController.gridId;
|
||||
|
||||
GridFieldPB get field => _dataController.field;
|
||||
|
||||
T get typeOption {
|
||||
if (_typeOptionObject != null) {
|
||||
return _typeOptionObject!;
|
||||
}
|
||||
|
||||
final T object = dataParser.fromBuffer(_dataController.typeOptionData);
|
||||
_typeOptionObject = object;
|
||||
return object;
|
||||
}
|
||||
|
||||
set typeOption(T typeOption) {
|
||||
_dataController.typeOptionData = typeOption.writeToBuffer();
|
||||
_typeOptionObject = typeOption;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TypeOptionFieldDelegate {
|
||||
void onFieldChanged(void Function(String) callback);
|
||||
void dispose();
|
||||
}
|
||||
|
||||
class TypeOptionContext2<T> {
|
||||
final String gridId;
|
||||
final GridFieldPB field;
|
||||
final FieldService _fieldService;
|
||||
T? _data;
|
||||
final TypeOptionDataParser dataBuilder;
|
||||
|
||||
TypeOptionContext2({
|
||||
required this.gridId,
|
||||
required this.field,
|
||||
required this.dataBuilder,
|
||||
Uint8List? data,
|
||||
}) : _fieldService = FieldService(gridId: gridId, fieldId: field.id) {
|
||||
if (data != null) {
|
||||
_data = dataBuilder.fromBuffer(data);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Either<T, FlowyError>> typeOptionData() {
|
||||
if (_data != null) {
|
||||
return Future(() => left(_data!));
|
||||
}
|
||||
|
||||
return _fieldService
|
||||
.getFieldTypeOptionData(fieldType: field.fieldType)
|
||||
.then((result) {
|
||||
return result.fold(
|
||||
(data) {
|
||||
_data = dataBuilder.fromBuffer(data.typeOptionData);
|
||||
return left(_data!);
|
||||
},
|
||||
(err) => right(err),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,40 +1,23 @@
|
||||
import 'dart:async';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'block/block_cache.dart';
|
||||
import 'grid_service.dart';
|
||||
import 'row/row_service.dart';
|
||||
import 'grid_data_controller.dart';
|
||||
import 'row/row_cache.dart';
|
||||
import 'dart:collection';
|
||||
|
||||
part 'grid_bloc.freezed.dart';
|
||||
|
||||
class GridBloc extends Bloc<GridEvent, GridState> {
|
||||
final String gridId;
|
||||
final GridService _gridService;
|
||||
final GridFieldCache fieldCache;
|
||||
|
||||
// key: the block id
|
||||
final LinkedHashMap<String, GridBlockCache> _blocks;
|
||||
|
||||
List<GridRowInfo> get rowInfos {
|
||||
final List<GridRowInfo> rows = [];
|
||||
for (var block in _blocks.values) {
|
||||
rows.addAll(block.rows);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
final GridDataController dataController;
|
||||
|
||||
GridBloc({required ViewPB view})
|
||||
: gridId = view.id,
|
||||
_blocks = LinkedHashMap.identity(),
|
||||
_gridService = GridService(gridId: view.id),
|
||||
fieldCache = GridFieldCache(gridId: view.id),
|
||||
: dataController = GridDataController(view: view),
|
||||
super(GridState.initial(view.id)) {
|
||||
on<GridEvent>(
|
||||
(event, emit) async {
|
||||
@ -44,13 +27,21 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
||||
await _loadGrid(emit);
|
||||
},
|
||||
createRow: () {
|
||||
_gridService.createRow();
|
||||
dataController.createRow();
|
||||
},
|
||||
didReceiveRowUpdate: (newRowInfos, reason) {
|
||||
emit(state.copyWith(rowInfos: newRowInfos, reason: reason));
|
||||
didReceiveGridUpdate: (grid) {
|
||||
emit(state.copyWith(grid: Some(grid)));
|
||||
},
|
||||
didReceiveFieldUpdate: (fields) {
|
||||
emit(state.copyWith(rowInfos: rowInfos, fields: GridFieldEquatable(fields)));
|
||||
emit(state.copyWith(
|
||||
fields: GridFieldEquatable(fields),
|
||||
));
|
||||
},
|
||||
didReceiveRowUpdate: (newRowInfos, reason) {
|
||||
emit(state.copyWith(
|
||||
rowInfos: newRowInfos,
|
||||
reason: reason,
|
||||
));
|
||||
},
|
||||
);
|
||||
},
|
||||
@ -59,89 +50,63 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _gridService.closeGrid();
|
||||
await fieldCache.dispose();
|
||||
|
||||
for (final blockCache in _blocks.values) {
|
||||
blockCache.dispose();
|
||||
}
|
||||
await dataController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
GridRowCache? getRowCache(String blockId, String rowId) {
|
||||
final GridBlockCache? blockCache = _blocks[blockId];
|
||||
final GridBlockCache? blockCache = dataController.blocks[blockId];
|
||||
return blockCache?.rowCache;
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
fieldCache.addListener(
|
||||
listenWhen: () => !isClosed,
|
||||
onFields: (fields) => add(GridEvent.didReceiveFieldUpdate(fields)),
|
||||
dataController.addListener(
|
||||
onGridChanged: (grid) {
|
||||
if (!isClosed) {
|
||||
add(GridEvent.didReceiveGridUpdate(grid));
|
||||
}
|
||||
},
|
||||
onRowsChanged: (rowInfos, reason) {
|
||||
if (!isClosed) {
|
||||
add(GridEvent.didReceiveRowUpdate(rowInfos, reason));
|
||||
}
|
||||
},
|
||||
onFieldsChanged: (fields) {
|
||||
if (!isClosed) {
|
||||
add(GridEvent.didReceiveFieldUpdate(fields));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _loadGrid(Emitter<GridState> emit) async {
|
||||
final result = await _gridService.loadGrid();
|
||||
return Future(
|
||||
() => result.fold(
|
||||
(grid) async {
|
||||
_initialBlocks(grid.blocks);
|
||||
await _loadFields(grid, emit);
|
||||
},
|
||||
(err) => emit(state.copyWith(loadingState: GridLoadingState.finish(right(err)))),
|
||||
final result = await dataController.loadData();
|
||||
result.fold(
|
||||
(grid) => emit(
|
||||
state.copyWith(loadingState: GridLoadingState.finish(left(unit))),
|
||||
),
|
||||
(err) => emit(
|
||||
state.copyWith(loadingState: GridLoadingState.finish(right(err))),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _loadFields(GridPB grid, Emitter<GridState> emit) async {
|
||||
final result = await _gridService.getFields(fieldIds: grid.fields);
|
||||
return Future(
|
||||
() => result.fold(
|
||||
(fields) {
|
||||
fieldCache.fields = fields.items;
|
||||
|
||||
emit(state.copyWith(
|
||||
grid: Some(grid),
|
||||
fields: GridFieldEquatable(fieldCache.fields),
|
||||
rowInfos: rowInfos,
|
||||
loadingState: GridLoadingState.finish(left(unit)),
|
||||
));
|
||||
},
|
||||
(err) => emit(state.copyWith(loadingState: GridLoadingState.finish(right(err)))),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _initialBlocks(List<GridBlockPB> blocks) {
|
||||
for (final block in blocks) {
|
||||
if (_blocks[block.id] != null) {
|
||||
Log.warn("Intial duplicate block's cache: ${block.id}");
|
||||
return;
|
||||
}
|
||||
|
||||
final cache = GridBlockCache(
|
||||
gridId: gridId,
|
||||
block: block,
|
||||
fieldCache: fieldCache,
|
||||
);
|
||||
|
||||
cache.addListener(
|
||||
listenWhen: () => !isClosed,
|
||||
onChangeReason: (reason) => add(GridEvent.didReceiveRowUpdate(rowInfos, reason)),
|
||||
);
|
||||
|
||||
_blocks[block.id] = cache;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridEvent with _$GridEvent {
|
||||
const factory GridEvent.initial() = InitialGrid;
|
||||
const factory GridEvent.createRow() = _CreateRow;
|
||||
const factory GridEvent.didReceiveRowUpdate(List<GridRowInfo> rows, GridRowChangeReason listState) =
|
||||
_DidReceiveRowUpdate;
|
||||
const factory GridEvent.didReceiveFieldUpdate(List<GridFieldPB> fields) = _DidReceiveFieldUpdate;
|
||||
const factory GridEvent.didReceiveRowUpdate(
|
||||
List<RowInfo> rows,
|
||||
RowsChangedReason listState,
|
||||
) = _DidReceiveRowUpdate;
|
||||
const factory GridEvent.didReceiveFieldUpdate(
|
||||
UnmodifiableListView<FieldPB> fields,
|
||||
) = _DidReceiveFieldUpdate;
|
||||
|
||||
const factory GridEvent.didReceiveGridUpdate(
|
||||
GridPB grid,
|
||||
) = _DidReceiveGridUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
@ -150,13 +115,13 @@ class GridState with _$GridState {
|
||||
required String gridId,
|
||||
required Option<GridPB> grid,
|
||||
required GridFieldEquatable fields,
|
||||
required List<GridRowInfo> rowInfos,
|
||||
required List<RowInfo> rowInfos,
|
||||
required GridLoadingState loadingState,
|
||||
required GridRowChangeReason reason,
|
||||
required RowsChangedReason reason,
|
||||
}) = _GridState;
|
||||
|
||||
factory GridState.initial(String gridId) => GridState(
|
||||
fields: const GridFieldEquatable([]),
|
||||
fields: GridFieldEquatable(UnmodifiableListView([])),
|
||||
rowInfos: [],
|
||||
grid: none(),
|
||||
gridId: gridId,
|
||||
@ -168,20 +133,29 @@ class GridState with _$GridState {
|
||||
@freezed
|
||||
class GridLoadingState with _$GridLoadingState {
|
||||
const factory GridLoadingState.loading() = _Loading;
|
||||
const factory GridLoadingState.finish(Either<Unit, FlowyError> successOrFail) = _Finish;
|
||||
const factory GridLoadingState.finish(
|
||||
Either<Unit, FlowyError> successOrFail) = _Finish;
|
||||
}
|
||||
|
||||
class GridFieldEquatable extends Equatable {
|
||||
final List<GridFieldPB> _fields;
|
||||
const GridFieldEquatable(List<GridFieldPB> fields) : _fields = fields;
|
||||
final UnmodifiableListView<FieldPB> _fields;
|
||||
const GridFieldEquatable(
|
||||
UnmodifiableListView<FieldPB> fields,
|
||||
) : _fields = fields;
|
||||
|
||||
@override
|
||||
List<Object?> get props {
|
||||
if (_fields.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
_fields.length,
|
||||
_fields.map((field) => field.width).reduce((value, element) => value + element),
|
||||
_fields
|
||||
.map((field) => field.width)
|
||||
.reduce((value, element) => value + element),
|
||||
];
|
||||
}
|
||||
|
||||
UnmodifiableListView<GridFieldPB> get value => UnmodifiableListView(_fields);
|
||||
UnmodifiableListView<FieldPB> get value => UnmodifiableListView(_fields);
|
||||
}
|
||||
|
@ -0,0 +1,130 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
|
||||
import 'dart:async';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'block/block_cache.dart';
|
||||
import 'field/field_cache.dart';
|
||||
import 'prelude.dart';
|
||||
import 'row/row_cache.dart';
|
||||
|
||||
typedef OnFieldsChanged = void Function(UnmodifiableListView<FieldPB>);
|
||||
typedef OnGridChanged = void Function(GridPB);
|
||||
|
||||
typedef OnRowsChanged = void Function(
|
||||
List<RowInfo> rowInfos,
|
||||
RowsChangedReason,
|
||||
);
|
||||
typedef ListenOnRowChangedCondition = bool Function();
|
||||
|
||||
class GridDataController {
|
||||
final String gridId;
|
||||
final GridFFIService _gridFFIService;
|
||||
final GridFieldCache fieldCache;
|
||||
|
||||
// key: the block id
|
||||
final LinkedHashMap<String, GridBlockCache> _blocks;
|
||||
UnmodifiableMapView<String, GridBlockCache> get blocks =>
|
||||
UnmodifiableMapView(_blocks);
|
||||
|
||||
OnRowsChanged? _onRowChanged;
|
||||
OnFieldsChanged? _onFieldsChanged;
|
||||
OnGridChanged? _onGridChanged;
|
||||
|
||||
List<RowInfo> get rowInfos {
|
||||
final List<RowInfo> rows = [];
|
||||
for (var block in _blocks.values) {
|
||||
rows.addAll(block.rows);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
GridDataController({required ViewPB view})
|
||||
: gridId = view.id,
|
||||
_blocks = LinkedHashMap.new(),
|
||||
_gridFFIService = GridFFIService(gridId: view.id),
|
||||
fieldCache = GridFieldCache(gridId: view.id);
|
||||
|
||||
void addListener({
|
||||
required OnGridChanged onGridChanged,
|
||||
required OnRowsChanged onRowsChanged,
|
||||
required OnFieldsChanged onFieldsChanged,
|
||||
}) {
|
||||
_onGridChanged = onGridChanged;
|
||||
_onRowChanged = onRowsChanged;
|
||||
_onFieldsChanged = onFieldsChanged;
|
||||
|
||||
fieldCache.addListener(onFields: (fields) {
|
||||
_onFieldsChanged?.call(UnmodifiableListView(fields));
|
||||
});
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> loadData() async {
|
||||
final result = await _gridFFIService.loadGrid();
|
||||
return Future(
|
||||
() => result.fold(
|
||||
(grid) async {
|
||||
_initialBlocks(grid.blocks);
|
||||
_onGridChanged?.call(grid);
|
||||
return await _loadFields(grid);
|
||||
},
|
||||
(err) => right(err),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void createRow() {
|
||||
_gridFFIService.createRow();
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
await _gridFFIService.closeGrid();
|
||||
await fieldCache.dispose();
|
||||
|
||||
for (final blockCache in _blocks.values) {
|
||||
blockCache.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
void _initialBlocks(List<BlockPB> blocks) {
|
||||
for (final block in blocks) {
|
||||
if (_blocks[block.id] != null) {
|
||||
Log.warn("Initial duplicate block's cache: ${block.id}");
|
||||
return;
|
||||
}
|
||||
|
||||
final cache = GridBlockCache(
|
||||
gridId: gridId,
|
||||
block: block,
|
||||
fieldCache: fieldCache,
|
||||
);
|
||||
|
||||
cache.addListener(
|
||||
onRowsChanged: (reason) {
|
||||
_onRowChanged?.call(rowInfos, reason);
|
||||
},
|
||||
);
|
||||
|
||||
_blocks[block.id] = cache;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> _loadFields(GridPB grid) async {
|
||||
final result = await _gridFFIService.getFields(fieldIds: grid.fields);
|
||||
return Future(
|
||||
() => result.fold(
|
||||
(fields) {
|
||||
fieldCache.fields = fields.items;
|
||||
_onFieldsChanged?.call(UnmodifiableListView(fieldCache.fields));
|
||||
return left(unit);
|
||||
},
|
||||
(err) => right(err),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -4,7 +4,8 @@ import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'grid_service.dart';
|
||||
|
||||
import 'field/field_cache.dart';
|
||||
|
||||
part 'grid_header_bloc.freezed.dart';
|
||||
|
||||
@ -35,7 +36,7 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
|
||||
|
||||
Future<void> _moveField(
|
||||
_MoveField value, Emitter<GridHeaderState> emit) async {
|
||||
final fields = List<GridFieldPB>.from(state.fields);
|
||||
final fields = List<FieldPB>.from(state.fields);
|
||||
fields.insert(value.toIndex, fields.removeAt(value.fromIndex));
|
||||
emit(state.copyWith(fields: fields));
|
||||
|
||||
@ -63,19 +64,19 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
|
||||
@freezed
|
||||
class GridHeaderEvent with _$GridHeaderEvent {
|
||||
const factory GridHeaderEvent.initial() = _InitialHeader;
|
||||
const factory GridHeaderEvent.didReceiveFieldUpdate(
|
||||
List<GridFieldPB> fields) = _DidReceiveFieldUpdate;
|
||||
const factory GridHeaderEvent.didReceiveFieldUpdate(List<FieldPB> fields) =
|
||||
_DidReceiveFieldUpdate;
|
||||
const factory GridHeaderEvent.moveField(
|
||||
GridFieldPB field, int fromIndex, int toIndex) = _MoveField;
|
||||
FieldPB field, int fromIndex, int toIndex) = _MoveField;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridHeaderState with _$GridHeaderState {
|
||||
const factory GridHeaderState({required List<GridFieldPB> fields}) =
|
||||
const factory GridHeaderState({required List<FieldPB> fields}) =
|
||||
_GridHeaderState;
|
||||
|
||||
factory GridHeaderState.initial(List<GridFieldPB> fields) {
|
||||
// final List<GridFieldPB> newFields = List.from(fields);
|
||||
factory GridHeaderState.initial(List<FieldPB> fields) {
|
||||
// final List<FieldPB> newFields = List.from(fields);
|
||||
// newFields.retainWhere((field) => field.visibility);
|
||||
return GridHeaderState(fields: fields);
|
||||
}
|
||||
|
@ -1,21 +1,17 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:app_flowy/plugins/grid/application/field/grid_listener.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/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/board_card.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'row/row_service.dart';
|
||||
|
||||
class GridService {
|
||||
class GridFFIService {
|
||||
final String gridId;
|
||||
GridService({
|
||||
GridFFIService({
|
||||
required this.gridId,
|
||||
});
|
||||
|
||||
@ -26,18 +22,24 @@ class GridService {
|
||||
return GridEventGetGrid(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<GridRowPB, FlowyError>> createRow(
|
||||
{Option<String>? startRowId}) {
|
||||
CreateRowPayloadPB payload = CreateRowPayloadPB.create()..gridId = gridId;
|
||||
Future<Either<RowPB, FlowyError>> createRow({Option<String>? startRowId}) {
|
||||
var payload = CreateTableRowPayloadPB.create()..gridId = gridId;
|
||||
startRowId?.fold(() => null, (id) => payload.startRowId = id);
|
||||
return GridEventCreateRow(payload).send();
|
||||
return GridEventCreateTableRow(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<RepeatedGridFieldPB, FlowyError>> getFields(
|
||||
{required List<GridFieldIdPB> fieldIds}) {
|
||||
Future<Either<RowPB, FlowyError>> createBoardCard(String groupId) {
|
||||
CreateBoardCardPayloadPB payload = CreateBoardCardPayloadPB.create()
|
||||
..gridId = gridId
|
||||
..groupId = groupId;
|
||||
return GridEventCreateBoardCard(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<RepeatedFieldPB, FlowyError>> getFields(
|
||||
{required List<FieldIdPB> fieldIds}) {
|
||||
final payload = QueryFieldPayloadPB.create()
|
||||
..gridId = gridId
|
||||
..fieldIds = RepeatedGridFieldIdPB(items: fieldIds);
|
||||
..fieldIds = RepeatedFieldIdPB(items: fieldIds);
|
||||
return GridEventGetFields(payload).send();
|
||||
}
|
||||
|
||||
@ -45,188 +47,9 @@ class GridService {
|
||||
final request = ViewIdPB(value: gridId);
|
||||
return FolderEventCloseView(request).send();
|
||||
}
|
||||
}
|
||||
|
||||
class FieldsNotifier extends ChangeNotifier {
|
||||
List<GridFieldPB> _fields = [];
|
||||
|
||||
set fields(List<GridFieldPB> fields) {
|
||||
_fields = fields;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
List<GridFieldPB> get fields => _fields;
|
||||
}
|
||||
|
||||
typedef FieldChangesetCallback = void Function(GridFieldChangesetPB);
|
||||
typedef FieldsCallback = void Function(List<GridFieldPB>);
|
||||
|
||||
class GridFieldCache {
|
||||
final String gridId;
|
||||
final GridFieldsListener _fieldListener;
|
||||
FieldsNotifier? _fieldNotifier = FieldsNotifier();
|
||||
final Map<FieldsCallback, VoidCallback> _fieldsCallbackMap = {};
|
||||
final Map<FieldChangesetCallback, FieldChangesetCallback>
|
||||
_changesetCallbackMap = {};
|
||||
|
||||
GridFieldCache({required this.gridId})
|
||||
: _fieldListener = GridFieldsListener(gridId: gridId) {
|
||||
_fieldListener.start(onFieldsChanged: (result) {
|
||||
result.fold(
|
||||
(changeset) {
|
||||
_deleteFields(changeset.deletedFields);
|
||||
_insertFields(changeset.insertedFields);
|
||||
_updateFields(changeset.updatedFields);
|
||||
for (final listener in _changesetCallbackMap.values) {
|
||||
listener(changeset);
|
||||
}
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
await _fieldListener.stop();
|
||||
_fieldNotifier?.dispose();
|
||||
_fieldNotifier = null;
|
||||
}
|
||||
|
||||
UnmodifiableListView<GridFieldPB> get unmodifiableFields =>
|
||||
UnmodifiableListView(_fieldNotifier?.fields ?? []);
|
||||
|
||||
List<GridFieldPB> get fields => [..._fieldNotifier?.fields ?? []];
|
||||
|
||||
set fields(List<GridFieldPB> fields) {
|
||||
_fieldNotifier?.fields = [...fields];
|
||||
}
|
||||
|
||||
void addListener({
|
||||
FieldsCallback? onFields,
|
||||
FieldChangesetCallback? onChangeset,
|
||||
bool Function()? listenWhen,
|
||||
}) {
|
||||
if (onChangeset != null) {
|
||||
fn(c) {
|
||||
if (listenWhen != null && listenWhen() == false) {
|
||||
return;
|
||||
}
|
||||
onChangeset(c);
|
||||
}
|
||||
|
||||
_changesetCallbackMap[onChangeset] = fn;
|
||||
}
|
||||
|
||||
if (onFields != null) {
|
||||
fn() {
|
||||
if (listenWhen != null && listenWhen() == false) {
|
||||
return;
|
||||
}
|
||||
onFields(fields);
|
||||
}
|
||||
|
||||
_fieldsCallbackMap[onFields] = fn;
|
||||
_fieldNotifier?.addListener(fn);
|
||||
}
|
||||
}
|
||||
|
||||
void removeListener({
|
||||
FieldsCallback? onFieldsListener,
|
||||
FieldChangesetCallback? onChangesetListener,
|
||||
}) {
|
||||
if (onFieldsListener != null) {
|
||||
final fn = _fieldsCallbackMap.remove(onFieldsListener);
|
||||
if (fn != null) {
|
||||
_fieldNotifier?.removeListener(fn);
|
||||
}
|
||||
}
|
||||
|
||||
if (onChangesetListener != null) {
|
||||
_changesetCallbackMap.remove(onChangesetListener);
|
||||
}
|
||||
}
|
||||
|
||||
void _deleteFields(List<GridFieldIdPB> deletedFields) {
|
||||
if (deletedFields.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final List<GridFieldPB> newFields = fields;
|
||||
final Map<String, GridFieldIdPB> deletedFieldMap = {
|
||||
for (var fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder
|
||||
};
|
||||
|
||||
newFields.retainWhere((field) => (deletedFieldMap[field.id] == null));
|
||||
_fieldNotifier?.fields = newFields;
|
||||
}
|
||||
|
||||
void _insertFields(List<IndexFieldPB> insertedFields) {
|
||||
if (insertedFields.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final List<GridFieldPB> newFields = fields;
|
||||
for (final indexField in insertedFields) {
|
||||
if (newFields.length > indexField.index) {
|
||||
newFields.insert(indexField.index, indexField.field_1);
|
||||
} else {
|
||||
newFields.add(indexField.field_1);
|
||||
}
|
||||
}
|
||||
_fieldNotifier?.fields = newFields;
|
||||
}
|
||||
|
||||
void _updateFields(List<GridFieldPB> updatedFields) {
|
||||
if (updatedFields.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final List<GridFieldPB> newFields = fields;
|
||||
for (final updatedField in updatedFields) {
|
||||
final index =
|
||||
newFields.indexWhere((field) => field.id == updatedField.id);
|
||||
if (index != -1) {
|
||||
newFields.removeAt(index);
|
||||
newFields.insert(index, updatedField);
|
||||
}
|
||||
}
|
||||
_fieldNotifier?.fields = newFields;
|
||||
}
|
||||
}
|
||||
|
||||
class GridRowCacheFieldNotifierImpl extends GridRowCacheFieldNotifier {
|
||||
final GridFieldCache _cache;
|
||||
FieldChangesetCallback? _onChangesetFn;
|
||||
FieldsCallback? _onFieldFn;
|
||||
GridRowCacheFieldNotifierImpl(GridFieldCache cache) : _cache = cache;
|
||||
|
||||
@override
|
||||
UnmodifiableListView<GridFieldPB> get fields => _cache.unmodifiableFields;
|
||||
|
||||
@override
|
||||
void onFieldsChanged(VoidCallback callback) {
|
||||
_onFieldFn = (_) => callback();
|
||||
_cache.addListener(onFields: _onFieldFn);
|
||||
}
|
||||
|
||||
@override
|
||||
void onFieldChanged(void Function(GridFieldPB) callback) {
|
||||
_onChangesetFn = (GridFieldChangesetPB changeset) {
|
||||
for (final updatedField in changeset.updatedFields) {
|
||||
callback(updatedField);
|
||||
}
|
||||
};
|
||||
|
||||
_cache.addListener(onChangeset: _onChangesetFn);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (_onFieldFn != null) {
|
||||
_cache.removeListener(onFieldsListener: _onFieldFn!);
|
||||
_onFieldFn = null;
|
||||
}
|
||||
|
||||
if (_onChangesetFn != null) {
|
||||
_cache.removeListener(onChangesetListener: _onChangesetFn!);
|
||||
_onChangesetFn = null;
|
||||
}
|
||||
Future<Either<RepeatedGridGroupPB, FlowyError>> loadGroups() {
|
||||
final payload = GridIdPB(value: gridId);
|
||||
return GridEventGetGroup(payload).send();
|
||||
}
|
||||
}
|
||||
|
@ -4,13 +4,13 @@ export 'row/row_service.dart';
|
||||
export 'grid_service.dart';
|
||||
export 'grid_header_bloc.dart';
|
||||
|
||||
// GridFieldPB
|
||||
// FieldPB
|
||||
export 'field/field_service.dart';
|
||||
export 'field/field_action_sheet_bloc.dart';
|
||||
export 'field/field_editor_bloc.dart';
|
||||
export 'field/field_type_option_edit_bloc.dart';
|
||||
|
||||
// GridFieldPB Type Option
|
||||
// FieldPB Type Option
|
||||
export 'field/type_option/date_bloc.dart';
|
||||
export 'field/type_option/number_bloc.dart';
|
||||
export 'field/type_option/single_select_type_option.dart';
|
||||
|
@ -6,28 +6,30 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
import 'row_cache.dart';
|
||||
|
||||
part 'row_action_sheet_bloc.freezed.dart';
|
||||
|
||||
class RowActionSheetBloc
|
||||
extends Bloc<RowActionSheetEvent, RowActionSheetState> {
|
||||
final RowService _rowService;
|
||||
final RowFFIService _rowService;
|
||||
|
||||
RowActionSheetBloc({required GridRowInfo rowData})
|
||||
: _rowService = RowService(
|
||||
gridId: rowData.gridId,
|
||||
blockId: rowData.blockId,
|
||||
rowId: rowData.id,
|
||||
RowActionSheetBloc({required RowInfo rowInfo})
|
||||
: _rowService = RowFFIService(
|
||||
gridId: rowInfo.gridId,
|
||||
blockId: rowInfo.rowPB.blockId,
|
||||
),
|
||||
super(RowActionSheetState.initial(rowData)) {
|
||||
super(RowActionSheetState.initial(rowInfo)) {
|
||||
on<RowActionSheetEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
deleteRow: (_DeleteRow value) async {
|
||||
final result = await _rowService.deleteRow();
|
||||
final result = await _rowService.deleteRow(state.rowData.rowPB.id);
|
||||
logResult(result);
|
||||
},
|
||||
duplicateRow: (_DuplicateRow value) async {
|
||||
final result = await _rowService.duplicateRow();
|
||||
final result =
|
||||
await _rowService.duplicateRow(state.rowData.rowPB.id);
|
||||
logResult(result);
|
||||
},
|
||||
);
|
||||
@ -54,11 +56,10 @@ class RowActionSheetEvent with _$RowActionSheetEvent {
|
||||
@freezed
|
||||
class RowActionSheetState with _$RowActionSheetState {
|
||||
const factory RowActionSheetState({
|
||||
required GridRowInfo rowData,
|
||||
required RowInfo rowData,
|
||||
}) = _RowActionSheetState;
|
||||
|
||||
factory RowActionSheetState.initial(GridRowInfo rowData) =>
|
||||
RowActionSheetState(
|
||||
factory RowActionSheetState.initial(RowInfo rowData) => RowActionSheetState(
|
||||
rowData: rowData,
|
||||
);
|
||||
}
|
||||
|
@ -5,25 +5,25 @@ import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'row_cache.dart';
|
||||
import 'row_data_controller.dart';
|
||||
import 'row_service.dart';
|
||||
|
||||
part 'row_bloc.freezed.dart';
|
||||
|
||||
class RowBloc extends Bloc<RowEvent, RowState> {
|
||||
final RowService _rowService;
|
||||
final GridRowCache _rowCache;
|
||||
void Function()? _rowListenFn;
|
||||
final RowFFIService _rowService;
|
||||
final GridRowDataController _dataController;
|
||||
|
||||
RowBloc({
|
||||
required GridRowInfo rowInfo,
|
||||
required GridRowCache rowCache,
|
||||
}) : _rowService = RowService(
|
||||
required RowInfo rowInfo,
|
||||
required GridRowDataController dataController,
|
||||
}) : _rowService = RowFFIService(
|
||||
gridId: rowInfo.gridId,
|
||||
blockId: rowInfo.blockId,
|
||||
rowId: rowInfo.id,
|
||||
blockId: rowInfo.rowPB.blockId,
|
||||
),
|
||||
_rowCache = rowCache,
|
||||
super(RowState.initial(rowInfo, rowCache.loadGridCells(rowInfo.id))) {
|
||||
_dataController = dataController,
|
||||
super(RowState.initial(rowInfo, dataController.loadData())) {
|
||||
on<RowEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
@ -31,16 +31,15 @@ class RowBloc extends Bloc<RowEvent, RowState> {
|
||||
await _startListening();
|
||||
},
|
||||
createRow: (_CreateRow value) {
|
||||
_rowService.createRow();
|
||||
_rowService.createRow(rowInfo.rowPB.id);
|
||||
},
|
||||
didReceiveCellDatas: (_DidReceiveCellDatas value) async {
|
||||
final fields = value.gridCellMap.values
|
||||
didReceiveCells: (_DidReceiveCells value) async {
|
||||
final cells = value.gridCellMap.values
|
||||
.map((e) => GridCellEquatable(e.field))
|
||||
.toList();
|
||||
final snapshots = UnmodifiableListView(fields);
|
||||
emit(state.copyWith(
|
||||
gridCellMap: value.gridCellMap,
|
||||
snapshots: snapshots,
|
||||
cells: UnmodifiableListView(cells),
|
||||
changeReason: value.reason,
|
||||
));
|
||||
},
|
||||
@ -51,19 +50,17 @@ class RowBloc extends Bloc<RowEvent, RowState> {
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_rowListenFn != null) {
|
||||
_rowCache.removeRowListener(_rowListenFn!);
|
||||
}
|
||||
|
||||
_dataController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
Future<void> _startListening() async {
|
||||
_rowListenFn = _rowCache.addListener(
|
||||
rowId: state.rowInfo.id,
|
||||
onCellUpdated: (cellDatas, reason) =>
|
||||
add(RowEvent.didReceiveCellDatas(cellDatas, reason)),
|
||||
listenWhen: () => !isClosed,
|
||||
_dataController.addListener(
|
||||
onRowChanged: (cells, reason) {
|
||||
if (!isClosed) {
|
||||
add(RowEvent.didReceiveCells(cells, reason));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -72,33 +69,33 @@ class RowBloc extends Bloc<RowEvent, RowState> {
|
||||
class RowEvent with _$RowEvent {
|
||||
const factory RowEvent.initial() = _InitialRow;
|
||||
const factory RowEvent.createRow() = _CreateRow;
|
||||
const factory RowEvent.didReceiveCellDatas(
|
||||
GridCellMap gridCellMap, GridRowChangeReason reason) =
|
||||
_DidReceiveCellDatas;
|
||||
const factory RowEvent.didReceiveCells(
|
||||
GridCellMap gridCellMap, RowsChangedReason reason) = _DidReceiveCells;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class RowState with _$RowState {
|
||||
const factory RowState({
|
||||
required GridRowInfo rowInfo,
|
||||
required RowInfo rowInfo,
|
||||
required GridCellMap gridCellMap,
|
||||
required UnmodifiableListView<GridCellEquatable> snapshots,
|
||||
GridRowChangeReason? changeReason,
|
||||
required UnmodifiableListView<GridCellEquatable> cells,
|
||||
RowsChangedReason? changeReason,
|
||||
}) = _RowState;
|
||||
|
||||
factory RowState.initial(GridRowInfo rowInfo, GridCellMap cellDataMap) =>
|
||||
factory RowState.initial(RowInfo rowInfo, GridCellMap cellDataMap) =>
|
||||
RowState(
|
||||
rowInfo: rowInfo,
|
||||
gridCellMap: cellDataMap,
|
||||
snapshots: UnmodifiableListView(
|
||||
cellDataMap.values.map((e) => GridCellEquatable(e.field)).toList()),
|
||||
cells: UnmodifiableListView(
|
||||
cellDataMap.values.map((e) => GridCellEquatable(e.field)).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class GridCellEquatable extends Equatable {
|
||||
final GridFieldPB _field;
|
||||
final FieldPB _field;
|
||||
|
||||
const GridCellEquatable(GridFieldPB field) : _field = field;
|
||||
const GridCellEquatable(FieldPB field) : _field = field;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
|
@ -0,0 +1,328 @@
|
||||
import 'dart:collection';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'row_cache.freezed.dart';
|
||||
|
||||
typedef RowUpdateCallback = void Function();
|
||||
|
||||
abstract class IGridRowFieldNotifier {
|
||||
UnmodifiableListView<FieldPB> get fields;
|
||||
void onRowFieldsChanged(VoidCallback callback);
|
||||
void onRowFieldChanged(void Function(FieldPB) callback);
|
||||
void onRowDispose();
|
||||
}
|
||||
|
||||
/// Cache the rows in memory
|
||||
/// Insert / delete / update row
|
||||
///
|
||||
/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid for more information.
|
||||
|
||||
class GridRowCache {
|
||||
final String gridId;
|
||||
final BlockPB block;
|
||||
|
||||
/// _rows containers the current block's rows
|
||||
/// Use List to reverse the order of the GridRow.
|
||||
List<RowInfo> _rowInfos = [];
|
||||
|
||||
/// Use Map for faster access the raw row data.
|
||||
final HashMap<String, RowPB> _rowByRowId;
|
||||
|
||||
final GridCellCache _cellCache;
|
||||
final IGridRowFieldNotifier _fieldNotifier;
|
||||
final _RowChangesetNotifier _rowChangeReasonNotifier;
|
||||
|
||||
UnmodifiableListView<RowInfo> get rows => UnmodifiableListView(_rowInfos);
|
||||
GridCellCache get cellCache => _cellCache;
|
||||
|
||||
GridRowCache({
|
||||
required this.gridId,
|
||||
required this.block,
|
||||
required IGridRowFieldNotifier notifier,
|
||||
}) : _cellCache = GridCellCache(gridId: gridId),
|
||||
_rowByRowId = HashMap(),
|
||||
_rowChangeReasonNotifier = _RowChangesetNotifier(),
|
||||
_fieldNotifier = notifier {
|
||||
//
|
||||
notifier.onRowFieldsChanged(() => _rowChangeReasonNotifier
|
||||
.receive(const RowsChangedReason.fieldDidChange()));
|
||||
notifier.onRowFieldChanged((field) => _cellCache.remove(field.id));
|
||||
_rowInfos = block.rows.map((rowPB) => buildGridRow(rowPB)).toList();
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
_fieldNotifier.onRowDispose();
|
||||
_rowChangeReasonNotifier.dispose();
|
||||
await _cellCache.dispose();
|
||||
}
|
||||
|
||||
void applyChangesets(List<GridBlockChangesetPB> changesets) {
|
||||
for (final changeset in changesets) {
|
||||
_deleteRows(changeset.deletedRows);
|
||||
_insertRows(changeset.insertedRows);
|
||||
_updateRows(changeset.updatedRows);
|
||||
_hideRows(changeset.hideRows);
|
||||
_showRows(changeset.visibleRows);
|
||||
}
|
||||
}
|
||||
|
||||
void _deleteRows(List<String> deletedRows) {
|
||||
if (deletedRows.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<RowInfo> newRows = [];
|
||||
final DeletedIndexs deletedIndex = [];
|
||||
final Map<String, String> deletedRowByRowId = {
|
||||
for (var rowId in deletedRows) rowId: rowId
|
||||
};
|
||||
|
||||
_rowInfos.asMap().forEach((index, RowInfo rowInfo) {
|
||||
if (deletedRowByRowId[rowInfo.rowPB.id] == null) {
|
||||
newRows.add(rowInfo);
|
||||
} else {
|
||||
_rowByRowId.remove(rowInfo.rowPB.id);
|
||||
deletedIndex.add(DeletedIndex(index: index, row: rowInfo));
|
||||
}
|
||||
});
|
||||
_rowInfos = newRows;
|
||||
_rowChangeReasonNotifier.receive(RowsChangedReason.delete(deletedIndex));
|
||||
}
|
||||
|
||||
void _insertRows(List<InsertedRowPB> insertRows) {
|
||||
if (insertRows.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
InsertedIndexs insertIndexs = [];
|
||||
for (final InsertedRowPB insertRow in insertRows) {
|
||||
final insertIndex = InsertedIndex(
|
||||
index: insertRow.index,
|
||||
rowId: insertRow.row.id,
|
||||
);
|
||||
insertIndexs.add(insertIndex);
|
||||
_rowInfos.insert(
|
||||
insertRow.index,
|
||||
(buildGridRow(insertRow.row)),
|
||||
);
|
||||
}
|
||||
|
||||
_rowChangeReasonNotifier.receive(RowsChangedReason.insert(insertIndexs));
|
||||
}
|
||||
|
||||
void _updateRows(List<RowPB> updatedRows) {
|
||||
if (updatedRows.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final UpdatedIndexs updatedIndexs = UpdatedIndexs();
|
||||
for (final RowPB updatedRow in updatedRows) {
|
||||
final rowId = updatedRow.id;
|
||||
final index = _rowInfos.indexWhere(
|
||||
(rowInfo) => rowInfo.rowPB.id == rowId,
|
||||
);
|
||||
if (index != -1) {
|
||||
_rowByRowId[rowId] = updatedRow;
|
||||
|
||||
_rowInfos.removeAt(index);
|
||||
_rowInfos.insert(index, buildGridRow(updatedRow));
|
||||
updatedIndexs[rowId] = UpdatedIndex(index: index, rowId: rowId);
|
||||
}
|
||||
}
|
||||
|
||||
_rowChangeReasonNotifier.receive(RowsChangedReason.update(updatedIndexs));
|
||||
}
|
||||
|
||||
void _hideRows(List<String> hideRows) {}
|
||||
|
||||
void _showRows(List<String> visibleRows) {}
|
||||
|
||||
void onRowsChanged(
|
||||
void Function(RowsChangedReason) onRowChanged,
|
||||
) {
|
||||
_rowChangeReasonNotifier.addListener(() {
|
||||
onRowChanged(_rowChangeReasonNotifier.reason);
|
||||
});
|
||||
}
|
||||
|
||||
RowUpdateCallback addListener({
|
||||
required String rowId,
|
||||
void Function(GridCellMap, RowsChangedReason)? onCellUpdated,
|
||||
bool Function()? listenWhen,
|
||||
}) {
|
||||
listenerHandler() async {
|
||||
if (listenWhen != null && listenWhen() == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
notifyUpdate() {
|
||||
if (onCellUpdated != null) {
|
||||
final row = _rowByRowId[rowId];
|
||||
if (row != null) {
|
||||
final GridCellMap cellDataMap = _makeGridCells(rowId, row);
|
||||
onCellUpdated(cellDataMap, _rowChangeReasonNotifier.reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_rowChangeReasonNotifier.reason.whenOrNull(
|
||||
update: (indexs) {
|
||||
if (indexs[rowId] != null) notifyUpdate();
|
||||
},
|
||||
fieldDidChange: () => notifyUpdate(),
|
||||
);
|
||||
}
|
||||
|
||||
_rowChangeReasonNotifier.addListener(listenerHandler);
|
||||
return listenerHandler;
|
||||
}
|
||||
|
||||
void removeRowListener(VoidCallback callback) {
|
||||
_rowChangeReasonNotifier.removeListener(callback);
|
||||
}
|
||||
|
||||
GridCellMap loadGridCells(String rowId) {
|
||||
final RowPB? data = _rowByRowId[rowId];
|
||||
if (data == null) {
|
||||
_loadRow(rowId);
|
||||
}
|
||||
return _makeGridCells(rowId, data);
|
||||
}
|
||||
|
||||
Future<void> _loadRow(String rowId) async {
|
||||
final payload = RowIdPB.create()
|
||||
..gridId = gridId
|
||||
..blockId = block.id
|
||||
..rowId = rowId;
|
||||
|
||||
final result = await GridEventGetRow(payload).send();
|
||||
result.fold(
|
||||
(optionRow) => _refreshRow(optionRow),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}
|
||||
|
||||
GridCellMap _makeGridCells(String rowId, RowPB? row) {
|
||||
var cellDataMap = GridCellMap.new();
|
||||
for (final field in _fieldNotifier.fields) {
|
||||
if (field.visibility) {
|
||||
cellDataMap[field.id] = GridCellIdentifier(
|
||||
rowId: rowId,
|
||||
gridId: gridId,
|
||||
field: field,
|
||||
);
|
||||
}
|
||||
}
|
||||
return cellDataMap;
|
||||
}
|
||||
|
||||
void _refreshRow(OptionalRowPB optionRow) {
|
||||
if (!optionRow.hasRow()) {
|
||||
return;
|
||||
}
|
||||
final updatedRow = optionRow.row;
|
||||
updatedRow.freeze();
|
||||
|
||||
_rowByRowId[updatedRow.id] = updatedRow;
|
||||
final index =
|
||||
_rowInfos.indexWhere((rowInfo) => rowInfo.rowPB.id == updatedRow.id);
|
||||
if (index != -1) {
|
||||
// update the corresponding row in _rows if they are not the same
|
||||
if (_rowInfos[index].rowPB != updatedRow) {
|
||||
final rowInfo = _rowInfos.removeAt(index).copyWith(rowPB: updatedRow);
|
||||
_rowInfos.insert(index, rowInfo);
|
||||
|
||||
// Calculate the update index
|
||||
final UpdatedIndexs updatedIndexs = UpdatedIndexs();
|
||||
updatedIndexs[rowInfo.rowPB.id] = UpdatedIndex(
|
||||
index: index,
|
||||
rowId: rowInfo.rowPB.id,
|
||||
);
|
||||
|
||||
//
|
||||
_rowChangeReasonNotifier
|
||||
.receive(RowsChangedReason.update(updatedIndexs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowInfo buildGridRow(RowPB rowPB) {
|
||||
return RowInfo(
|
||||
gridId: gridId,
|
||||
fields: _fieldNotifier.fields,
|
||||
rowPB: rowPB,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RowChangesetNotifier extends ChangeNotifier {
|
||||
RowsChangedReason reason = const InitialListState();
|
||||
|
||||
_RowChangesetNotifier();
|
||||
|
||||
void receive(RowsChangedReason newReason) {
|
||||
reason = newReason;
|
||||
reason.map(
|
||||
insert: (_) => notifyListeners(),
|
||||
delete: (_) => notifyListeners(),
|
||||
update: (_) => notifyListeners(),
|
||||
fieldDidChange: (_) => notifyListeners(),
|
||||
initial: (_) {},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class RowInfo with _$RowInfo {
|
||||
const factory RowInfo({
|
||||
required String gridId,
|
||||
required UnmodifiableListView<FieldPB> fields,
|
||||
required RowPB rowPB,
|
||||
}) = _RowInfo;
|
||||
}
|
||||
|
||||
typedef InsertedIndexs = List<InsertedIndex>;
|
||||
typedef DeletedIndexs = List<DeletedIndex>;
|
||||
typedef UpdatedIndexs = LinkedHashMap<String, UpdatedIndex>;
|
||||
|
||||
@freezed
|
||||
class RowsChangedReason with _$RowsChangedReason {
|
||||
const factory RowsChangedReason.insert(InsertedIndexs items) = _Insert;
|
||||
const factory RowsChangedReason.delete(DeletedIndexs items) = _Delete;
|
||||
const factory RowsChangedReason.update(UpdatedIndexs indexs) = _Update;
|
||||
const factory RowsChangedReason.fieldDidChange() = _FieldDidChange;
|
||||
const factory RowsChangedReason.initial() = InitialListState;
|
||||
}
|
||||
|
||||
class InsertedIndex {
|
||||
final int index;
|
||||
final String rowId;
|
||||
InsertedIndex({
|
||||
required this.index,
|
||||
required this.rowId,
|
||||
});
|
||||
}
|
||||
|
||||
class DeletedIndex {
|
||||
final int index;
|
||||
final RowInfo row;
|
||||
DeletedIndex({
|
||||
required this.index,
|
||||
required this.row,
|
||||
});
|
||||
}
|
||||
|
||||
class UpdatedIndex {
|
||||
final int index;
|
||||
final String rowId;
|
||||
UpdatedIndex({
|
||||
required this.index,
|
||||
required this.rowId,
|
||||
});
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_field_notifier.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../presentation/widgets/cell/cell_builder.dart';
|
||||
import '../cell/cell_service/cell_service.dart';
|
||||
import '../field/field_cache.dart';
|
||||
import 'row_cache.dart';
|
||||
|
||||
typedef OnRowChanged = void Function(GridCellMap, RowsChangedReason);
|
||||
|
||||
class GridRowDataController extends GridCellBuilderDelegate {
|
||||
final RowInfo rowInfo;
|
||||
final List<VoidCallback> _onRowChangedListeners = [];
|
||||
final GridFieldCache _fieldCache;
|
||||
final GridRowCache _rowCache;
|
||||
|
||||
GridRowDataController({
|
||||
required this.rowInfo,
|
||||
required GridFieldCache fieldCache,
|
||||
required GridRowCache rowCache,
|
||||
}) : _fieldCache = fieldCache,
|
||||
_rowCache = rowCache;
|
||||
|
||||
GridCellMap loadData() {
|
||||
return _rowCache.loadGridCells(rowInfo.rowPB.id);
|
||||
}
|
||||
|
||||
void addListener({OnRowChanged? onRowChanged}) {
|
||||
_onRowChangedListeners.add(_rowCache.addListener(
|
||||
rowId: rowInfo.rowPB.id,
|
||||
onCellUpdated: onRowChanged,
|
||||
));
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
for (final fn in _onRowChangedListeners) {
|
||||
_rowCache.removeRowListener(fn);
|
||||
}
|
||||
}
|
||||
|
||||
// GridCellBuilderDelegate implementation
|
||||
@override
|
||||
GridCellFieldNotifier buildFieldNotifier() {
|
||||
return GridCellFieldNotifier(
|
||||
notifier: GridCellFieldNotifierImpl(_fieldCache));
|
||||
}
|
||||
|
||||
@override
|
||||
GridCellCache get cellCache => _rowCache.cellCache;
|
||||
}
|
@ -2,26 +2,24 @@ import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_servic
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'row_service.dart';
|
||||
|
||||
import 'row_data_controller.dart';
|
||||
part 'row_detail_bloc.freezed.dart';
|
||||
|
||||
class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
|
||||
final GridRowInfo rowInfo;
|
||||
final GridRowCache _rowCache;
|
||||
void Function()? _rowListenFn;
|
||||
final GridRowDataController dataController;
|
||||
|
||||
RowDetailBloc({
|
||||
required this.rowInfo,
|
||||
required GridRowCache rowCache,
|
||||
}) : _rowCache = rowCache,
|
||||
super(RowDetailState.initial()) {
|
||||
required this.dataController,
|
||||
}) : super(RowDetailState.initial()) {
|
||||
on<RowDetailEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
initial: (_Initial value) async {
|
||||
await _startListening();
|
||||
_loadCellData();
|
||||
final cells = dataController.loadData();
|
||||
if (!isClosed) {
|
||||
add(RowDetailEvent.didReceiveCellDatas(cells.values.toList()));
|
||||
}
|
||||
},
|
||||
didReceiveCellDatas: (_DidReceiveCellDatas value) {
|
||||
emit(state.copyWith(gridCells: value.gridCells));
|
||||
@ -33,27 +31,19 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_rowListenFn != null) {
|
||||
_rowCache.removeRowListener(_rowListenFn!);
|
||||
}
|
||||
dataController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
Future<void> _startListening() async {
|
||||
_rowListenFn = _rowCache.addListener(
|
||||
rowId: rowInfo.id,
|
||||
onCellUpdated: (cellDatas, reason) =>
|
||||
add(RowDetailEvent.didReceiveCellDatas(cellDatas.values.toList())),
|
||||
listenWhen: () => !isClosed,
|
||||
dataController.addListener(
|
||||
onRowChanged: (cells, reason) {
|
||||
if (!isClosed) {
|
||||
add(RowDetailEvent.didReceiveCellDatas(cells.values.toList()));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _loadCellData() async {
|
||||
final cellDataMap = _rowCache.loadGridCells(rowInfo.id);
|
||||
if (!isClosed) {
|
||||
add(RowDetailEvent.didReceiveCellDatas(cellDataMap.values.toList()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
@ -8,12 +8,13 @@ import 'dart:typed_data';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
|
||||
typedef UpdateRowNotifiedValue = Either<GridRowPB, FlowyError>;
|
||||
typedef UpdateFieldNotifiedValue = Either<List<GridFieldPB>, FlowyError>;
|
||||
typedef UpdateRowNotifiedValue = Either<RowPB, FlowyError>;
|
||||
typedef UpdateFieldNotifiedValue = Either<List<FieldPB>, FlowyError>;
|
||||
|
||||
class RowListener {
|
||||
final String rowId;
|
||||
PublishNotifier<UpdateRowNotifiedValue>? updateRowNotifier = PublishNotifier();
|
||||
PublishNotifier<UpdateRowNotifiedValue>? updateRowNotifier =
|
||||
PublishNotifier();
|
||||
GridNotificationListener? _listener;
|
||||
|
||||
RowListener({required this.rowId});
|
||||
@ -26,7 +27,8 @@ class RowListener {
|
||||
switch (ty) {
|
||||
case GridNotification.DidUpdateRow:
|
||||
result.fold(
|
||||
(payload) => updateRowNotifier?.value = left(GridRowPB.fromBuffer(payload)),
|
||||
(payload) =>
|
||||
updateRowNotifier?.value = left(RowPB.fromBuffer(payload)),
|
||||
(error) => updateRowNotifier?.value = right(error),
|
||||
);
|
||||
break;
|
||||
|
@ -1,314 +1,29 @@
|
||||
import 'dart:collection';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.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-grid/block_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'row_service.freezed.dart';
|
||||
|
||||
typedef RowUpdateCallback = void Function();
|
||||
|
||||
abstract class GridRowCacheFieldNotifier {
|
||||
UnmodifiableListView<GridFieldPB> get fields;
|
||||
void onFieldsChanged(VoidCallback callback);
|
||||
void onFieldChanged(void Function(GridFieldPB) callback);
|
||||
void dispose();
|
||||
}
|
||||
|
||||
/// Cache the rows in memory
|
||||
/// Insert / delete / update row
|
||||
///
|
||||
/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid for more information.
|
||||
|
||||
class GridRowCache {
|
||||
final String gridId;
|
||||
final GridBlockPB block;
|
||||
|
||||
/// _rows containers the current block's rows
|
||||
/// Use List to reverse the order of the GridRow.
|
||||
List<GridRowInfo> _rowInfos = [];
|
||||
|
||||
/// Use Map for faster access the raw row data.
|
||||
final HashMap<String, GridRowPB> _rowByRowId;
|
||||
|
||||
final GridCellCache _cellCache;
|
||||
final GridRowCacheFieldNotifier _fieldNotifier;
|
||||
final _GridRowChangesetNotifier _rowChangeReasonNotifier;
|
||||
|
||||
UnmodifiableListView<GridRowInfo> get rows => UnmodifiableListView(_rowInfos);
|
||||
GridCellCache get cellCache => _cellCache;
|
||||
|
||||
GridRowCache({
|
||||
required this.gridId,
|
||||
required this.block,
|
||||
required GridRowCacheFieldNotifier notifier,
|
||||
}) : _cellCache = GridCellCache(gridId: gridId),
|
||||
_rowByRowId = HashMap(),
|
||||
_rowChangeReasonNotifier = _GridRowChangesetNotifier(),
|
||||
_fieldNotifier = notifier {
|
||||
//
|
||||
notifier.onFieldsChanged(() => _rowChangeReasonNotifier
|
||||
.receive(const GridRowChangeReason.fieldDidChange()));
|
||||
notifier.onFieldChanged((field) => _cellCache.remove(field.id));
|
||||
_rowInfos = block.rows
|
||||
.map((rowInfo) => buildGridRow(rowInfo.id, rowInfo.height.toDouble()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
_fieldNotifier.dispose();
|
||||
_rowChangeReasonNotifier.dispose();
|
||||
await _cellCache.dispose();
|
||||
}
|
||||
|
||||
void applyChangesets(List<GridBlockChangesetPB> changesets) {
|
||||
for (final changeset in changesets) {
|
||||
_deleteRows(changeset.deletedRows);
|
||||
_insertRows(changeset.insertedRows);
|
||||
_updateRows(changeset.updatedRows);
|
||||
_hideRows(changeset.hideRows);
|
||||
_showRows(changeset.visibleRows);
|
||||
}
|
||||
}
|
||||
|
||||
void _deleteRows(List<String> deletedRows) {
|
||||
if (deletedRows.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<GridRowInfo> newRows = [];
|
||||
final DeletedIndexs deletedIndex = [];
|
||||
final Map<String, String> deletedRowByRowId = {
|
||||
for (var rowId in deletedRows) rowId: rowId
|
||||
};
|
||||
|
||||
_rowInfos.asMap().forEach((index, row) {
|
||||
if (deletedRowByRowId[row.id] == null) {
|
||||
newRows.add(row);
|
||||
} else {
|
||||
_rowByRowId.remove(row.id);
|
||||
deletedIndex.add(DeletedIndex(index: index, row: row));
|
||||
}
|
||||
});
|
||||
_rowInfos = newRows;
|
||||
_rowChangeReasonNotifier.receive(GridRowChangeReason.delete(deletedIndex));
|
||||
}
|
||||
|
||||
void _insertRows(List<InsertedRowPB> insertRows) {
|
||||
if (insertRows.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
InsertedIndexs insertIndexs = [];
|
||||
for (final insertRow in insertRows) {
|
||||
final insertIndex = InsertedIndex(
|
||||
index: insertRow.index,
|
||||
rowId: insertRow.rowId,
|
||||
);
|
||||
insertIndexs.add(insertIndex);
|
||||
_rowInfos.insert(insertRow.index,
|
||||
(buildGridRow(insertRow.rowId, insertRow.height.toDouble())));
|
||||
}
|
||||
|
||||
_rowChangeReasonNotifier.receive(GridRowChangeReason.insert(insertIndexs));
|
||||
}
|
||||
|
||||
void _updateRows(List<UpdatedRowPB> updatedRows) {
|
||||
if (updatedRows.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final UpdatedIndexs updatedIndexs = UpdatedIndexs();
|
||||
for (final updatedRow in updatedRows) {
|
||||
final rowId = updatedRow.rowId;
|
||||
final index = _rowInfos.indexWhere((row) => row.id == rowId);
|
||||
if (index != -1) {
|
||||
_rowByRowId[rowId] = updatedRow.row;
|
||||
|
||||
_rowInfos.removeAt(index);
|
||||
_rowInfos.insert(
|
||||
index, buildGridRow(rowId, updatedRow.row.height.toDouble()));
|
||||
updatedIndexs[rowId] = UpdatedIndex(index: index, rowId: rowId);
|
||||
}
|
||||
}
|
||||
|
||||
_rowChangeReasonNotifier.receive(GridRowChangeReason.update(updatedIndexs));
|
||||
}
|
||||
|
||||
void _hideRows(List<String> hideRows) {}
|
||||
|
||||
void _showRows(List<String> visibleRows) {}
|
||||
|
||||
void onRowsChanged(
|
||||
void Function(GridRowChangeReason) onRowChanged,
|
||||
) {
|
||||
_rowChangeReasonNotifier.addListener(() {
|
||||
onRowChanged(_rowChangeReasonNotifier.reason);
|
||||
});
|
||||
}
|
||||
|
||||
RowUpdateCallback addListener({
|
||||
required String rowId,
|
||||
void Function(GridCellMap, GridRowChangeReason)? onCellUpdated,
|
||||
bool Function()? listenWhen,
|
||||
}) {
|
||||
listenrHandler() async {
|
||||
if (listenWhen != null && listenWhen() == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
notifyUpdate() {
|
||||
if (onCellUpdated != null) {
|
||||
final row = _rowByRowId[rowId];
|
||||
if (row != null) {
|
||||
final GridCellMap cellDataMap = _makeGridCells(rowId, row);
|
||||
onCellUpdated(cellDataMap, _rowChangeReasonNotifier.reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_rowChangeReasonNotifier.reason.whenOrNull(
|
||||
update: (indexs) {
|
||||
if (indexs[rowId] != null) notifyUpdate();
|
||||
},
|
||||
fieldDidChange: () => notifyUpdate(),
|
||||
);
|
||||
}
|
||||
|
||||
_rowChangeReasonNotifier.addListener(listenrHandler);
|
||||
return listenrHandler;
|
||||
}
|
||||
|
||||
void removeRowListener(VoidCallback callback) {
|
||||
_rowChangeReasonNotifier.removeListener(callback);
|
||||
}
|
||||
|
||||
GridCellMap loadGridCells(String rowId) {
|
||||
final GridRowPB? data = _rowByRowId[rowId];
|
||||
if (data == null) {
|
||||
_loadRow(rowId);
|
||||
}
|
||||
return _makeGridCells(rowId, data);
|
||||
}
|
||||
|
||||
Future<void> _loadRow(String rowId) async {
|
||||
final payload = GridRowIdPB.create()
|
||||
..gridId = gridId
|
||||
..blockId = block.id
|
||||
..rowId = rowId;
|
||||
|
||||
final result = await GridEventGetRow(payload).send();
|
||||
result.fold(
|
||||
(optionRow) => _refreshRow(optionRow),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}
|
||||
|
||||
GridCellMap _makeGridCells(String rowId, GridRowPB? row) {
|
||||
var cellDataMap = GridCellMap.new();
|
||||
for (final field in _fieldNotifier.fields) {
|
||||
if (field.visibility) {
|
||||
cellDataMap[field.id] = GridCellIdentifier(
|
||||
rowId: rowId,
|
||||
gridId: gridId,
|
||||
field: field,
|
||||
);
|
||||
}
|
||||
}
|
||||
return cellDataMap;
|
||||
}
|
||||
|
||||
void _refreshRow(OptionalRowPB optionRow) {
|
||||
if (!optionRow.hasRow()) {
|
||||
return;
|
||||
}
|
||||
final updatedRow = optionRow.row;
|
||||
updatedRow.freeze();
|
||||
|
||||
_rowByRowId[updatedRow.id] = updatedRow;
|
||||
final index =
|
||||
_rowInfos.indexWhere((gridRow) => gridRow.id == updatedRow.id);
|
||||
if (index != -1) {
|
||||
// update the corresponding row in _rows if they are not the same
|
||||
if (_rowInfos[index].rawRow != updatedRow) {
|
||||
final row = _rowInfos.removeAt(index).copyWith(rawRow: updatedRow);
|
||||
_rowInfos.insert(index, row);
|
||||
|
||||
// Calculate the update index
|
||||
final UpdatedIndexs updatedIndexs = UpdatedIndexs();
|
||||
updatedIndexs[row.id] = UpdatedIndex(index: index, rowId: row.id);
|
||||
|
||||
//
|
||||
_rowChangeReasonNotifier
|
||||
.receive(GridRowChangeReason.update(updatedIndexs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GridRowInfo buildGridRow(String rowId, double rowHeight) {
|
||||
return GridRowInfo(
|
||||
gridId: gridId,
|
||||
blockId: block.id,
|
||||
fields: _fieldNotifier.fields,
|
||||
id: rowId,
|
||||
height: rowHeight,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _GridRowChangesetNotifier extends ChangeNotifier {
|
||||
GridRowChangeReason reason = const InitialListState();
|
||||
|
||||
_GridRowChangesetNotifier();
|
||||
|
||||
void receive(GridRowChangeReason newReason) {
|
||||
reason = newReason;
|
||||
reason.map(
|
||||
insert: (_) => notifyListeners(),
|
||||
delete: (_) => notifyListeners(),
|
||||
update: (_) => notifyListeners(),
|
||||
fieldDidChange: (_) => notifyListeners(),
|
||||
initial: (_) {},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RowService {
|
||||
class RowFFIService {
|
||||
final String gridId;
|
||||
final String blockId;
|
||||
final String rowId;
|
||||
|
||||
RowService(
|
||||
{required this.gridId, required this.blockId, required this.rowId});
|
||||
RowFFIService({
|
||||
required this.gridId,
|
||||
required this.blockId,
|
||||
});
|
||||
|
||||
Future<Either<GridRowPB, FlowyError>> createRow() {
|
||||
CreateRowPayloadPB payload = CreateRowPayloadPB.create()
|
||||
Future<Either<RowPB, FlowyError>> createRow(String rowId) {
|
||||
final payload = CreateTableRowPayloadPB.create()
|
||||
..gridId = gridId
|
||||
..startRowId = rowId;
|
||||
|
||||
return GridEventCreateRow(payload).send();
|
||||
return GridEventCreateTableRow(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> moveRow(
|
||||
String rowId, int fromIndex, int toIndex) {
|
||||
final payload = MoveItemPayloadPB.create()
|
||||
..gridId = gridId
|
||||
..itemId = rowId
|
||||
..ty = MoveItemTypePB.MoveRow
|
||||
..fromIndex = fromIndex
|
||||
..toIndex = toIndex;
|
||||
|
||||
return GridEventMoveItem(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<OptionalRowPB, FlowyError>> getRow() {
|
||||
final payload = GridRowIdPB.create()
|
||||
Future<Either<OptionalRowPB, FlowyError>> getRow(String rowId) {
|
||||
final payload = RowIdPB.create()
|
||||
..gridId = gridId
|
||||
..blockId = blockId
|
||||
..rowId = rowId;
|
||||
@ -316,8 +31,8 @@ class RowService {
|
||||
return GridEventGetRow(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> deleteRow() {
|
||||
final payload = GridRowIdPB.create()
|
||||
Future<Either<Unit, FlowyError>> deleteRow(String rowId) {
|
||||
final payload = RowIdPB.create()
|
||||
..gridId = gridId
|
||||
..blockId = blockId
|
||||
..rowId = rowId;
|
||||
@ -325,8 +40,8 @@ class RowService {
|
||||
return GridEventDeleteRow(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> duplicateRow() {
|
||||
final payload = GridRowIdPB.create()
|
||||
Future<Either<Unit, FlowyError>> duplicateRow(String rowId) {
|
||||
final payload = RowIdPB.create()
|
||||
..gridId = gridId
|
||||
..blockId = blockId
|
||||
..rowId = rowId;
|
||||
@ -335,54 +50,22 @@ class RowService {
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridRowInfo with _$GridRowInfo {
|
||||
const factory GridRowInfo({
|
||||
required String gridId,
|
||||
required String blockId,
|
||||
required String id,
|
||||
required UnmodifiableListView<GridFieldPB> fields,
|
||||
required double height,
|
||||
GridRowPB? rawRow,
|
||||
}) = _GridRowInfo;
|
||||
}
|
||||
class MoveRowFFIService {
|
||||
final String gridId;
|
||||
|
||||
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,
|
||||
MoveRowFFIService({
|
||||
required this.gridId,
|
||||
});
|
||||
}
|
||||
|
||||
class DeletedIndex {
|
||||
final int index;
|
||||
final GridRowInfo row;
|
||||
DeletedIndex({
|
||||
required this.index,
|
||||
required this.row,
|
||||
});
|
||||
}
|
||||
Future<Either<Unit, FlowyError>> moveRow({
|
||||
required String fromRowId,
|
||||
required String toRowId,
|
||||
}) {
|
||||
var payload = MoveRowPayloadPB.create()
|
||||
..viewId = gridId
|
||||
..fromRowId = fromRowId
|
||||
..toRowId = toRowId;
|
||||
|
||||
class UpdatedIndex {
|
||||
final int index;
|
||||
final String rowId;
|
||||
UpdatedIndex({
|
||||
required this.index,
|
||||
required this.rowId,
|
||||
});
|
||||
return GridEventMoveRow(payload).send();
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,17 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import '../field/field_cache.dart';
|
||||
|
||||
part 'property_bloc.freezed.dart';
|
||||
|
||||
class GridPropertyBloc extends Bloc<GridPropertyEvent, GridPropertyState> {
|
||||
final GridFieldCache _fieldCache;
|
||||
Function(List<GridFieldPB>)? _onFieldsFn;
|
||||
Function(List<FieldPB>)? _onFieldsFn;
|
||||
|
||||
GridPropertyBloc({required String gridId, required GridFieldCache fieldCache})
|
||||
: _fieldCache = fieldCache,
|
||||
@ -66,8 +67,8 @@ class GridPropertyEvent with _$GridPropertyEvent {
|
||||
const factory GridPropertyEvent.initial() = _Initial;
|
||||
const factory GridPropertyEvent.setFieldVisibility(
|
||||
String fieldId, bool visibility) = _SetFieldVisibility;
|
||||
const factory GridPropertyEvent.didReceiveFieldUpdate(
|
||||
List<GridFieldPB> fields) = _DidReceiveFieldUpdate;
|
||||
const factory GridPropertyEvent.didReceiveFieldUpdate(List<FieldPB> fields) =
|
||||
_DidReceiveFieldUpdate;
|
||||
const factory GridPropertyEvent.moveField(int fromIndex, int toIndex) =
|
||||
_MoveField;
|
||||
}
|
||||
@ -76,10 +77,10 @@ class GridPropertyEvent with _$GridPropertyEvent {
|
||||
class GridPropertyState with _$GridPropertyState {
|
||||
const factory GridPropertyState({
|
||||
required String gridId,
|
||||
required List<GridFieldPB> fields,
|
||||
required List<FieldPB> fields,
|
||||
}) = _GridPropertyState;
|
||||
|
||||
factory GridPropertyState.initial(String gridId, List<GridFieldPB> fields) =>
|
||||
factory GridPropertyState.initial(String gridId, List<FieldPB> fields) =>
|
||||
GridPropertyState(
|
||||
gridId: gridId,
|
||||
fields: fields,
|
||||
|
@ -22,10 +22,13 @@ class GridPluginBuilder implements PluginBuilder {
|
||||
String get menuName => LocaleKeys.grid_menuName.tr();
|
||||
|
||||
@override
|
||||
PluginType get pluginType => DefaultPlugin.grid.type();
|
||||
PluginType get pluginType => PluginType.grid;
|
||||
|
||||
@override
|
||||
ViewDataType get dataType => ViewDataType.Grid;
|
||||
ViewDataTypePB get dataType => ViewDataTypePB.Database;
|
||||
|
||||
@override
|
||||
ViewLayoutTypePB? get subDataType => ViewLayoutTypePB.Grid;
|
||||
}
|
||||
|
||||
class GridPluginConfig implements PluginConfig {
|
||||
|
@ -2,19 +2,20 @@ import 'package:flutter/material.dart';
|
||||
import 'package:linked_scroll_controller/linked_scroll_controller.dart';
|
||||
|
||||
class GridScrollController {
|
||||
final LinkedScrollControllerGroup _scrollGroupContorller;
|
||||
final LinkedScrollControllerGroup _scrollGroupController;
|
||||
final ScrollController verticalController;
|
||||
final ScrollController horizontalController;
|
||||
|
||||
final List<ScrollController> _linkHorizontalControllers = [];
|
||||
|
||||
GridScrollController({required LinkedScrollControllerGroup scrollGroupContorller})
|
||||
: _scrollGroupContorller = scrollGroupContorller,
|
||||
GridScrollController(
|
||||
{required LinkedScrollControllerGroup scrollGroupController})
|
||||
: _scrollGroupController = scrollGroupController,
|
||||
verticalController = ScrollController(),
|
||||
horizontalController = scrollGroupContorller.addAndGet();
|
||||
horizontalController = scrollGroupController.addAndGet();
|
||||
|
||||
ScrollController linkHorizontalController() {
|
||||
final controller = _scrollGroupContorller.addAndGet();
|
||||
final controller = _scrollGroupController.addAndGet();
|
||||
_linkHorizontalControllers.add(controller);
|
||||
return controller;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/grid_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_service.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
|
||||
@ -11,12 +12,15 @@ import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:linked_scroll_controller/linked_scroll_controller.dart';
|
||||
import '../application/row/row_cache.dart';
|
||||
import 'controller/grid_scroll.dart';
|
||||
import 'layout/layout.dart';
|
||||
import 'layout/sizes.dart';
|
||||
import 'widgets/cell/cell_builder.dart';
|
||||
import 'widgets/row/grid_row.dart';
|
||||
import 'widgets/footer/grid_footer.dart';
|
||||
import 'widgets/header/grid_header.dart';
|
||||
import 'widgets/row/row_detail.dart';
|
||||
import 'widgets/shortcuts.dart';
|
||||
import 'widgets/toolbar/grid_toolbar.dart';
|
||||
|
||||
@ -79,7 +83,7 @@ class FlowyGrid extends StatefulWidget {
|
||||
|
||||
class _FlowyGridState extends State<FlowyGrid> {
|
||||
final _scrollController = GridScrollController(
|
||||
scrollGroupContorller: LinkedScrollControllerGroup());
|
||||
scrollGroupController: LinkedScrollControllerGroup());
|
||||
late ScrollController headerScrollController;
|
||||
|
||||
@override
|
||||
@ -153,7 +157,7 @@ class _FlowyGridState extends State<FlowyGrid> {
|
||||
}
|
||||
|
||||
Widget _gridHeader(BuildContext context, String gridId) {
|
||||
final fieldCache = context.read<GridBloc>().fieldCache;
|
||||
final fieldCache = context.read<GridBloc>().dataController.fieldCache;
|
||||
return GridHeaderSliverAdaptor(
|
||||
gridId: gridId,
|
||||
fieldCache: fieldCache,
|
||||
@ -169,7 +173,7 @@ class _GridToolbarAdaptor extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocSelector<GridBloc, GridState, GridToolbarContext>(
|
||||
selector: (state) {
|
||||
final fieldCache = context.read<GridBloc>().fieldCache;
|
||||
final fieldCache = context.read<GridBloc>().dataController.fieldCache;
|
||||
return GridToolbarContext(
|
||||
gridId: state.gridId,
|
||||
fieldCache: fieldCache,
|
||||
@ -221,7 +225,7 @@ class _GridRowsState extends State<_GridRows> {
|
||||
initialItemCount: context.read<GridBloc>().state.rowInfos.length,
|
||||
itemBuilder:
|
||||
(BuildContext context, int index, Animation<double> animation) {
|
||||
final GridRowInfo rowInfo =
|
||||
final RowInfo rowInfo =
|
||||
context.read<GridBloc>().state.rowInfos[index];
|
||||
return _renderRow(context, rowInfo, animation);
|
||||
},
|
||||
@ -232,25 +236,61 @@ class _GridRowsState extends State<_GridRows> {
|
||||
|
||||
Widget _renderRow(
|
||||
BuildContext context,
|
||||
GridRowInfo rowInfo,
|
||||
RowInfo rowInfo,
|
||||
Animation<double> animation,
|
||||
) {
|
||||
final rowCache =
|
||||
context.read<GridBloc>().getRowCache(rowInfo.blockId, rowInfo.id);
|
||||
final fieldCache = context.read<GridBloc>().fieldCache;
|
||||
if (rowCache != null) {
|
||||
return SizeTransition(
|
||||
sizeFactor: animation,
|
||||
child: GridRowWidget(
|
||||
rowData: rowInfo,
|
||||
rowCache: rowCache,
|
||||
fieldCache: fieldCache,
|
||||
key: ValueKey(rowInfo.id),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
final rowCache = context.read<GridBloc>().getRowCache(
|
||||
rowInfo.rowPB.blockId,
|
||||
rowInfo.rowPB.id,
|
||||
);
|
||||
|
||||
/// Return placeholder widget if the rowCache is null.
|
||||
if (rowCache == null) return const SizedBox();
|
||||
|
||||
final fieldCache = context.read<GridBloc>().dataController.fieldCache;
|
||||
final dataController = GridRowDataController(
|
||||
rowInfo: rowInfo,
|
||||
fieldCache: fieldCache,
|
||||
rowCache: rowCache,
|
||||
);
|
||||
|
||||
return SizeTransition(
|
||||
sizeFactor: animation,
|
||||
child: GridRowWidget(
|
||||
rowInfo: rowInfo,
|
||||
dataController: dataController,
|
||||
cellBuilder: GridCellBuilder(delegate: dataController),
|
||||
openDetailPage: (context, cellBuilder) {
|
||||
_openRowDetailPage(
|
||||
context,
|
||||
rowInfo,
|
||||
fieldCache,
|
||||
rowCache,
|
||||
cellBuilder,
|
||||
);
|
||||
},
|
||||
key: ValueKey(rowInfo.rowPB.id),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _openRowDetailPage(
|
||||
BuildContext context,
|
||||
RowInfo rowInfo,
|
||||
GridFieldCache fieldCache,
|
||||
GridRowCache rowCache,
|
||||
GridCellBuilder cellBuilder,
|
||||
) {
|
||||
final dataController = GridRowDataController(
|
||||
rowInfo: rowInfo,
|
||||
fieldCache: fieldCache,
|
||||
rowCache: rowCache,
|
||||
);
|
||||
|
||||
RowDetailPage(
|
||||
cellBuilder: cellBuilder,
|
||||
dataController: dataController,
|
||||
).show(context);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,11 +2,15 @@ import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'sizes.dart';
|
||||
|
||||
class GridLayout {
|
||||
static double headerWidth(List<GridFieldPB> fields) {
|
||||
static double headerWidth(List<FieldPB> fields) {
|
||||
if (fields.isEmpty) return 0;
|
||||
|
||||
final fieldsWidth = fields.map((field) => field.width.toDouble()).reduce((value, element) => value + element);
|
||||
final fieldsWidth = fields
|
||||
.map((field) => field.width.toDouble())
|
||||
.reduce((value, element) => value + element);
|
||||
|
||||
return fieldsWidth + GridSize.leadingHeaderPadding + GridSize.trailHeaderPadding;
|
||||
return fieldsWidth +
|
||||
GridSize.leadingHeaderPadding +
|
||||
GridSize.trailHeaderPadding;
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
import 'cell_builder.dart';
|
||||
|
||||
class GridCellAccessoryBuildContext {
|
||||
final BuildContext anchorContext;
|
||||
final bool isCellEditing;
|
||||
@ -57,18 +59,6 @@ class PrimaryCellAccessory extends StatelessWidget with GridCellAccessory {
|
||||
bool enable() => !isCellEditing;
|
||||
}
|
||||
|
||||
typedef AccessoryBuilder = List<GridCellAccessory> Function(
|
||||
GridCellAccessoryBuildContext buildContext);
|
||||
|
||||
abstract class CellAccessory extends Widget {
|
||||
const CellAccessory({Key? key}) : super(key: key);
|
||||
|
||||
// The hover will show if the isHover's value is true
|
||||
ValueNotifier<bool>? get onAccessoryHover;
|
||||
|
||||
AccessoryBuilder? get accessoryBuilder;
|
||||
}
|
||||
|
||||
class AccessoryHover extends StatefulWidget {
|
||||
final CellAccessory child;
|
||||
final EdgeInsets contentPadding;
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@ -13,53 +12,66 @@ import 'select_option_cell/select_option_cell.dart';
|
||||
import 'text_cell.dart';
|
||||
import 'url_cell/url_cell.dart';
|
||||
|
||||
abstract class GridCellBuilderDelegate
|
||||
extends GridCellControllerBuilderDelegate {
|
||||
GridCellCache get cellCache;
|
||||
}
|
||||
|
||||
class GridCellBuilder {
|
||||
final GridCellCache cellCache;
|
||||
final GridFieldCache fieldCache;
|
||||
final GridCellBuilderDelegate delegate;
|
||||
GridCellBuilder({
|
||||
required this.cellCache,
|
||||
required this.fieldCache,
|
||||
required this.delegate,
|
||||
});
|
||||
|
||||
GridCellWidget build(GridCellIdentifier cell, {GridCellStyle? style}) {
|
||||
GridCellWidget build(GridCellIdentifier cellId, {GridCellStyle? style}) {
|
||||
final cellControllerBuilder = GridCellControllerBuilder(
|
||||
cellId: cell,
|
||||
cellCache: cellCache,
|
||||
fieldCache: fieldCache,
|
||||
cellId: cellId,
|
||||
cellCache: delegate.cellCache,
|
||||
delegate: delegate,
|
||||
);
|
||||
final key = cell.key();
|
||||
switch (cell.fieldType) {
|
||||
|
||||
final key = cellId.key();
|
||||
switch (cellId.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
return GridCheckboxCell(
|
||||
cellControllerBuilder: cellControllerBuilder, key: key);
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.DateTime:
|
||||
return GridDateCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
style: style);
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
style: style,
|
||||
);
|
||||
case FieldType.SingleSelect:
|
||||
return GridSingleSelectCell(
|
||||
cellContorllerBuilder: cellControllerBuilder,
|
||||
style: style,
|
||||
key: key);
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
style: style,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.MultiSelect:
|
||||
return GridMultiSelectCell(
|
||||
cellContorllerBuilder: cellControllerBuilder,
|
||||
style: style,
|
||||
key: key);
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
style: style,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.Number:
|
||||
return GridNumberCell(
|
||||
cellContorllerBuilder: cellControllerBuilder, key: key);
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.RichText:
|
||||
return GridTextCell(
|
||||
cellContorllerBuilder: cellControllerBuilder,
|
||||
style: style,
|
||||
key: key);
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
style: style,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.URL:
|
||||
return GridURLCell(
|
||||
cellContorllerBuilder: cellControllerBuilder,
|
||||
style: style,
|
||||
key: key);
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
style: style,
|
||||
key: key,
|
||||
);
|
||||
}
|
||||
throw UnimplementedError;
|
||||
}
|
||||
@ -82,6 +94,18 @@ abstract class CellEditable {
|
||||
ValueNotifier<bool> get onCellEditing;
|
||||
}
|
||||
|
||||
typedef AccessoryBuilder = List<GridCellAccessory> Function(
|
||||
GridCellAccessoryBuildContext buildContext);
|
||||
|
||||
abstract class CellAccessory extends Widget {
|
||||
const CellAccessory({Key? key}) : super(key: key);
|
||||
|
||||
// The hover will show if the isHover's value is true
|
||||
ValueNotifier<bool>? get onAccessoryHover;
|
||||
|
||||
AccessoryBuilder? get accessoryBuilder;
|
||||
}
|
||||
|
||||
abstract class GridCellWidget extends StatefulWidget
|
||||
implements CellAccessory, CellEditable, CellShortcuts {
|
||||
GridCellWidget({Key? key}) : super(key: key) {
|
||||
@ -93,7 +117,7 @@ abstract class GridCellWidget extends StatefulWidget
|
||||
@override
|
||||
final ValueNotifier<bool> onCellFocus = ValueNotifier<bool>(false);
|
||||
|
||||
// When the cell is focused, we assume that the accessory alse be hovered.
|
||||
// When the cell is focused, we assume that the accessory also be hovered.
|
||||
@override
|
||||
ValueNotifier<bool> get onAccessoryHover => onCellFocus;
|
||||
|
||||
@ -150,7 +174,7 @@ abstract class GridCellState<T extends GridCellWidget> extends State<T> {
|
||||
|
||||
abstract class GridFocusNodeCellState<T extends GridCellWidget>
|
||||
extends GridCellState<T> {
|
||||
SingleListenrFocusNode focusNode = SingleListenrFocusNode();
|
||||
SingleListenerFocusNode focusNode = SingleListenerFocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -219,7 +243,7 @@ class GridCellFocusListener extends ChangeNotifier {
|
||||
|
||||
abstract class GridCellStyle {}
|
||||
|
||||
class SingleListenrFocusNode extends FocusNode {
|
||||
class SingleListenerFocusNode extends FocusNode {
|
||||
VoidCallback? _listener;
|
||||
|
||||
void setListener(VoidCallback listener) {
|
||||
|
@ -25,24 +25,28 @@ class CellContainer extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProxyProvider<RegionStateNotifier,
|
||||
CellContainerNotifier>(
|
||||
create: (_) => CellContainerNotifier(child),
|
||||
_CellContainerNotifier>(
|
||||
create: (_) => _CellContainerNotifier(child),
|
||||
update: (_, rowStateNotifier, cellStateNotifier) =>
|
||||
cellStateNotifier!..onEnter = rowStateNotifier.onEnter,
|
||||
child: Selector<CellContainerNotifier, bool>(
|
||||
child: Selector<_CellContainerNotifier, bool>(
|
||||
selector: (context, notifier) => notifier.isFocus,
|
||||
builder: (context, isFocus, _) {
|
||||
Widget container = Center(child: GridCellShortcuts(child: child));
|
||||
|
||||
if (accessoryBuilder != null) {
|
||||
final accessories = accessoryBuilder!(GridCellAccessoryBuildContext(
|
||||
anchorContext: context,
|
||||
isCellEditing: isFocus,
|
||||
));
|
||||
final accessories = accessoryBuilder!(
|
||||
GridCellAccessoryBuildContext(
|
||||
anchorContext: context,
|
||||
isCellEditing: isFocus,
|
||||
),
|
||||
);
|
||||
|
||||
if (accessories.isNotEmpty) {
|
||||
container =
|
||||
CellEnterRegion(child: container, accessories: accessories);
|
||||
container = _GridCellEnterRegion(
|
||||
child: container,
|
||||
accessories: accessories,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,16 +78,16 @@ class CellContainer extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class CellEnterRegion extends StatelessWidget {
|
||||
class _GridCellEnterRegion extends StatelessWidget {
|
||||
final Widget child;
|
||||
final List<GridCellAccessory> accessories;
|
||||
const CellEnterRegion(
|
||||
const _GridCellEnterRegion(
|
||||
{required this.child, required this.accessories, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<CellContainerNotifier, bool>(
|
||||
return Selector<_CellContainerNotifier, bool>(
|
||||
selector: (context, notifier) => notifier.onEnter,
|
||||
builder: (context, onEnter, _) {
|
||||
List<Widget> children = [child];
|
||||
@ -95,10 +99,10 @@ class CellEnterRegion extends StatelessWidget {
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
onEnter: (p) =>
|
||||
Provider.of<CellContainerNotifier>(context, listen: false)
|
||||
Provider.of<_CellContainerNotifier>(context, listen: false)
|
||||
.onEnter = true,
|
||||
onExit: (p) =>
|
||||
Provider.of<CellContainerNotifier>(context, listen: false)
|
||||
Provider.of<_CellContainerNotifier>(context, listen: false)
|
||||
.onEnter = false,
|
||||
child: Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
@ -111,13 +115,13 @@ class CellEnterRegion extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class CellContainerNotifier extends ChangeNotifier {
|
||||
class _CellContainerNotifier extends ChangeNotifier {
|
||||
final CellEditable cellEditable;
|
||||
VoidCallback? _onCellFocusListener;
|
||||
bool _isFocus = false;
|
||||
bool _onEnter = false;
|
||||
|
||||
CellContainerNotifier(this.cellEditable) {
|
||||
_CellContainerNotifier(this.cellEditable) {
|
||||
_onCellFocusListener = () => isFocus = cellEditable.onCellFocus.value;
|
||||
cellEditable.onCellFocus.addListener(_onCellFocusListener!);
|
||||
}
|
@ -24,14 +24,16 @@ class GridCellShortcuts extends StatelessWidget {
|
||||
return Shortcuts(
|
||||
shortcuts: {
|
||||
LogicalKeySet(LogicalKeyboardKey.enter): const GridCellEnterIdent(),
|
||||
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyC): const GridCellCopyIntent(),
|
||||
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyV): const GridCellInsertIntent(),
|
||||
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyC):
|
||||
const GridCellCopyIntent(),
|
||||
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyV):
|
||||
const GridCellPasteIntent(),
|
||||
},
|
||||
child: Actions(
|
||||
actions: {
|
||||
GridCellEnterIdent: GridCellEnterAction(child: child),
|
||||
GridCellCopyIntent: GridCellCopyAction(child: child),
|
||||
GridCellInsertIntent: GridCellInsertAction(child: child),
|
||||
GridCellPasteIntent: GridCellPasteAction(child: child),
|
||||
},
|
||||
child: child,
|
||||
),
|
||||
@ -78,16 +80,16 @@ class GridCellCopyAction extends Action<GridCellCopyIntent> {
|
||||
}
|
||||
}
|
||||
|
||||
class GridCellInsertIntent extends Intent {
|
||||
const GridCellInsertIntent();
|
||||
class GridCellPasteIntent extends Intent {
|
||||
const GridCellPasteIntent();
|
||||
}
|
||||
|
||||
class GridCellInsertAction extends Action<GridCellInsertIntent> {
|
||||
class GridCellPasteAction extends Action<GridCellPasteIntent> {
|
||||
final CellShortcuts child;
|
||||
GridCellInsertAction({required this.child});
|
||||
GridCellPasteAction({required this.child});
|
||||
|
||||
@override
|
||||
void invoke(covariant GridCellInsertIntent intent) {
|
||||
void invoke(covariant GridCellPasteIntent intent) {
|
||||
final callback = child.shortcutHandlers[CellKeyboardKey.onInsert];
|
||||
if (callback != null) {
|
||||
callback();
|
||||
|
@ -22,8 +22,9 @@ class _CheckboxCellState extends GridCellState<GridCheckboxCell> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellContext = widget.cellControllerBuilder.build();
|
||||
_cellBloc = getIt<CheckboxCellBloc>(param1: cellContext)
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridCheckboxCellController;
|
||||
_cellBloc = getIt<CheckboxCellBloc>(param1: cellController)
|
||||
..add(const CheckboxCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
@ -43,8 +43,8 @@ class _DateCellState extends GridCellState<GridDateCell> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellContext = widget.cellControllerBuilder.build();
|
||||
_cellBloc = getIt<DateCellBloc>(param1: cellContext)
|
||||
final cellController = widget.cellControllerBuilder.build();
|
||||
_cellBloc = getIt<DateCellBloc>(param1: cellController)
|
||||
..add(const DateCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
@ -84,7 +84,7 @@ class _DateCellState extends GridCellState<GridDateCell> {
|
||||
DateCellEditor(onDismissed: () => widget.onCellEditing.value = false);
|
||||
calendar.show(
|
||||
context,
|
||||
cellController: bloc.cellContext.clone(),
|
||||
cellController: bloc.cellController.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/date_cal_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
@ -14,7 +15,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:table_calendar/table_calendar.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/prelude.dart';
|
||||
|
||||
import '../../../layout/sizes.dart';
|
||||
import '../../header/type_option/date.dart';
|
||||
|
||||
@ -38,11 +38,12 @@ class DateCellEditor with FlowyOverlayDelegate {
|
||||
|
||||
final result =
|
||||
await cellController.getFieldTypeOption(DateTypeOptionDataParser());
|
||||
|
||||
result.fold(
|
||||
(dateTypeOption) {
|
||||
(dateTypeOptionPB) {
|
||||
final calendar = _CellCalendarWidget(
|
||||
cellContext: cellController,
|
||||
dateTypeOption: dateTypeOption,
|
||||
dateTypeOptionPB: dateTypeOptionPB,
|
||||
);
|
||||
|
||||
FlowyOverlay.of(context).insertWithAnchor(
|
||||
@ -78,11 +79,11 @@ class DateCellEditor with FlowyOverlayDelegate {
|
||||
|
||||
class _CellCalendarWidget extends StatelessWidget {
|
||||
final GridDateCellController cellContext;
|
||||
final DateTypeOption dateTypeOption;
|
||||
final DateTypeOptionPB dateTypeOptionPB;
|
||||
|
||||
const _CellCalendarWidget({
|
||||
required this.cellContext,
|
||||
required this.dateTypeOption,
|
||||
required this.dateTypeOptionPB,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -92,9 +93,9 @@ class _CellCalendarWidget extends StatelessWidget {
|
||||
return BlocProvider(
|
||||
create: (context) {
|
||||
return DateCalBloc(
|
||||
dateTypeOption: dateTypeOption,
|
||||
dateTypeOptionPB: dateTypeOptionPB,
|
||||
cellData: cellContext.getCellData(),
|
||||
cellContext: cellContext,
|
||||
cellController: cellContext,
|
||||
)..add(const DateCalEvent.initial());
|
||||
},
|
||||
child: BlocBuilder<DateCalBloc, DateCalState>(
|
||||
@ -196,7 +197,7 @@ class _IncludeTimeButton extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return BlocSelector<DateCalBloc, DateCalState, bool>(
|
||||
selector: (state) => state.dateTypeOption.includeTime,
|
||||
selector: (state) => state.dateTypeOptionPB.includeTime,
|
||||
builder: (context, includeTime) {
|
||||
return SizedBox(
|
||||
height: 50,
|
||||
@ -243,7 +244,7 @@ class _TimeTextFieldState extends State<_TimeTextField> {
|
||||
void initState() {
|
||||
_focusNode = FocusNode();
|
||||
_controller = TextEditingController(text: widget.bloc.state.time);
|
||||
if (widget.bloc.state.dateTypeOption.includeTime) {
|
||||
if (widget.bloc.state.dateTypeOptionPB.includeTime) {
|
||||
_focusNode.addListener(() {
|
||||
if (mounted) {
|
||||
_CalDateTimeSetting.hide(context);
|
||||
@ -264,7 +265,7 @@ class _TimeTextFieldState extends State<_TimeTextField> {
|
||||
},
|
||||
listenWhen: (p, c) => p.time != c.time,
|
||||
builder: (context, state) {
|
||||
if (state.dateTypeOption.includeTime) {
|
||||
if (state.dateTypeOptionPB.includeTime) {
|
||||
return Padding(
|
||||
padding: kMargin,
|
||||
child: RoundedInputField(
|
||||
@ -306,23 +307,24 @@ class _DateTypeOptionButton extends StatelessWidget {
|
||||
final title = LocaleKeys.grid_field_dateFormat.tr() +
|
||||
" &" +
|
||||
LocaleKeys.grid_field_timeFormat.tr();
|
||||
return BlocSelector<DateCalBloc, DateCalState, DateTypeOption>(
|
||||
selector: (state) => state.dateTypeOption,
|
||||
builder: (context, dateTypeOption) {
|
||||
return BlocSelector<DateCalBloc, DateCalState, DateTypeOptionPB>(
|
||||
selector: (state) => state.dateTypeOptionPB,
|
||||
builder: (context, dateTypeOptionPB) {
|
||||
return FlowyButton(
|
||||
text: FlowyText.medium(title, fontSize: 12),
|
||||
hoverColor: theme.hover,
|
||||
margin: kMargin,
|
||||
onTap: () => _showTimeSetting(dateTypeOption, context),
|
||||
onTap: () => _showTimeSetting(dateTypeOptionPB, context),
|
||||
rightIcon: svgWidget("grid/more", color: theme.iconColor),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showTimeSetting(DateTypeOption dateTypeOption, BuildContext context) {
|
||||
void _showTimeSetting(
|
||||
DateTypeOptionPB dateTypeOptionPB, BuildContext context) {
|
||||
final setting = _CalDateTimeSetting(
|
||||
dateTypeOption: dateTypeOption,
|
||||
dateTypeOptionPB: dateTypeOptionPB,
|
||||
onEvent: (event) => context.read<DateCalBloc>().add(event),
|
||||
);
|
||||
setting.show(context);
|
||||
@ -330,10 +332,10 @@ class _DateTypeOptionButton extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _CalDateTimeSetting extends StatefulWidget {
|
||||
final DateTypeOption dateTypeOption;
|
||||
final DateTypeOptionPB dateTypeOptionPB;
|
||||
final Function(DateCalEvent) onEvent;
|
||||
const _CalDateTimeSetting(
|
||||
{required this.dateTypeOption, required this.onEvent, Key? key})
|
||||
{required this.dateTypeOptionPB, required this.onEvent, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
@ -370,17 +372,17 @@ class _CalDateTimeSettingState extends State<_CalDateTimeSetting> {
|
||||
List<Widget> children = [
|
||||
DateFormatButton(onTap: () {
|
||||
final list = DateFormatList(
|
||||
selectedFormat: widget.dateTypeOption.dateFormat,
|
||||
selectedFormat: widget.dateTypeOptionPB.dateFormat,
|
||||
onSelected: (format) =>
|
||||
widget.onEvent(DateCalEvent.setDateFormat(format)),
|
||||
);
|
||||
_showOverlay(context, list);
|
||||
}),
|
||||
TimeFormatButton(
|
||||
timeFormat: widget.dateTypeOption.timeFormat,
|
||||
timeFormat: widget.dateTypeOptionPB.timeFormat,
|
||||
onTap: () {
|
||||
final list = TimeFormatList(
|
||||
selectedFormat: widget.dateTypeOption.timeFormat,
|
||||
selectedFormat: widget.dateTypeOptionPB.timeFormat,
|
||||
onSelected: (format) =>
|
||||
widget.onEvent(DateCalEvent.setTimeFormat(format)),
|
||||
);
|
||||
|
@ -7,10 +7,10 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'cell_builder.dart';
|
||||
|
||||
class GridNumberCell extends GridCellWidget {
|
||||
final GridCellControllerBuilder cellContorllerBuilder;
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
|
||||
GridNumberCell({
|
||||
required this.cellContorllerBuilder,
|
||||
required this.cellControllerBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -25,8 +25,8 @@ class _NumberCellState extends GridFocusNodeCellState<GridNumberCell> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellContext = widget.cellContorllerBuilder.build();
|
||||
_cellBloc = getIt<NumberCellBloc>(param1: cellContext)
|
||||
final cellController = widget.cellControllerBuilder.build();
|
||||
_cellBloc = getIt<NumberCellBloc>(param1: cellController)
|
||||
..add(const NumberCellEvent.initial());
|
||||
_controller =
|
||||
TextEditingController(text: contentFromState(_cellBloc.state));
|
||||
@ -49,8 +49,10 @@ class _NumberCellState extends GridFocusNodeCellState<GridNumberCell> {
|
||||
controller: _controller,
|
||||
focusNode: focusNode,
|
||||
onEditingComplete: () => focusNode.unfocus(),
|
||||
maxLines: null,
|
||||
onSubmitted: (_) => focusNode.unfocus(),
|
||||
maxLines: 1,
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
textInputAction: TextInputAction.done,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
border: InputBorder.none,
|
||||
@ -63,7 +65,7 @@ class _NumberCellState extends GridFocusNodeCellState<GridNumberCell> {
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_delayOperation?.cancel();
|
||||
_delayOperation = null;
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
@ -72,7 +74,7 @@ class _NumberCellState extends GridFocusNodeCellState<GridNumberCell> {
|
||||
Future<void> focusChanged() async {
|
||||
if (mounted) {
|
||||
_delayOperation?.cancel();
|
||||
_delayOperation = Timer(const Duration(milliseconds: 300), () {
|
||||
_delayOperation = Timer(const Duration(milliseconds: 30), () {
|
||||
if (_cellBloc.isClosed == false &&
|
||||
_controller.text != contentFromState(_cellBloc.state)) {
|
||||
_cellBloc.add(NumberCellEvent.updateCell(_controller.text));
|
||||
|
@ -73,7 +73,7 @@ class SelectOptionTag extends StatelessWidget {
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
factory SelectOptionTag.fromSelectOption({
|
||||
factory SelectOptionTag.fromOption({
|
||||
required BuildContext context,
|
||||
required SelectOptionPB option,
|
||||
VoidCallback? onSelected,
|
||||
@ -91,7 +91,8 @@ class SelectOptionTag extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return ChoiceChip(
|
||||
pressElevation: 1,
|
||||
label: FlowyText.medium(name, fontSize: 12, overflow: TextOverflow.ellipsis),
|
||||
label:
|
||||
FlowyText.medium(name, fontSize: 12, overflow: TextOverflow.ellipsis),
|
||||
selectedColor: color,
|
||||
backgroundColor: color,
|
||||
labelPadding: const EdgeInsets.symmetric(horizontal: 6),
|
||||
@ -133,7 +134,7 @@ class SelectOptionTagCell extends StatelessWidget {
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
flex: 2,
|
||||
child: SelectOptionTag.fromSelectOption(
|
||||
child: SelectOptionTag.fromOption(
|
||||
context: context,
|
||||
option: option,
|
||||
onSelected: () => onSelected(option),
|
||||
|