mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge branch 'main' into add_translated_titles_304
This commit is contained in:
commit
827e542c6e
3
.github/workflows/ci.yaml
vendored
3
.github/workflows/ci.yaml
vendored
@ -18,7 +18,7 @@ jobs:
|
|||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
flutter_profile: development-linux-x86
|
flutter_profile: development-linux-x86
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
flutter_profile: development-mac
|
flutter_profile: development-mac-x86_64
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -34,6 +34,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
channel: 'stable'
|
channel: 'stable'
|
||||||
cache: true
|
cache: true
|
||||||
|
flutter-version: '3.0.0'
|
||||||
|
|
||||||
- name: Cache Cargo
|
- name: Cache Cargo
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
|
2
.github/workflows/dart_lint.yml
vendored
2
.github/workflows/dart_lint.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- uses: subosito/flutter-action@v1
|
- uses: subosito/flutter-action@v1
|
||||||
with:
|
with:
|
||||||
flutter-version: '2.10.0'
|
flutter-version: '3.0.0'
|
||||||
channel: "stable"
|
channel: "stable"
|
||||||
- name: Deps Flutter
|
- name: Deps Flutter
|
||||||
run: flutter packages pub get
|
run: flutter packages pub get
|
||||||
|
1
.github/workflows/dart_test.yml
vendored
1
.github/workflows/dart_test.yml
vendored
@ -25,6 +25,7 @@ jobs:
|
|||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: 'stable'
|
channel: 'stable'
|
||||||
|
flutter-version: '3.0.0'
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Cache Cargo
|
- name: Cache Cargo
|
||||||
|
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@ -50,6 +50,7 @@ jobs:
|
|||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: 'stable'
|
channel: 'stable'
|
||||||
|
flutter-version: '3.0.0'
|
||||||
|
|
||||||
- name: Pre build
|
- name: Pre build
|
||||||
working-directory: frontend
|
working-directory: frontend
|
||||||
@ -98,6 +99,7 @@ jobs:
|
|||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: 'stable'
|
channel: 'stable'
|
||||||
|
flutter-version: '3.0.0'
|
||||||
|
|
||||||
- name: Pre build
|
- name: Pre build
|
||||||
working-directory: frontend
|
working-directory: frontend
|
||||||
@ -111,7 +113,7 @@ jobs:
|
|||||||
working-directory: frontend
|
working-directory: frontend
|
||||||
run: |
|
run: |
|
||||||
flutter config --enable-macos-desktop
|
flutter config --enable-macos-desktop
|
||||||
cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-mac-x86 appflowy
|
cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-mac-x86_64 appflowy
|
||||||
|
|
||||||
- name: Archive macOS app
|
- name: Archive macOS app
|
||||||
working-directory: ${{ env.MACOS_APP_RELEASE_PATH }}
|
working-directory: ${{ env.MACOS_APP_RELEASE_PATH }}
|
||||||
|
35
CHANGELOG.md
35
CHANGELOG.md
@ -1,5 +1,40 @@
|
|||||||
# Release Notes
|
# Release Notes
|
||||||
|
|
||||||
|
## Version 0.0.4 - 2022-06-06
|
||||||
|
- Drag to adjust the width of a column
|
||||||
|
- Upgrade to Flutter 3.0
|
||||||
|
- Native support for M1 chip
|
||||||
|
- Date supports time formats
|
||||||
|
- New property: URL
|
||||||
|
- Keyboard shortcuts support for Grid: press Enter to leave the edit mode; control c/v to copy-paste cell values
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
- Fixed some bugs
|
||||||
|
|
||||||
|
|
||||||
|
## Version 0.0.4 - beta.3 - 2022-05-02
|
||||||
|
- Drag to reorder app/ view/ field
|
||||||
|
- Row record open as a page
|
||||||
|
- Auto resize the height of the row in the grid
|
||||||
|
- Support more number formats
|
||||||
|
- Search column options, supporting Single select, Multi-select, and number format
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes & Improvements
|
||||||
|
- Improved row/cell data cache
|
||||||
|
- Fixed some bugs
|
||||||
|
|
||||||
|
|
||||||
|
## Version 0.0.4 - beta.2 - 2022-04-11
|
||||||
|
|
||||||
|
- Support properties: Text, Number, Date, Checkbox, Select, Multi-select
|
||||||
|
- Insert / delete rows
|
||||||
|
- Add / delete / hide columns
|
||||||
|
- Edit property
|
||||||
|

|
||||||
|
|
||||||
## Version 0.0.4 - beta.1 - 2022-04-08
|
## Version 0.0.4 - beta.1 - 2022-04-08
|
||||||
v0.0.4 - beta.1 is pre-release
|
v0.0.4 - beta.1 is pre-release
|
||||||
|
|
||||||
|
46
frontend/.vscode/launch.json
vendored
46
frontend/.vscode/launch.json
vendored
@ -5,40 +5,60 @@
|
|||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "app_flowy",
|
// This task builds the Rust and Dart code of AppFlowy.
|
||||||
|
"name": "AF: Build All",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "./lib/main.dart",
|
"program": "./lib/main.dart",
|
||||||
"type": "dart",
|
"type": "dart",
|
||||||
"preLaunchTask": "build_flowy_sdk",
|
"preLaunchTask": "AF: build_flowy_sdk",
|
||||||
"env":{
|
"env":{
|
||||||
"RUST_LOG":"info"
|
"RUST_LOG":"info"
|
||||||
},
|
},
|
||||||
"cwd": "${workspaceRoot}/app_flowy"
|
"cwd": "${workspaceRoot}/app_flowy"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "app_flowy(trace)",
|
// This task only builds the Dart code of AppFlowy.
|
||||||
|
"name": "AF: Build Dart Only",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceRoot}/lib/main.dart",
|
||||||
|
"type": "dart",
|
||||||
|
"env": {
|
||||||
|
"RUST_LOG": "debug"
|
||||||
|
},
|
||||||
|
"cwd": "${workspaceRoot}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// This task builds will:
|
||||||
|
// - call the clean task,
|
||||||
|
// - rebuild all the generated Files (including freeze and language files)
|
||||||
|
// - rebuild the the Rust and Dart code of AppFlowy.
|
||||||
|
"name": "AF: Clean + Rebuild All",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "./lib/main.dart",
|
"program": "./lib/main.dart",
|
||||||
"type": "dart",
|
"type": "dart",
|
||||||
"preLaunchTask": "build_flowy_sdk",
|
"preLaunchTask": "AF: Clean + Rebuild All",
|
||||||
|
"env":{
|
||||||
|
"RUST_LOG":"info"
|
||||||
|
},
|
||||||
|
"cwd": "${workspaceRoot}/app_flowy"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "AF: Build All (rustlog: trace)",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "./lib/main.dart",
|
||||||
|
"type": "dart",
|
||||||
|
"preLaunchTask": "AF: build_flowy_sdk",
|
||||||
"env":{
|
"env":{
|
||||||
"RUST_LOG":"trace"
|
"RUST_LOG":"trace"
|
||||||
},
|
},
|
||||||
"cwd": "${workspaceRoot}/app_flowy"
|
"cwd": "${workspaceRoot}/app_flowy"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "app_flowy (profile mode)",
|
"name": "AF: app_flowy (profile mode)",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"type": "dart",
|
"type": "dart",
|
||||||
"flutterMode": "profile"
|
"flutterMode": "profile"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "Generate Language Files",
|
|
||||||
"request": "launch",
|
|
||||||
"program": "./lib/main.dart",
|
|
||||||
"type": "dart",
|
|
||||||
"preLaunchTask": "Generate Language Files",
|
|
||||||
"cwd": "${workspaceRoot}/app_flowy/"
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
}
|
}
|
100
frontend/.vscode/tasks.json
vendored
100
frontend/.vscode/tasks.json
vendored
@ -10,13 +10,35 @@
|
|||||||
// ${cwd}: the current working directory of the spawned process
|
// ${cwd}: the current working directory of the spawned process
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
{
|
||||||
"label": "build_flowy_sdk",
|
"label": "AF: Clean + Rebuild All",
|
||||||
|
"type": "shell",
|
||||||
|
"dependsOrder": "sequence",
|
||||||
|
"dependsOn": [
|
||||||
|
"AF: Clean",
|
||||||
|
"AF: Flutter Pub",
|
||||||
|
"AF: Flutter Package Get",
|
||||||
|
"AF: Generate Language Files",
|
||||||
|
"AF: Generate Freezed Files",
|
||||||
|
"AF: build_flowy_sdk"
|
||||||
|
],
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true,
|
||||||
|
},
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "always",
|
||||||
|
"panel": "new"
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "AF: build_flowy_sdk",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "sh ./scripts/build_sdk.sh",
|
"command": "sh ./scripts/build_sdk.sh",
|
||||||
"windows": {
|
"windows": {
|
||||||
"options": {
|
"options": {
|
||||||
"env": {
|
"env": {
|
||||||
"FLOWY_DEV_ENV": "Windows",
|
"FLOWY_DEV_ENV": "Windows"
|
||||||
},
|
},
|
||||||
"shell": {
|
"shell": {
|
||||||
"executable": "cmd.exe",
|
"executable": "cmd.exe",
|
||||||
@ -31,27 +53,67 @@
|
|||||||
"linux": {
|
"linux": {
|
||||||
"options": {
|
"options": {
|
||||||
"env": {
|
"env": {
|
||||||
"FLOWY_DEV_ENV": "Linux-x86",
|
"FLOWY_DEV_ENV": "Linux-x86"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"osx": {
|
"osx": {
|
||||||
"options": {
|
"options": {
|
||||||
"env": {
|
"env": {
|
||||||
"FLOWY_DEV_ENV": "macOS",
|
"FLOWY_DEV_ENV": "macOS"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"options": {
|
"options": {
|
||||||
"cwd": "${workspaceFolder}"
|
"cwd": "${workspaceFolder}"
|
||||||
},
|
}
|
||||||
// "problemMatcher": [
|
|
||||||
// "$rustc"
|
|
||||||
// ],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Generate Language Files",
|
"label": "AF: Code Gen",
|
||||||
|
"type": "shell",
|
||||||
|
"dependsOrder": "sequence",
|
||||||
|
"dependsOn": [
|
||||||
|
"AF: Flutter Pub",
|
||||||
|
"AF: Flutter Package Get",
|
||||||
|
"AF: Generate Language Files",
|
||||||
|
"AF: Generate Freezed Files"
|
||||||
|
],
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true,
|
||||||
|
},
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "always",
|
||||||
|
"panel": "new"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "AF: Flutter Pub",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "flutter pub get",
|
||||||
|
"options": {
|
||||||
|
"cwd": "${workspaceFolder}/app_flowy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "AF: Flutter Package Get",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "flutter packages pub get",
|
||||||
|
"options": {
|
||||||
|
"cwd": "${workspaceFolder}/app_flowy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "AF: Generate Freezed Files",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "flutter pub run build_runner build --delete-conflicting-outputs",
|
||||||
|
"options": {
|
||||||
|
"cwd": "${workspaceFolder}/app_flowy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "AF: Generate Language Files",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "sh ./scripts/generate_language_files.sh",
|
"command": "sh ./scripts/generate_language_files.sh",
|
||||||
"windows": {
|
"windows": {
|
||||||
@ -69,10 +131,10 @@
|
|||||||
"group": "build",
|
"group": "build",
|
||||||
"options": {
|
"options": {
|
||||||
"cwd": "${workspaceFolder}"
|
"cwd": "${workspaceFolder}"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Clean",
|
"label": "AF: Clean",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "sh ./scripts/clean.sh",
|
"command": "sh ./scripts/clean.sh",
|
||||||
"windows": {
|
"windows": {
|
||||||
@ -90,7 +152,19 @@
|
|||||||
"group": "build",
|
"group": "build",
|
||||||
"options": {
|
"options": {
|
||||||
"cwd": "${workspaceFolder}"
|
"cwd": "${workspaceFolder}"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": "AF: flutter build aar",
|
||||||
|
"type": "flutter",
|
||||||
|
"command": "flutter",
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"aar"
|
||||||
|
],
|
||||||
|
"group": "build",
|
||||||
|
"problemMatcher": [],
|
||||||
|
"detail": "app_flowy"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -7,6 +7,7 @@ extend = [
|
|||||||
{ path = "scripts/makefile/docker.toml" },
|
{ path = "scripts/makefile/docker.toml" },
|
||||||
{ path = "scripts/makefile/env.toml" },
|
{ path = "scripts/makefile/env.toml" },
|
||||||
{ path = "scripts/makefile/flutter.toml" },
|
{ path = "scripts/makefile/flutter.toml" },
|
||||||
|
{ path = "scripts/makefile/tool.toml" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[config]
|
[config]
|
||||||
@ -44,7 +45,15 @@ APP_ENVIRONMENT = "local"
|
|||||||
FLUTTER_FLOWY_SDK_PATH="app_flowy/packages/flowy_sdk"
|
FLUTTER_FLOWY_SDK_PATH="app_flowy/packages/flowy_sdk"
|
||||||
PROTOBUF_DERIVE_CACHE="../shared-lib/flowy-derive/src/derive_cache/derive_cache.rs"
|
PROTOBUF_DERIVE_CACHE="../shared-lib/flowy-derive/src/derive_cache/derive_cache.rs"
|
||||||
|
|
||||||
[env.development-mac]
|
[env.development-mac-arm64]
|
||||||
|
RUST_LOG = "info"
|
||||||
|
TARGET_OS = "macos"
|
||||||
|
RUST_COMPILE_TARGET = "aarch64-apple-darwin"
|
||||||
|
BUILD_FLAG = "debug"
|
||||||
|
FLUTTER_OUTPUT_DIR = "Debug"
|
||||||
|
PRODUCT_EXT = "app"
|
||||||
|
|
||||||
|
[env.development-mac-x86_64]
|
||||||
RUST_LOG = "info"
|
RUST_LOG = "info"
|
||||||
TARGET_OS = "macos"
|
TARGET_OS = "macos"
|
||||||
RUST_COMPILE_TARGET = "x86_64-apple-darwin"
|
RUST_COMPILE_TARGET = "x86_64-apple-darwin"
|
||||||
@ -52,21 +61,23 @@ BUILD_FLAG = "debug"
|
|||||||
FLUTTER_OUTPUT_DIR = "Debug"
|
FLUTTER_OUTPUT_DIR = "Debug"
|
||||||
PRODUCT_EXT = "app"
|
PRODUCT_EXT = "app"
|
||||||
|
|
||||||
[env.production-mac-aarch64]
|
[env.production-mac-arm64]
|
||||||
BUILD_FLAG = "release"
|
BUILD_FLAG = "release"
|
||||||
TARGET_OS = "macos"
|
TARGET_OS = "macos"
|
||||||
RUST_COMPILE_TARGET = "aarch64-apple-darwin"
|
RUST_COMPILE_TARGET = "aarch64-apple-darwin"
|
||||||
FLUTTER_OUTPUT_DIR = "Release"
|
FLUTTER_OUTPUT_DIR = "Release"
|
||||||
PRODUCT_EXT = "app"
|
PRODUCT_EXT = "app"
|
||||||
APP_ENVIRONMENT = "production"
|
APP_ENVIRONMENT = "production"
|
||||||
|
BUILD_ARCHS = "arm64"
|
||||||
|
|
||||||
[env.production-mac-x86]
|
[env.production-mac-x86_64]
|
||||||
BUILD_FLAG = "release"
|
BUILD_FLAG = "release"
|
||||||
TARGET_OS = "macos"
|
TARGET_OS = "macos"
|
||||||
RUST_COMPILE_TARGET = "x86_64-apple-darwin"
|
RUST_COMPILE_TARGET = "x86_64-apple-darwin"
|
||||||
FLUTTER_OUTPUT_DIR = "Release"
|
FLUTTER_OUTPUT_DIR = "Release"
|
||||||
PRODUCT_EXT = "app"
|
PRODUCT_EXT = "app"
|
||||||
APP_ENVIRONMENT = "production"
|
APP_ENVIRONMENT = "production"
|
||||||
|
BUILD_ARCHS = "x86_64"
|
||||||
|
|
||||||
[env.development-windows-x86]
|
[env.development-windows-x86]
|
||||||
TARGET_OS = "windows"
|
TARGET_OS = "windows"
|
||||||
@ -137,6 +148,7 @@ script = [
|
|||||||
echo PRODUCT_EXT: ${PRODUCT_EXT}
|
echo PRODUCT_EXT: ${PRODUCT_EXT}
|
||||||
echo APP_ENVIRONMENT: ${APP_ENVIRONMENT}
|
echo APP_ENVIRONMENT: ${APP_ENVIRONMENT}
|
||||||
echo ${platforms}
|
echo ${platforms}
|
||||||
|
echo ${BUILD_ARCHS}
|
||||||
'''
|
'''
|
||||||
]
|
]
|
||||||
script_runner = "@shell"
|
script_runner = "@shell"
|
||||||
|
36
frontend/app_flowy/.vscode/launch.json
vendored
36
frontend/app_flowy/.vscode/launch.json
vendored
@ -1,36 +0,0 @@
|
|||||||
{
|
|
||||||
// Use IntelliSense to learn about possible attributes.
|
|
||||||
// Hover to view descriptions of existing attributes.
|
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "app_flowy",
|
|
||||||
"request": "launch",
|
|
||||||
"program": "${workspaceRoot}/lib/main.dart",
|
|
||||||
"type": "dart",
|
|
||||||
"preLaunchTask": "build_flowy_sdk",
|
|
||||||
"env": {
|
|
||||||
"RUST_LOG": "debug"
|
|
||||||
},
|
|
||||||
"cwd": "${workspaceRoot}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "app_flowy(trace)",
|
|
||||||
"request": "launch",
|
|
||||||
"program": "${workspaceRoot}/lib/main.dart",
|
|
||||||
"type": "dart",
|
|
||||||
"preLaunchTask": "build_flowy_sdk",
|
|
||||||
"env": {
|
|
||||||
"RUST_LOG": "trace"
|
|
||||||
},
|
|
||||||
"cwd": "${workspaceRoot}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "app_flowy (profile mode)",
|
|
||||||
"request": "launch",
|
|
||||||
"type": "dart",
|
|
||||||
"flutterMode": "profile"
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
26
frontend/app_flowy/.vscode/settings.json
vendored
26
frontend/app_flowy/.vscode/settings.json
vendored
@ -1,26 +0,0 @@
|
|||||||
{
|
|
||||||
"[dart]": {
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"editor.formatOnType": true,
|
|
||||||
"editor.rulers": [
|
|
||||||
120
|
|
||||||
],
|
|
||||||
"editor.selectionHighlight": false,
|
|
||||||
"editor.suggest.snippetsPreventQuickSuggestions": false,
|
|
||||||
"editor.suggestSelection": "first",
|
|
||||||
"editor.tabCompletion": "onlySnippets",
|
|
||||||
"editor.wordBasedSuggestions": false
|
|
||||||
},
|
|
||||||
"svgviewer.enableautopreview": true,
|
|
||||||
"svgviewer.previewcolumn": "Active",
|
|
||||||
"svgviewer.showzoominout": true,
|
|
||||||
"editor.wordWrapColumn": 120,
|
|
||||||
"editor.minimap.maxColumn": 140,
|
|
||||||
"prettier.printWidth": 140,
|
|
||||||
"editor.wordWrap": "wordWrapColumn",
|
|
||||||
"dart.lineLength": 120,
|
|
||||||
"files.associations": {
|
|
||||||
"*.log.*": "log"
|
|
||||||
},
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
}
|
|
129
frontend/app_flowy/.vscode/tasks.json
vendored
129
frontend/app_flowy/.vscode/tasks.json
vendored
@ -1,129 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "2.0.0",
|
|
||||||
// https://code.visualstudio.com/docs/editor/tasks
|
|
||||||
// https://gist.github.com/deadalusai/9e13e36d61ec7fb72148
|
|
||||||
// ${workspaceRoot}: the root folder of the team
|
|
||||||
// ${file}: the current opened file
|
|
||||||
// ${fileBasename}: the current opened file's basename
|
|
||||||
// ${fileDirname}: the current opened file's dirname
|
|
||||||
// ${fileExtname}: the current opened file's extension
|
|
||||||
// ${cwd}: the current working directory of the spawned process
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"label": "build_flowy_sdk",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "sh ./scripts/build_sdk.sh",
|
|
||||||
"windows": {
|
|
||||||
"options": {
|
|
||||||
"env": {
|
|
||||||
"FLOWY_DEV_ENV": "Windows",
|
|
||||||
},
|
|
||||||
"shell": {
|
|
||||||
"executable": "cmd.exe",
|
|
||||||
"args": [
|
|
||||||
"/d",
|
|
||||||
"/c",
|
|
||||||
".\\scripts\\build_sdk.cmd"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"linux": {
|
|
||||||
"options": {
|
|
||||||
"env": {
|
|
||||||
"FLOWY_DEV_ENV": "Linux-x86",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"osx": {
|
|
||||||
"options": {
|
|
||||||
"env": {
|
|
||||||
"FLOWY_DEV_ENV": "macOS",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"group": "build",
|
|
||||||
"options": {
|
|
||||||
"cwd": "${workspaceFolder}/../"
|
|
||||||
},
|
|
||||||
// "problemMatcher": [
|
|
||||||
// "$rustc"
|
|
||||||
// ],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Code Gen",
|
|
||||||
"type": "shell",
|
|
||||||
"dependsOn": [
|
|
||||||
"Flutter Pub",
|
|
||||||
"Flutter Package Get",
|
|
||||||
"Generate Language Files",
|
|
||||||
"Generate Freezed Files"
|
|
||||||
],
|
|
||||||
"group": {
|
|
||||||
"kind": "build",
|
|
||||||
"isDefault": true,
|
|
||||||
},
|
|
||||||
"dependsOrder": "sequence",
|
|
||||||
"presentation": {
|
|
||||||
"reveal": "always",
|
|
||||||
"panel": "new"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Flutter Pub",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "flutter pub get",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Flutter Package Get",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "flutter packages pub get",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Generate Freezed Files",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "flutter pub run build_runner build --delete-conflicting-outputs",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Generate Language Files",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "sh ./scripts/generate_language_files.sh",
|
|
||||||
"windows": {
|
|
||||||
"options": {
|
|
||||||
"shell": {
|
|
||||||
"executable": "cmd.exe",
|
|
||||||
"args": [
|
|
||||||
"/d",
|
|
||||||
"/c",
|
|
||||||
".\\scripts\\generate_language_files.cmd"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"group": "build",
|
|
||||||
"options": {
|
|
||||||
"cwd": "${workspaceFolder}/../"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Clean",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "sh ./scripts/clean.sh",
|
|
||||||
"windows": {
|
|
||||||
"options": {
|
|
||||||
"shell": {
|
|
||||||
"executable": "cmd.exe",
|
|
||||||
"args": [
|
|
||||||
"/d",
|
|
||||||
"/c",
|
|
||||||
".\\scripts\\clean.cmd"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"options": {
|
|
||||||
"cwd": "${workspaceFolder}/../"
|
|
||||||
},
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
3
frontend/app_flowy/assets/images/grid/field/url.svg
Normal file
3
frontend/app_flowy/assets/images/grid/field/url.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M13 7.688L8.27223 12.1469C7.69304 12.6931 6.90749 13 6.0884 13C5.26931 13 4.48376 12.6931 3.90457 12.1469C3.32538 11.6006 3 10.8598 3 10.0873C3 9.31474 3.32538 8.57387 3.90457 8.02763L8.63234 3.56875C9.01847 3.20459 9.54216 3 10.0882 3C10.6343 3 11.158 3.20459 11.5441 3.56875C11.9302 3.93291 12.1472 4.42683 12.1472 4.94183C12.1472 5.45684 11.9302 5.95075 11.5441 6.31491L6.8112 10.7738C6.61814 10.9559 6.35629 11.0582 6.08326 11.0582C5.81022 11.0582 5.54838 10.9559 5.35531 10.7738C5.16225 10.5917 5.05379 10.3448 5.05379 10.0873C5.05379 9.82975 5.16225 9.58279 5.35531 9.40071L9.72297 5.28632" stroke="#333333" stroke-width="0.9989" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 797 B |
@ -96,6 +96,12 @@
|
|||||||
"lightMode": "Switch to Light mode",
|
"lightMode": "Switch to Light mode",
|
||||||
"darkMode": "Switch to Dark mode"
|
"darkMode": "Switch to Dark mode"
|
||||||
},
|
},
|
||||||
|
"notifications": {
|
||||||
|
"export": {
|
||||||
|
"markdown": "Exported Note To Markdown",
|
||||||
|
"path": "Documents/flowy"
|
||||||
|
}
|
||||||
|
},
|
||||||
"contactsPage": {
|
"contactsPage": {
|
||||||
"title": "Contacts",
|
"title": "Contacts",
|
||||||
"whatsHappening": "What's happening this week?",
|
"whatsHappening": "What's happening this week?",
|
||||||
@ -160,6 +166,7 @@
|
|||||||
"numberFieldName": "Numbers",
|
"numberFieldName": "Numbers",
|
||||||
"singleSelectFieldName": "Select",
|
"singleSelectFieldName": "Select",
|
||||||
"multiSelectFieldName": "Multiselect",
|
"multiSelectFieldName": "Multiselect",
|
||||||
|
"urlFieldName": "URL",
|
||||||
"numberFormat": " Number format",
|
"numberFormat": " Number format",
|
||||||
"dateFormat": " Date format",
|
"dateFormat": " Date format",
|
||||||
"includeTime": " Include time",
|
"includeTime": " Include time",
|
||||||
@ -168,6 +175,7 @@
|
|||||||
"dateFormatLocal": "Month/Month/Day",
|
"dateFormatLocal": "Month/Month/Day",
|
||||||
"dateFormatUS": "Month/Month/Day",
|
"dateFormatUS": "Month/Month/Day",
|
||||||
"timeFormat": " Time format",
|
"timeFormat": " Time format",
|
||||||
|
"invalidTimeFormat": "Invalid format",
|
||||||
"timeFormatTwelveHour": "12 hour",
|
"timeFormatTwelveHour": "12 hour",
|
||||||
"timeFormatTwentyFourHour": "24 hour",
|
"timeFormatTwentyFourHour": "24 hour",
|
||||||
"addSelectOption": "Add an option",
|
"addSelectOption": "Add an option",
|
||||||
@ -178,7 +186,8 @@
|
|||||||
"row": {
|
"row": {
|
||||||
"duplicate": "Duplicate",
|
"duplicate": "Duplicate",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"textPlaceholder": "Empty"
|
"textPlaceholder": "Empty",
|
||||||
|
"copyProperty": "Copied property to clipboard"
|
||||||
},
|
},
|
||||||
"selectOption": {
|
"selectOption": {
|
||||||
"create": "Create",
|
"create": "Create",
|
||||||
@ -200,5 +209,9 @@
|
|||||||
},
|
},
|
||||||
"document":{
|
"document":{
|
||||||
"menuName":"Doc"
|
"menuName":"Doc"
|
||||||
|
"date": {
|
||||||
|
"timeHintTextInTwelveHour": "12:00 AM",
|
||||||
|
"timeHintTextInTwentyFourHour": "12:00"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,11 @@
|
|||||||
"letsGoButtonText": "Vamos lá",
|
"letsGoButtonText": "Vamos lá",
|
||||||
"title": "Título",
|
"title": "Título",
|
||||||
"signUp": {
|
"signUp": {
|
||||||
"buttonText": "Inscreve-se",
|
"buttonText": "Se inscreva",
|
||||||
"title": "Inscrever-se @:appName",
|
"title": "Se inscreva no @:appName",
|
||||||
"getStartedText": "Começar",
|
"getStartedText": "Começar",
|
||||||
"emptyPasswordError": "Senha não pode ser em branco.",
|
"emptyPasswordError": "Senha não pode estar em branco.",
|
||||||
"repeatPasswordEmptyError": "Confirmar a senha não pode ser em branco.",
|
"repeatPasswordEmptyError": "Confirmar a senha não pode estar em branco.",
|
||||||
"unmatchedPasswordError": "As senhas não conferem.",
|
"unmatchedPasswordError": "As senhas não conferem.",
|
||||||
"alreadyHaveAnAccount": "Já possui uma conta?",
|
"alreadyHaveAnAccount": "Já possui uma conta?",
|
||||||
"emailHint": "Email",
|
"emailHint": "Email",
|
||||||
@ -19,14 +19,14 @@
|
|||||||
"repeatPasswordHint": "Confirme a senha"
|
"repeatPasswordHint": "Confirme a senha"
|
||||||
},
|
},
|
||||||
"signIn": {
|
"signIn": {
|
||||||
"loginTitle": "Login to @:appName",
|
"loginTitle": "Entre no @:appName",
|
||||||
"loginButtonText": "Login",
|
"loginButtonText": "Login",
|
||||||
"buttonText": "Entre",
|
"buttonText": "Entre",
|
||||||
"forgotPassword": "Esqueceu a senha?",
|
"forgotPassword": "Esqueceu a senha?",
|
||||||
"emailHint": "Email",
|
"emailHint": "Email",
|
||||||
"passwordHint": "Senha",
|
"passwordHint": "Senha",
|
||||||
"dontHaveAnAccount": "Não possui uma conta?",
|
"dontHaveAnAccount": "Não possui uma conta?",
|
||||||
"repeatPasswordEmptyError": "Confirmar a senha não pode ser em branco.",
|
"repeatPasswordEmptyError": "Confirmar a senha não pode estar em branco.",
|
||||||
"unmatchedPasswordError": "As senhas não conferem."
|
"unmatchedPasswordError": "As senhas não conferem."
|
||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
@ -67,7 +67,7 @@
|
|||||||
"whatsNew": "O que há de novo?",
|
"whatsNew": "O que há de novo?",
|
||||||
"help": "Ajuda & Suporte",
|
"help": "Ajuda & Suporte",
|
||||||
"debug": {
|
"debug": {
|
||||||
"name": "Informação de debug",
|
"name": "Informação de depuração",
|
||||||
"success": "Copiar informação de debug para o clipboard!",
|
"success": "Copiar informação de debug para o clipboard!",
|
||||||
"fail": "Falha em copiar a informação de debug para o clipboard"
|
"fail": "Falha em copiar a informação de debug para o clipboard"
|
||||||
}
|
}
|
||||||
@ -104,7 +104,7 @@
|
|||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
"OK": "OK",
|
"OK": "OK",
|
||||||
"Cancel": "Canelar",
|
"Cancel": "Cancelar",
|
||||||
"signIn": "Entrar",
|
"signIn": "Entrar",
|
||||||
"signOut": "Sair",
|
"signOut": "Sair",
|
||||||
"complete": "Completar",
|
"complete": "Completar",
|
||||||
@ -144,3 +144,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
146
frontend/app_flowy/assets/translations/pt-PT.json
Normal file
146
frontend/app_flowy/assets/translations/pt-PT.json
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
{
|
||||||
|
"appName": "AppFlowy",
|
||||||
|
"defaultUsername": "Me",
|
||||||
|
"welcomeText": "Bem vindo ao @:appName",
|
||||||
|
"githubStarText": "Star on GitHub",
|
||||||
|
"subscribeNewsletterText": "Inscreve-te ao Newsletter",
|
||||||
|
"letsGoButtonText": "Bora",
|
||||||
|
"title": "Título",
|
||||||
|
"signUp": {
|
||||||
|
"buttonText": "Inscreve-te",
|
||||||
|
"title": "Inscreve-te ao @:appName",
|
||||||
|
"getStartedText": "Começar",
|
||||||
|
"emptyPasswordError": "A palavra-passe não pode estar em branco.",
|
||||||
|
"repeatPasswordEmptyError": "Confirmar a palavra-passe não pode estar em branco.",
|
||||||
|
"unmatchedPasswordError": "As palavras-passes não coincidem.",
|
||||||
|
"alreadyHaveAnAccount": "Já possuis uma conta?",
|
||||||
|
"emailHint": "Email",
|
||||||
|
"passwordHint": "Password",
|
||||||
|
"repeatPasswordHint": "Confirma a tua password"
|
||||||
|
},
|
||||||
|
"signIn": {
|
||||||
|
"loginTitle": "Entre no @:appName",
|
||||||
|
"loginButtonText": "Login",
|
||||||
|
"buttonText": "Entre",
|
||||||
|
"forgotPassword": "Esqueceste-te da tua palavra-passe?",
|
||||||
|
"emailHint": "Email",
|
||||||
|
"passwordHint": "Palavra-passe",
|
||||||
|
"dontHaveAnAccount": "Não possuis uma conta?",
|
||||||
|
"repeatPasswordEmptyError": "Confirmar a palavra-passe não pode estar em branco.",
|
||||||
|
"unmatchedPasswordError": "As palavras-passes não conferem."
|
||||||
|
},
|
||||||
|
"workspace": {
|
||||||
|
"create": "Cria um ambiente de trabalho",
|
||||||
|
"hint": "ambiente de trabalho",
|
||||||
|
"notFoundError": "Ambiente de trabalho não encontrada"
|
||||||
|
},
|
||||||
|
"shareAction": {
|
||||||
|
"buttonText": "Partilhar",
|
||||||
|
"workInProgress": "Em breve",
|
||||||
|
"markdown": "Markdown",
|
||||||
|
"copyLink": "Copiar o link"
|
||||||
|
},
|
||||||
|
"disclosureAction": {
|
||||||
|
"rename": "Renomear",
|
||||||
|
"delete": "Apagar",
|
||||||
|
"duplicate": "Duplicar"
|
||||||
|
},
|
||||||
|
"blankPageTitle": "Página em branco",
|
||||||
|
"newPageText": "Nova página",
|
||||||
|
"trash": {
|
||||||
|
"text": "Lixo",
|
||||||
|
"restoreAll": "Restaurar todos",
|
||||||
|
"deleteAll": "Apagar todos",
|
||||||
|
"pageHeader": {
|
||||||
|
"fileName": "Nome do ficheiro",
|
||||||
|
"lastModified": "Última modificação",
|
||||||
|
"created": "Criado"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deletePagePrompt": {
|
||||||
|
"text": "Esta página está no lixo",
|
||||||
|
"restore": "Restaurar a página",
|
||||||
|
"deletePermanent": "Apagar permanentemente"
|
||||||
|
},
|
||||||
|
"dialogCreatePageNameHint": "Nome da página",
|
||||||
|
"questionBubble": {
|
||||||
|
"whatsNew": "O que há de novo?",
|
||||||
|
"help": "Ajuda & Suporte",
|
||||||
|
"debug": {
|
||||||
|
"name": "Informação de depuração",
|
||||||
|
"success": "Copiar informação de depuração para o clipboard!",
|
||||||
|
"fail": "Falha em copiar a informação de depuração para o clipboard"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menuAppHeader": {
|
||||||
|
"addPageTooltip": "Adiciona uma nova página.",
|
||||||
|
"defaultNewPageName": "Sem título",
|
||||||
|
"renameDialog": "Renomear"
|
||||||
|
},
|
||||||
|
"toolbar": {
|
||||||
|
"undo": "Desfazer",
|
||||||
|
"redo": "Refazer",
|
||||||
|
"bold": "Negrito",
|
||||||
|
"italic": "Itálico",
|
||||||
|
"underline": "Sublinhado",
|
||||||
|
"strike": "Riscado",
|
||||||
|
"numList": "Lista numerada",
|
||||||
|
"bulletList": "Lista com marcadores",
|
||||||
|
"checkList": "Lista de verificação",
|
||||||
|
"inlineCode": "Embutir código",
|
||||||
|
"quote": "Citação em bloco",
|
||||||
|
"header": "Cabeçalho",
|
||||||
|
"highlight": "Realçar"
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"lightMode": "Mudar para o modo Claro.",
|
||||||
|
"darkMode": "Mudar para o modo Escuro."
|
||||||
|
},
|
||||||
|
"contactsPage": {
|
||||||
|
"title": "Conctatos",
|
||||||
|
"whatsHappening": "O que está a acontecer nesta semana?",
|
||||||
|
"addContact": "Adicionar um conctato",
|
||||||
|
"editContact": "Editar um conctato"
|
||||||
|
},
|
||||||
|
"button": {
|
||||||
|
"OK": "OK",
|
||||||
|
"Cancel": "Cancelar",
|
||||||
|
"signIn": "Entrar",
|
||||||
|
"signOut": "Sair",
|
||||||
|
"complete": "Completar",
|
||||||
|
"save": "Guardar"
|
||||||
|
},
|
||||||
|
"label": {
|
||||||
|
"welcome": "Bem vindo!",
|
||||||
|
"firstName": "Nome",
|
||||||
|
"middleName": "Nome do Meio",
|
||||||
|
"lastName": "Apelido",
|
||||||
|
"stepX": "Passo {X}"
|
||||||
|
},
|
||||||
|
"oAuth": {
|
||||||
|
"err": {
|
||||||
|
"failedTitle": "Erro ao conectar à sua conta.",
|
||||||
|
"failedMsg": "Verifica se concluiste o processo de login no teu navegador."
|
||||||
|
},
|
||||||
|
"google": {
|
||||||
|
"title": "GOOGLE SIGN-IN",
|
||||||
|
"instruction1": "Para importar os teus Conctatos do Google, tens de autorizar esta aplicação usando o teu navegador web.",
|
||||||
|
"instruction2": "Copia este código para a tua área de transferências clicando no ícone ou selecionando o texto:",
|
||||||
|
"instruction3": "Navega até o link a seguir no seu navegador e digite o código acima:",
|
||||||
|
"instruction4": "Clica no botão abaixo ao concluir a inscrição:"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "Definições",
|
||||||
|
"menu": {
|
||||||
|
"appearance": "Aparência",
|
||||||
|
"language": "Idioma",
|
||||||
|
"open": "Abrir as Definições"
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"lightLabel": "Modo Claro",
|
||||||
|
"darkLabel": "Modo Escuro"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -141,6 +141,68 @@
|
|||||||
"lightLabel": "Светлая тема",
|
"lightLabel": "Светлая тема",
|
||||||
"darkLabel": "Тёмная тема"
|
"darkLabel": "Тёмная тема"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"grid": {
|
||||||
|
"settings": {
|
||||||
|
"filter": "Фильтр",
|
||||||
|
"sortBy": "Сортировать",
|
||||||
|
"Properties": "Свойства"
|
||||||
|
},
|
||||||
|
"field": {
|
||||||
|
"hide": "Скрыть",
|
||||||
|
"insertLeft": "Вставить слева",
|
||||||
|
"insertRight": "Вставить справа",
|
||||||
|
"duplicate": "Дублировать",
|
||||||
|
"delete": "Удалить",
|
||||||
|
"textFieldName": "Текст",
|
||||||
|
"checkboxFieldName": "Checkbox",
|
||||||
|
"dateFieldName": "Дата",
|
||||||
|
"numberFieldName": "Число",
|
||||||
|
"singleSelectFieldName": "Выбор",
|
||||||
|
"multiSelectFieldName": "Выбор многих",
|
||||||
|
"urlFieldName": "URL",
|
||||||
|
"numberFormat": " Формат числа",
|
||||||
|
"dateFormat": " Формат даты",
|
||||||
|
"includeTime": " Время",
|
||||||
|
"dateFormatFriendly": "День Месяц, Год",
|
||||||
|
"dateFormatISO": "Год-Месяц-День",
|
||||||
|
"dateFormatLocal": "Год/Месяц/День",
|
||||||
|
"dateFormatUS": "Год/Месяц/День",
|
||||||
|
"timeFormat": " Форматировать время",
|
||||||
|
"invalidTimeFormat": "Неверный формат",
|
||||||
|
"timeFormatTwelveHour": "12 часов",
|
||||||
|
"timeFormatTwentyFourHour": "24 часа",
|
||||||
|
"addSelectOption": "Добавить вариант",
|
||||||
|
"optionTitle": "Варианты",
|
||||||
|
"addOption": "Добавить",
|
||||||
|
"editProperty": "Редактировать свойство"
|
||||||
|
},
|
||||||
|
"row": {
|
||||||
|
"duplicate": "Дублировать",
|
||||||
|
"delete": "Удалить",
|
||||||
|
"textPlaceholder": "Пусто",
|
||||||
|
"copyProperty": "Свойство скопировано"
|
||||||
|
},
|
||||||
|
"selectOption": {
|
||||||
|
"create": "Создать",
|
||||||
|
"purpleColor": "Фиолетовый",
|
||||||
|
"pinkColor": "Розовый",
|
||||||
|
"lightPinkColor": "Светло-розовый",
|
||||||
|
"orangeColor": "Оранжевый",
|
||||||
|
"yellowColor": "Желтый",
|
||||||
|
"limeColor": "Ярко-зелёный",
|
||||||
|
"greenColor": "Зелёный",
|
||||||
|
"aquaColor": "Морской волны",
|
||||||
|
"blueColor": "Синий",
|
||||||
|
"deleteTag": "Удалить вариант",
|
||||||
|
"colorPannelTitle": "Цвета",
|
||||||
|
"pannelTitle": "Выберите или создайте вариант",
|
||||||
|
"searchOption": "Поиск"
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"timeHintTextInTwelveHour": "12:00 AM",
|
||||||
|
"timeHintTextInTwentyFourHour": "12:00"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
67
frontend/app_flowy/lib/core/frameless_window.dart
Normal file
67
frontend/app_flowy/lib/core/frameless_window.dart
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'dart:io' show Platform;
|
||||||
|
|
||||||
|
class CocoaWindowChannel {
|
||||||
|
CocoaWindowChannel._();
|
||||||
|
|
||||||
|
final MethodChannel _channel = const MethodChannel("flutter/cocoaWindow");
|
||||||
|
|
||||||
|
static final CocoaWindowChannel instance = CocoaWindowChannel._();
|
||||||
|
|
||||||
|
Future<void> setWindowPosition(Offset offset) async {
|
||||||
|
await _channel.invokeMethod("setWindowPosition", [offset.dx, offset.dy]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<double>> getWindowPosition() async {
|
||||||
|
final raw = await _channel.invokeMethod("getWindowPosition");
|
||||||
|
final arr = raw as List<dynamic>;
|
||||||
|
final List<double> result = arr.map((s) => s as double).toList();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> zoom() async {
|
||||||
|
await _channel.invokeMethod("zoom");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MoveWindowDetector extends StatefulWidget {
|
||||||
|
const MoveWindowDetector({Key? key, this.child}) : super(key: key);
|
||||||
|
|
||||||
|
final Widget? child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_MoveWindowDetectorState createState() => _MoveWindowDetectorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MoveWindowDetectorState extends State<MoveWindowDetector> {
|
||||||
|
double winX = 0;
|
||||||
|
double winY = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (!Platform.isMacOS) {
|
||||||
|
return widget.child ?? Container();
|
||||||
|
}
|
||||||
|
return GestureDetector(
|
||||||
|
// https://stackoverflow.com/questions/52965799/flutter-gesturedetector-not-working-with-containers-in-stack
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
onDoubleTap: () async {
|
||||||
|
await CocoaWindowChannel.instance.zoom();
|
||||||
|
},
|
||||||
|
onPanStart: (DragStartDetails details) {
|
||||||
|
winX = details.globalPosition.dx;
|
||||||
|
winY = details.globalPosition.dy;
|
||||||
|
},
|
||||||
|
onPanUpdate: (DragUpdateDetails details) async {
|
||||||
|
final windowPos = await CocoaWindowChannel.instance.getWindowPosition();
|
||||||
|
final double dx = windowPos[0];
|
||||||
|
final double dy = windowPos[1];
|
||||||
|
final deltaX = details.globalPosition.dx - winX;
|
||||||
|
final deltaY = details.globalPosition.dy - winY;
|
||||||
|
await CocoaWindowChannel.instance.setWindowPosition(Offset(dx + deltaX, dy - deltaY));
|
||||||
|
},
|
||||||
|
child: widget.child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -15,10 +15,8 @@ import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
|||||||
import 'package:app_flowy/workspace/presentation/home/menu/menu.dart';
|
import 'package:app_flowy/workspace/presentation/home/menu/menu.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show EditFieldContext;
|
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
|
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
|
|
||||||
import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
|
||||||
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
|
|
||||||
class DependencyResolver {
|
class DependencyResolver {
|
||||||
@ -49,6 +47,8 @@ void _resolveUserDeps(GetIt getIt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _resolveHomeDeps(GetIt getIt) {
|
void _resolveHomeDeps(GetIt getIt) {
|
||||||
|
getIt.registerSingleton(FToast());
|
||||||
|
|
||||||
getIt.registerSingleton(MenuSharedState());
|
getIt.registerSingleton(MenuSharedState());
|
||||||
|
|
||||||
getIt.registerFactoryParam<UserListener, UserProfile, void>(
|
getIt.registerFactoryParam<UserListener, UserProfile, void>(
|
||||||
@ -157,21 +157,14 @@ void _resolveGridDeps(GetIt getIt) {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
getIt.registerFactoryParam<FieldEditorBloc, String, EditFieldContextLoader>(
|
|
||||||
(gridId, fieldLoader) => FieldEditorBloc(
|
|
||||||
gridId: gridId,
|
|
||||||
fieldLoader: fieldLoader,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
getIt.registerFactoryParam<TextCellBloc, GridCellContext, void>(
|
getIt.registerFactoryParam<TextCellBloc, GridCellContext, void>(
|
||||||
(context, _) => TextCellBloc(
|
(context, _) => TextCellBloc(
|
||||||
cellContext: context,
|
cellContext: context,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
getIt.registerFactoryParam<SelectionCellBloc, GridSelectOptionCellContext, void>(
|
getIt.registerFactoryParam<SelectOptionCellBloc, GridSelectOptionCellContext, void>(
|
||||||
(context, _) => SelectionCellBloc(
|
(context, _) => SelectOptionCellBloc(
|
||||||
cellContext: context,
|
cellContext: context,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -195,18 +188,6 @@ void _resolveGridDeps(GetIt getIt) {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
getIt.registerFactoryParam<FieldEditorPannelBloc, EditFieldContext, void>(
|
|
||||||
(context, _) => FieldEditorPannelBloc(context),
|
|
||||||
);
|
|
||||||
|
|
||||||
getIt.registerFactoryParam<DateTypeOptionBloc, DateTypeOption, void>(
|
|
||||||
(typeOption, _) => DateTypeOptionBloc(typeOption: typeOption),
|
|
||||||
);
|
|
||||||
|
|
||||||
getIt.registerFactoryParam<NumberTypeOptionBloc, NumberTypeOption, void>(
|
|
||||||
(typeOption, _) => NumberTypeOptionBloc(typeOption: typeOption),
|
|
||||||
);
|
|
||||||
|
|
||||||
getIt.registerFactoryParam<GridPropertyBloc, String, GridFieldCache>(
|
getIt.registerFactoryParam<GridPropertyBloc, String, GridFieldCache>(
|
||||||
(gridId, cache) => GridPropertyBloc(gridId: gridId, fieldCache: cache),
|
(gridId, cache) => GridPropertyBloc(gridId: gridId, fieldCache: cache),
|
||||||
);
|
);
|
||||||
|
@ -67,7 +67,8 @@ class ApplicationWidget extends StatelessWidget {
|
|||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => ChangeNotifierProvider.value(
|
Widget build(BuildContext context) {
|
||||||
|
return ChangeNotifierProvider.value(
|
||||||
value: settingModel,
|
value: settingModel,
|
||||||
builder: (context, _) {
|
builder: (context, _) {
|
||||||
const ratio = 1.73;
|
const ratio = 1.73;
|
||||||
@ -101,6 +102,7 @@ class ApplicationWidget extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppGlobals {
|
class AppGlobals {
|
||||||
|
@ -88,8 +88,9 @@ class _SkipLogInScreenState extends State<SkipLogInScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_launchURL(String url) async {
|
_launchURL(String url) async {
|
||||||
if (await canLaunch(url)) {
|
final uri = Uri.parse(url);
|
||||||
await launch(url);
|
if (await canLaunchUrl(uri)) {
|
||||||
|
await launchUrl(uri);
|
||||||
} else {
|
} else {
|
||||||
throw 'Could not launch $url';
|
throw 'Could not launch $url';
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
import 'package:app_flowy/plugin/plugin.dart';
|
import 'package:app_flowy/plugin/plugin.dart';
|
||||||
import 'package:app_flowy/startup/startup.dart';
|
import 'package:app_flowy/startup/startup.dart';
|
||||||
import 'package:app_flowy/workspace/application/app/app_listener.dart';
|
import 'package:app_flowy/workspace/application/app/app_listener.dart';
|
||||||
|
@ -12,14 +12,14 @@ class DocumentService {
|
|||||||
await FolderEventSetLatestView(ViewId(value: docId)).send();
|
await FolderEventSetLatestView(ViewId(value: docId)).send();
|
||||||
|
|
||||||
final payload = TextBlockId(value: docId);
|
final payload = TextBlockId(value: docId);
|
||||||
return BlockEventGetBlockData(payload).send();
|
return TextBlockEventGetBlockData(payload).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Either<TextBlockDelta, FlowyError>> composeDelta({required String docId, required String data}) {
|
Future<Either<TextBlockDelta, FlowyError>> composeDelta({required String docId, required String data}) {
|
||||||
final payload = TextBlockDelta.create()
|
final payload = TextBlockDelta.create()
|
||||||
..blockId = docId
|
..blockId = docId
|
||||||
..deltaStr = data;
|
..deltaStr = data;
|
||||||
return BlockEventApplyDelta(payload).send();
|
return TextBlockEventApplyDelta(payload).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Either<Unit, FlowyError>> closeDocument({required String docId}) {
|
Future<Either<Unit, FlowyError>> closeDocument({required String docId}) {
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:app_flowy/startup/tasks/rust_sdk.dart';
|
||||||
import 'package:app_flowy/workspace/application/doc/share_service.dart';
|
import 'package:app_flowy/workspace/application/doc/share_service.dart';
|
||||||
import 'package:app_flowy/workspace/application/markdown/delta_markdown.dart';
|
import 'package:app_flowy/workspace/application/markdown/delta_markdown.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-text-block/entities.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-text-block/entities.pb.dart';
|
||||||
@ -33,8 +36,30 @@ class DocShareBloc extends Bloc<DocShareEvent, DocShareState> {
|
|||||||
ExportData _convertDeltaToMarkdown(ExportData value) {
|
ExportData _convertDeltaToMarkdown(ExportData value) {
|
||||||
final result = deltaToMarkdown(value.data);
|
final result = deltaToMarkdown(value.data);
|
||||||
value.data = result;
|
value.data = result;
|
||||||
|
writeFile(result);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Directory> get _exportDir async {
|
||||||
|
Directory documentsDir = await appFlowyDocumentDirectory();
|
||||||
|
|
||||||
|
return documentsDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> get _localPath async {
|
||||||
|
final dir = await _exportDir;
|
||||||
|
return dir.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<File> get _localFile async {
|
||||||
|
final path = await _localPath;
|
||||||
|
return File('$path/${view.name}.md');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<File> writeFile(String md) async {
|
||||||
|
final file = await _localFile;
|
||||||
|
return file.writeAsString(md);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
|
@ -10,7 +10,7 @@ class ShareService {
|
|||||||
..viewId = docId
|
..viewId = docId
|
||||||
..exportType = type;
|
..exportType = type;
|
||||||
|
|
||||||
return BlockEventExportDocument(request).send();
|
return TextBlockEventExportDocument(request).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Either<ExportData, FlowyError>> exportText(String docId) {
|
Future<Either<ExportData, FlowyError>> exportText(String docId) {
|
||||||
|
@ -10,13 +10,13 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
|||||||
import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||||
|
import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
|
import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
|
||||||
import 'package:app_flowy/workspace/application/grid/cell/select_option_service.dart';
|
import 'package:app_flowy/workspace/application/grid/cell/select_option_service.dart';
|
||||||
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
|
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
|
||||||
|
import 'dart:convert' show utf8;
|
||||||
part 'cell_service.freezed.dart';
|
part 'cell_service.freezed.dart';
|
||||||
part 'data_loader.dart';
|
part 'data_loader.dart';
|
||||||
part 'context_builder.dart';
|
part 'context_builder.dart';
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
part of 'cell_service.dart';
|
part of 'cell_service.dart';
|
||||||
|
|
||||||
typedef GridCellContext = _GridCellContext<Cell, String>;
|
typedef GridCellContext = _GridCellContext<String, String>;
|
||||||
typedef GridSelectOptionCellContext = _GridCellContext<SelectOptionCellData, String>;
|
typedef GridSelectOptionCellContext = _GridCellContext<SelectOptionCellData, String>;
|
||||||
typedef GridDateCellContext = _GridCellContext<DateCellData, DateCalData>;
|
typedef GridDateCellContext = _GridCellContext<DateCellData, CalendarData>;
|
||||||
|
typedef GridURLCellContext = _GridCellContext<URLCellData, String>;
|
||||||
|
|
||||||
class GridCellContextBuilder {
|
class GridCellContextBuilder {
|
||||||
final GridCellCache _cellCache;
|
final GridCellCache _cellCache;
|
||||||
@ -16,61 +17,100 @@ class GridCellContextBuilder {
|
|||||||
_GridCellContext build() {
|
_GridCellContext build() {
|
||||||
switch (_gridCell.field.fieldType) {
|
switch (_gridCell.field.fieldType) {
|
||||||
case FieldType.Checkbox:
|
case FieldType.Checkbox:
|
||||||
|
final cellDataLoader = GridCellDataLoader(
|
||||||
|
gridCell: _gridCell,
|
||||||
|
parser: StringCellDataParser(),
|
||||||
|
);
|
||||||
return GridCellContext(
|
return GridCellContext(
|
||||||
gridCell: _gridCell,
|
gridCell: _gridCell,
|
||||||
cellCache: _cellCache,
|
cellCache: _cellCache,
|
||||||
cellDataLoader: CellDataLoader(gridCell: _gridCell),
|
cellDataLoader: cellDataLoader,
|
||||||
cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
|
cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
|
||||||
);
|
);
|
||||||
case FieldType.DateTime:
|
case FieldType.DateTime:
|
||||||
|
final cellDataLoader = GridCellDataLoader(
|
||||||
|
gridCell: _gridCell,
|
||||||
|
parser: DateCellDataParser(),
|
||||||
|
config: const GridCellDataConfig(reloadOnFieldChanged: true),
|
||||||
|
);
|
||||||
|
|
||||||
return GridDateCellContext(
|
return GridDateCellContext(
|
||||||
gridCell: _gridCell,
|
gridCell: _gridCell,
|
||||||
cellCache: _cellCache,
|
cellCache: _cellCache,
|
||||||
cellDataLoader: DateCellDataLoader(gridCell: _gridCell),
|
cellDataLoader: cellDataLoader,
|
||||||
cellDataPersistence: DateCellDataPersistence(gridCell: _gridCell),
|
cellDataPersistence: DateCellDataPersistence(gridCell: _gridCell),
|
||||||
);
|
);
|
||||||
case FieldType.Number:
|
case FieldType.Number:
|
||||||
|
final cellDataLoader = GridCellDataLoader(
|
||||||
|
gridCell: _gridCell,
|
||||||
|
parser: StringCellDataParser(),
|
||||||
|
config: const GridCellDataConfig(reloadOnCellChanged: true, reloadOnFieldChanged: true),
|
||||||
|
);
|
||||||
return GridCellContext(
|
return GridCellContext(
|
||||||
gridCell: _gridCell,
|
gridCell: _gridCell,
|
||||||
cellCache: _cellCache,
|
cellCache: _cellCache,
|
||||||
cellDataLoader: CellDataLoader(gridCell: _gridCell, reloadOnCellChanged: true),
|
cellDataLoader: cellDataLoader,
|
||||||
cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
|
cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
|
||||||
);
|
);
|
||||||
case FieldType.RichText:
|
case FieldType.RichText:
|
||||||
|
final cellDataLoader = GridCellDataLoader(
|
||||||
|
gridCell: _gridCell,
|
||||||
|
parser: StringCellDataParser(),
|
||||||
|
);
|
||||||
return GridCellContext(
|
return GridCellContext(
|
||||||
gridCell: _gridCell,
|
gridCell: _gridCell,
|
||||||
cellCache: _cellCache,
|
cellCache: _cellCache,
|
||||||
cellDataLoader: CellDataLoader(gridCell: _gridCell),
|
cellDataLoader: cellDataLoader,
|
||||||
cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
|
cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
|
||||||
);
|
);
|
||||||
case FieldType.MultiSelect:
|
case FieldType.MultiSelect:
|
||||||
case FieldType.SingleSelect:
|
case FieldType.SingleSelect:
|
||||||
|
final cellDataLoader = GridCellDataLoader(
|
||||||
|
gridCell: _gridCell,
|
||||||
|
parser: SelectOptionCellDataParser(),
|
||||||
|
config: const GridCellDataConfig(reloadOnFieldChanged: true),
|
||||||
|
);
|
||||||
|
|
||||||
return GridSelectOptionCellContext(
|
return GridSelectOptionCellContext(
|
||||||
gridCell: _gridCell,
|
gridCell: _gridCell,
|
||||||
cellCache: _cellCache,
|
cellCache: _cellCache,
|
||||||
cellDataLoader: SelectOptionCellDataLoader(gridCell: _gridCell),
|
cellDataLoader: cellDataLoader,
|
||||||
|
cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
|
||||||
|
);
|
||||||
|
|
||||||
|
case FieldType.URL:
|
||||||
|
final cellDataLoader = GridCellDataLoader(
|
||||||
|
gridCell: _gridCell,
|
||||||
|
parser: URLCellDataParser(),
|
||||||
|
);
|
||||||
|
return GridURLCellContext(
|
||||||
|
gridCell: _gridCell,
|
||||||
|
cellCache: _cellCache,
|
||||||
|
cellDataLoader: cellDataLoader,
|
||||||
cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
|
cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
|
||||||
);
|
);
|
||||||
default:
|
|
||||||
throw UnimplementedError;
|
|
||||||
}
|
}
|
||||||
|
throw UnimplementedError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// T: the type of the CellData
|
||||||
|
// D: the type of the data that will be save to disk
|
||||||
// ignore: must_be_immutable
|
// ignore: must_be_immutable
|
||||||
class _GridCellContext<T, D> extends Equatable {
|
class _GridCellContext<T, D> extends Equatable {
|
||||||
final GridCell gridCell;
|
final GridCell gridCell;
|
||||||
final GridCellCache cellCache;
|
final GridCellCache cellCache;
|
||||||
final GridCellCacheKey _cacheKey;
|
final GridCellCacheKey _cacheKey;
|
||||||
final _GridCellDataLoader<T> cellDataLoader;
|
final IGridCellDataLoader<T> cellDataLoader;
|
||||||
final _GridCellDataPersistence<D> cellDataPersistence;
|
final _GridCellDataPersistence<D> cellDataPersistence;
|
||||||
final FieldService _fieldService;
|
final FieldService _fieldService;
|
||||||
|
|
||||||
late final CellListener _cellListener;
|
late final CellListener _cellListener;
|
||||||
late final ValueNotifier<T?> _cellDataNotifier;
|
late final ValueNotifier<T?>? _cellDataNotifier;
|
||||||
bool isListening = false;
|
bool isListening = false;
|
||||||
VoidCallback? _onFieldChangedFn;
|
VoidCallback? _onFieldChangedFn;
|
||||||
Timer? _delayOperation;
|
Timer? _loadDataOperation;
|
||||||
|
Timer? _saveDataOperation;
|
||||||
|
|
||||||
_GridCellContext({
|
_GridCellContext({
|
||||||
required this.gridCell,
|
required this.gridCell,
|
||||||
@ -100,7 +140,7 @@ class _GridCellContext<T, D> extends Equatable {
|
|||||||
|
|
||||||
FieldType get fieldType => gridCell.field.fieldType;
|
FieldType get fieldType => gridCell.field.fieldType;
|
||||||
|
|
||||||
VoidCallback? startListening({required void Function(T) onCellChanged}) {
|
VoidCallback? startListening({required void Function(T?) onCellChanged}) {
|
||||||
if (isListening) {
|
if (isListening) {
|
||||||
Log.error("Already started. It seems like you should call clone first");
|
Log.error("Already started. It seems like you should call clone first");
|
||||||
return null;
|
return null;
|
||||||
@ -124,52 +164,64 @@ class _GridCellContext<T, D> extends Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onCellChangedFn() {
|
onCellChangedFn() {
|
||||||
final value = _cellDataNotifier.value;
|
onCellChanged(_cellDataNotifier?.value);
|
||||||
if (value is T) {
|
|
||||||
onCellChanged(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cellDataLoader.config.reloadOnCellChanged) {
|
if (cellDataLoader.config.reloadOnCellChanged) {
|
||||||
_loadData();
|
_loadData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_cellDataNotifier.addListener(onCellChangedFn);
|
_cellDataNotifier?.addListener(onCellChangedFn);
|
||||||
return onCellChangedFn;
|
return onCellChangedFn;
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeListener(VoidCallback fn) {
|
void removeListener(VoidCallback fn) {
|
||||||
_cellDataNotifier.removeListener(fn);
|
_cellDataNotifier?.removeListener(fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
T? getCellData() {
|
T? getCellData({bool loadIfNoCache = true}) {
|
||||||
final data = cellCache.get(_cacheKey);
|
final data = cellCache.get(_cacheKey);
|
||||||
if (data == null) {
|
if (data == null && loadIfNoCache) {
|
||||||
_loadData();
|
_loadData();
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Either<List<int>, FlowyError>> getTypeOptionData() {
|
Future<Either<FieldTypeOptionData, FlowyError>> getTypeOptionData() {
|
||||||
return _fieldService.getTypeOptionData(fieldType: fieldType);
|
return _fieldService.getFieldTypeOptionData(fieldType: fieldType);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Option<FlowyError>> saveCellData(D data) {
|
void saveCellData(D data, {bool deduplicate = false, void Function(Option<FlowyError>)? resultCallback}) async {
|
||||||
return cellDataPersistence.save(data);
|
if (deduplicate) {
|
||||||
|
_loadDataOperation?.cancel();
|
||||||
|
_loadDataOperation = Timer(const Duration(milliseconds: 300), () async {
|
||||||
|
final result = await cellDataPersistence.save(data);
|
||||||
|
if (resultCallback != null) {
|
||||||
|
resultCallback(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
final result = await cellDataPersistence.save(data);
|
||||||
|
if (resultCallback != null) {
|
||||||
|
resultCallback(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _loadData() {
|
void _loadData() {
|
||||||
_delayOperation?.cancel();
|
_loadDataOperation?.cancel();
|
||||||
_delayOperation = Timer(const Duration(milliseconds: 10), () {
|
_loadDataOperation = Timer(const Duration(milliseconds: 10), () {
|
||||||
cellDataLoader.loadData().then((data) {
|
cellDataLoader.loadData().then((data) {
|
||||||
_cellDataNotifier.value = data;
|
_cellDataNotifier?.value = data;
|
||||||
cellCache.insert(GridCellCacheData(key: _cacheKey, object: data));
|
cellCache.insert(GridCellCacheData(key: _cacheKey, object: data));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_delayOperation?.cancel();
|
_cellListener.stop();
|
||||||
|
_loadDataOperation?.cancel();
|
||||||
|
_saveDataOperation?.cancel();
|
||||||
|
|
||||||
if (_onFieldChangedFn != null) {
|
if (_onFieldChangedFn != null) {
|
||||||
cellCache.removeFieldListener(_cacheKey, _onFieldChangedFn!);
|
cellCache.removeFieldListener(_cacheKey, _onFieldChangedFn!);
|
||||||
|
@ -104,6 +104,8 @@ class GridCellCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
|
_fieldListenerByFieldId.clear();
|
||||||
|
_cellDataByFieldId.clear();
|
||||||
fieldDelegate.dispose();
|
fieldDelegate.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,94 +1,78 @@
|
|||||||
part of 'cell_service.dart';
|
part of 'cell_service.dart';
|
||||||
|
|
||||||
abstract class GridCellDataConfig {
|
abstract class IGridCellDataConfig {
|
||||||
// The cell data will reload if it receives the field's change notification.
|
// The cell data will reload if it receives the field's change notification.
|
||||||
bool get reloadOnFieldChanged;
|
bool get reloadOnFieldChanged;
|
||||||
|
|
||||||
// The cell data will reload if it receives the cell's change notification.
|
// When the reloadOnCellChanged is true, it will load the cell data after user input.
|
||||||
// For example, the number cell should be reloaded after user input the number.
|
// For example: The number cell reload the cell data that carries the format
|
||||||
// user input: 12
|
// user input: 12
|
||||||
// cell display: $12
|
// cell display: $12
|
||||||
bool get reloadOnCellChanged;
|
bool get reloadOnCellChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
class DefaultCellDataConfig implements GridCellDataConfig {
|
class GridCellDataConfig implements IGridCellDataConfig {
|
||||||
@override
|
@override
|
||||||
final bool reloadOnCellChanged;
|
final bool reloadOnCellChanged;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final bool reloadOnFieldChanged;
|
final bool reloadOnFieldChanged;
|
||||||
|
|
||||||
DefaultCellDataConfig({
|
const GridCellDataConfig({
|
||||||
this.reloadOnCellChanged = false,
|
this.reloadOnCellChanged = false,
|
||||||
this.reloadOnFieldChanged = false,
|
this.reloadOnFieldChanged = false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class _GridCellDataLoader<T> {
|
abstract class IGridCellDataLoader<T> {
|
||||||
Future<T?> loadData();
|
Future<T?> loadData();
|
||||||
|
|
||||||
GridCellDataConfig get config;
|
IGridCellDataConfig get config;
|
||||||
}
|
}
|
||||||
|
|
||||||
class CellDataLoader extends _GridCellDataLoader<Cell> {
|
abstract class ICellDataParser<T> {
|
||||||
|
T? parserData(List<int> data);
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridCellDataLoader<T> extends IGridCellDataLoader<T> {
|
||||||
final CellService service = CellService();
|
final CellService service = CellService();
|
||||||
final GridCell gridCell;
|
final GridCell gridCell;
|
||||||
final GridCellDataConfig _config;
|
final ICellDataParser<T> parser;
|
||||||
|
|
||||||
CellDataLoader({
|
|
||||||
required this.gridCell,
|
|
||||||
bool reloadOnCellChanged = false,
|
|
||||||
}) : _config = DefaultCellDataConfig(reloadOnCellChanged: reloadOnCellChanged);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Cell?> loadData() {
|
final IGridCellDataConfig config;
|
||||||
|
|
||||||
|
GridCellDataLoader({
|
||||||
|
required this.gridCell,
|
||||||
|
required this.parser,
|
||||||
|
this.config = const GridCellDataConfig(),
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<T?> loadData() {
|
||||||
final fut = service.getCell(
|
final fut = service.getCell(
|
||||||
gridId: gridCell.gridId,
|
gridId: gridCell.gridId,
|
||||||
fieldId: gridCell.field.id,
|
fieldId: gridCell.field.id,
|
||||||
rowId: gridCell.rowId,
|
rowId: gridCell.rowId,
|
||||||
);
|
);
|
||||||
return fut.then((result) {
|
return fut.then(
|
||||||
return result.fold((data) => data, (err) {
|
(result) => result.fold((Cell cell) {
|
||||||
Log.error(err);
|
try {
|
||||||
|
return parser.parserData(cell.data);
|
||||||
|
} catch (e, s) {
|
||||||
|
Log.error('$parser parser cellData failed, $e');
|
||||||
|
Log.error('Stack trace \n $s');
|
||||||
return null;
|
return null;
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
}, (err) {
|
||||||
@override
|
|
||||||
GridCellDataConfig get config => _config;
|
|
||||||
}
|
|
||||||
|
|
||||||
class DateCellDataLoader extends _GridCellDataLoader<DateCellData> {
|
|
||||||
final GridCell gridCell;
|
|
||||||
final GridCellDataConfig _config;
|
|
||||||
DateCellDataLoader({
|
|
||||||
required this.gridCell,
|
|
||||||
}) : _config = DefaultCellDataConfig(reloadOnFieldChanged: true);
|
|
||||||
|
|
||||||
@override
|
|
||||||
GridCellDataConfig get config => _config;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<DateCellData?> loadData() {
|
|
||||||
final payload = CellIdentifierPayload.create()
|
|
||||||
..gridId = gridCell.gridId
|
|
||||||
..fieldId = gridCell.field.id
|
|
||||||
..rowId = gridCell.rowId;
|
|
||||||
|
|
||||||
return GridEventGetDateCellData(payload).send().then((result) {
|
|
||||||
return result.fold(
|
|
||||||
(data) => data,
|
|
||||||
(err) {
|
|
||||||
Log.error(err);
|
Log.error(err);
|
||||||
return null;
|
return null;
|
||||||
},
|
}),
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SelectOptionCellDataLoader extends _GridCellDataLoader<SelectOptionCellData> {
|
class SelectOptionCellDataLoader extends IGridCellDataLoader<SelectOptionCellData> {
|
||||||
final SelectOptionService service;
|
final SelectOptionService service;
|
||||||
final GridCell gridCell;
|
final GridCell gridCell;
|
||||||
SelectOptionCellDataLoader({
|
SelectOptionCellDataLoader({
|
||||||
@ -108,5 +92,43 @@ class SelectOptionCellDataLoader extends _GridCellDataLoader<SelectOptionCellDat
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
GridCellDataConfig get config => DefaultCellDataConfig();
|
IGridCellDataConfig get config => const GridCellDataConfig(reloadOnFieldChanged: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
class StringCellDataParser implements ICellDataParser<String> {
|
||||||
|
@override
|
||||||
|
String? parserData(List<int> data) {
|
||||||
|
final s = utf8.decode(data);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DateCellDataParser implements ICellDataParser<DateCellData> {
|
||||||
|
@override
|
||||||
|
DateCellData? parserData(List<int> data) {
|
||||||
|
if (data.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return DateCellData.fromBuffer(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelectOptionCellDataParser implements ICellDataParser<SelectOptionCellData> {
|
||||||
|
@override
|
||||||
|
SelectOptionCellData? parserData(List<int> data) {
|
||||||
|
if (data.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return SelectOptionCellData.fromBuffer(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class URLCellDataParser implements ICellDataParser<URLCellData> {
|
||||||
|
@override
|
||||||
|
URLCellData? parserData(List<int> data) {
|
||||||
|
if (data.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return URLCellData.fromBuffer(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,18 +31,18 @@ class CellDataPersistence implements _GridCellDataPersistence<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class DateCalData with _$DateCalData {
|
class CalendarData with _$CalendarData {
|
||||||
const factory DateCalData({required DateTime date, String? time}) = _DateCellPersistenceData;
|
const factory CalendarData({required DateTime date, String? time}) = _CalendarData;
|
||||||
}
|
}
|
||||||
|
|
||||||
class DateCellDataPersistence implements _GridCellDataPersistence<DateCalData> {
|
class DateCellDataPersistence implements _GridCellDataPersistence<CalendarData> {
|
||||||
final GridCell gridCell;
|
final GridCell gridCell;
|
||||||
DateCellDataPersistence({
|
DateCellDataPersistence({
|
||||||
required this.gridCell,
|
required this.gridCell,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Option<FlowyError>> save(DateCalData data) {
|
Future<Option<FlowyError>> save(CalendarData data) {
|
||||||
var payload = DateChangesetPayload.create()..cellIdentifier = _cellIdentifier(gridCell);
|
var payload = DateChangesetPayload.create()..cellIdentifier = _cellIdentifier(gridCell);
|
||||||
|
|
||||||
final date = (data.date.millisecondsSinceEpoch ~/ 1000).toString();
|
final date = (data.date.millisecondsSinceEpoch ~/ 1000).toString();
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell;
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
@ -16,15 +15,15 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
|
|||||||
}) : super(CheckboxCellState.initial(cellContext)) {
|
}) : super(CheckboxCellState.initial(cellContext)) {
|
||||||
on<CheckboxCellEvent>(
|
on<CheckboxCellEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
await event.map(
|
await event.when(
|
||||||
initial: (_Initial value) {
|
initial: () {
|
||||||
_startListening();
|
_startListening();
|
||||||
},
|
},
|
||||||
select: (_Selected value) async {
|
select: () async {
|
||||||
_updateCellData();
|
_updateCellData();
|
||||||
},
|
},
|
||||||
didReceiveCellUpdate: (_DidReceiveCellUpdate value) {
|
didReceiveCellUpdate: (cellData) {
|
||||||
emit(state.copyWith(isSelected: _isSelected(value.cell)));
|
emit(state.copyWith(isSelected: _isSelected(cellData)));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -43,9 +42,9 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _startListening() {
|
void _startListening() {
|
||||||
_onCellChangedFn = cellContext.startListening(onCellChanged: ((cell) {
|
_onCellChangedFn = cellContext.startListening(onCellChanged: ((cellData) {
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
add(CheckboxCellEvent.didReceiveCellUpdate(cell));
|
add(CheckboxCellEvent.didReceiveCellUpdate(cellData));
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -59,7 +58,7 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
|
|||||||
class CheckboxCellEvent with _$CheckboxCellEvent {
|
class CheckboxCellEvent with _$CheckboxCellEvent {
|
||||||
const factory CheckboxCellEvent.initial() = _Initial;
|
const factory CheckboxCellEvent.initial() = _Initial;
|
||||||
const factory CheckboxCellEvent.select() = _Selected;
|
const factory CheckboxCellEvent.select() = _Selected;
|
||||||
const factory CheckboxCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate;
|
const factory CheckboxCellEvent.didReceiveCellUpdate(String? cellData) = _DidReceiveCellUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@ -73,7 +72,6 @@ class CheckboxCellState with _$CheckboxCellState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isSelected(Cell? cell) {
|
bool _isSelected(String? cellData) {
|
||||||
final content = cell?.content ?? "";
|
return cellData == "Yes";
|
||||||
return content == "Yes";
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
|
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||||
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
|
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart' show StringTranslateExtension;
|
||||||
import 'package:flowy_sdk/log.dart';
|
import 'package:flowy_sdk/log.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-error-code/code.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-error-code/code.pb.dart';
|
||||||
|
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
@ -34,10 +37,10 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
|||||||
setFocusedDay: (focusedDay) {
|
setFocusedDay: (focusedDay) {
|
||||||
emit(state.copyWith(focusedDay: focusedDay));
|
emit(state.copyWith(focusedDay: focusedDay));
|
||||||
},
|
},
|
||||||
didReceiveCellUpdate: (DateCellData cellData) {
|
didReceiveCellUpdate: (DateCellData? cellData) {
|
||||||
final dateData = dateDataFromCellData(cellData);
|
final calData = calDataFromCellData(cellData);
|
||||||
final time = dateData.foldRight("", (dateData, previous) => dateData.time);
|
final time = calData.foldRight("", (dateData, previous) => dateData.time);
|
||||||
emit(state.copyWith(dateData: dateData, time: time));
|
emit(state.copyWith(calData: calData, time: time));
|
||||||
},
|
},
|
||||||
setIncludeTime: (includeTime) async {
|
setIncludeTime: (includeTime) async {
|
||||||
await _updateTypeOption(emit, includeTime: includeTime);
|
await _updateTypeOption(emit, includeTime: includeTime);
|
||||||
@ -49,7 +52,12 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
|||||||
await _updateTypeOption(emit, timeFormat: timeFormat);
|
await _updateTypeOption(emit, timeFormat: timeFormat);
|
||||||
},
|
},
|
||||||
setTime: (time) async {
|
setTime: (time) async {
|
||||||
|
if (state.calData.isSome()) {
|
||||||
await _updateDateData(emit, time: time);
|
await _updateDateData(emit, time: time);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
didUpdateCalData: (Option<CalendarData> data, Option<String> timeFormatError) {
|
||||||
|
emit(state.copyWith(calData: data, timeFormatError: timeFormatError));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -57,8 +65,8 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _updateDateData(Emitter<DateCalState> emit, {DateTime? date, String? time}) {
|
Future<void> _updateDateData(Emitter<DateCalState> emit, {DateTime? date, String? time}) {
|
||||||
final DateCalData newDateData = state.dateData.fold(
|
final CalendarData newDateData = state.calData.fold(
|
||||||
() => DateCalData(date: date ?? DateTime.now(), time: time),
|
() => CalendarData(date: date ?? DateTime.now(), time: time),
|
||||||
(dateData) {
|
(dateData) {
|
||||||
var newDateData = dateData;
|
var newDateData = dateData;
|
||||||
if (date != null && !isSameDay(newDateData.date, date)) {
|
if (date != null && !isSameDay(newDateData.date, date)) {
|
||||||
@ -75,30 +83,44 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
|||||||
return _saveDateData(emit, newDateData);
|
return _saveDateData(emit, newDateData);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _saveDateData(Emitter<DateCalState> emit, DateCalData newDateData) async {
|
Future<void> _saveDateData(Emitter<DateCalState> emit, CalendarData newCalData) async {
|
||||||
if (state.dateData == Some(newDateData)) {
|
if (state.calData == Some(newCalData)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final result = await cellContext.saveCellData(newDateData);
|
updateCalData(Option<CalendarData> calData, Option<String> timeFormatError) {
|
||||||
|
if (!isClosed) add(DateCalEvent.didUpdateCalData(calData, timeFormatError));
|
||||||
|
}
|
||||||
|
|
||||||
|
cellContext.saveCellData(newCalData, resultCallback: (result) {
|
||||||
result.fold(
|
result.fold(
|
||||||
() => emit(state.copyWith(
|
() => updateCalData(Some(newCalData), none()),
|
||||||
dateData: Some(newDateData),
|
|
||||||
timeFormatError: none(),
|
|
||||||
)),
|
|
||||||
(err) {
|
(err) {
|
||||||
switch (ErrorCode.valueOf(err.code)!) {
|
switch (ErrorCode.valueOf(err.code)!) {
|
||||||
case ErrorCode.InvalidDateTimeFormat:
|
case ErrorCode.InvalidDateTimeFormat:
|
||||||
emit(state.copyWith(
|
updateCalData(none(), Some(timeFormatPrompt(err)));
|
||||||
dateData: Some(newDateData),
|
|
||||||
timeFormatError: Some(err.toString()),
|
|
||||||
));
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
Log.error(err);
|
Log.error(err);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
String timeFormatPrompt(FlowyError error) {
|
||||||
|
String msg = LocaleKeys.grid_field_invalidTimeFormat.tr() + ". ";
|
||||||
|
switch (state.dateTypeOption.timeFormat) {
|
||||||
|
case TimeFormat.TwelveHour:
|
||||||
|
msg = msg + "e.g. 01: 00 AM";
|
||||||
|
break;
|
||||||
|
case TimeFormat.TwentyFourHour:
|
||||||
|
msg = msg + "e.g. 13: 00";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -149,7 +171,7 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
result.fold(
|
result.fold(
|
||||||
(l) => emit(state.copyWith(dateTypeOption: newDateTypeOption)),
|
(l) => emit(state.copyWith(dateTypeOption: newDateTypeOption, timeHintText: _timeHintText(newDateTypeOption))),
|
||||||
(err) => Log.error(err),
|
(err) => Log.error(err),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -165,7 +187,9 @@ class DateCalEvent with _$DateCalEvent {
|
|||||||
const factory DateCalEvent.setDateFormat(DateFormat dateFormat) = _DateFormat;
|
const factory DateCalEvent.setDateFormat(DateFormat dateFormat) = _DateFormat;
|
||||||
const factory DateCalEvent.setIncludeTime(bool includeTime) = _IncludeTime;
|
const factory DateCalEvent.setIncludeTime(bool includeTime) = _IncludeTime;
|
||||||
const factory DateCalEvent.setTime(String time) = _Time;
|
const factory DateCalEvent.setTime(String time) = _Time;
|
||||||
const factory DateCalEvent.didReceiveCellUpdate(DateCellData data) = _DidReceiveCellUpdate;
|
const factory DateCalEvent.didReceiveCellUpdate(DateCellData? data) = _DidReceiveCellUpdate;
|
||||||
|
const factory DateCalEvent.didUpdateCalData(Option<CalendarData> data, Option<String> timeFormatError) =
|
||||||
|
_DidUpdateCalData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@ -175,36 +199,48 @@ class DateCalState with _$DateCalState {
|
|||||||
required CalendarFormat format,
|
required CalendarFormat format,
|
||||||
required DateTime focusedDay,
|
required DateTime focusedDay,
|
||||||
required Option<String> timeFormatError,
|
required Option<String> timeFormatError,
|
||||||
required Option<DateCalData> dateData,
|
required Option<CalendarData> calData,
|
||||||
required String? time,
|
required String? time,
|
||||||
|
required String timeHintText,
|
||||||
}) = _DateCalState;
|
}) = _DateCalState;
|
||||||
|
|
||||||
factory DateCalState.initial(
|
factory DateCalState.initial(
|
||||||
DateTypeOption dateTypeOption,
|
DateTypeOption dateTypeOption,
|
||||||
DateCellData? cellData,
|
DateCellData? cellData,
|
||||||
) {
|
) {
|
||||||
Option<DateCalData> dateData = dateDataFromCellData(cellData);
|
Option<CalendarData> calData = calDataFromCellData(cellData);
|
||||||
final time = dateData.foldRight("", (dateData, previous) => dateData.time);
|
final time = calData.foldRight("", (dateData, previous) => dateData.time);
|
||||||
return DateCalState(
|
return DateCalState(
|
||||||
dateTypeOption: dateTypeOption,
|
dateTypeOption: dateTypeOption,
|
||||||
format: CalendarFormat.month,
|
format: CalendarFormat.month,
|
||||||
focusedDay: DateTime.now(),
|
focusedDay: DateTime.now(),
|
||||||
time: time,
|
time: time,
|
||||||
dateData: dateData,
|
calData: calData,
|
||||||
timeFormatError: none(),
|
timeFormatError: none(),
|
||||||
|
timeHintText: _timeHintText(dateTypeOption),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Option<DateCalData> dateDataFromCellData(DateCellData? cellData) {
|
String _timeHintText(DateTypeOption typeOption) {
|
||||||
|
switch (typeOption.timeFormat) {
|
||||||
|
case TimeFormat.TwelveHour:
|
||||||
|
return LocaleKeys.grid_date_timeHintTextInTwelveHour.tr();
|
||||||
|
case TimeFormat.TwentyFourHour:
|
||||||
|
return LocaleKeys.grid_date_timeHintTextInTwentyFourHour.tr();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
Option<CalendarData> calDataFromCellData(DateCellData? cellData) {
|
||||||
String? time = timeFromCellData(cellData);
|
String? time = timeFromCellData(cellData);
|
||||||
Option<DateCalData> dateData = none();
|
Option<CalendarData> calData = none();
|
||||||
if (cellData != null) {
|
if (cellData != null) {
|
||||||
final timestamp = cellData.timestamp * 1000;
|
final timestamp = cellData.timestamp * 1000;
|
||||||
final date = DateTime.fromMillisecondsSinceEpoch(timestamp.toInt());
|
final date = DateTime.fromMillisecondsSinceEpoch(timestamp.toInt());
|
||||||
dateData = Some(DateCalData(date: date, time: time));
|
calData = Some(CalendarData(date: date, time: time));
|
||||||
}
|
}
|
||||||
return dateData;
|
return calData;
|
||||||
}
|
}
|
||||||
|
|
||||||
$fixnum.Int64 timestampFromDateTime(DateTime dateTime) {
|
$fixnum.Int64 timestampFromDateTime(DateTime dateTime) {
|
||||||
|
@ -4,7 +4,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'cell_service/cell_service.dart';
|
import 'cell_service/cell_service.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
|
||||||
part 'date_cell_bloc.freezed.dart';
|
part 'date_cell_bloc.freezed.dart';
|
||||||
|
|
||||||
class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
|
class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
|
||||||
@ -16,7 +15,9 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
|
|||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
event.when(
|
event.when(
|
||||||
initial: () => _startListening(),
|
initial: () => _startListening(),
|
||||||
didReceiveCellUpdate: (DateCellData value) => emit(state.copyWith(data: Some(value))),
|
didReceiveCellUpdate: (DateCellData? cellData) {
|
||||||
|
emit(state.copyWith(data: cellData, dateStr: _dateStrFromCellData(cellData)));
|
||||||
|
},
|
||||||
didReceiveFieldUpdate: (Field value) => emit(state.copyWith(field: value)),
|
didReceiveFieldUpdate: (Field value) => emit(state.copyWith(field: value)),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -47,28 +48,33 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
|
|||||||
@freezed
|
@freezed
|
||||||
class DateCellEvent with _$DateCellEvent {
|
class DateCellEvent with _$DateCellEvent {
|
||||||
const factory DateCellEvent.initial() = _InitialCell;
|
const factory DateCellEvent.initial() = _InitialCell;
|
||||||
const factory DateCellEvent.didReceiveCellUpdate(DateCellData data) = _DidReceiveCellUpdate;
|
const factory DateCellEvent.didReceiveCellUpdate(DateCellData? data) = _DidReceiveCellUpdate;
|
||||||
const factory DateCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
|
const factory DateCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class DateCellState with _$DateCellState {
|
class DateCellState with _$DateCellState {
|
||||||
const factory DateCellState({
|
const factory DateCellState({
|
||||||
required Option<DateCellData> data,
|
required DateCellData? data,
|
||||||
|
required String dateStr,
|
||||||
required Field field,
|
required Field field,
|
||||||
}) = _DateCellState;
|
}) = _DateCellState;
|
||||||
|
|
||||||
factory DateCellState.initial(GridDateCellContext context) {
|
factory DateCellState.initial(GridDateCellContext context) {
|
||||||
final cellData = context.getCellData();
|
final cellData = context.getCellData();
|
||||||
Option<DateCellData> data = none();
|
|
||||||
|
|
||||||
if (cellData != null) {
|
|
||||||
data = Some(cellData);
|
|
||||||
}
|
|
||||||
|
|
||||||
return DateCellState(
|
return DateCellState(
|
||||||
field: context.field,
|
field: context.field,
|
||||||
data: data,
|
data: cellData,
|
||||||
|
dateStr: _dateStrFromCellData(cellData),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _dateStrFromCellData(DateCellData? cellData) {
|
||||||
|
String dateStr = "";
|
||||||
|
if (cellData != null) {
|
||||||
|
dateStr = cellData.date + " " + cellData.time;
|
||||||
|
}
|
||||||
|
return dateStr;
|
||||||
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:dartz/dartz.dart';
|
||||||
import 'cell_service/cell_service.dart';
|
import 'cell_service/cell_service.dart';
|
||||||
|
|
||||||
part 'number_cell_bloc.freezed.dart';
|
part 'number_cell_bloc.freezed.dart';
|
||||||
@ -15,25 +16,28 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
|
|||||||
}) : super(NumberCellState.initial(cellContext)) {
|
}) : super(NumberCellState.initial(cellContext)) {
|
||||||
on<NumberCellEvent>(
|
on<NumberCellEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
await event.map(
|
event.when(
|
||||||
initial: (_Initial value) async {
|
initial: () {
|
||||||
_startListening();
|
_startListening();
|
||||||
},
|
},
|
||||||
didReceiveCellUpdate: (_DidReceiveCellUpdate value) {
|
didReceiveCellUpdate: (content) {
|
||||||
emit(state.copyWith(content: value.cell.content));
|
emit(state.copyWith(content: content));
|
||||||
},
|
},
|
||||||
updateCell: (_UpdateCell value) async {
|
updateCell: (text) {
|
||||||
await _updateCellValue(value, emit);
|
cellContext.saveCellData(text, resultCallback: (result) {
|
||||||
|
result.fold(
|
||||||
|
() => null,
|
||||||
|
(err) {
|
||||||
|
if (!isClosed) add(NumberCellEvent.didReceiveCellUpdate(right(err)));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _updateCellValue(_UpdateCell value, Emitter<NumberCellState> emit) async {
|
|
||||||
cellContext.saveCellData(value.text);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
if (_onCellChangedFn != null) {
|
if (_onCellChangedFn != null) {
|
||||||
@ -46,9 +50,9 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
|
|||||||
|
|
||||||
void _startListening() {
|
void _startListening() {
|
||||||
_onCellChangedFn = cellContext.startListening(
|
_onCellChangedFn = cellContext.startListening(
|
||||||
onCellChanged: ((cell) {
|
onCellChanged: ((cellContent) {
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
add(NumberCellEvent.didReceiveCellUpdate(cell));
|
add(NumberCellEvent.didReceiveCellUpdate(left(cellContent ?? "")));
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -59,17 +63,19 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
|
|||||||
class NumberCellEvent with _$NumberCellEvent {
|
class NumberCellEvent with _$NumberCellEvent {
|
||||||
const factory NumberCellEvent.initial() = _Initial;
|
const factory NumberCellEvent.initial() = _Initial;
|
||||||
const factory NumberCellEvent.updateCell(String text) = _UpdateCell;
|
const factory NumberCellEvent.updateCell(String text) = _UpdateCell;
|
||||||
const factory NumberCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate;
|
const factory NumberCellEvent.didReceiveCellUpdate(Either<String, FlowyError> cellContent) = _DidReceiveCellUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class NumberCellState with _$NumberCellState {
|
class NumberCellState with _$NumberCellState {
|
||||||
const factory NumberCellState({
|
const factory NumberCellState({
|
||||||
required String content,
|
required Either<String, FlowyError> content,
|
||||||
}) = _NumberCellState;
|
}) = _NumberCellState;
|
||||||
|
|
||||||
factory NumberCellState.initial(GridCellContext context) {
|
factory NumberCellState.initial(GridCellContext context) {
|
||||||
final cell = context.getCellData();
|
final cellContent = context.getCellData() ?? "";
|
||||||
return NumberCellState(content: cell?.content ?? "");
|
return NumberCellState(
|
||||||
|
content: left(cellContent),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,16 +4,16 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
|
import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
|
||||||
|
|
||||||
part 'selection_cell_bloc.freezed.dart';
|
part 'select_option_cell_bloc.freezed.dart';
|
||||||
|
|
||||||
class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
|
class SelectOptionCellBloc extends Bloc<SelectOptionCellEvent, SelectOptionCellState> {
|
||||||
final GridSelectOptionCellContext cellContext;
|
final GridSelectOptionCellContext cellContext;
|
||||||
void Function()? _onCellChangedFn;
|
void Function()? _onCellChangedFn;
|
||||||
|
|
||||||
SelectionCellBloc({
|
SelectOptionCellBloc({
|
||||||
required this.cellContext,
|
required this.cellContext,
|
||||||
}) : super(SelectionCellState.initial(cellContext)) {
|
}) : super(SelectOptionCellState.initial(cellContext)) {
|
||||||
on<SelectionCellEvent>(
|
on<SelectOptionCellEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
await event.map(
|
await event.map(
|
||||||
initial: (_InitialCell value) async {
|
initial: (_InitialCell value) async {
|
||||||
@ -21,7 +21,6 @@ class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
|
|||||||
},
|
},
|
||||||
didReceiveOptions: (_DidReceiveOptions value) {
|
didReceiveOptions: (_DidReceiveOptions value) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
options: value.options,
|
|
||||||
selectedOptions: value.selectedOptions,
|
selectedOptions: value.selectedOptions,
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
@ -44,9 +43,8 @@ class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
|
|||||||
_onCellChangedFn = cellContext.startListening(
|
_onCellChangedFn = cellContext.startListening(
|
||||||
onCellChanged: ((selectOptionContext) {
|
onCellChanged: ((selectOptionContext) {
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
add(SelectionCellEvent.didReceiveOptions(
|
add(SelectOptionCellEvent.didReceiveOptions(
|
||||||
selectOptionContext.options,
|
selectOptionContext?.selectOptions ?? [],
|
||||||
selectOptionContext.selectOptions,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@ -55,26 +53,23 @@ class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class SelectionCellEvent with _$SelectionCellEvent {
|
class SelectOptionCellEvent with _$SelectOptionCellEvent {
|
||||||
const factory SelectionCellEvent.initial() = _InitialCell;
|
const factory SelectOptionCellEvent.initial() = _InitialCell;
|
||||||
const factory SelectionCellEvent.didReceiveOptions(
|
const factory SelectOptionCellEvent.didReceiveOptions(
|
||||||
List<SelectOption> options,
|
|
||||||
List<SelectOption> selectedOptions,
|
List<SelectOption> selectedOptions,
|
||||||
) = _DidReceiveOptions;
|
) = _DidReceiveOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class SelectionCellState with _$SelectionCellState {
|
class SelectOptionCellState with _$SelectOptionCellState {
|
||||||
const factory SelectionCellState({
|
const factory SelectOptionCellState({
|
||||||
required List<SelectOption> options,
|
|
||||||
required List<SelectOption> selectedOptions,
|
required List<SelectOption> selectedOptions,
|
||||||
}) = _SelectionCellState;
|
}) = _SelectOptionCellState;
|
||||||
|
|
||||||
factory SelectionCellState.initial(GridSelectOptionCellContext context) {
|
factory SelectOptionCellState.initial(GridSelectOptionCellContext context) {
|
||||||
final data = context.getCellData();
|
final data = context.getCellData();
|
||||||
|
|
||||||
return SelectionCellState(
|
return SelectOptionCellState(
|
||||||
options: data?.options ?? [],
|
|
||||||
selectedOptions: data?.selectOptions ?? [],
|
selectedOptions: data?.selectOptions ?? [],
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:app_flowy/workspace/application/grid/field/grid_listenr.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:flowy_sdk/log.dart';
|
import 'package:flowy_sdk/log.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||||
@ -6,23 +7,28 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
|
import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
|
||||||
import 'select_option_service.dart';
|
import 'select_option_service.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
part 'selection_editor_bloc.freezed.dart';
|
part 'select_option_editor_bloc.freezed.dart';
|
||||||
|
|
||||||
class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionEditorState> {
|
class SelectOptionCellEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionEditorState> {
|
||||||
final SelectOptionService _selectOptionService;
|
final SelectOptionService _selectOptionService;
|
||||||
final GridSelectOptionCellContext cellContext;
|
final GridSelectOptionCellContext cellContext;
|
||||||
|
late final GridFieldsListener _fieldListener;
|
||||||
void Function()? _onCellChangedFn;
|
void Function()? _onCellChangedFn;
|
||||||
|
Timer? _delayOperation;
|
||||||
|
|
||||||
SelectOptionEditorBloc({
|
SelectOptionCellEditorBloc({
|
||||||
required this.cellContext,
|
required this.cellContext,
|
||||||
}) : _selectOptionService = SelectOptionService(gridCell: cellContext.gridCell),
|
}) : _selectOptionService = SelectOptionService(gridCell: cellContext.gridCell),
|
||||||
|
_fieldListener = GridFieldsListener(gridId: cellContext.gridId),
|
||||||
super(SelectOptionEditorState.initial(cellContext)) {
|
super(SelectOptionEditorState.initial(cellContext)) {
|
||||||
on<SelectOptionEditorEvent>(
|
on<SelectOptionEditorEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
await event.map(
|
await event.map(
|
||||||
initial: (_Initial value) async {
|
initial: (_Initial value) async {
|
||||||
_startListening();
|
_startListening();
|
||||||
|
_loadOptions();
|
||||||
},
|
},
|
||||||
didReceiveOptions: (_DidReceiveOptions value) {
|
didReceiveOptions: (_DidReceiveOptions value) {
|
||||||
final result = _makeOptions(state.filter, value.options);
|
final result = _makeOptions(state.filter, value.options);
|
||||||
@ -62,6 +68,8 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
|
|||||||
cellContext.removeListener(_onCellChangedFn!);
|
cellContext.removeListener(_onCellChangedFn!);
|
||||||
_onCellChangedFn = null;
|
_onCellChangedFn = null;
|
||||||
}
|
}
|
||||||
|
_delayOperation?.cancel();
|
||||||
|
await _fieldListener.stop();
|
||||||
cellContext.dispose();
|
cellContext.dispose();
|
||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
@ -105,6 +113,24 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _loadOptions() {
|
||||||
|
_delayOperation?.cancel();
|
||||||
|
_delayOperation = Timer(const Duration(milliseconds: 10), () {
|
||||||
|
_selectOptionService.getOpitonContext().then((result) {
|
||||||
|
if (isClosed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return result.fold(
|
||||||
|
(data) => add(SelectOptionEditorEvent.didReceiveOptions(data.options, data.selectOptions)),
|
||||||
|
(err) {
|
||||||
|
Log.error(err);
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_MakeOptionResult _makeOptions(Option<String> filter, List<SelectOption> allOptions) {
|
_MakeOptionResult _makeOptions(Option<String> filter, List<SelectOption> allOptions) {
|
||||||
final List<SelectOption> options = List.from(allOptions);
|
final List<SelectOption> options = List.from(allOptions);
|
||||||
Option<String> createOption = filter;
|
Option<String> createOption = filter;
|
||||||
@ -134,13 +160,21 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
|
|||||||
_onCellChangedFn = cellContext.startListening(
|
_onCellChangedFn = cellContext.startListening(
|
||||||
onCellChanged: ((selectOptionContext) {
|
onCellChanged: ((selectOptionContext) {
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
add(SelectOptionEditorEvent.didReceiveOptions(
|
_loadOptions();
|
||||||
selectOptionContext.options,
|
|
||||||
selectOptionContext.selectOptions,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
_fieldListener.start(onFieldsChanged: (result) {
|
||||||
|
result.fold(
|
||||||
|
(changeset) {
|
||||||
|
if (changeset.updatedFields.isNotEmpty) {
|
||||||
|
_loadOptions();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(err) => Log.error(err),
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,7 +201,7 @@ class SelectOptionEditorState with _$SelectOptionEditorState {
|
|||||||
}) = _SelectOptionEditorState;
|
}) = _SelectOptionEditorState;
|
||||||
|
|
||||||
factory SelectOptionEditorState.initial(GridSelectOptionCellContext context) {
|
factory SelectOptionEditorState.initial(GridSelectOptionCellContext context) {
|
||||||
final data = context.getCellData();
|
final data = context.getCellData(loadIfNoCache: false);
|
||||||
return SelectOptionEditorState(
|
return SelectOptionEditorState(
|
||||||
options: data?.options ?? [],
|
options: data?.options ?? [],
|
||||||
allOptions: data?.options ?? [],
|
allOptions: data?.options ?? [],
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell;
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
@ -14,21 +13,16 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
|
|||||||
}) : super(TextCellState.initial(cellContext)) {
|
}) : super(TextCellState.initial(cellContext)) {
|
||||||
on<TextCellEvent>(
|
on<TextCellEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
await event.map(
|
await event.when(
|
||||||
initial: (_InitialCell value) async {
|
initial: () async {
|
||||||
_startListening();
|
_startListening();
|
||||||
},
|
},
|
||||||
updateText: (_UpdateText value) {
|
updateText: (text) {
|
||||||
cellContext.saveCellData(value.text);
|
cellContext.saveCellData(text);
|
||||||
emit(state.copyWith(content: value.text));
|
emit(state.copyWith(content: text));
|
||||||
},
|
},
|
||||||
didReceiveCellData: (_DidReceiveCellData value) {
|
didReceiveCellUpdate: (content) {
|
||||||
emit(state.copyWith(content: value.cellData.cell?.content ?? ""));
|
emit(state.copyWith(content: content));
|
||||||
},
|
|
||||||
didReceiveCellUpdate: (_DidReceiveCellUpdate value) {
|
|
||||||
emit(state.copyWith(
|
|
||||||
content: value.cell.content,
|
|
||||||
));
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -47,9 +41,9 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
|
|||||||
|
|
||||||
void _startListening() {
|
void _startListening() {
|
||||||
_onCellChangedFn = cellContext.startListening(
|
_onCellChangedFn = cellContext.startListening(
|
||||||
onCellChanged: ((cell) {
|
onCellChanged: ((cellContent) {
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
add(TextCellEvent.didReceiveCellUpdate(cell));
|
add(TextCellEvent.didReceiveCellUpdate(cellContent ?? ""));
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -59,8 +53,7 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
|
|||||||
@freezed
|
@freezed
|
||||||
class TextCellEvent with _$TextCellEvent {
|
class TextCellEvent with _$TextCellEvent {
|
||||||
const factory TextCellEvent.initial() = _InitialCell;
|
const factory TextCellEvent.initial() = _InitialCell;
|
||||||
const factory TextCellEvent.didReceiveCellData(GridCell cellData) = _DidReceiveCellData;
|
const factory TextCellEvent.didReceiveCellUpdate(String cellContent) = _DidReceiveCellUpdate;
|
||||||
const factory TextCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate;
|
|
||||||
const factory TextCellEvent.updateText(String text) = _UpdateText;
|
const factory TextCellEvent.updateText(String text) = _UpdateText;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,6 +64,6 @@ class TextCellState with _$TextCellState {
|
|||||||
}) = _TextCellState;
|
}) = _TextCellState;
|
||||||
|
|
||||||
factory TextCellState.initial(GridCellContext context) => TextCellState(
|
factory TextCellState.initial(GridCellContext context) => TextCellState(
|
||||||
content: context.getCellData()?.content ?? "",
|
content: context.getCellData() ?? "",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
import 'cell_service/cell_service.dart';
|
||||||
|
|
||||||
|
part 'url_cell_bloc.freezed.dart';
|
||||||
|
|
||||||
|
class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
|
||||||
|
final GridURLCellContext cellContext;
|
||||||
|
void Function()? _onCellChangedFn;
|
||||||
|
URLCellBloc({
|
||||||
|
required this.cellContext,
|
||||||
|
}) : super(URLCellState.initial(cellContext)) {
|
||||||
|
on<URLCellEvent>(
|
||||||
|
(event, emit) async {
|
||||||
|
event.when(
|
||||||
|
initial: () {
|
||||||
|
_startListening();
|
||||||
|
},
|
||||||
|
didReceiveCellUpdate: (cellData) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
content: cellData?.content ?? "",
|
||||||
|
url: cellData?.url ?? "",
|
||||||
|
));
|
||||||
|
},
|
||||||
|
updateURL: (String url) {
|
||||||
|
cellContext.saveCellData(url, deduplicate: true);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() async {
|
||||||
|
if (_onCellChangedFn != null) {
|
||||||
|
cellContext.removeListener(_onCellChangedFn!);
|
||||||
|
_onCellChangedFn = null;
|
||||||
|
}
|
||||||
|
cellContext.dispose();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startListening() {
|
||||||
|
_onCellChangedFn = cellContext.startListening(
|
||||||
|
onCellChanged: ((cellData) {
|
||||||
|
if (!isClosed) {
|
||||||
|
add(URLCellEvent.didReceiveCellUpdate(cellData));
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class URLCellEvent with _$URLCellEvent {
|
||||||
|
const factory URLCellEvent.initial() = _InitialCell;
|
||||||
|
const factory URLCellEvent.updateURL(String url) = _UpdateURL;
|
||||||
|
const factory URLCellEvent.didReceiveCellUpdate(URLCellData? cell) = _DidReceiveCellUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class URLCellState with _$URLCellState {
|
||||||
|
const factory URLCellState({
|
||||||
|
required String content,
|
||||||
|
required String url,
|
||||||
|
}) = _URLCellState;
|
||||||
|
|
||||||
|
factory URLCellState.initial(GridURLCellContext context) {
|
||||||
|
final cellData = context.getCellData();
|
||||||
|
return URLCellState(
|
||||||
|
content: cellData?.content ?? "",
|
||||||
|
url: cellData?.url ?? "",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
import 'cell_service/cell_service.dart';
|
||||||
|
|
||||||
|
part 'url_cell_editor_bloc.freezed.dart';
|
||||||
|
|
||||||
|
class URLCellEditorBloc extends Bloc<URLCellEditorEvent, URLCellEditorState> {
|
||||||
|
final GridURLCellContext cellContext;
|
||||||
|
void Function()? _onCellChangedFn;
|
||||||
|
URLCellEditorBloc({
|
||||||
|
required this.cellContext,
|
||||||
|
}) : super(URLCellEditorState.initial(cellContext)) {
|
||||||
|
on<URLCellEditorEvent>(
|
||||||
|
(event, emit) async {
|
||||||
|
event.when(
|
||||||
|
initial: () {
|
||||||
|
_startListening();
|
||||||
|
},
|
||||||
|
updateText: (text) {
|
||||||
|
cellContext.saveCellData(text, deduplicate: true);
|
||||||
|
emit(state.copyWith(content: text));
|
||||||
|
},
|
||||||
|
didReceiveCellUpdate: (cellData) {
|
||||||
|
emit(state.copyWith(content: cellData?.content ?? ""));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() async {
|
||||||
|
if (_onCellChangedFn != null) {
|
||||||
|
cellContext.removeListener(_onCellChangedFn!);
|
||||||
|
_onCellChangedFn = null;
|
||||||
|
}
|
||||||
|
cellContext.dispose();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startListening() {
|
||||||
|
_onCellChangedFn = cellContext.startListening(
|
||||||
|
onCellChanged: ((cellData) {
|
||||||
|
if (!isClosed) {
|
||||||
|
add(URLCellEditorEvent.didReceiveCellUpdate(cellData));
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class URLCellEditorEvent with _$URLCellEditorEvent {
|
||||||
|
const factory URLCellEditorEvent.initial() = _InitialCell;
|
||||||
|
const factory URLCellEditorEvent.didReceiveCellUpdate(URLCellData? cell) = _DidReceiveCellUpdate;
|
||||||
|
const factory URLCellEditorEvent.updateText(String text) = _UpdateText;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class URLCellEditorState with _$URLCellEditorState {
|
||||||
|
const factory URLCellEditorState({
|
||||||
|
required String content,
|
||||||
|
}) = _URLCellEditorState;
|
||||||
|
|
||||||
|
factory URLCellEditorState.initial(GridURLCellContext context) {
|
||||||
|
final cellData = context.getCellData();
|
||||||
|
return URLCellEditorState(
|
||||||
|
content: cellData?.content ?? "",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ class FieldActionSheetBloc extends Bloc<FieldActionSheetEvent, FieldActionSheetS
|
|||||||
final FieldService fieldService;
|
final FieldService fieldService;
|
||||||
|
|
||||||
FieldActionSheetBloc({required Field field, required this.fieldService})
|
FieldActionSheetBloc({required Field field, required this.fieldService})
|
||||||
: super(FieldActionSheetState.initial(EditFieldContext.create()..gridField = field)) {
|
: super(FieldActionSheetState.initial(FieldTypeOptionData.create()..field_2 = field)) {
|
||||||
on<FieldActionSheetEvent>(
|
on<FieldActionSheetEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
await event.map(
|
await event.map(
|
||||||
@ -67,14 +67,14 @@ class FieldActionSheetEvent with _$FieldActionSheetEvent {
|
|||||||
@freezed
|
@freezed
|
||||||
class FieldActionSheetState with _$FieldActionSheetState {
|
class FieldActionSheetState with _$FieldActionSheetState {
|
||||||
const factory FieldActionSheetState({
|
const factory FieldActionSheetState({
|
||||||
required EditFieldContext editContext,
|
required FieldTypeOptionData fieldTypeOptionData,
|
||||||
required String errorText,
|
required String errorText,
|
||||||
required String fieldName,
|
required String fieldName,
|
||||||
}) = _FieldActionSheetState;
|
}) = _FieldActionSheetState;
|
||||||
|
|
||||||
factory FieldActionSheetState.initial(EditFieldContext editContext) => FieldActionSheetState(
|
factory FieldActionSheetState.initial(FieldTypeOptionData data) => FieldActionSheetState(
|
||||||
editContext: editContext,
|
fieldTypeOptionData: data,
|
||||||
errorText: '',
|
errorText: '',
|
||||||
fieldName: editContext.gridField.name,
|
fieldName: data.field_2.name,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -19,18 +19,20 @@ class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
|
|||||||
super(FieldCellState.initial(cellContext)) {
|
super(FieldCellState.initial(cellContext)) {
|
||||||
on<FieldCellEvent>(
|
on<FieldCellEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
await event.map(
|
event.when(
|
||||||
initial: (_InitialCell value) async {
|
initial: () {
|
||||||
_startListening();
|
_startListening();
|
||||||
},
|
},
|
||||||
didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
|
didReceiveFieldUpdate: (field) {
|
||||||
emit(state.copyWith(field: value.field));
|
emit(state.copyWith(field: cellContext.field));
|
||||||
},
|
},
|
||||||
updateWidth: (_UpdateWidth value) {
|
startUpdateWidth: (offset) {
|
||||||
final defaultWidth = state.field.width.toDouble();
|
final width = state.width + offset;
|
||||||
final width = defaultWidth + value.offset;
|
emit(state.copyWith(width: width));
|
||||||
if (width > defaultWidth && width < 300) {
|
},
|
||||||
_fieldService.updateField(width: width);
|
endUpdateWidth: () {
|
||||||
|
if (state.width != state.field.width.toDouble()) {
|
||||||
|
_fieldService.updateField(width: state.width);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -61,7 +63,8 @@ class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
|
|||||||
class FieldCellEvent with _$FieldCellEvent {
|
class FieldCellEvent with _$FieldCellEvent {
|
||||||
const factory FieldCellEvent.initial() = _InitialCell;
|
const factory FieldCellEvent.initial() = _InitialCell;
|
||||||
const factory FieldCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
|
const factory FieldCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
|
||||||
const factory FieldCellEvent.updateWidth(double offset) = _UpdateWidth;
|
const factory FieldCellEvent.startUpdateWidth(double offset) = _StartUpdateWidth;
|
||||||
|
const factory FieldCellEvent.endUpdateWidth() = _EndUpdateWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@ -69,10 +72,12 @@ class FieldCellState with _$FieldCellState {
|
|||||||
const factory FieldCellState({
|
const factory FieldCellState({
|
||||||
required String gridId,
|
required String gridId,
|
||||||
required Field field,
|
required Field field,
|
||||||
|
required double width,
|
||||||
}) = _FieldCellState;
|
}) = _FieldCellState;
|
||||||
|
|
||||||
factory FieldCellState.initial(GridFieldCellContext cellContext) => FieldCellState(
|
factory FieldCellState.initial(GridFieldCellContext cellContext) => FieldCellState(
|
||||||
gridId: cellContext.gridId,
|
gridId: cellContext.gridId,
|
||||||
field: cellContext.field,
|
field: cellContext.field,
|
||||||
|
width: cellContext.field.width.toDouble(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,41 +1,31 @@
|
|||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flowy_sdk/log.dart';
|
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'field_service.dart';
|
import 'field_service.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:protobuf/protobuf.dart';
|
|
||||||
|
|
||||||
part 'field_editor_bloc.freezed.dart';
|
part 'field_editor_bloc.freezed.dart';
|
||||||
|
|
||||||
class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
||||||
final String gridId;
|
|
||||||
final EditFieldContextLoader _loader;
|
|
||||||
|
|
||||||
FieldEditorBloc({
|
FieldEditorBloc({
|
||||||
required this.gridId,
|
required String gridId,
|
||||||
required EditFieldContextLoader fieldLoader,
|
required String fieldName,
|
||||||
}) : _loader = fieldLoader,
|
required IFieldContextLoader fieldContextLoader,
|
||||||
super(FieldEditorState.initial(gridId)) {
|
}) : super(FieldEditorState.initial(gridId, fieldName, fieldContextLoader)) {
|
||||||
on<FieldEditorEvent>(
|
on<FieldEditorEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
await event.map(
|
await event.when(
|
||||||
initial: (_InitialField value) async {
|
initial: () async {
|
||||||
await _getEditFieldContext(emit);
|
final fieldContext = GridFieldContext(gridId: gridId, loader: fieldContextLoader);
|
||||||
|
await fieldContext.loadData().then((result) {
|
||||||
|
result.fold(
|
||||||
|
(l) => emit(state.copyWith(fieldContext: Some(fieldContext), name: fieldContext.field.name)),
|
||||||
|
(r) => null,
|
||||||
|
);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
updateName: (_UpdateName value) {
|
updateName: (name) {
|
||||||
final newContext = _updateEditContext(name: value.name);
|
state.fieldContext.fold(() => null, (fieldContext) => fieldContext.fieldName = name);
|
||||||
emit(state.copyWith(editFieldContext: newContext));
|
emit(state.copyWith(name: name));
|
||||||
},
|
|
||||||
updateField: (_UpdateField value) {
|
|
||||||
final newContext = _updateEditContext(field: value.field, typeOptionData: value.typeOptionData);
|
|
||||||
|
|
||||||
emit(state.copyWith(editFieldContext: newContext));
|
|
||||||
},
|
|
||||||
done: (_Done value) async {
|
|
||||||
await _saveField(emit);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -46,78 +36,12 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
|||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
Option<EditFieldContext> _updateEditContext({
|
|
||||||
String? name,
|
|
||||||
Field? field,
|
|
||||||
List<int>? typeOptionData,
|
|
||||||
}) {
|
|
||||||
return state.editFieldContext.fold(
|
|
||||||
() => none(),
|
|
||||||
(context) {
|
|
||||||
context.freeze();
|
|
||||||
final newContext = context.rebuild((newContext) {
|
|
||||||
newContext.gridField.rebuild((newField) {
|
|
||||||
if (name != null) {
|
|
||||||
newField.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
newContext.gridField = newField;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (field != null) {
|
|
||||||
newContext.gridField = field;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeOptionData != null) {
|
|
||||||
newContext.typeOptionData = typeOptionData;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
FieldService.insertField(
|
|
||||||
gridId: gridId,
|
|
||||||
field: newContext.gridField,
|
|
||||||
typeOptionData: newContext.typeOptionData,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Some(newContext);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _saveField(Emitter<FieldEditorState> emit) async {
|
|
||||||
await state.editFieldContext.fold(
|
|
||||||
() async => null,
|
|
||||||
(context) async {
|
|
||||||
final result = await FieldService.insertField(
|
|
||||||
gridId: gridId,
|
|
||||||
field: context.gridField,
|
|
||||||
typeOptionData: context.typeOptionData,
|
|
||||||
);
|
|
||||||
result.fold((l) => null, (r) => null);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _getEditFieldContext(Emitter<FieldEditorState> emit) async {
|
|
||||||
final result = await _loader.load();
|
|
||||||
result.fold(
|
|
||||||
(context) {
|
|
||||||
emit(state.copyWith(
|
|
||||||
editFieldContext: Some(context),
|
|
||||||
));
|
|
||||||
},
|
|
||||||
(err) => Log.error(err),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class FieldEditorEvent with _$FieldEditorEvent {
|
class FieldEditorEvent with _$FieldEditorEvent {
|
||||||
const factory FieldEditorEvent.initial() = _InitialField;
|
const factory FieldEditorEvent.initial() = _InitialField;
|
||||||
const factory FieldEditorEvent.updateName(String name) = _UpdateName;
|
const factory FieldEditorEvent.updateName(String name) = _UpdateName;
|
||||||
const factory FieldEditorEvent.updateField(Field field, Uint8List typeOptionData) = _UpdateField;
|
|
||||||
const factory FieldEditorEvent.done() = _Done;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@ -125,12 +49,14 @@ class FieldEditorState with _$FieldEditorState {
|
|||||||
const factory FieldEditorState({
|
const factory FieldEditorState({
|
||||||
required String gridId,
|
required String gridId,
|
||||||
required String errorText,
|
required String errorText,
|
||||||
required Option<EditFieldContext> editFieldContext,
|
required String name,
|
||||||
|
required Option<GridFieldContext> fieldContext,
|
||||||
}) = _FieldEditorState;
|
}) = _FieldEditorState;
|
||||||
|
|
||||||
factory FieldEditorState.initial(String gridId) => FieldEditorState(
|
factory FieldEditorState.initial(String gridId, String fieldName, IFieldContextLoader loader) => FieldEditorState(
|
||||||
gridId: gridId,
|
gridId: gridId,
|
||||||
editFieldContext: none(),
|
fieldContext: none(),
|
||||||
errorText: '',
|
errorText: '',
|
||||||
|
name: fieldName,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,29 @@
|
|||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'field_service.dart';
|
||||||
|
|
||||||
part 'field_editor_pannel_bloc.freezed.dart';
|
part 'field_editor_pannel_bloc.freezed.dart';
|
||||||
|
|
||||||
class FieldEditorPannelBloc extends Bloc<FieldEditorPannelEvent, FieldEditorPannelState> {
|
class FieldEditorPannelBloc extends Bloc<FieldEditorPannelEvent, FieldEditorPannelState> {
|
||||||
FieldEditorPannelBloc(EditFieldContext editContext) : super(FieldEditorPannelState.initial(editContext)) {
|
final GridFieldContext _fieldContext;
|
||||||
|
void Function()? _fieldListenFn;
|
||||||
|
|
||||||
|
FieldEditorPannelBloc(GridFieldContext fieldContext)
|
||||||
|
: _fieldContext = fieldContext,
|
||||||
|
super(FieldEditorPannelState.initial(fieldContext)) {
|
||||||
on<FieldEditorPannelEvent>(
|
on<FieldEditorPannelEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
await event.map(
|
event.when(
|
||||||
toFieldType: (_ToFieldType value) async {
|
initial: () {
|
||||||
emit(state.copyWith(
|
_fieldListenFn = fieldContext.addFieldListener((field) {
|
||||||
field: value.field,
|
add(FieldEditorPannelEvent.didReceiveFieldUpdated(field));
|
||||||
typeOptionData: Uint8List.fromList(value.typeOptionData),
|
});
|
||||||
));
|
|
||||||
},
|
},
|
||||||
didUpdateTypeOptionData: (_DidUpdateTypeOptionData value) {
|
didReceiveFieldUpdated: (field) {
|
||||||
emit(state.copyWith(typeOptionData: value.typeOptionData));
|
emit(state.copyWith(field: field));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -27,27 +32,26 @@ class FieldEditorPannelBloc extends Bloc<FieldEditorPannelEvent, FieldEditorPann
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
|
if (_fieldListenFn != null) {
|
||||||
|
_fieldContext.removeFieldListener(_fieldListenFn!);
|
||||||
|
}
|
||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class FieldEditorPannelEvent with _$FieldEditorPannelEvent {
|
class FieldEditorPannelEvent with _$FieldEditorPannelEvent {
|
||||||
const factory FieldEditorPannelEvent.toFieldType(Field field, List<int> typeOptionData) = _ToFieldType;
|
const factory FieldEditorPannelEvent.initial() = _Initial;
|
||||||
const factory FieldEditorPannelEvent.didUpdateTypeOptionData(Uint8List typeOptionData) = _DidUpdateTypeOptionData;
|
const factory FieldEditorPannelEvent.didReceiveFieldUpdated(Field field) = _DidReceiveFieldUpdated;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class FieldEditorPannelState with _$FieldEditorPannelState {
|
class FieldEditorPannelState with _$FieldEditorPannelState {
|
||||||
const factory FieldEditorPannelState({
|
const factory FieldEditorPannelState({
|
||||||
required String gridId,
|
|
||||||
required Field field,
|
required Field field,
|
||||||
required Uint8List typeOptionData,
|
|
||||||
}) = _FieldEditorPannelState;
|
}) = _FieldEditorPannelState;
|
||||||
|
|
||||||
factory FieldEditorPannelState.initial(EditFieldContext context) => FieldEditorPannelState(
|
factory FieldEditorPannelState.initial(GridFieldContext fieldContext) => FieldEditorPannelState(
|
||||||
gridId: context.gridId,
|
field: fieldContext.field,
|
||||||
field: context.gridField,
|
|
||||||
typeOptionData: Uint8List.fromList(context.typeOptionData),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||||
|
import 'package:flowy_sdk/log.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:protobuf/protobuf.dart';
|
||||||
part 'field_service.freezed.dart';
|
part 'field_service.freezed.dart';
|
||||||
|
|
||||||
class FieldService {
|
class FieldService {
|
||||||
@ -12,24 +15,6 @@ class FieldService {
|
|||||||
|
|
||||||
FieldService({required this.gridId, required this.fieldId});
|
FieldService({required this.gridId, required this.fieldId});
|
||||||
|
|
||||||
Future<Either<EditFieldContext, FlowyError>> switchToField(FieldType fieldType) {
|
|
||||||
final payload = EditFieldPayload.create()
|
|
||||||
..gridId = gridId
|
|
||||||
..fieldId = fieldId
|
|
||||||
..fieldType = fieldType;
|
|
||||||
|
|
||||||
return GridEventSwitchToField(payload).send();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Either<EditFieldContext, FlowyError>> getEditFieldContext(FieldType fieldType) {
|
|
||||||
final payload = EditFieldPayload.create()
|
|
||||||
..gridId = gridId
|
|
||||||
..fieldId = fieldId
|
|
||||||
..fieldType = fieldType;
|
|
||||||
|
|
||||||
return GridEventGetEditFieldContext(payload).send();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Either<Unit, FlowyError>> moveField(int fromIndex, int toIndex) {
|
Future<Either<Unit, FlowyError>> moveField(int fromIndex, int toIndex) {
|
||||||
final payload = MoveItemPayload.create()
|
final payload = MoveItemPayload.create()
|
||||||
..gridId = gridId
|
..gridId = gridId
|
||||||
@ -128,7 +113,7 @@ class FieldService {
|
|||||||
return GridEventDuplicateField(payload).send();
|
return GridEventDuplicateField(payload).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Either<List<int>, FlowyError>> getTypeOptionData({
|
Future<Either<FieldTypeOptionData, FlowyError>> getFieldTypeOptionData({
|
||||||
required FieldType fieldType,
|
required FieldType fieldType,
|
||||||
}) {
|
}) {
|
||||||
final payload = EditFieldPayload.create()
|
final payload = EditFieldPayload.create()
|
||||||
@ -137,7 +122,7 @@ class FieldService {
|
|||||||
..fieldType = fieldType;
|
..fieldType = fieldType;
|
||||||
return GridEventGetFieldTypeOption(payload).send().then((result) {
|
return GridEventGetFieldTypeOption(payload).send().then((result) {
|
||||||
return result.fold(
|
return result.fold(
|
||||||
(data) => left(data.typeOptionData),
|
(data) => left(data),
|
||||||
(err) => right(err),
|
(err) => right(err),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -152,59 +137,162 @@ class GridFieldCellContext with _$GridFieldCellContext {
|
|||||||
}) = _GridFieldCellContext;
|
}) = _GridFieldCellContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class EditFieldContextLoader {
|
abstract class IFieldContextLoader {
|
||||||
Future<Either<EditFieldContext, FlowyError>> load();
|
String get gridId;
|
||||||
|
Future<Either<FieldTypeOptionData, FlowyError>> load();
|
||||||
|
|
||||||
Future<Either<EditFieldContext, FlowyError>> switchToField(String fieldId, FieldType fieldType);
|
Future<Either<FieldTypeOptionData, FlowyError>> switchToField(String fieldId, FieldType fieldType) {
|
||||||
|
final payload = EditFieldPayload.create()
|
||||||
|
..gridId = gridId
|
||||||
|
..fieldId = fieldId
|
||||||
|
..fieldType = fieldType;
|
||||||
|
|
||||||
|
return GridEventSwitchToField(payload).send();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NewFieldContextLoader extends EditFieldContextLoader {
|
class NewFieldContextLoader extends IFieldContextLoader {
|
||||||
|
@override
|
||||||
final String gridId;
|
final String gridId;
|
||||||
NewFieldContextLoader({
|
NewFieldContextLoader({
|
||||||
required this.gridId,
|
required this.gridId,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Either<EditFieldContext, FlowyError>> load() {
|
Future<Either<FieldTypeOptionData, FlowyError>> load() {
|
||||||
final payload = EditFieldPayload.create()
|
final payload = EditFieldPayload.create()
|
||||||
..gridId = gridId
|
..gridId = gridId
|
||||||
..fieldType = FieldType.RichText;
|
..fieldType = FieldType.RichText;
|
||||||
|
|
||||||
return GridEventGetEditFieldContext(payload).send();
|
return GridEventCreateFieldTypeOption(payload).send();
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Either<EditFieldContext, FlowyError>> switchToField(String fieldId, FieldType fieldType) {
|
|
||||||
final payload = EditFieldPayload.create()
|
|
||||||
..gridId = gridId
|
|
||||||
..fieldType = fieldType;
|
|
||||||
|
|
||||||
return GridEventGetEditFieldContext(payload).send();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FieldContextLoaderAdaptor extends EditFieldContextLoader {
|
class FieldContextLoader extends IFieldContextLoader {
|
||||||
|
@override
|
||||||
final String gridId;
|
final String gridId;
|
||||||
final Field field;
|
final Field field;
|
||||||
|
|
||||||
FieldContextLoaderAdaptor({
|
FieldContextLoader({
|
||||||
required this.gridId,
|
required this.gridId,
|
||||||
required this.field,
|
required this.field,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Either<EditFieldContext, FlowyError>> load() {
|
Future<Either<FieldTypeOptionData, FlowyError>> load() {
|
||||||
final payload = EditFieldPayload.create()
|
final payload = EditFieldPayload.create()
|
||||||
..gridId = gridId
|
..gridId = gridId
|
||||||
..fieldId = field.id
|
..fieldId = field.id
|
||||||
..fieldType = field.fieldType;
|
..fieldType = field.fieldType;
|
||||||
|
|
||||||
return GridEventGetEditFieldContext(payload).send();
|
return GridEventGetFieldTypeOption(payload).send();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@override
|
|
||||||
Future<Either<EditFieldContext, FlowyError>> switchToField(String fieldId, FieldType fieldType) async {
|
class GridFieldContext {
|
||||||
final fieldService = FieldService(gridId: gridId, fieldId: fieldId);
|
final String gridId;
|
||||||
return fieldService.switchToField(fieldType);
|
final IFieldContextLoader _loader;
|
||||||
|
|
||||||
|
late FieldTypeOptionData _data;
|
||||||
|
ValueNotifier<Field>? _fieldNotifier;
|
||||||
|
|
||||||
|
GridFieldContext({
|
||||||
|
required this.gridId,
|
||||||
|
required IFieldContextLoader loader,
|
||||||
|
}) : _loader = loader;
|
||||||
|
|
||||||
|
Future<Either<Unit, FlowyError>> loadData() async {
|
||||||
|
final result = await _loader.load();
|
||||||
|
return result.fold(
|
||||||
|
(data) {
|
||||||
|
data.freeze();
|
||||||
|
_data = data;
|
||||||
|
|
||||||
|
if (_fieldNotifier == null) {
|
||||||
|
_fieldNotifier = ValueNotifier(data.field_2);
|
||||||
|
} else {
|
||||||
|
_fieldNotifier?.value = data.field_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return left(unit);
|
||||||
|
},
|
||||||
|
(err) {
|
||||||
|
Log.error(err);
|
||||||
|
return right(err);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Field get field => _data.field_2;
|
||||||
|
|
||||||
|
set field(Field 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, Field? 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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (_data.field_2 != _fieldNotifier?.value) {
|
||||||
|
_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(Field) callback) {
|
||||||
|
listener() {
|
||||||
|
callback(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
_fieldNotifier?.addListener(listener);
|
||||||
|
return listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeFieldListener(void Function() listener) {
|
||||||
|
_fieldNotifier?.removeListener(listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
@ -5,8 +6,18 @@ import 'dart:async';
|
|||||||
import 'package:protobuf/protobuf.dart';
|
import 'package:protobuf/protobuf.dart';
|
||||||
part 'date_bloc.freezed.dart';
|
part 'date_bloc.freezed.dart';
|
||||||
|
|
||||||
|
typedef DateTypeOptionContext = TypeOptionContext<DateTypeOption>;
|
||||||
|
|
||||||
|
class DateTypeOptionDataBuilder extends TypeOptionDataBuilder<DateTypeOption> {
|
||||||
|
@override
|
||||||
|
DateTypeOption fromBuffer(List<int> buffer) {
|
||||||
|
return DateTypeOption.fromBuffer(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class DateTypeOptionBloc extends Bloc<DateTypeOptionEvent, DateTypeOptionState> {
|
class DateTypeOptionBloc extends Bloc<DateTypeOptionEvent, DateTypeOptionState> {
|
||||||
DateTypeOptionBloc({required DateTypeOption typeOption}) : super(DateTypeOptionState.initial(typeOption)) {
|
DateTypeOptionBloc({required DateTypeOptionContext typeOptionContext})
|
||||||
|
: super(DateTypeOptionState.initial(typeOptionContext.typeOption)) {
|
||||||
on<DateTypeOptionEvent>(
|
on<DateTypeOptionEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
event.map(
|
event.map(
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
||||||
import 'dart:async';
|
|
||||||
import 'package:dartz/dartz.dart';
|
|
||||||
part 'field_option_pannel_bloc.freezed.dart';
|
|
||||||
|
|
||||||
class FieldOptionPannelBloc extends Bloc<FieldOptionPannelEvent, FieldOptionPannelState> {
|
|
||||||
FieldOptionPannelBloc({required List<SelectOption> options}) : super(FieldOptionPannelState.initial(options)) {
|
|
||||||
on<FieldOptionPannelEvent>(
|
|
||||||
(event, emit) async {
|
|
||||||
await event.map(
|
|
||||||
createOption: (_CreateOption value) async {
|
|
||||||
emit(state.copyWith(isEditingOption: false, newOptionName: Some(value.optionName)));
|
|
||||||
},
|
|
||||||
beginAddingOption: (_BeginAddingOption value) {
|
|
||||||
emit(state.copyWith(isEditingOption: true, newOptionName: none()));
|
|
||||||
},
|
|
||||||
endAddingOption: (_EndAddingOption value) {
|
|
||||||
emit(state.copyWith(isEditingOption: false, newOptionName: none()));
|
|
||||||
},
|
|
||||||
updateOption: (_UpdateOption value) {
|
|
||||||
emit(state.copyWith(updateOption: Some(value.option)));
|
|
||||||
},
|
|
||||||
deleteOption: (_DeleteOption value) {
|
|
||||||
emit(state.copyWith(deleteOption: Some(value.option)));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> close() async {
|
|
||||||
return super.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
class FieldOptionPannelEvent with _$FieldOptionPannelEvent {
|
|
||||||
const factory FieldOptionPannelEvent.createOption(String optionName) = _CreateOption;
|
|
||||||
const factory FieldOptionPannelEvent.beginAddingOption() = _BeginAddingOption;
|
|
||||||
const factory FieldOptionPannelEvent.endAddingOption() = _EndAddingOption;
|
|
||||||
const factory FieldOptionPannelEvent.updateOption(SelectOption option) = _UpdateOption;
|
|
||||||
const factory FieldOptionPannelEvent.deleteOption(SelectOption option) = _DeleteOption;
|
|
||||||
}
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
class FieldOptionPannelState with _$FieldOptionPannelState {
|
|
||||||
const factory FieldOptionPannelState({
|
|
||||||
required List<SelectOption> options,
|
|
||||||
required bool isEditingOption,
|
|
||||||
required Option<String> newOptionName,
|
|
||||||
required Option<SelectOption> updateOption,
|
|
||||||
required Option<SelectOption> deleteOption,
|
|
||||||
}) = _FieldOptionPannelState;
|
|
||||||
|
|
||||||
factory FieldOptionPannelState.initial(List<SelectOption> options) => FieldOptionPannelState(
|
|
||||||
options: options,
|
|
||||||
isEditingOption: false,
|
|
||||||
newOptionName: none(),
|
|
||||||
updateOption: none(),
|
|
||||||
deleteOption: none(),
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
import 'package:flowy_sdk/log.dart';
|
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
||||||
import 'dart:async';
|
|
||||||
import 'package:protobuf/protobuf.dart';
|
|
||||||
import 'type_option_service.dart';
|
|
||||||
|
|
||||||
part 'multi_select_bloc.freezed.dart';
|
|
||||||
|
|
||||||
class MultiSelectTypeOptionBloc extends Bloc<MultiSelectTypeOptionEvent, MultiSelectTypeOptionState> {
|
|
||||||
final TypeOptionService service;
|
|
||||||
|
|
||||||
MultiSelectTypeOptionBloc(TypeOptionContext typeOptionContext)
|
|
||||||
: service = TypeOptionService(gridId: typeOptionContext.gridId, fieldId: typeOptionContext.field.id),
|
|
||||||
super(MultiSelectTypeOptionState.initial(MultiSelectTypeOption.fromBuffer(typeOptionContext.data))) {
|
|
||||||
on<MultiSelectTypeOptionEvent>(
|
|
||||||
(event, emit) async {
|
|
||||||
await event.map(
|
|
||||||
createOption: (_CreateOption value) async {
|
|
||||||
final result = await service.newOption(name: value.optionName);
|
|
||||||
result.fold(
|
|
||||||
(option) {
|
|
||||||
emit(state.copyWith(typeOption: _insertOption(option)));
|
|
||||||
},
|
|
||||||
(err) => Log.error(err),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
updateOption: (_UpdateOption value) async {
|
|
||||||
emit(state.copyWith(typeOption: _updateOption(value.option)));
|
|
||||||
},
|
|
||||||
deleteOption: (_DeleteOption value) {
|
|
||||||
emit(state.copyWith(typeOption: _deleteOption(value.option)));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> close() async {
|
|
||||||
return super.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
MultiSelectTypeOption _insertOption(SelectOption option) {
|
|
||||||
state.typeOption.freeze();
|
|
||||||
return state.typeOption.rebuild((typeOption) {
|
|
||||||
typeOption.options.insert(0, option);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
MultiSelectTypeOption _updateOption(SelectOption option) {
|
|
||||||
state.typeOption.freeze();
|
|
||||||
return state.typeOption.rebuild((typeOption) {
|
|
||||||
final index = typeOption.options.indexWhere((element) => element.id == option.id);
|
|
||||||
if (index != -1) {
|
|
||||||
typeOption.options[index] = option;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
MultiSelectTypeOption _deleteOption(SelectOption option) {
|
|
||||||
state.typeOption.freeze();
|
|
||||||
return state.typeOption.rebuild((typeOption) {
|
|
||||||
final index = typeOption.options.indexWhere((element) => element.id == option.id);
|
|
||||||
if (index != -1) {
|
|
||||||
typeOption.options.removeAt(index);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
class MultiSelectTypeOptionEvent with _$MultiSelectTypeOptionEvent {
|
|
||||||
const factory MultiSelectTypeOptionEvent.createOption(String optionName) = _CreateOption;
|
|
||||||
const factory MultiSelectTypeOptionEvent.updateOption(SelectOption option) = _UpdateOption;
|
|
||||||
const factory MultiSelectTypeOptionEvent.deleteOption(SelectOption option) = _DeleteOption;
|
|
||||||
}
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
class MultiSelectTypeOptionState with _$MultiSelectTypeOptionState {
|
|
||||||
const factory MultiSelectTypeOptionState({
|
|
||||||
required MultiSelectTypeOption typeOption,
|
|
||||||
}) = _MultiSelectTypeOptionState;
|
|
||||||
|
|
||||||
factory MultiSelectTypeOptionState.initial(MultiSelectTypeOption typeOption) => MultiSelectTypeOptionState(
|
|
||||||
typeOption: typeOption,
|
|
||||||
);
|
|
||||||
}
|
|
@ -0,0 +1,77 @@
|
|||||||
|
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
|
||||||
|
import 'package:flowy_sdk/log.dart';
|
||||||
|
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:protobuf/protobuf.dart';
|
||||||
|
import 'select_option_type_option_bloc.dart';
|
||||||
|
import 'type_option_service.dart';
|
||||||
|
|
||||||
|
class MultiSelectTypeOptionContext extends TypeOptionContext<MultiSelectTypeOption> with SelectOptionTypeOptionAction {
|
||||||
|
final TypeOptionService service;
|
||||||
|
|
||||||
|
MultiSelectTypeOptionContext({
|
||||||
|
required MultiSelectTypeOptionDataBuilder dataBuilder,
|
||||||
|
required GridFieldContext fieldContext,
|
||||||
|
}) : service = TypeOptionService(
|
||||||
|
gridId: fieldContext.gridId,
|
||||||
|
fieldId: fieldContext.field.id,
|
||||||
|
),
|
||||||
|
super(dataBuilder: dataBuilder, fieldContext: fieldContext);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<SelectOption> Function(SelectOption) get deleteOption {
|
||||||
|
return (SelectOption option) {
|
||||||
|
typeOption.freeze();
|
||||||
|
typeOption = typeOption.rebuild((typeOption) {
|
||||||
|
final index = typeOption.options.indexWhere((element) => element.id == option.id);
|
||||||
|
if (index != -1) {
|
||||||
|
typeOption.options.removeAt(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return typeOption.options;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<SelectOption>> Function(String) get insertOption {
|
||||||
|
return (String optionName) {
|
||||||
|
return service.newOption(name: optionName).then((result) {
|
||||||
|
return result.fold(
|
||||||
|
(option) {
|
||||||
|
typeOption.freeze();
|
||||||
|
typeOption = typeOption.rebuild((typeOption) {
|
||||||
|
typeOption.options.insert(0, option);
|
||||||
|
});
|
||||||
|
|
||||||
|
return typeOption.options;
|
||||||
|
},
|
||||||
|
(err) {
|
||||||
|
Log.error(err);
|
||||||
|
return typeOption.options;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<SelectOption> Function(SelectOption) get udpateOption {
|
||||||
|
return (SelectOption option) {
|
||||||
|
typeOption.freeze();
|
||||||
|
typeOption = typeOption.rebuild((typeOption) {
|
||||||
|
final index = typeOption.options.indexWhere((element) => element.id == option.id);
|
||||||
|
if (index != -1) {
|
||||||
|
typeOption.options[index] = option;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return typeOption.options;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MultiSelectTypeOptionDataBuilder extends TypeOptionDataBuilder<MultiSelectTypeOption> {
|
||||||
|
@override
|
||||||
|
MultiSelectTypeOption fromBuffer(List<int> buffer) {
|
||||||
|
return MultiSelectTypeOption.fromBuffer(buffer);
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'package:app_flowy/workspace/application/grid/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:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
@ -6,8 +8,18 @@ import 'package:protobuf/protobuf.dart';
|
|||||||
|
|
||||||
part 'number_bloc.freezed.dart';
|
part 'number_bloc.freezed.dart';
|
||||||
|
|
||||||
|
typedef NumberTypeOptionContext = TypeOptionContext<NumberTypeOption>;
|
||||||
|
|
||||||
|
class NumberTypeOptionDataBuilder extends TypeOptionDataBuilder<NumberTypeOption> {
|
||||||
|
@override
|
||||||
|
NumberTypeOption fromBuffer(List<int> buffer) {
|
||||||
|
return NumberTypeOption.fromBuffer(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class NumberTypeOptionBloc extends Bloc<NumberTypeOptionEvent, NumberTypeOptionState> {
|
class NumberTypeOptionBloc extends Bloc<NumberTypeOptionEvent, NumberTypeOptionState> {
|
||||||
NumberTypeOptionBloc({required NumberTypeOption typeOption}) : super(NumberTypeOptionState.initial(typeOption)) {
|
NumberTypeOptionBloc({required NumberTypeOptionContext typeOptionContext})
|
||||||
|
: super(NumberTypeOptionState.initial(typeOptionContext.typeOption)) {
|
||||||
on<NumberTypeOptionEvent>(
|
on<NumberTypeOptionEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
event.map(
|
event.map(
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/format.pbenum.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
part 'select_option_type_option_bloc.freezed.dart';
|
||||||
|
|
||||||
|
abstract class SelectOptionTypeOptionAction {
|
||||||
|
Future<List<SelectOption>> Function(String) get insertOption;
|
||||||
|
|
||||||
|
List<SelectOption> Function(SelectOption) get deleteOption;
|
||||||
|
|
||||||
|
List<SelectOption> Function(SelectOption) get udpateOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelectOptionTypeOptionBloc extends Bloc<SelectOptionTypeOptionEvent, SelectOptionTypeOptionState> {
|
||||||
|
final SelectOptionTypeOptionAction typeOptionAction;
|
||||||
|
|
||||||
|
SelectOptionTypeOptionBloc({
|
||||||
|
required List<SelectOption> options,
|
||||||
|
required this.typeOptionAction,
|
||||||
|
}) : super(SelectOptionTypeOptionState.initial(options)) {
|
||||||
|
on<SelectOptionTypeOptionEvent>(
|
||||||
|
(event, emit) async {
|
||||||
|
await event.when(
|
||||||
|
createOption: (optionName) async {
|
||||||
|
final List<SelectOption> options = await typeOptionAction.insertOption(optionName);
|
||||||
|
emit(state.copyWith(options: options));
|
||||||
|
},
|
||||||
|
addingOption: () {
|
||||||
|
emit(state.copyWith(isEditingOption: true, newOptionName: none()));
|
||||||
|
},
|
||||||
|
endAddingOption: () {
|
||||||
|
emit(state.copyWith(isEditingOption: false, newOptionName: none()));
|
||||||
|
},
|
||||||
|
updateOption: (option) {
|
||||||
|
final List<SelectOption> options = typeOptionAction.udpateOption(option);
|
||||||
|
emit(state.copyWith(options: options));
|
||||||
|
},
|
||||||
|
deleteOption: (option) {
|
||||||
|
final List<SelectOption> options = typeOptionAction.deleteOption(option);
|
||||||
|
emit(state.copyWith(options: options));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() async {
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SelectOptionTypeOptionEvent with _$SelectOptionTypeOptionEvent {
|
||||||
|
const factory SelectOptionTypeOptionEvent.createOption(String optionName) = _CreateOption;
|
||||||
|
const factory SelectOptionTypeOptionEvent.addingOption() = _AddingOption;
|
||||||
|
const factory SelectOptionTypeOptionEvent.endAddingOption() = _EndAddingOption;
|
||||||
|
const factory SelectOptionTypeOptionEvent.updateOption(SelectOption option) = _UpdateOption;
|
||||||
|
const factory SelectOptionTypeOptionEvent.deleteOption(SelectOption option) = _DeleteOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SelectOptionTypeOptionState with _$SelectOptionTypeOptionState {
|
||||||
|
const factory SelectOptionTypeOptionState({
|
||||||
|
required List<SelectOption> options,
|
||||||
|
required bool isEditingOption,
|
||||||
|
required Option<String> newOptionName,
|
||||||
|
}) = _SelectOptionTyepOptionState;
|
||||||
|
|
||||||
|
factory SelectOptionTypeOptionState.initial(List<SelectOption> options) => SelectOptionTypeOptionState(
|
||||||
|
options: options,
|
||||||
|
isEditingOption: false,
|
||||||
|
newOptionName: none(),
|
||||||
|
);
|
||||||
|
}
|
@ -1,92 +0,0 @@
|
|||||||
import 'package:flowy_sdk/log.dart';
|
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
||||||
import 'dart:async';
|
|
||||||
import 'package:protobuf/protobuf.dart';
|
|
||||||
import 'type_option_service.dart';
|
|
||||||
|
|
||||||
part 'single_select_bloc.freezed.dart';
|
|
||||||
|
|
||||||
class SingleSelectTypeOptionBloc extends Bloc<SingleSelectTypeOptionEvent, SingleSelectTypeOptionState> {
|
|
||||||
final TypeOptionService service;
|
|
||||||
|
|
||||||
SingleSelectTypeOptionBloc(
|
|
||||||
TypeOptionContext typeOptionContext,
|
|
||||||
) : service = TypeOptionService(gridId: typeOptionContext.gridId, fieldId: typeOptionContext.field.id),
|
|
||||||
super(
|
|
||||||
SingleSelectTypeOptionState.initial(SingleSelectTypeOption.fromBuffer(typeOptionContext.data)),
|
|
||||||
) {
|
|
||||||
on<SingleSelectTypeOptionEvent>(
|
|
||||||
(event, emit) async {
|
|
||||||
await event.map(
|
|
||||||
createOption: (_CreateOption value) async {
|
|
||||||
final result = await service.newOption(name: value.optionName);
|
|
||||||
result.fold(
|
|
||||||
(option) {
|
|
||||||
emit(state.copyWith(typeOption: _insertOption(option)));
|
|
||||||
},
|
|
||||||
(err) => Log.error(err),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
updateOption: (_UpdateOption value) async {
|
|
||||||
emit(state.copyWith(typeOption: _updateOption(value.option)));
|
|
||||||
},
|
|
||||||
deleteOption: (_DeleteOption value) {
|
|
||||||
emit(state.copyWith(typeOption: _deleteOption(value.option)));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> close() async {
|
|
||||||
return super.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
SingleSelectTypeOption _insertOption(SelectOption option) {
|
|
||||||
state.typeOption.freeze();
|
|
||||||
return state.typeOption.rebuild((typeOption) {
|
|
||||||
typeOption.options.insert(0, option);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
SingleSelectTypeOption _updateOption(SelectOption option) {
|
|
||||||
state.typeOption.freeze();
|
|
||||||
return state.typeOption.rebuild((typeOption) {
|
|
||||||
final index = typeOption.options.indexWhere((element) => element.id == option.id);
|
|
||||||
if (index != -1) {
|
|
||||||
typeOption.options[index] = option;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
SingleSelectTypeOption _deleteOption(SelectOption option) {
|
|
||||||
state.typeOption.freeze();
|
|
||||||
return state.typeOption.rebuild((typeOption) {
|
|
||||||
final index = typeOption.options.indexWhere((element) => element.id == option.id);
|
|
||||||
if (index != -1) {
|
|
||||||
typeOption.options.removeAt(index);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
class SingleSelectTypeOptionEvent with _$SingleSelectTypeOptionEvent {
|
|
||||||
const factory SingleSelectTypeOptionEvent.createOption(String optionName) = _CreateOption;
|
|
||||||
const factory SingleSelectTypeOptionEvent.updateOption(SelectOption option) = _UpdateOption;
|
|
||||||
const factory SingleSelectTypeOptionEvent.deleteOption(SelectOption option) = _DeleteOption;
|
|
||||||
}
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
class SingleSelectTypeOptionState with _$SingleSelectTypeOptionState {
|
|
||||||
const factory SingleSelectTypeOptionState({
|
|
||||||
required SingleSelectTypeOption typeOption,
|
|
||||||
}) = _SingleSelectTypeOptionState;
|
|
||||||
|
|
||||||
factory SingleSelectTypeOptionState.initial(SingleSelectTypeOption typeOption) => SingleSelectTypeOptionState(
|
|
||||||
typeOption: typeOption,
|
|
||||||
);
|
|
||||||
}
|
|
@ -0,0 +1,78 @@
|
|||||||
|
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
|
||||||
|
import 'package:flowy_sdk/log.dart';
|
||||||
|
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:protobuf/protobuf.dart';
|
||||||
|
import 'select_option_type_option_bloc.dart';
|
||||||
|
import 'type_option_service.dart';
|
||||||
|
|
||||||
|
class SingleSelectTypeOptionContext extends TypeOptionContext<SingleSelectTypeOption>
|
||||||
|
with SelectOptionTypeOptionAction {
|
||||||
|
final TypeOptionService service;
|
||||||
|
|
||||||
|
SingleSelectTypeOptionContext({
|
||||||
|
required SingleSelectTypeOptionDataBuilder dataBuilder,
|
||||||
|
required GridFieldContext fieldContext,
|
||||||
|
}) : service = TypeOptionService(
|
||||||
|
gridId: fieldContext.gridId,
|
||||||
|
fieldId: fieldContext.field.id,
|
||||||
|
),
|
||||||
|
super(dataBuilder: dataBuilder, fieldContext: fieldContext);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<SelectOption> Function(SelectOption) get deleteOption {
|
||||||
|
return (SelectOption option) {
|
||||||
|
typeOption.freeze();
|
||||||
|
typeOption = typeOption.rebuild((typeOption) {
|
||||||
|
final index = typeOption.options.indexWhere((element) => element.id == option.id);
|
||||||
|
if (index != -1) {
|
||||||
|
typeOption.options.removeAt(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return typeOption.options;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<SelectOption>> Function(String) get insertOption {
|
||||||
|
return (String optionName) {
|
||||||
|
return service.newOption(name: optionName).then((result) {
|
||||||
|
return result.fold(
|
||||||
|
(option) {
|
||||||
|
typeOption.freeze();
|
||||||
|
typeOption = typeOption.rebuild((typeOption) {
|
||||||
|
typeOption.options.insert(0, option);
|
||||||
|
});
|
||||||
|
|
||||||
|
return typeOption.options;
|
||||||
|
},
|
||||||
|
(err) {
|
||||||
|
Log.error(err);
|
||||||
|
return typeOption.options;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<SelectOption> Function(SelectOption) get udpateOption {
|
||||||
|
return (SelectOption option) {
|
||||||
|
typeOption.freeze();
|
||||||
|
typeOption = typeOption.rebuild((typeOption) {
|
||||||
|
final index = typeOption.options.indexWhere((element) => element.id == option.id);
|
||||||
|
if (index != -1) {
|
||||||
|
typeOption.options[index] = option;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return typeOption.options;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SingleSelectTypeOptionDataBuilder extends TypeOptionDataBuilder<SingleSelectTypeOption> {
|
||||||
|
@override
|
||||||
|
SingleSelectTypeOption fromBuffer(List<int> buffer) {
|
||||||
|
return SingleSelectTypeOption.fromBuffer(buffer);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||||
@ -7,6 +8,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
|||||||
import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||||
|
import 'package:protobuf/protobuf.dart';
|
||||||
|
|
||||||
class TypeOptionService {
|
class TypeOptionService {
|
||||||
final String gridId;
|
final String gridId;
|
||||||
@ -32,13 +34,76 @@ class TypeOptionService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TypeOptionContext {
|
abstract class TypeOptionDataBuilder<T> {
|
||||||
|
T fromBuffer(List<int> buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
class TypeOptionContext<T extends GeneratedMessage> {
|
||||||
|
T? _typeOptionObject;
|
||||||
|
final GridFieldContext _fieldContext;
|
||||||
|
final TypeOptionDataBuilder<T> dataBuilder;
|
||||||
|
|
||||||
|
TypeOptionContext({
|
||||||
|
required this.dataBuilder,
|
||||||
|
required GridFieldContext fieldContext,
|
||||||
|
}) : _fieldContext = fieldContext;
|
||||||
|
|
||||||
|
String get gridId => _fieldContext.gridId;
|
||||||
|
|
||||||
|
Field get field => _fieldContext.field;
|
||||||
|
|
||||||
|
T get typeOption {
|
||||||
|
if (_typeOptionObject != null) {
|
||||||
|
return _typeOptionObject!;
|
||||||
|
}
|
||||||
|
|
||||||
|
final T object = dataBuilder.fromBuffer(_fieldContext.typeOptionData);
|
||||||
|
_typeOptionObject = object;
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
set typeOption(T typeOption) {
|
||||||
|
_fieldContext.typeOptionData = typeOption.writeToBuffer();
|
||||||
|
_typeOptionObject = typeOption;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class TypeOptionFieldDelegate {
|
||||||
|
void onFieldChanged(void Function(String) callback);
|
||||||
|
void dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
class TypeOptionContext2<T> {
|
||||||
final String gridId;
|
final String gridId;
|
||||||
final Field field;
|
final Field field;
|
||||||
final Uint8List data;
|
final FieldService _fieldService;
|
||||||
const TypeOptionContext({
|
T? _data;
|
||||||
|
final TypeOptionDataBuilder dataBuilder;
|
||||||
|
|
||||||
|
TypeOptionContext2({
|
||||||
required this.gridId,
|
required this.gridId,
|
||||||
required this.field,
|
required this.field,
|
||||||
required this.data,
|
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,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/protobuf.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/protobuf.dart';
|
||||||
@ -8,6 +9,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
|||||||
import 'cell/cell_service/cell_service.dart';
|
import 'cell/cell_service/cell_service.dart';
|
||||||
import 'grid_service.dart';
|
import 'grid_service.dart';
|
||||||
import 'row/row_service.dart';
|
import 'row/row_service.dart';
|
||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
part 'grid_bloc.freezed.dart';
|
part 'grid_bloc.freezed.dart';
|
||||||
|
|
||||||
@ -33,19 +35,19 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
|||||||
|
|
||||||
on<GridEvent>(
|
on<GridEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
await event.map(
|
await event.when(
|
||||||
initial: (InitialGrid value) async {
|
initial: () async {
|
||||||
_startListening();
|
_startListening();
|
||||||
await _loadGrid(emit);
|
await _loadGrid(emit);
|
||||||
},
|
},
|
||||||
createRow: (_CreateRow value) {
|
createRow: () {
|
||||||
_gridService.createRow();
|
_gridService.createRow();
|
||||||
},
|
},
|
||||||
didReceiveRowUpdate: (_DidReceiveRowUpdate value) {
|
didReceiveRowUpdate: (rows, listState) {
|
||||||
emit(state.copyWith(rows: value.rows, listState: value.listState));
|
emit(state.copyWith(rows: rows, listState: listState));
|
||||||
},
|
},
|
||||||
didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
|
didReceiveFieldUpdate: (fields) {
|
||||||
emit(state.copyWith(rows: rowCache.clonedRows, fields: value.fields));
|
emit(state.copyWith(rows: rowCache.clonedRows, fields: GridFieldEquatable(fields)));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -93,7 +95,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
|||||||
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
grid: Some(grid),
|
grid: Some(grid),
|
||||||
fields: fieldCache.clonedFields,
|
fields: GridFieldEquatable(fieldCache.fields),
|
||||||
rows: rowCache.clonedRows,
|
rows: rowCache.clonedRows,
|
||||||
loadingState: GridLoadingState.finish(left(unit)),
|
loadingState: GridLoadingState.finish(left(unit)),
|
||||||
));
|
));
|
||||||
@ -117,14 +119,14 @@ class GridState with _$GridState {
|
|||||||
const factory GridState({
|
const factory GridState({
|
||||||
required String gridId,
|
required String gridId,
|
||||||
required Option<Grid> grid,
|
required Option<Grid> grid,
|
||||||
required List<Field> fields,
|
required GridFieldEquatable fields,
|
||||||
required List<GridRow> rows,
|
required List<GridRow> rows,
|
||||||
required GridLoadingState loadingState,
|
required GridLoadingState loadingState,
|
||||||
required GridRowChangeReason listState,
|
required GridRowChangeReason listState,
|
||||||
}) = _GridState;
|
}) = _GridState;
|
||||||
|
|
||||||
factory GridState.initial(String gridId) => GridState(
|
factory GridState.initial(String gridId) => GridState(
|
||||||
fields: [],
|
fields: const GridFieldEquatable([]),
|
||||||
rows: [],
|
rows: [],
|
||||||
grid: none(),
|
grid: none(),
|
||||||
gridId: gridId,
|
gridId: gridId,
|
||||||
@ -138,3 +140,19 @@ class GridLoadingState with _$GridLoadingState {
|
|||||||
const factory GridLoadingState.loading() = _Loading;
|
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<Field> _fields;
|
||||||
|
|
||||||
|
const GridFieldEquatable(List<Field> fields) : _fields = fields;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props {
|
||||||
|
return [
|
||||||
|
_fields.length,
|
||||||
|
_fields.map((field) => field.width).reduce((value, element) => value + element),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
UnmodifiableListView<Field> get value => UnmodifiableListView(_fields);
|
||||||
|
}
|
||||||
|
@ -15,7 +15,7 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
|
|||||||
GridHeaderBloc({
|
GridHeaderBloc({
|
||||||
required this.gridId,
|
required this.gridId,
|
||||||
required this.fieldCache,
|
required this.fieldCache,
|
||||||
}) : super(GridHeaderState.initial(fieldCache.clonedFields)) {
|
}) : super(GridHeaderState.initial(fieldCache.fields)) {
|
||||||
on<GridHeaderEvent>(
|
on<GridHeaderEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
await event.map(
|
await event.map(
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
import 'package:app_flowy/workspace/application/grid/field/grid_listenr.dart';
|
import 'package:app_flowy/workspace/application/grid/field/grid_listenr.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||||
@ -6,8 +8,6 @@ import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
|||||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
||||||
|
|
||||||
import 'cell/cell_service/cell_service.dart';
|
import 'cell/cell_service/cell_service.dart';
|
||||||
import 'row/row_service.dart';
|
import 'row/row_service.dart';
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ typedef ChangesetListener = void Function(GridFieldChangeset);
|
|||||||
class GridFieldCache {
|
class GridFieldCache {
|
||||||
final String gridId;
|
final String gridId;
|
||||||
late final GridFieldsListener _fieldListener;
|
late final GridFieldsListener _fieldListener;
|
||||||
final FieldsNotifier _fieldNotifier = FieldsNotifier();
|
FieldsNotifier? _fieldNotifier = FieldsNotifier();
|
||||||
final List<ChangesetListener> _changesetListener = [];
|
final List<ChangesetListener> _changesetListener = [];
|
||||||
|
|
||||||
GridFieldCache({required this.gridId}) {
|
GridFieldCache({required this.gridId}) {
|
||||||
@ -81,15 +81,16 @@ class GridFieldCache {
|
|||||||
|
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
await _fieldListener.stop();
|
await _fieldListener.stop();
|
||||||
_fieldNotifier.dispose();
|
_fieldNotifier?.dispose();
|
||||||
|
_fieldNotifier = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
UnmodifiableListView<Field> get unmodifiableFields => UnmodifiableListView(_fieldNotifier.fields);
|
UnmodifiableListView<Field> get unmodifiableFields => UnmodifiableListView(_fieldNotifier?.fields ?? []);
|
||||||
|
|
||||||
List<Field> get clonedFields => [..._fieldNotifier.fields];
|
List<Field> get fields => [..._fieldNotifier?.fields ?? []];
|
||||||
|
|
||||||
set fields(List<Field> fields) {
|
set fields(List<Field> fields) {
|
||||||
_fieldNotifier.fields = [...fields];
|
_fieldNotifier?.fields = [...fields];
|
||||||
}
|
}
|
||||||
|
|
||||||
VoidCallback addListener(
|
VoidCallback addListener(
|
||||||
@ -100,7 +101,7 @@ class GridFieldCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (onChanged != null) {
|
if (onChanged != null) {
|
||||||
onChanged(clonedFields);
|
onChanged(fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
@ -108,12 +109,12 @@ class GridFieldCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_fieldNotifier.addListener(f);
|
_fieldNotifier?.addListener(f);
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeListener(VoidCallback f) {
|
void removeListener(VoidCallback f) {
|
||||||
_fieldNotifier.removeListener(f);
|
_fieldNotifier?.removeListener(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void addChangesetListener(ChangesetListener listener) {
|
void addChangesetListener(ChangesetListener listener) {
|
||||||
@ -131,43 +132,43 @@ class GridFieldCache {
|
|||||||
if (deletedFields.isEmpty) {
|
if (deletedFields.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final List<Field> fields = _fieldNotifier.fields;
|
final List<Field> newFields = fields;
|
||||||
final Map<String, FieldOrder> deletedFieldMap = {
|
final Map<String, FieldOrder> deletedFieldMap = {
|
||||||
for (var fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder
|
for (var fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder
|
||||||
};
|
};
|
||||||
|
|
||||||
fields.retainWhere((field) => (deletedFieldMap[field.id] == null));
|
newFields.retainWhere((field) => (deletedFieldMap[field.id] == null));
|
||||||
_fieldNotifier.fields = fields;
|
_fieldNotifier?.fields = newFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _insertFields(List<IndexField> insertedFields) {
|
void _insertFields(List<IndexField> insertedFields) {
|
||||||
if (insertedFields.isEmpty) {
|
if (insertedFields.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final List<Field> fields = _fieldNotifier.fields;
|
final List<Field> newFields = fields;
|
||||||
for (final indexField in insertedFields) {
|
for (final indexField in insertedFields) {
|
||||||
if (fields.length > indexField.index) {
|
if (newFields.length > indexField.index) {
|
||||||
fields.insert(indexField.index, indexField.field_1);
|
newFields.insert(indexField.index, indexField.field_1);
|
||||||
} else {
|
} else {
|
||||||
fields.add(indexField.field_1);
|
newFields.add(indexField.field_1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_fieldNotifier.fields = fields;
|
_fieldNotifier?.fields = newFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateFields(List<Field> updatedFields) {
|
void _updateFields(List<Field> updatedFields) {
|
||||||
if (updatedFields.isEmpty) {
|
if (updatedFields.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final List<Field> fields = _fieldNotifier.fields;
|
final List<Field> newFields = fields;
|
||||||
for (final updatedField in updatedFields) {
|
for (final updatedField in updatedFields) {
|
||||||
final index = fields.indexWhere((field) => field.id == updatedField.id);
|
final index = newFields.indexWhere((field) => field.id == updatedField.id);
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
fields.removeAt(index);
|
newFields.removeAt(index);
|
||||||
fields.insert(index, updatedField);
|
newFields.insert(index, updatedField);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_fieldNotifier.fields = fields;
|
_fieldNotifier?.fields = newFields;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,12 +13,12 @@ export 'field/field_editor_pannel_bloc.dart';
|
|||||||
// Field Type Option
|
// Field Type Option
|
||||||
export 'field/type_option/date_bloc.dart';
|
export 'field/type_option/date_bloc.dart';
|
||||||
export 'field/type_option/number_bloc.dart';
|
export 'field/type_option/number_bloc.dart';
|
||||||
export 'field/type_option/single_select_bloc.dart';
|
export 'field/type_option/single_select_type_option.dart';
|
||||||
|
|
||||||
// Cell
|
// Cell
|
||||||
export 'cell/text_cell_bloc.dart';
|
export 'cell/text_cell_bloc.dart';
|
||||||
export 'cell/number_cell_bloc.dart';
|
export 'cell/number_cell_bloc.dart';
|
||||||
export 'cell/selection_cell_bloc.dart';
|
export 'cell/select_option_cell_bloc.dart';
|
||||||
export 'cell/date_cell_bloc.dart';
|
export 'cell/date_cell_bloc.dart';
|
||||||
export 'cell/checkbox_cell_bloc.dart';
|
export 'cell/checkbox_cell_bloc.dart';
|
||||||
export 'cell/cell_service/cell_service.dart';
|
export 'cell/cell_service/cell_service.dart';
|
||||||
|
@ -30,7 +30,7 @@ class RowBloc extends Bloc<RowEvent, RowState> {
|
|||||||
_rowService.createRow();
|
_rowService.createRow();
|
||||||
},
|
},
|
||||||
didReceiveCellDatas: (_DidReceiveCellDatas value) async {
|
didReceiveCellDatas: (_DidReceiveCellDatas value) async {
|
||||||
final fields = value.gridCellMap.values.map((e) => CellSnapshot(e.field)).toList();
|
final fields = value.gridCellMap.values.map((e) => GridCellEquatable(e.field)).toList();
|
||||||
final snapshots = UnmodifiableListView(fields);
|
final snapshots = UnmodifiableListView(fields);
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
gridCellMap: value.gridCellMap,
|
gridCellMap: value.gridCellMap,
|
||||||
@ -74,26 +74,27 @@ class RowState with _$RowState {
|
|||||||
const factory RowState({
|
const factory RowState({
|
||||||
required GridRow rowData,
|
required GridRow rowData,
|
||||||
required GridCellMap gridCellMap,
|
required GridCellMap gridCellMap,
|
||||||
required UnmodifiableListView<CellSnapshot> snapshots,
|
required UnmodifiableListView<GridCellEquatable> snapshots,
|
||||||
GridRowChangeReason? changeReason,
|
GridRowChangeReason? changeReason,
|
||||||
}) = _RowState;
|
}) = _RowState;
|
||||||
|
|
||||||
factory RowState.initial(GridRow rowData, GridCellMap cellDataMap) => RowState(
|
factory RowState.initial(GridRow rowData, GridCellMap cellDataMap) => RowState(
|
||||||
rowData: rowData,
|
rowData: rowData,
|
||||||
gridCellMap: cellDataMap,
|
gridCellMap: cellDataMap,
|
||||||
snapshots: UnmodifiableListView(cellDataMap.values.map((e) => CellSnapshot(e.field)).toList()),
|
snapshots: UnmodifiableListView(cellDataMap.values.map((e) => GridCellEquatable(e.field)).toList()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class CellSnapshot extends Equatable {
|
class GridCellEquatable extends Equatable {
|
||||||
final Field _field;
|
final Field _field;
|
||||||
|
|
||||||
const CellSnapshot(Field field) : _field = field;
|
const GridCellEquatable(Field field) : _field = field;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
_field.id,
|
_field.id,
|
||||||
_field.fieldType,
|
_field.fieldType,
|
||||||
_field.visibility,
|
_field.visibility,
|
||||||
|
_field.width,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ class GridPropertyBloc extends Bloc<GridPropertyEvent, GridPropertyState> {
|
|||||||
|
|
||||||
GridPropertyBloc({required String gridId, required GridFieldCache fieldCache})
|
GridPropertyBloc({required String gridId, required GridFieldCache fieldCache})
|
||||||
: _fieldCache = fieldCache,
|
: _fieldCache = fieldCache,
|
||||||
super(GridPropertyState.initial(gridId, fieldCache.clonedFields)) {
|
super(GridPropertyState.initial(gridId, fieldCache.fields)) {
|
||||||
on<GridPropertyEvent>(
|
on<GridPropertyEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
await event.map(
|
await event.map(
|
||||||
|
@ -49,6 +49,9 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
|||||||
unauthorized: (_Unauthorized value) {
|
unauthorized: (_Unauthorized value) {
|
||||||
emit(state.copyWith(unauthorized: true));
|
emit(state.copyWith(unauthorized: true));
|
||||||
},
|
},
|
||||||
|
collapseMenu: (e) {
|
||||||
|
emit(state.copyWith(isMenuCollapsed: !state.isMenuCollapsed));
|
||||||
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -77,6 +80,7 @@ class HomeEvent with _$HomeEvent {
|
|||||||
const factory HomeEvent.dismissEditPannel() = _DismissEditPannel;
|
const factory HomeEvent.dismissEditPannel() = _DismissEditPannel;
|
||||||
const factory HomeEvent.didReceiveWorkspaceSetting(CurrentWorkspaceSetting setting) = _DidReceiveWorkspaceSetting;
|
const factory HomeEvent.didReceiveWorkspaceSetting(CurrentWorkspaceSetting setting) = _DidReceiveWorkspaceSetting;
|
||||||
const factory HomeEvent.unauthorized(String msg) = _Unauthorized;
|
const factory HomeEvent.unauthorized(String msg) = _Unauthorized;
|
||||||
|
const factory HomeEvent.collapseMenu() = _CollapseMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@ -87,6 +91,7 @@ class HomeState with _$HomeState {
|
|||||||
required Option<EditPannelContext> pannelContext,
|
required Option<EditPannelContext> pannelContext,
|
||||||
required CurrentWorkspaceSetting workspaceSetting,
|
required CurrentWorkspaceSetting workspaceSetting,
|
||||||
required bool unauthorized,
|
required bool unauthorized,
|
||||||
|
required bool isMenuCollapsed,
|
||||||
}) = _HomeState;
|
}) = _HomeState;
|
||||||
|
|
||||||
factory HomeState.initial(CurrentWorkspaceSetting workspaceSetting) => HomeState(
|
factory HomeState.initial(CurrentWorkspaceSetting workspaceSetting) => HomeState(
|
||||||
@ -95,5 +100,6 @@ class HomeState with _$HomeState {
|
|||||||
pannelContext: none(),
|
pannelContext: none(),
|
||||||
workspaceSetting: workspaceSetting,
|
workspaceSetting: workspaceSetting,
|
||||||
unauthorized: false,
|
unauthorized: false,
|
||||||
|
isMenuCollapsed: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -25,10 +25,6 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> {
|
|||||||
listener.start(addAppCallback: _handleAppsOrFail);
|
listener.start(addAppCallback: _handleAppsOrFail);
|
||||||
await _fetchApps(emit);
|
await _fetchApps(emit);
|
||||||
},
|
},
|
||||||
collapse: (e) async {
|
|
||||||
final isCollapse = state.isCollapse;
|
|
||||||
emit(state.copyWith(isCollapse: !isCollapse));
|
|
||||||
},
|
|
||||||
openPage: (e) async {
|
openPage: (e) async {
|
||||||
emit(state.copyWith(plugin: e.plugin));
|
emit(state.copyWith(plugin: e.plugin));
|
||||||
},
|
},
|
||||||
@ -94,7 +90,6 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> {
|
|||||||
@freezed
|
@freezed
|
||||||
class MenuEvent with _$MenuEvent {
|
class MenuEvent with _$MenuEvent {
|
||||||
const factory MenuEvent.initial() = _Initial;
|
const factory MenuEvent.initial() = _Initial;
|
||||||
const factory MenuEvent.collapse() = _Collapse;
|
|
||||||
const factory MenuEvent.openPage(Plugin plugin) = _OpenPage;
|
const factory MenuEvent.openPage(Plugin plugin) = _OpenPage;
|
||||||
const factory MenuEvent.createApp(String name, {String? desc}) = _CreateApp;
|
const factory MenuEvent.createApp(String name, {String? desc}) = _CreateApp;
|
||||||
const factory MenuEvent.moveApp(int fromIndex, int toIndex) = _MoveApp;
|
const factory MenuEvent.moveApp(int fromIndex, int toIndex) = _MoveApp;
|
||||||
@ -104,14 +99,12 @@ class MenuEvent with _$MenuEvent {
|
|||||||
@freezed
|
@freezed
|
||||||
class MenuState with _$MenuState {
|
class MenuState with _$MenuState {
|
||||||
const factory MenuState({
|
const factory MenuState({
|
||||||
required bool isCollapse,
|
|
||||||
required List<App> apps,
|
required List<App> apps,
|
||||||
required Either<Unit, FlowyError> successOrFailure,
|
required Either<Unit, FlowyError> successOrFailure,
|
||||||
required Plugin plugin,
|
required Plugin plugin,
|
||||||
}) = _MenuState;
|
}) = _MenuState;
|
||||||
|
|
||||||
factory MenuState.initial() => MenuState(
|
factory MenuState.initial() => MenuState(
|
||||||
isCollapse: false,
|
|
||||||
apps: [],
|
apps: [],
|
||||||
successOrFailure: left(unit),
|
successOrFailure: left(unit),
|
||||||
plugin: makePlugin(pluginType: DefaultPlugin.blank.type()),
|
plugin: makePlugin(pluginType: DefaultPlugin.blank.type()),
|
||||||
|
@ -18,7 +18,6 @@ import 'home_stack.dart';
|
|||||||
import 'menu/menu.dart';
|
import 'menu/menu.dart';
|
||||||
|
|
||||||
class HomeScreen extends StatefulWidget {
|
class HomeScreen extends StatefulWidget {
|
||||||
static GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
|
|
||||||
final UserProfile user;
|
final UserProfile user;
|
||||||
final CurrentWorkspaceSetting workspaceSetting;
|
final CurrentWorkspaceSetting workspaceSetting;
|
||||||
const HomeScreen(this.user, this.workspaceSetting, {Key? key}) : super(key: key);
|
const HomeScreen(this.user, this.workspaceSetting, {Key? key}) : super(key: key);
|
||||||
@ -52,7 +51,6 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
key: HomeScreen.scaffoldKey,
|
|
||||||
body: BlocListener<HomeBloc, HomeState>(
|
body: BlocListener<HomeBloc, HomeState>(
|
||||||
listenWhen: (p, c) => p.unauthorized != c.unauthorized,
|
listenWhen: (p, c) => p.unauthorized != c.unauthorized,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
|
@ -1,27 +1,26 @@
|
|||||||
|
import 'dart:io' show Platform;
|
||||||
|
|
||||||
import 'package:app_flowy/startup/startup.dart';
|
import 'package:app_flowy/startup/startup.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/home/home_screen.dart';
|
import 'package:app_flowy/workspace/application/home/home_bloc.dart';
|
||||||
|
import 'package:app_flowy/workspace/presentation/home/toast.dart';
|
||||||
import 'package:flowy_infra/theme.dart';
|
import 'package:flowy_infra/theme.dart';
|
||||||
import 'package:flowy_sdk/log.dart';
|
import 'package:flowy_sdk/log.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:time/time.dart';
|
import 'package:time/time.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
|
||||||
|
|
||||||
import 'package:app_flowy/plugin/plugin.dart';
|
import 'package:app_flowy/plugin/plugin.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/blank/blank.dart';
|
import 'package:app_flowy/workspace/presentation/plugins/blank/blank.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
|
import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/home/navigation.dart';
|
import 'package:app_flowy/workspace/presentation/home/navigation.dart';
|
||||||
|
import 'package:app_flowy/core/frameless_window.dart';
|
||||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/extension.dart';
|
import 'package:flowy_infra_ui/style_widget/extension.dart';
|
||||||
import 'package:flowy_infra/notifier.dart';
|
import 'package:flowy_infra/notifier.dart';
|
||||||
|
|
||||||
typedef NavigationCallback = void Function(String id);
|
typedef NavigationCallback = void Function(String id);
|
||||||
|
|
||||||
late FToast fToast;
|
|
||||||
|
|
||||||
class HomeStack extends StatelessWidget {
|
class HomeStack extends StatelessWidget {
|
||||||
static GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
|
|
||||||
// final Size size;
|
|
||||||
const HomeStack({Key? key}) : super(key: key);
|
const HomeStack({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -69,8 +68,7 @@ class _FadingIndexedStackState extends State<FadingIndexedStack> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
fToast = FToast();
|
initToastWithContext(context);
|
||||||
fToast.init(HomeScreen.scaffoldKey.currentState!.context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -152,7 +150,7 @@ class HomeStackManager {
|
|||||||
child: Selector<HomeStackNotifier, Widget>(
|
child: Selector<HomeStackNotifier, Widget>(
|
||||||
selector: (context, notifier) => notifier.titleWidget,
|
selector: (context, notifier) => notifier.titleWidget,
|
||||||
builder: (context, widget, child) {
|
builder: (context, widget, child) {
|
||||||
return const HomeTopBar();
|
return const MoveWindowDetector(child: HomeTopBar());
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -191,6 +189,14 @@ class HomeTopBar extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
|
BlocBuilder<HomeBloc, HomeState>(
|
||||||
|
buildWhen: ((previous, current) => previous.isMenuCollapsed != current.isMenuCollapsed),
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state.isMenuCollapsed && Platform.isMacOS) {
|
||||||
|
return const HSpace(80);
|
||||||
|
}
|
||||||
|
return const HSpace(0);
|
||||||
|
}),
|
||||||
const FlowyNavigation(),
|
const FlowyNavigation(),
|
||||||
const HSpace(16),
|
const HSpace(16),
|
||||||
ChangeNotifierProvider.value(
|
ChangeNotifierProvider.value(
|
||||||
|
@ -28,7 +28,7 @@ class AddButton extends StatelessWidget {
|
|||||||
onSelected: onSelected,
|
onSelected: onSelected,
|
||||||
).show(context);
|
).show(context);
|
||||||
},
|
},
|
||||||
icon: svgWidget("home/add").padding(horizontal: 3, vertical: 3),
|
icon: svgWidget("home/add", color: theme.iconColor).padding(horizontal: 3, vertical: 3),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -46,8 +46,8 @@ class ActionList {
|
|||||||
return CreateItem(
|
return CreateItem(
|
||||||
pluginBuilder: pluginBuilder,
|
pluginBuilder: pluginBuilder,
|
||||||
onSelected: (builder) {
|
onSelected: (builder) {
|
||||||
FlowyOverlay.of(buildContext).remove(_identifier);
|
|
||||||
onSelected(builder);
|
onSelected(builder);
|
||||||
|
FlowyOverlay.of(buildContext).remove(_identifier);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -26,7 +26,7 @@ class ViewSection extends StatelessWidget {
|
|||||||
listenWhen: (p, c) => p.selectedView != c.selectedView,
|
listenWhen: (p, c) => p.selectedView != c.selectedView,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (state.selectedView != null) {
|
if (state.selectedView != null) {
|
||||||
WidgetsBinding.instance?.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
getIt<HomeStackManager>().setPlugin(state.selectedView!.plugin());
|
getIt<HomeStackManager>().setPlugin(state.selectedView!.plugin());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
export './app/header/header.dart';
|
export './app/header/header.dart';
|
||||||
export './app/menu_app.dart';
|
export './app/menu_app.dart';
|
||||||
|
|
||||||
|
import 'dart:io' show Platform;
|
||||||
|
import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/trash/menu.dart';
|
import 'package:app_flowy/workspace/presentation/plugins/trash/menu.dart';
|
||||||
import 'package:flowy_infra/notifier.dart';
|
import 'package:flowy_infra/notifier.dart';
|
||||||
@ -18,7 +20,9 @@ import 'package:expandable/expandable.dart';
|
|||||||
import 'package:flowy_infra/time/duration.dart';
|
import 'package:flowy_infra/time/duration.dart';
|
||||||
import 'package:app_flowy/startup/startup.dart';
|
import 'package:app_flowy/startup/startup.dart';
|
||||||
import 'package:app_flowy/workspace/application/menu/menu_bloc.dart';
|
import 'package:app_flowy/workspace/application/menu/menu_bloc.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
|
import 'package:app_flowy/workspace/application/home/home_bloc.dart';
|
||||||
|
import 'package:app_flowy/core/frameless_window.dart';
|
||||||
|
// import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
|
||||||
import 'package:flowy_infra/image.dart';
|
import 'package:flowy_infra/image.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||||
|
|
||||||
@ -59,10 +63,10 @@ class HomeMenu extends StatelessWidget {
|
|||||||
getIt<HomeStackManager>().setPlugin(state.plugin);
|
getIt<HomeStackManager>().setPlugin(state.plugin);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
BlocListener<MenuBloc, MenuState>(
|
BlocListener<HomeBloc, HomeState>(
|
||||||
listenWhen: (p, c) => p.isCollapse != c.isCollapse,
|
listenWhen: (p, c) => p.isMenuCollapsed != c.isMenuCollapsed,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
_collapsedNotifier.value = state.isCollapse;
|
_collapsedNotifier.value = state.isMenuCollapsed;
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@ -179,6 +183,17 @@ class MenuSharedState {
|
|||||||
|
|
||||||
class MenuTopBar extends StatelessWidget {
|
class MenuTopBar extends StatelessWidget {
|
||||||
const MenuTopBar({Key? key}) : super(key: key);
|
const MenuTopBar({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
Widget renderIcon(BuildContext context) {
|
||||||
|
if (Platform.isMacOS) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
final theme = context.watch<AppTheme>();
|
||||||
|
return (theme.isDark
|
||||||
|
? svgWithSize("flowy_logo_dark_mode", const Size(92, 17))
|
||||||
|
: svgWithSize("flowy_logo_with_text", const Size(92, 17)));
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
@ -186,20 +201,19 @@ class MenuTopBar extends StatelessWidget {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: HomeSizes.topBarHeight,
|
height: HomeSizes.topBarHeight,
|
||||||
|
child: MoveWindowDetector(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
(theme.isDark
|
renderIcon(context),
|
||||||
? svgWithSize("flowy_logo_dark_mode", const Size(92, 17))
|
|
||||||
: svgWithSize("flowy_logo_with_text", const Size(92, 17))),
|
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
FlowyIconButton(
|
FlowyIconButton(
|
||||||
width: 28,
|
width: 28,
|
||||||
onPressed: () => context.read<MenuBloc>().add(const MenuEvent.collapse()),
|
onPressed: () => context.read<HomeBloc>().add(const HomeEvent.collapseMenu()),
|
||||||
iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
|
iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
|
||||||
icon: svgWidget("home/hide_menu", color: theme.iconColor),
|
icon: svgWidget("home/hide_menu", color: theme.iconColor),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
)),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:app_flowy/workspace/application/home/home_bloc.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
||||||
import 'package:flowy_infra/image.dart';
|
import 'package:flowy_infra/image.dart';
|
||||||
import 'package:flowy_infra/notifier.dart';
|
import 'package:flowy_infra/notifier.dart';
|
||||||
@ -95,6 +96,7 @@ class FlowyNavigation extends StatelessWidget {
|
|||||||
width: 24,
|
width: 24,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
notifier.value = false;
|
notifier.value = false;
|
||||||
|
ctx.read<HomeBloc>().add(const HomeEvent.collapseMenu());
|
||||||
},
|
},
|
||||||
iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2),
|
iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2),
|
||||||
icon: svgWidget("home/hide_menu", color: theme.iconColor),
|
icon: svgWidget("home/hide_menu", color: theme.iconColor),
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
import 'package:app_flowy/startup/startup.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
|
|
||||||
|
class FlowyMessageToast extends StatelessWidget {
|
||||||
|
final String message;
|
||||||
|
const FlowyMessageToast({required this.message, Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
|
child: FlowyText.medium(message, color: Colors.white),
|
||||||
|
),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(4)),
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void initToastWithContext(BuildContext context) {
|
||||||
|
getIt<FToast>().init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void showMessageToast(String message) {
|
||||||
|
final child = FlowyMessageToast(message: message);
|
||||||
|
|
||||||
|
getIt<FToast>().showToast(
|
||||||
|
child: child,
|
||||||
|
gravity: ToastGravity.BOTTOM,
|
||||||
|
toastDuration: const Duration(seconds: 3),
|
||||||
|
);
|
||||||
|
}
|
@ -7,6 +7,7 @@ import 'package:app_flowy/workspace/application/appearance.dart';
|
|||||||
import 'package:app_flowy/workspace/application/doc/share_bloc.dart';
|
import 'package:app_flowy/workspace/application/doc/share_bloc.dart';
|
||||||
import 'package:app_flowy/workspace/application/view/view_listener.dart';
|
import 'package:app_flowy/workspace/application/view/view_listener.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
||||||
|
import 'package:app_flowy/workspace/presentation/home/toast.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/widgets/left_bar_item.dart';
|
import 'package:app_flowy/workspace/presentation/plugins/widgets/left_bar_item.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
|
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
|
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||||
@ -179,6 +180,7 @@ class DocumentShareButton extends StatelessWidget {
|
|||||||
switch (action) {
|
switch (action) {
|
||||||
case ShareAction.markdown:
|
case ShareAction.markdown:
|
||||||
context.read<DocShareBloc>().add(const DocShareEvent.shareMarkdown());
|
context.read<DocShareBloc>().add(const DocShareEvent.shareMarkdown());
|
||||||
|
showMessageToast('Exported to: ${LocaleKeys.notifications_export_path.tr()}');
|
||||||
break;
|
break;
|
||||||
case ShareAction.copyLink:
|
case ShareAction.copyLink:
|
||||||
FlowyAlertDialog(title: LocaleKeys.shareAction_workInProgress.tr()).show(context);
|
FlowyAlertDialog(title: LocaleKeys.shareAction_workInProgress.tr()).show(context);
|
||||||
|
@ -184,7 +184,7 @@ class _ToolbarButtonListState extends State<ToolbarButtonList> with WidgetsBindi
|
|||||||
// Listening to the WidgetsBinding instance is necessary so that we can
|
// Listening to the WidgetsBinding instance is necessary so that we can
|
||||||
// hide the arrows when the window gets a new size and thus the toolbar
|
// hide the arrows when the window gets a new size and thus the toolbar
|
||||||
// becomes scrollable/unscrollable.
|
// becomes scrollable/unscrollable.
|
||||||
WidgetsBinding.instance!.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
|
||||||
// Workaround to allow the scroll controller attach to our ListView so that
|
// Workaround to allow the scroll controller attach to our ListView so that
|
||||||
// we can detect if overflow arrows need to be shown on init.
|
// we can detect if overflow arrows need to be shown on init.
|
||||||
@ -226,7 +226,7 @@ class _ToolbarButtonListState extends State<ToolbarButtonList> with WidgetsBindi
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_controller.dispose();
|
_controller.dispose();
|
||||||
WidgetsBinding.instance!.removeObserver(this);
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,9 +29,9 @@ class ToolbarIconButton extends StatelessWidget {
|
|||||||
iconPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
|
iconPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
width: width,
|
width: width,
|
||||||
icon: isToggled == true ? svgWidget(iconName, color: Colors.white) : svgWidget(iconName),
|
icon: isToggled == true ? svgWidget(iconName, color: Colors.white) : svgWidget(iconName, color: theme.iconColor),
|
||||||
fillColor: isToggled == true ? theme.main1 : theme.shader6,
|
fillColor: isToggled == true ? theme.main1 : theme.shader6,
|
||||||
hoverColor: isToggled == true ? theme.main1 : theme.shader5,
|
hoverColor: isToggled == true ? theme.main1 : theme.hover,
|
||||||
tooltipText: tooltipText,
|
tooltipText: tooltipText,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import 'layout/sizes.dart';
|
|||||||
import 'widgets/row/grid_row.dart';
|
import 'widgets/row/grid_row.dart';
|
||||||
import 'widgets/footer/grid_footer.dart';
|
import 'widgets/footer/grid_footer.dart';
|
||||||
import 'widgets/header/grid_header.dart';
|
import 'widgets/header/grid_header.dart';
|
||||||
|
import 'widgets/shortcuts.dart';
|
||||||
import 'widgets/toolbar/grid_toolbar.dart';
|
import 'widgets/toolbar/grid_toolbar.dart';
|
||||||
|
|
||||||
class GridPage extends StatefulWidget {
|
class GridPage extends StatefulWidget {
|
||||||
@ -40,7 +41,7 @@ class _GridPageState extends State<GridPage> {
|
|||||||
return state.loadingState.map(
|
return state.loadingState.map(
|
||||||
loading: (_) => const Center(child: CircularProgressIndicator.adaptive()),
|
loading: (_) => const Center(child: CircularProgressIndicator.adaptive()),
|
||||||
finish: (result) => result.successOrFail.fold(
|
finish: (result) => result.successOrFail.fold(
|
||||||
(_) => const FlowyGrid(),
|
(_) => const GridShortcuts(child: FlowyGrid()),
|
||||||
(err) => FlowyErrorPage(err.toString()),
|
(err) => FlowyErrorPage(err.toString()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -91,9 +92,9 @@ class _FlowyGridState extends State<FlowyGrid> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<GridBloc, GridState>(
|
return BlocBuilder<GridBloc, GridState>(
|
||||||
buildWhen: (previous, current) => previous.fields.length != current.fields.length,
|
buildWhen: (previous, current) => previous.fields != current.fields,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final contentWidth = GridLayout.headerWidth(state.fields);
|
final contentWidth = GridLayout.headerWidth(state.fields.value);
|
||||||
final child = _wrapScrollView(
|
final child = _wrapScrollView(
|
||||||
contentWidth,
|
contentWidth,
|
||||||
[
|
[
|
||||||
|
@ -0,0 +1,198 @@
|
|||||||
|
import 'package:flowy_infra/image.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flowy_infra/theme.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:flowy_infra/size.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
|
class GridCellAccessoryBuildContext {
|
||||||
|
final BuildContext anchorContext;
|
||||||
|
final bool isCellEditing;
|
||||||
|
|
||||||
|
GridCellAccessoryBuildContext({
|
||||||
|
required this.anchorContext,
|
||||||
|
required this.isCellEditing,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class GridCellAccessory implements Widget {
|
||||||
|
void onTap();
|
||||||
|
|
||||||
|
// The accessory will be hidden if enable() return false;
|
||||||
|
bool enable() => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PrimaryCellAccessory extends StatelessWidget with GridCellAccessory {
|
||||||
|
final VoidCallback onTapCallback;
|
||||||
|
final bool isCellEditing;
|
||||||
|
const PrimaryCellAccessory({
|
||||||
|
required this.onTapCallback,
|
||||||
|
required this.isCellEditing,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (isCellEditing) {
|
||||||
|
return const SizedBox();
|
||||||
|
} else {
|
||||||
|
final theme = context.watch<AppTheme>();
|
||||||
|
return svgWidget("grid/expander", color: theme.main1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTap() => onTapCallback();
|
||||||
|
|
||||||
|
@override
|
||||||
|
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;
|
||||||
|
const AccessoryHover({
|
||||||
|
required this.child,
|
||||||
|
this.contentPadding = EdgeInsets.zero,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AccessoryHover> createState() => _AccessoryHoverState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AccessoryHoverState extends State<AccessoryHover> {
|
||||||
|
late AccessoryHoverState _hoverState;
|
||||||
|
VoidCallback? _listenerFn;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_hoverState = AccessoryHoverState();
|
||||||
|
_listenerFn = () => _hoverState.onHover = widget.child.onAccessoryHover?.value ?? false;
|
||||||
|
widget.child.onAccessoryHover?.addListener(_listenerFn!);
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_hoverState.dispose();
|
||||||
|
|
||||||
|
if (_listenerFn != null) {
|
||||||
|
widget.child.onAccessoryHover?.removeListener(_listenerFn!);
|
||||||
|
_listenerFn = null;
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
List<Widget> children = [
|
||||||
|
const _Background(),
|
||||||
|
Padding(padding: widget.contentPadding, child: widget.child),
|
||||||
|
];
|
||||||
|
|
||||||
|
final accessoryBuilder = widget.child.accessoryBuilder;
|
||||||
|
if (accessoryBuilder != null) {
|
||||||
|
final accessories = accessoryBuilder((GridCellAccessoryBuildContext(
|
||||||
|
anchorContext: context,
|
||||||
|
isCellEditing: false,
|
||||||
|
)));
|
||||||
|
children.add(
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 6),
|
||||||
|
child: CellAccessoryContainer(accessories: accessories),
|
||||||
|
).positioned(right: 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ChangeNotifierProvider.value(
|
||||||
|
value: _hoverState,
|
||||||
|
child: MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
opaque: false,
|
||||||
|
onEnter: (p) => setState(() => _hoverState.onHover = true),
|
||||||
|
onExit: (p) => setState(() => _hoverState.onHover = false),
|
||||||
|
child: Stack(
|
||||||
|
fit: StackFit.loose,
|
||||||
|
alignment: AlignmentDirectional.center,
|
||||||
|
children: children,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccessoryHoverState extends ChangeNotifier {
|
||||||
|
bool _onHover = false;
|
||||||
|
|
||||||
|
set onHover(bool value) {
|
||||||
|
if (_onHover != value) {
|
||||||
|
_onHover = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get onHover => _onHover;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Background extends StatelessWidget {
|
||||||
|
const _Background({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = context.watch<AppTheme>();
|
||||||
|
return Consumer<AccessoryHoverState>(
|
||||||
|
builder: (context, state, child) {
|
||||||
|
if (state.onHover) {
|
||||||
|
return FlowyHoverContainer(
|
||||||
|
style: HoverStyle(borderRadius: Corners.s6Border, hoverColor: theme.shader6),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CellAccessoryContainer extends StatelessWidget {
|
||||||
|
final List<GridCellAccessory> accessories;
|
||||||
|
const CellAccessoryContainer({required this.accessories, Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = context.watch<AppTheme>();
|
||||||
|
final children = accessories.where((accessory) => accessory.enable()).map((accessory) {
|
||||||
|
final hover = FlowyHover(
|
||||||
|
style: HoverStyle(hoverColor: theme.bg3, 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(),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
return Wrap(children: children, spacing: 6);
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,16 @@
|
|||||||
import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
|
import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType;
|
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType;
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart';
|
|
||||||
import 'package:flowy_infra/theme.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'cell_accessory.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
|
import 'cell_shortcuts.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
|
||||||
import 'checkbox_cell.dart';
|
import 'checkbox_cell.dart';
|
||||||
import 'date_cell/date_cell.dart';
|
import 'date_cell/date_cell.dart';
|
||||||
import 'number_cell.dart';
|
import 'number_cell.dart';
|
||||||
import 'selection_cell/selection_cell.dart';
|
import 'select_option_cell/select_option_cell.dart';
|
||||||
import 'text_cell.dart';
|
import 'text_cell.dart';
|
||||||
|
import 'url_cell/url_cell.dart';
|
||||||
|
|
||||||
GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, {GridCellStyle? style}) {
|
GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, {GridCellStyle? style}) {
|
||||||
final key = ValueKey(gridCell.cellId());
|
final key = ValueKey(gridCell.cellId());
|
||||||
@ -32,10 +30,10 @@ GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, {
|
|||||||
return NumberCell(cellContextBuilder: cellContextBuilder, key: key);
|
return NumberCell(cellContextBuilder: cellContextBuilder, key: key);
|
||||||
case FieldType.RichText:
|
case FieldType.RichText:
|
||||||
return GridTextCell(cellContextBuilder: cellContextBuilder, style: style, key: key);
|
return GridTextCell(cellContextBuilder: cellContextBuilder, style: style, key: key);
|
||||||
|
case FieldType.URL:
|
||||||
default:
|
return GridURLCell(cellContextBuilder: cellContextBuilder, style: style, key: key);
|
||||||
throw UnimplementedError;
|
|
||||||
}
|
}
|
||||||
|
throw UnimplementedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BlankCell extends StatelessWidget {
|
class BlankCell extends StatelessWidget {
|
||||||
@ -47,26 +45,132 @@ class BlankCell extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class GridCellWidget extends HoverWidget {
|
abstract class CellEditable {
|
||||||
@override
|
GridCellFocusListener get beginFocus;
|
||||||
final ValueNotifier<bool> onFocus = ValueNotifier<bool>(false);
|
|
||||||
|
|
||||||
final GridCellRequestFocusNotifier requestFocus = GridCellRequestFocusNotifier();
|
ValueNotifier<bool> get onCellFocus;
|
||||||
|
|
||||||
GridCellWidget({Key? key}) : super(key: key);
|
ValueNotifier<bool> get onCellEditing;
|
||||||
}
|
}
|
||||||
|
|
||||||
class GridCellRequestFocusNotifier extends ChangeNotifier {
|
abstract class GridCellWidget extends StatefulWidget implements CellAccessory, CellEditable, CellShortcuts {
|
||||||
VoidCallback? _listener;
|
GridCellWidget({Key? key}) : super(key: key) {
|
||||||
|
onCellEditing.addListener(() {
|
||||||
|
onCellFocus.value = onCellEditing.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void addListener(VoidCallback listener) {
|
final ValueNotifier<bool> onCellFocus = ValueNotifier<bool>(false);
|
||||||
|
|
||||||
|
// When the cell is focused, we assume that the accessory alse be hovered.
|
||||||
|
@override
|
||||||
|
ValueNotifier<bool> get onAccessoryHover => onCellFocus;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final ValueNotifier<bool> onCellEditing = ValueNotifier<bool>(false);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<GridCellAccessory> Function(GridCellAccessoryBuildContext buildContext)? get accessoryBuilder => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final GridCellFocusListener beginFocus = GridCellFocusListener();
|
||||||
|
|
||||||
|
@override
|
||||||
|
final Map<CellKeyboardKey, CellKeyboardAction> shortcutHandlers = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class GridCellState<T extends GridCellWidget> extends State<T> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
widget.beginFocus.setListener(() => requestBeginFocus());
|
||||||
|
widget.shortcutHandlers[CellKeyboardKey.onCopy] = () => onCopy();
|
||||||
|
widget.shortcutHandlers[CellKeyboardKey.onInsert] = () {
|
||||||
|
Clipboard.getData("text/plain").then((data) {
|
||||||
|
final s = data?.text;
|
||||||
|
if (s is String) {
|
||||||
|
onInsert(s);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant T oldWidget) {
|
||||||
|
if (oldWidget != this) {
|
||||||
|
widget.beginFocus.setListener(() => requestBeginFocus());
|
||||||
|
}
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
widget.beginFocus.removeAllListener();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void requestBeginFocus();
|
||||||
|
|
||||||
|
String? onCopy() => null;
|
||||||
|
|
||||||
|
void onInsert(String value) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class GridFocusNodeCellState<T extends GridCellWidget> extends GridCellState<T> {
|
||||||
|
SingleListenrFocusNode focusNode = SingleListenrFocusNode();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
widget.shortcutHandlers[CellKeyboardKey.onEnter] = () => focusNode.unfocus();
|
||||||
|
_listenOnFocusNodeChanged();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant T oldWidget) {
|
||||||
|
if (oldWidget != this) {
|
||||||
|
_listenOnFocusNodeChanged();
|
||||||
|
}
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
widget.shortcutHandlers.clear();
|
||||||
|
focusNode.removeAllListener();
|
||||||
|
focusNode.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void requestBeginFocus() {
|
||||||
|
if (focusNode.hasFocus == false && focusNode.canRequestFocus) {
|
||||||
|
FocusScope.of(context).requestFocus(focusNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _listenOnFocusNodeChanged() {
|
||||||
|
widget.onCellEditing.value = focusNode.hasFocus;
|
||||||
|
focusNode.setListener(() {
|
||||||
|
widget.onCellEditing.value = focusNode.hasFocus;
|
||||||
|
focusChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> focusChanged() async {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridCellFocusListener extends ChangeNotifier {
|
||||||
|
VoidCallback? _listener;
|
||||||
|
|
||||||
|
void setListener(VoidCallback listener) {
|
||||||
if (_listener != null) {
|
if (_listener != null) {
|
||||||
removeListener(_listener!);
|
removeListener(_listener!);
|
||||||
}
|
}
|
||||||
|
|
||||||
_listener = listener;
|
_listener = listener;
|
||||||
super.addListener(listener);
|
addListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeAllListener() {
|
void removeAllListener() {
|
||||||
@ -82,10 +186,10 @@ class GridCellRequestFocusNotifier extends ChangeNotifier {
|
|||||||
|
|
||||||
abstract class GridCellStyle {}
|
abstract class GridCellStyle {}
|
||||||
|
|
||||||
class CellSingleFocusNode extends FocusNode {
|
class SingleListenrFocusNode extends FocusNode {
|
||||||
VoidCallback? _listener;
|
VoidCallback? _listener;
|
||||||
|
|
||||||
void setSingleListener(VoidCallback listener) {
|
void setListener(VoidCallback listener) {
|
||||||
if (_listener != null) {
|
if (_listener != null) {
|
||||||
removeListener(_listener!);
|
removeListener(_listener!);
|
||||||
}
|
}
|
||||||
@ -94,120 +198,9 @@ class CellSingleFocusNode extends FocusNode {
|
|||||||
super.addListener(listener);
|
super.addListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeSingleListener() {
|
void removeAllListener() {
|
||||||
if (_listener != null) {
|
if (_listener != null) {
|
||||||
removeListener(_listener!);
|
removeListener(_listener!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CellStateNotifier extends ChangeNotifier {
|
|
||||||
bool _isFocus = false;
|
|
||||||
bool _onEnter = false;
|
|
||||||
|
|
||||||
set isFocus(bool value) {
|
|
||||||
if (_isFocus != value) {
|
|
||||||
_isFocus = value;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set onEnter(bool value) {
|
|
||||||
if (_onEnter != value) {
|
|
||||||
_onEnter = value;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get isFocus => _isFocus;
|
|
||||||
|
|
||||||
bool get onEnter => _onEnter;
|
|
||||||
}
|
|
||||||
|
|
||||||
class CellContainer extends StatelessWidget {
|
|
||||||
final GridCellWidget child;
|
|
||||||
final Widget? expander;
|
|
||||||
final double width;
|
|
||||||
final RegionStateNotifier rowStateNotifier;
|
|
||||||
const CellContainer({
|
|
||||||
Key? key,
|
|
||||||
required this.child,
|
|
||||||
required this.width,
|
|
||||||
required this.rowStateNotifier,
|
|
||||||
this.expander,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ChangeNotifierProxyProvider<RegionStateNotifier, CellStateNotifier>(
|
|
||||||
create: (_) => CellStateNotifier(),
|
|
||||||
update: (_, row, cell) => cell!..onEnter = row.onEnter,
|
|
||||||
child: Selector<CellStateNotifier, bool>(
|
|
||||||
selector: (context, notifier) => notifier.isFocus,
|
|
||||||
builder: (context, isFocus, _) {
|
|
||||||
Widget container = Center(child: child);
|
|
||||||
child.onFocus.addListener(() {
|
|
||||||
Provider.of<CellStateNotifier>(context, listen: false).isFocus = child.onFocus.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (expander != null) {
|
|
||||||
container = _CellEnterRegion(child: container, expander: expander!);
|
|
||||||
}
|
|
||||||
|
|
||||||
return GestureDetector(
|
|
||||||
behavior: HitTestBehavior.translucent,
|
|
||||||
onTap: () => child.requestFocus.notify(),
|
|
||||||
child: Container(
|
|
||||||
constraints: BoxConstraints(maxWidth: width, minHeight: 46),
|
|
||||||
decoration: _makeBoxDecoration(context, isFocus),
|
|
||||||
padding: GridSize.cellContentInsets,
|
|
||||||
child: container,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
BoxDecoration _makeBoxDecoration(BuildContext context, bool isFocus) {
|
|
||||||
final theme = context.watch<AppTheme>();
|
|
||||||
if (isFocus) {
|
|
||||||
final borderSide = BorderSide(color: theme.main1, width: 1.0);
|
|
||||||
return BoxDecoration(border: Border.fromBorderSide(borderSide));
|
|
||||||
} else {
|
|
||||||
final borderSide = BorderSide(color: theme.shader5, width: 1.0);
|
|
||||||
return BoxDecoration(border: Border(right: borderSide, bottom: borderSide));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _CellEnterRegion extends StatelessWidget {
|
|
||||||
final Widget child;
|
|
||||||
final Widget expander;
|
|
||||||
const _CellEnterRegion({required this.child, required this.expander, Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Selector<CellStateNotifier, bool>(
|
|
||||||
selector: (context, notifier) => notifier.onEnter,
|
|
||||||
builder: (context, onEnter, _) {
|
|
||||||
List<Widget> children = [child];
|
|
||||||
if (onEnter) {
|
|
||||||
children.add(expander.positioned(right: 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
return MouseRegion(
|
|
||||||
cursor: SystemMouseCursors.click,
|
|
||||||
onEnter: (p) => Provider.of<CellStateNotifier>(context, listen: false).onEnter = true,
|
|
||||||
onExit: (p) => Provider.of<CellStateNotifier>(context, listen: false).onEnter = false,
|
|
||||||
child: Stack(
|
|
||||||
alignment: AlignmentDirectional.center,
|
|
||||||
fit: StackFit.expand,
|
|
||||||
// alignment: AlignmentDirectional.centerEnd,
|
|
||||||
children: children,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,140 @@
|
|||||||
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
|
||||||
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart';
|
||||||
|
import 'package:flowy_infra/theme.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
|
import 'cell_accessory.dart';
|
||||||
|
import 'cell_builder.dart';
|
||||||
|
import 'cell_shortcuts.dart';
|
||||||
|
|
||||||
|
class CellContainer extends StatelessWidget {
|
||||||
|
final GridCellWidget child;
|
||||||
|
final AccessoryBuilder? accessoryBuilder;
|
||||||
|
final double width;
|
||||||
|
final RegionStateNotifier rowStateNotifier;
|
||||||
|
const CellContainer({
|
||||||
|
Key? key,
|
||||||
|
required this.child,
|
||||||
|
required this.width,
|
||||||
|
required this.rowStateNotifier,
|
||||||
|
this.accessoryBuilder,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ChangeNotifierProxyProvider<RegionStateNotifier, CellContainerNotifier>(
|
||||||
|
create: (_) => CellContainerNotifier(child),
|
||||||
|
update: (_, rowStateNotifier, cellStateNotifier) => cellStateNotifier!..onEnter = rowStateNotifier.onEnter,
|
||||||
|
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,
|
||||||
|
));
|
||||||
|
|
||||||
|
if (accessories.isNotEmpty) {
|
||||||
|
container = CellEnterRegion(child: container, accessories: accessories);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
onTap: () => child.beginFocus.notify(),
|
||||||
|
child: Container(
|
||||||
|
constraints: BoxConstraints(maxWidth: width, minHeight: 46),
|
||||||
|
decoration: _makeBoxDecoration(context, isFocus),
|
||||||
|
padding: GridSize.cellContentInsets,
|
||||||
|
child: container,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BoxDecoration _makeBoxDecoration(BuildContext context, bool isFocus) {
|
||||||
|
final theme = context.watch<AppTheme>();
|
||||||
|
if (isFocus) {
|
||||||
|
final borderSide = BorderSide(color: theme.main1, width: 1.0);
|
||||||
|
return BoxDecoration(border: Border.fromBorderSide(borderSide));
|
||||||
|
} else {
|
||||||
|
final borderSide = BorderSide(color: theme.shader5, width: 1.0);
|
||||||
|
return BoxDecoration(border: Border(right: borderSide, bottom: borderSide));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CellEnterRegion extends StatelessWidget {
|
||||||
|
final Widget child;
|
||||||
|
final List<GridCellAccessory> accessories;
|
||||||
|
const CellEnterRegion({required this.child, required this.accessories, Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Selector<CellContainerNotifier, bool>(
|
||||||
|
selector: (context, notifier) => notifier.onEnter,
|
||||||
|
builder: (context, onEnter, _) {
|
||||||
|
List<Widget> children = [child];
|
||||||
|
if (onEnter) {
|
||||||
|
children.add(CellAccessoryContainer(accessories: accessories).positioned(right: 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
onEnter: (p) => Provider.of<CellContainerNotifier>(context, listen: false).onEnter = true,
|
||||||
|
onExit: (p) => Provider.of<CellContainerNotifier>(context, listen: false).onEnter = false,
|
||||||
|
child: Stack(
|
||||||
|
alignment: AlignmentDirectional.center,
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: children,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CellContainerNotifier extends ChangeNotifier {
|
||||||
|
final CellEditable cellEditable;
|
||||||
|
bool mouted = false;
|
||||||
|
VoidCallback? _onCellFocusListener;
|
||||||
|
bool _isFocus = false;
|
||||||
|
bool _onEnter = false;
|
||||||
|
|
||||||
|
CellContainerNotifier(this.cellEditable) {
|
||||||
|
_onCellFocusListener = () => isFocus = cellEditable.onCellFocus.value;
|
||||||
|
cellEditable.onCellFocus.addListener(_onCellFocusListener!);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
if (_onCellFocusListener != null) {
|
||||||
|
cellEditable.onCellFocus.removeListener(_onCellFocusListener!);
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
set isFocus(bool value) {
|
||||||
|
if (_isFocus != value) {
|
||||||
|
_isFocus = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set onEnter(bool value) {
|
||||||
|
if (_onEnter != value) {
|
||||||
|
_onEnter = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isFocus => _isFocus;
|
||||||
|
|
||||||
|
bool get onEnter => _onEnter;
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
typedef CellKeyboardAction = dynamic Function();
|
||||||
|
|
||||||
|
enum CellKeyboardKey {
|
||||||
|
onEnter,
|
||||||
|
onCopy,
|
||||||
|
onInsert,
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class CellShortcuts extends Widget {
|
||||||
|
const CellShortcuts({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
Map<CellKeyboardKey, CellKeyboardAction> get shortcutHandlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridCellShortcuts extends StatelessWidget {
|
||||||
|
final CellShortcuts child;
|
||||||
|
const GridCellShortcuts({required this.child, Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Shortcuts(
|
||||||
|
shortcuts: {
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.enter): const GridCellEnterIdent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyC): const GridCellCopyIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyV): const GridCellInsertIntent(),
|
||||||
|
},
|
||||||
|
child: Actions(
|
||||||
|
actions: {
|
||||||
|
GridCellEnterIdent: GridCellEnterAction(child: child),
|
||||||
|
GridCellCopyIntent: GridCellCopyAction(child: child),
|
||||||
|
GridCellInsertIntent: GridCellInsertAction(child: child),
|
||||||
|
},
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridCellEnterIdent extends Intent {
|
||||||
|
const GridCellEnterIdent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridCellEnterAction extends Action<GridCellEnterIdent> {
|
||||||
|
final CellShortcuts child;
|
||||||
|
GridCellEnterAction({required this.child});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void invoke(covariant GridCellEnterIdent intent) {
|
||||||
|
final callback = child.shortcutHandlers[CellKeyboardKey.onEnter];
|
||||||
|
if (callback != null) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridCellCopyIntent extends Intent {
|
||||||
|
const GridCellCopyIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridCellCopyAction extends Action<GridCellCopyIntent> {
|
||||||
|
final CellShortcuts child;
|
||||||
|
GridCellCopyAction({required this.child});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void invoke(covariant GridCellCopyIntent intent) {
|
||||||
|
final callback = child.shortcutHandlers[CellKeyboardKey.onCopy];
|
||||||
|
if (callback == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final s = callback();
|
||||||
|
if (s is String) {
|
||||||
|
Clipboard.setData(ClipboardData(text: s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridCellInsertIntent extends Intent {
|
||||||
|
const GridCellInsertIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridCellInsertAction extends Action<GridCellInsertIntent> {
|
||||||
|
final CellShortcuts child;
|
||||||
|
GridCellInsertAction({required this.child});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void invoke(covariant GridCellInsertIntent intent) {
|
||||||
|
final callback = child.shortcutHandlers[CellKeyboardKey.onInsert];
|
||||||
|
if (callback != null) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,17 +14,16 @@ class CheckboxCell extends GridCellWidget {
|
|||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CheckboxCell> createState() => _CheckboxCellState();
|
GridCellState<CheckboxCell> createState() => _CheckboxCellState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CheckboxCellState extends State<CheckboxCell> {
|
class _CheckboxCellState extends GridCellState<CheckboxCell> {
|
||||||
late CheckboxCellBloc _cellBloc;
|
late CheckboxCellBloc _cellBloc;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
final cellContext = widget.cellContextBuilder.build();
|
final cellContext = widget.cellContextBuilder.build();
|
||||||
_cellBloc = getIt<CheckboxCellBloc>(param1: cellContext)..add(const CheckboxCellEvent.initial());
|
_cellBloc = getIt<CheckboxCellBloc>(param1: cellContext)..add(const CheckboxCellEvent.initial());
|
||||||
_listenCellRequestFocus();
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,7 +40,7 @@ class _CheckboxCellState extends State<CheckboxCell> {
|
|||||||
onPressed: () => context.read<CheckboxCellBloc>().add(const CheckboxCellEvent.select()),
|
onPressed: () => context.read<CheckboxCellBloc>().add(const CheckboxCellEvent.select()),
|
||||||
iconPadding: EdgeInsets.zero,
|
iconPadding: EdgeInsets.zero,
|
||||||
icon: icon,
|
icon: icon,
|
||||||
width: 23,
|
width: 20,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -49,22 +48,23 @@ class _CheckboxCellState extends State<CheckboxCell> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(covariant CheckboxCell oldWidget) {
|
|
||||||
_listenCellRequestFocus();
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
widget.requestFocus.removeAllListener();
|
|
||||||
_cellBloc.close();
|
_cellBloc.close();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenCellRequestFocus() {
|
@override
|
||||||
widget.requestFocus.addListener(() {
|
void requestBeginFocus() {
|
||||||
_cellBloc.add(const CheckboxCellEvent.select());
|
_cellBloc.add(const CheckboxCellEvent.select());
|
||||||
});
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? onCopy() {
|
||||||
|
if (_cellBloc.state.isSelected) {
|
||||||
|
return "Yes";
|
||||||
|
} else {
|
||||||
|
return "No";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import 'package:app_flowy/startup/startup.dart';
|
|||||||
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
||||||
|
|
||||||
import '../cell_builder.dart';
|
import '../cell_builder.dart';
|
||||||
import 'calendar.dart';
|
import 'date_editor.dart';
|
||||||
|
|
||||||
class DateCellStyle extends GridCellStyle {
|
class DateCellStyle extends GridCellStyle {
|
||||||
Alignment alignment;
|
Alignment alignment;
|
||||||
@ -35,10 +35,10 @@ class DateCell extends GridCellWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<DateCell> createState() => _DateCellState();
|
GridCellState<DateCell> createState() => _DateCellState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DateCellState extends State<DateCell> {
|
class _DateCellState extends GridCellState<DateCell> {
|
||||||
late DateCellBloc _cellBloc;
|
late DateCellBloc _cellBloc;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -64,7 +64,7 @@ class _DateCellState extends State<DateCell> {
|
|||||||
cursor: SystemMouseCursors.click,
|
cursor: SystemMouseCursors.click,
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: alignment,
|
alignment: alignment,
|
||||||
child: FlowyText.medium(state.data.foldRight("", (data, _) => data.date), fontSize: 12),
|
child: FlowyText.medium(state.dateStr, fontSize: 12),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -76,8 +76,8 @@ class _DateCellState extends State<DateCell> {
|
|||||||
|
|
||||||
void _showCalendar(BuildContext context) {
|
void _showCalendar(BuildContext context) {
|
||||||
final bloc = context.read<DateCellBloc>();
|
final bloc = context.read<DateCellBloc>();
|
||||||
widget.onFocus.value = true;
|
widget.onCellEditing.value = true;
|
||||||
final calendar = CellCalendar(onDismissed: () => widget.onFocus.value = false);
|
final calendar = DateCellEditor(onDismissed: () => widget.onCellEditing.value = false);
|
||||||
calendar.show(
|
calendar.show(
|
||||||
context,
|
context,
|
||||||
cellContext: bloc.cellContext.clone(),
|
cellContext: bloc.cellContext.clone(),
|
||||||
@ -89,4 +89,10 @@ class _DateCellState extends State<DateCell> {
|
|||||||
_cellBloc.close();
|
_cellBloc.close();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void requestBeginFocus() {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? onCopy() => _cellBloc.state.dateStr;
|
||||||
}
|
}
|
||||||
|
@ -22,10 +22,10 @@ final kFirstDay = DateTime(kToday.year, kToday.month - 3, kToday.day);
|
|||||||
final kLastDay = DateTime(kToday.year, kToday.month + 3, kToday.day);
|
final kLastDay = DateTime(kToday.year, kToday.month + 3, kToday.day);
|
||||||
const kMargin = EdgeInsets.symmetric(horizontal: 6, vertical: 10);
|
const kMargin = EdgeInsets.symmetric(horizontal: 6, vertical: 10);
|
||||||
|
|
||||||
class CellCalendar with FlowyOverlayDelegate {
|
class DateCellEditor with FlowyOverlayDelegate {
|
||||||
final VoidCallback onDismissed;
|
final VoidCallback onDismissed;
|
||||||
|
|
||||||
const CellCalendar({
|
const DateCellEditor({
|
||||||
required this.onDismissed,
|
required this.onDismissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -33,23 +33,14 @@ class CellCalendar with FlowyOverlayDelegate {
|
|||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required GridDateCellContext cellContext,
|
required GridDateCellContext cellContext,
|
||||||
}) async {
|
}) async {
|
||||||
CellCalendar.remove(context);
|
DateCellEditor.remove(context);
|
||||||
|
|
||||||
final result = await cellContext.getTypeOptionData();
|
final result = await cellContext.getTypeOptionData();
|
||||||
result.fold(
|
result.fold(
|
||||||
(data) {
|
(data) {
|
||||||
final typeOptionData = DateTypeOption.fromBuffer(data);
|
|
||||||
// DateTime? selectedDay;
|
|
||||||
// final cellData = cellContext.getCellData();
|
|
||||||
|
|
||||||
// if (cellData != null) {
|
|
||||||
// final timestamp = $fixnum.Int64.parseInt(cellData).toInt();
|
|
||||||
// selectedDay = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
|
|
||||||
// }
|
|
||||||
|
|
||||||
final calendar = _CellCalendarWidget(
|
final calendar = _CellCalendarWidget(
|
||||||
cellContext: cellContext,
|
cellContext: cellContext,
|
||||||
dateTypeOption: typeOptionData,
|
dateTypeOption: DateTypeOption.fromBuffer(data.typeOptionData),
|
||||||
);
|
);
|
||||||
|
|
||||||
FlowyOverlay.of(context).insertWithAnchor(
|
FlowyOverlay.of(context).insertWithAnchor(
|
||||||
@ -57,7 +48,7 @@ class CellCalendar with FlowyOverlayDelegate {
|
|||||||
child: calendar,
|
child: calendar,
|
||||||
constraints: BoxConstraints.loose(const Size(320, 500)),
|
constraints: BoxConstraints.loose(const Size(320, 500)),
|
||||||
),
|
),
|
||||||
identifier: CellCalendar.identifier(),
|
identifier: DateCellEditor.identifier(),
|
||||||
anchorContext: context,
|
anchorContext: context,
|
||||||
anchorDirection: AnchorDirection.leftWithCenterAligned,
|
anchorDirection: AnchorDirection.leftWithCenterAligned,
|
||||||
style: FlowyOverlayStyle(blur: false),
|
style: FlowyOverlayStyle(blur: false),
|
||||||
@ -73,7 +64,7 @@ class CellCalendar with FlowyOverlayDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static String identifier() {
|
static String identifier() {
|
||||||
return (CellCalendar).toString();
|
return (DateCellEditor).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -169,18 +160,21 @@ class _CellCalendarWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
selectedDayPredicate: (day) {
|
selectedDayPredicate: (day) {
|
||||||
return state.dateData.fold(
|
return state.calData.fold(
|
||||||
() => false,
|
() => false,
|
||||||
(dateData) => isSameDay(dateData.date, day),
|
(dateData) => isSameDay(dateData.date, day),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onDaySelected: (selectedDay, focusedDay) {
|
onDaySelected: (selectedDay, focusedDay) {
|
||||||
|
_CalDateTimeSetting.hide(context);
|
||||||
context.read<DateCalBloc>().add(DateCalEvent.selectDay(selectedDay));
|
context.read<DateCalBloc>().add(DateCalEvent.selectDay(selectedDay));
|
||||||
},
|
},
|
||||||
onFormatChanged: (format) {
|
onFormatChanged: (format) {
|
||||||
|
_CalDateTimeSetting.hide(context);
|
||||||
context.read<DateCalBloc>().add(DateCalEvent.setCalFormat(format));
|
context.read<DateCalBloc>().add(DateCalEvent.setCalFormat(format));
|
||||||
},
|
},
|
||||||
onPageChanged: (focusedDay) {
|
onPageChanged: (focusedDay) {
|
||||||
|
_CalDateTimeSetting.hide(context);
|
||||||
context.read<DateCalBloc>().add(DateCalEvent.setFocusedDay(focusedDay));
|
context.read<DateCalBloc>().add(DateCalEvent.setFocusedDay(focusedDay));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -243,6 +237,7 @@ class _TimeTextFieldState extends State<_TimeTextField> {
|
|||||||
if (widget.bloc.state.dateTypeOption.includeTime) {
|
if (widget.bloc.state.dateTypeOption.includeTime) {
|
||||||
_focusNode.addListener(() {
|
_focusNode.addListener(() {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
_CalDateTimeSetting.hide(context);
|
||||||
widget.bloc.add(DateCalEvent.setTime(_controller.text));
|
widget.bloc.add(DateCalEvent.setTime(_controller.text));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -266,6 +261,7 @@ class _TimeTextFieldState extends State<_TimeTextField> {
|
|||||||
child: RoundedInputField(
|
child: RoundedInputField(
|
||||||
height: 40,
|
height: 40,
|
||||||
focusNode: _focusNode,
|
focusNode: _focusNode,
|
||||||
|
hintText: state.timeHintText,
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||||
normalBorderColor: theme.shader4,
|
normalBorderColor: theme.shader4,
|
||||||
@ -335,6 +331,7 @@ class _CalDateTimeSetting extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void show(BuildContext context) {
|
void show(BuildContext context) {
|
||||||
|
hide(context);
|
||||||
FlowyOverlay.of(context).insertWithAnchor(
|
FlowyOverlay.of(context).insertWithAnchor(
|
||||||
widget: OverlayContainer(
|
widget: OverlayContainer(
|
||||||
child: this,
|
child: this,
|
||||||
@ -346,6 +343,10 @@ class _CalDateTimeSetting extends StatefulWidget {
|
|||||||
anchorOffset: const Offset(20, 0),
|
anchorOffset: const Offset(20, 0),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void hide(BuildContext context) {
|
||||||
|
FlowyOverlay.of(context).remove(identifier());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CalDateTimeSettingState extends State<_CalDateTimeSetting> {
|
class _CalDateTimeSettingState extends State<_CalDateTimeSetting> {
|
@ -1,5 +1,4 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:app_flowy/startup/startup.dart';
|
import 'package:app_flowy/startup/startup.dart';
|
||||||
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -16,41 +15,37 @@ class NumberCell extends GridCellWidget {
|
|||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<NumberCell> createState() => _NumberCellState();
|
GridFocusNodeCellState<NumberCell> createState() => _NumberCellState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _NumberCellState extends State<NumberCell> {
|
class _NumberCellState extends GridFocusNodeCellState<NumberCell> {
|
||||||
late NumberCellBloc _cellBloc;
|
late NumberCellBloc _cellBloc;
|
||||||
late TextEditingController _controller;
|
late TextEditingController _controller;
|
||||||
late CellSingleFocusNode _focusNode;
|
|
||||||
Timer? _delayOperation;
|
Timer? _delayOperation;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
final cellContext = widget.cellContextBuilder.build();
|
final cellContext = widget.cellContextBuilder.build();
|
||||||
_cellBloc = getIt<NumberCellBloc>(param1: cellContext)..add(const NumberCellEvent.initial());
|
_cellBloc = getIt<NumberCellBloc>(param1: cellContext)..add(const NumberCellEvent.initial());
|
||||||
_controller = TextEditingController(text: _cellBloc.state.content);
|
_controller = TextEditingController(text: contentFromState(_cellBloc.state));
|
||||||
_focusNode = CellSingleFocusNode();
|
|
||||||
_listenFocusNode();
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
_listenCellRequestFocus(context);
|
|
||||||
return BlocProvider.value(
|
return BlocProvider.value(
|
||||||
value: _cellBloc,
|
value: _cellBloc,
|
||||||
child: BlocConsumer<NumberCellBloc, NumberCellState>(
|
child: MultiBlocListener(
|
||||||
listener: (context, state) {
|
listeners: [
|
||||||
if (_controller.text != state.content) {
|
BlocListener<NumberCellBloc, NumberCellState>(
|
||||||
_controller.text = state.content;
|
listenWhen: (p, c) => p.content != c.content,
|
||||||
}
|
listener: (context, state) => _controller.text = contentFromState(state),
|
||||||
},
|
),
|
||||||
builder: (context, state) {
|
],
|
||||||
return TextField(
|
child: TextField(
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
focusNode: _focusNode,
|
focusNode: focusNode,
|
||||||
onEditingComplete: () => _focusNode.unfocus(),
|
onEditingComplete: () => focusNode.unfocus(),
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
@ -58,59 +53,41 @@ class _NumberCellState extends State<NumberCell> {
|
|||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
isDense: true,
|
isDense: true,
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
widget.requestFocus.removeAllListener();
|
|
||||||
_delayOperation?.cancel();
|
_delayOperation?.cancel();
|
||||||
_cellBloc.close();
|
_cellBloc.close();
|
||||||
_focusNode.removeSingleListener();
|
|
||||||
_focusNode.dispose();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant NumberCell oldWidget) {
|
|
||||||
if (oldWidget != widget) {
|
|
||||||
_listenFocusNode();
|
|
||||||
}
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> focusChanged() async {
|
Future<void> focusChanged() async {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
_delayOperation?.cancel();
|
_delayOperation?.cancel();
|
||||||
_delayOperation = Timer(const Duration(milliseconds: 300), () {
|
_delayOperation = Timer(const Duration(milliseconds: 300), () {
|
||||||
if (_cellBloc.isClosed == false && _controller.text != _cellBloc.state.content) {
|
if (_cellBloc.isClosed == false && _controller.text != contentFromState(_cellBloc.state)) {
|
||||||
final number = num.tryParse(_controller.text);
|
|
||||||
if (number != null) {
|
|
||||||
_cellBloc.add(NumberCellEvent.updateCell(_controller.text));
|
_cellBloc.add(NumberCellEvent.updateCell(_controller.text));
|
||||||
} else {
|
|
||||||
_controller.text = "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenFocusNode() {
|
String contentFromState(NumberCellState state) {
|
||||||
widget.onFocus.value = _focusNode.hasFocus;
|
return state.content.fold((l) => l, (r) => "");
|
||||||
_focusNode.setSingleListener(() {
|
|
||||||
widget.onFocus.value = _focusNode.hasFocus;
|
|
||||||
focusChanged();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenCellRequestFocus(BuildContext context) {
|
@override
|
||||||
widget.requestFocus.addListener(() {
|
String? onCopy() {
|
||||||
if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) {
|
return _cellBloc.state.content.fold((content) => content, (r) => null);
|
||||||
FocusScope.of(context).requestFocus(_focusNode);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
@override
|
||||||
|
void onInsert(String value) {
|
||||||
|
_cellBloc.add(NumberCellEvent.updateCell(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,4 +3,4 @@ export 'text_cell.dart';
|
|||||||
export 'number_cell.dart';
|
export 'number_cell.dart';
|
||||||
export 'date_cell/date_cell.dart';
|
export 'date_cell/date_cell.dart';
|
||||||
export 'checkbox_cell.dart';
|
export 'checkbox_cell.dart';
|
||||||
export 'selection_cell/selection_cell.dart';
|
export 'select_option_cell/select_option_cell.dart';
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:flowy_infra/theme.dart';
|
import 'package:flowy_infra/theme.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -63,9 +64,11 @@ class SelectOptionTag extends StatelessWidget {
|
|||||||
final String name;
|
final String name;
|
||||||
final Color color;
|
final Color color;
|
||||||
final bool isSelected;
|
final bool isSelected;
|
||||||
|
final VoidCallback? onSelected;
|
||||||
const SelectOptionTag({
|
const SelectOptionTag({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.color,
|
required this.color,
|
||||||
|
this.onSelected,
|
||||||
this.isSelected = false,
|
this.isSelected = false,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
@ -73,12 +76,14 @@ class SelectOptionTag extends StatelessWidget {
|
|||||||
factory SelectOptionTag.fromSelectOption({
|
factory SelectOptionTag.fromSelectOption({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required SelectOption option,
|
required SelectOption option,
|
||||||
|
VoidCallback? onSelected,
|
||||||
bool isSelected = false,
|
bool isSelected = false,
|
||||||
}) {
|
}) {
|
||||||
return SelectOptionTag(
|
return SelectOptionTag(
|
||||||
name: option.name,
|
name: option.name,
|
||||||
color: option.color.make(context),
|
color: option.color.make(context),
|
||||||
isSelected: isSelected,
|
isSelected: isSelected,
|
||||||
|
onSelected: onSelected,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,23 +91,63 @@ class SelectOptionTag extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ChoiceChip(
|
return ChoiceChip(
|
||||||
pressElevation: 1,
|
pressElevation: 1,
|
||||||
label: FlowyText.medium(name, fontSize: 12),
|
label: FlowyText.medium(name, fontSize: 12, overflow: TextOverflow.ellipsis),
|
||||||
selectedColor: color,
|
selectedColor: color,
|
||||||
backgroundColor: color,
|
backgroundColor: color,
|
||||||
labelPadding: const EdgeInsets.symmetric(horizontal: 6),
|
labelPadding: const EdgeInsets.symmetric(horizontal: 6),
|
||||||
selected: true,
|
selected: true,
|
||||||
onSelected: (_) {},
|
onSelected: (_) {
|
||||||
|
if (onSelected != null) {
|
||||||
|
onSelected!();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelectOptionTagCell extends StatelessWidget {
|
||||||
|
final List<Widget> children;
|
||||||
|
final void Function(SelectOption) onSelected;
|
||||||
|
final SelectOption option;
|
||||||
|
const SelectOptionTagCell({
|
||||||
|
required this.option,
|
||||||
|
required this.onSelected,
|
||||||
|
this.children = const [],
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = context.watch<AppTheme>();
|
||||||
|
return Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
FlowyHover(
|
||||||
|
style: HoverStyle(hoverColor: theme.hover),
|
||||||
|
child: InkWell(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 3),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
fit: FlexFit.loose,
|
||||||
|
flex: 2,
|
||||||
|
child: SelectOptionTag.fromSelectOption(
|
||||||
|
context: context,
|
||||||
|
option: option,
|
||||||
|
onSelected: () => onSelected(option),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
...children,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () => onSelected(option),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
// return Container(
|
|
||||||
// decoration: BoxDecoration(
|
|
||||||
// color: option.color.make(context),
|
|
||||||
// shape: BoxShape.rectangle,
|
|
||||||
// borderRadius: BorderRadius.circular(8.0),
|
|
||||||
// ),
|
|
||||||
// child: Center(child: FlowyText.medium(option.name, fontSize: 12)),
|
|
||||||
// margin: const EdgeInsets.symmetric(horizontal: 3.0),
|
|
||||||
// padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
|
||||||
// );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -10,7 +10,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import 'extension.dart';
|
import 'extension.dart';
|
||||||
import 'selection_editor.dart';
|
import 'select_option_editor.dart';
|
||||||
|
|
||||||
class SelectOptionCellStyle extends GridCellStyle {
|
class SelectOptionCellStyle extends GridCellStyle {
|
||||||
String placeholder;
|
String placeholder;
|
||||||
@ -41,12 +41,12 @@ class SingleSelectCell extends GridCellWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _SingleSelectCellState extends State<SingleSelectCell> {
|
class _SingleSelectCellState extends State<SingleSelectCell> {
|
||||||
late SelectionCellBloc _cellBloc;
|
late SelectOptionCellBloc _cellBloc;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
final cellContext = widget.cellContextBuilder.build() as GridSelectOptionCellContext;
|
final cellContext = widget.cellContextBuilder.build() as GridSelectOptionCellContext;
|
||||||
_cellBloc = getIt<SelectionCellBloc>(param1: cellContext)..add(const SelectionCellEvent.initial());
|
_cellBloc = getIt<SelectOptionCellBloc>(param1: cellContext)..add(const SelectOptionCellEvent.initial());
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,12 +54,12 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider.value(
|
return BlocProvider.value(
|
||||||
value: _cellBloc,
|
value: _cellBloc,
|
||||||
child: BlocBuilder<SelectionCellBloc, SelectionCellState>(
|
child: BlocBuilder<SelectOptionCellBloc, SelectOptionCellState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return _SelectOptionCell(
|
return _SelectOptionCell(
|
||||||
selectOptions: state.selectedOptions,
|
selectOptions: state.selectedOptions,
|
||||||
cellStyle: widget.cellStyle,
|
cellStyle: widget.cellStyle,
|
||||||
onFocus: (value) => widget.onFocus.value = value,
|
onFocus: (value) => widget.onCellEditing.value = value,
|
||||||
cellContextBuilder: widget.cellContextBuilder);
|
cellContextBuilder: widget.cellContextBuilder);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -95,12 +95,12 @@ class MultiSelectCell extends GridCellWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MultiSelectCellState extends State<MultiSelectCell> {
|
class _MultiSelectCellState extends State<MultiSelectCell> {
|
||||||
late SelectionCellBloc _cellBloc;
|
late SelectOptionCellBloc _cellBloc;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
final cellContext = widget.cellContextBuilder.build() as GridSelectOptionCellContext;
|
final cellContext = widget.cellContextBuilder.build() as GridSelectOptionCellContext;
|
||||||
_cellBloc = getIt<SelectionCellBloc>(param1: cellContext)..add(const SelectionCellEvent.initial());
|
_cellBloc = getIt<SelectOptionCellBloc>(param1: cellContext)..add(const SelectOptionCellEvent.initial());
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,12 +108,12 @@ class _MultiSelectCellState extends State<MultiSelectCell> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider.value(
|
return BlocProvider.value(
|
||||||
value: _cellBloc,
|
value: _cellBloc,
|
||||||
child: BlocBuilder<SelectionCellBloc, SelectionCellState>(
|
child: BlocBuilder<SelectOptionCellBloc, SelectOptionCellState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return _SelectOptionCell(
|
return _SelectOptionCell(
|
||||||
selectOptions: state.selectedOptions,
|
selectOptions: state.selectedOptions,
|
||||||
cellStyle: widget.cellStyle,
|
cellStyle: widget.cellStyle,
|
||||||
onFocus: (value) => widget.onFocus.value = value,
|
onFocus: (value) => widget.onCellEditing.value = value,
|
||||||
cellContextBuilder: widget.cellContextBuilder);
|
cellContextBuilder: widget.cellContextBuilder);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -160,7 +160,7 @@ class _SelectOptionCell extends StatelessWidget {
|
|||||||
.toList();
|
.toList();
|
||||||
child = Align(
|
child = Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Wrap(children: tags, spacing: 4, runSpacing: 4),
|
child: Wrap(children: tags, spacing: 4, runSpacing: 2),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1,13 +1,12 @@
|
|||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
|
import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
|
||||||
import 'package:app_flowy/workspace/application/grid/cell/selection_editor_bloc.dart';
|
import 'package:app_flowy/workspace/application/grid/cell/select_option_editor_bloc.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/edit_option_pannel.dart';
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option_editor.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/common/text_field.dart';
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/common/text_field.dart';
|
||||||
import 'package:flowy_infra/image.dart';
|
import 'package:flowy_infra/image.dart';
|
||||||
import 'package:flowy_infra/theme.dart';
|
import 'package:flowy_infra/theme.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
|
||||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
|
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
|
||||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
@ -37,10 +36,10 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => SelectOptionEditorBloc(
|
create: (context) => SelectOptionCellEditorBloc(
|
||||||
cellContext: cellContext,
|
cellContext: cellContext,
|
||||||
)..add(const SelectOptionEditorEvent.initial()),
|
)..add(const SelectOptionEditorEvent.initial()),
|
||||||
child: BlocBuilder<SelectOptionEditorBloc, SelectOptionEditorState>(
|
child: BlocBuilder<SelectOptionCellEditorBloc, SelectOptionEditorState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return CustomScrollView(
|
return CustomScrollView(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
@ -102,7 +101,7 @@ class _OptionList extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<SelectOptionEditorBloc, SelectOptionEditorState>(
|
return BlocBuilder<SelectOptionCellEditorBloc, SelectOptionEditorState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
List<Widget> cells = [];
|
List<Widget> cells = [];
|
||||||
cells.addAll(state.options.map((option) {
|
cells.addAll(state.options.map((option) {
|
||||||
@ -145,7 +144,7 @@ class _TextField extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<SelectOptionEditorBloc, SelectOptionEditorState>(
|
return BlocBuilder<SelectOptionCellEditorBloc, SelectOptionEditorState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final optionMap = LinkedHashMap<String, SelectOption>.fromIterable(state.selectedOptions,
|
final optionMap = LinkedHashMap<String, SelectOption>.fromIterable(state.selectedOptions,
|
||||||
key: (option) => option.name, value: (option) => option);
|
key: (option) => option.name, value: (option) => option);
|
||||||
@ -157,11 +156,12 @@ class _TextField extends StatelessWidget {
|
|||||||
selectedOptionMap: optionMap,
|
selectedOptionMap: optionMap,
|
||||||
distanceToText: _editorPannelWidth * 0.7,
|
distanceToText: _editorPannelWidth * 0.7,
|
||||||
tagController: _tagController,
|
tagController: _tagController,
|
||||||
|
onClick: () => FlowyOverlay.of(context).remove(SelectOptionTypeOptionEditor.identifier),
|
||||||
newText: (text) {
|
newText: (text) {
|
||||||
context.read<SelectOptionEditorBloc>().add(SelectOptionEditorEvent.filterOption(text));
|
context.read<SelectOptionCellEditorBloc>().add(SelectOptionEditorEvent.filterOption(text));
|
||||||
},
|
},
|
||||||
onNewTag: (tagName) {
|
onNewTag: (tagName) {
|
||||||
context.read<SelectOptionEditorBloc>().add(SelectOptionEditorEvent.newOption(tagName));
|
context.read<SelectOptionCellEditorBloc>().add(SelectOptionEditorEvent.newOption(tagName));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -208,6 +208,7 @@ class _CreateOptionCell extends StatelessWidget {
|
|||||||
SelectOptionTag(
|
SelectOptionTag(
|
||||||
name: name,
|
name: name,
|
||||||
color: theme.shader6,
|
color: theme.shader6,
|
||||||
|
onSelected: () => context.read<SelectOptionCellEditorBloc>().add(SelectOptionEditorEvent.newOption(name)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -224,63 +225,47 @@ class _SelectOptionCell extends StatelessWidget {
|
|||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: GridSize.typeOptionItemHeight,
|
height: GridSize.typeOptionItemHeight,
|
||||||
child: Stack(
|
child: Row(
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [
|
children: [
|
||||||
_body(theme, context),
|
Flexible(
|
||||||
InkWell(
|
fit: FlexFit.loose,
|
||||||
onTap: () {
|
child: SelectOptionTagCell(
|
||||||
context.read<SelectOptionEditorBloc>().add(SelectOptionEditorEvent.selectOption(option.id));
|
option: option,
|
||||||
|
onSelected: (option) {
|
||||||
|
context.read<SelectOptionCellEditorBloc>().add(SelectOptionEditorEvent.selectOption(option.id));
|
||||||
},
|
},
|
||||||
|
children: [
|
||||||
|
if (isSelected)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 6),
|
||||||
|
child: svgWidget("grid/checkmark"),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
FlowyIconButton(
|
||||||
|
width: 30,
|
||||||
|
onPressed: () => _showEditPannel(context),
|
||||||
|
iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
|
||||||
|
icon: svgWidget("editor/details", color: theme.iconColor),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
FlowyHover _body(AppTheme theme, BuildContext context) {
|
|
||||||
return FlowyHover(
|
|
||||||
style: HoverStyle(hoverColor: theme.hover),
|
|
||||||
builder: (_, onHover) {
|
|
||||||
List<Widget> children = [
|
|
||||||
SelectOptionTag(
|
|
||||||
name: option.name,
|
|
||||||
color: option.color.make(context),
|
|
||||||
isSelected: isSelected,
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
];
|
|
||||||
|
|
||||||
if (isSelected) {
|
|
||||||
children.add(svgWidget("grid/checkmark"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onHover) {
|
|
||||||
children.add(FlowyIconButton(
|
|
||||||
width: 30,
|
|
||||||
onPressed: () => _showEditPannel(context),
|
|
||||||
iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
|
|
||||||
icon: svgWidget("editor/details", color: theme.iconColor),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Row(children: children);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showEditPannel(BuildContext context) {
|
void _showEditPannel(BuildContext context) {
|
||||||
final pannel = EditSelectOptionPannel(
|
final pannel = SelectOptionTypeOptionEditor(
|
||||||
option: option,
|
option: option,
|
||||||
onDeleted: () {
|
onDeleted: () {
|
||||||
context.read<SelectOptionEditorBloc>().add(SelectOptionEditorEvent.deleteOption(option));
|
context.read<SelectOptionCellEditorBloc>().add(SelectOptionEditorEvent.deleteOption(option));
|
||||||
},
|
},
|
||||||
onUpdated: (updatedOption) {
|
onUpdated: (updatedOption) {
|
||||||
context.read<SelectOptionEditorBloc>().add(SelectOptionEditorEvent.updateOption(updatedOption));
|
context.read<SelectOptionCellEditorBloc>().add(SelectOptionEditorEvent.updateOption(updatedOption));
|
||||||
},
|
},
|
||||||
key: ValueKey(option.id), // Use ValueKey to refresh the UI, otherwise, it will remain the old value.
|
key: ValueKey(option.id), // Use ValueKey to refresh the UI, otherwise, it will remain the old value.
|
||||||
);
|
);
|
||||||
final overlayIdentifier = (EditSelectOptionPannel).toString();
|
final overlayIdentifier = (SelectOptionTypeOptionEditor).toString();
|
||||||
|
|
||||||
FlowyOverlay.of(context).remove(overlayIdentifier);
|
FlowyOverlay.of(context).remove(overlayIdentifier);
|
||||||
FlowyOverlay.of(context).insertWithAnchor(
|
FlowyOverlay.of(context).insertWithAnchor(
|
@ -22,6 +22,7 @@ class SelectOptionTextField extends StatelessWidget {
|
|||||||
|
|
||||||
final Function(String) onNewTag;
|
final Function(String) onNewTag;
|
||||||
final Function(String) newText;
|
final Function(String) newText;
|
||||||
|
final VoidCallback? onClick;
|
||||||
|
|
||||||
SelectOptionTextField({
|
SelectOptionTextField({
|
||||||
required this.options,
|
required this.options,
|
||||||
@ -30,6 +31,7 @@ class SelectOptionTextField extends StatelessWidget {
|
|||||||
required this.tagController,
|
required this.tagController,
|
||||||
required this.onNewTag,
|
required this.onNewTag,
|
||||||
required this.newText,
|
required this.newText,
|
||||||
|
this.onClick,
|
||||||
TextEditingController? controller,
|
TextEditingController? controller,
|
||||||
FocusNode? focusNode,
|
FocusNode? focusNode,
|
||||||
Key? key,
|
Key? key,
|
||||||
@ -53,6 +55,7 @@ class SelectOptionTextField extends StatelessWidget {
|
|||||||
autofocus: true,
|
autofocus: true,
|
||||||
controller: editController,
|
controller: editController,
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
|
onTap: onClick,
|
||||||
onChanged: (text) {
|
onChanged: (text) {
|
||||||
if (onChanged != null) {
|
if (onChanged != null) {
|
||||||
onChanged(text);
|
onChanged(text);
|
@ -29,13 +29,12 @@ class GridTextCell extends GridCellWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<GridTextCell> createState() => _GridTextCellState();
|
GridFocusNodeCellState<GridTextCell> createState() => _GridTextCellState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GridTextCellState extends State<GridTextCell> {
|
class _GridTextCellState extends GridFocusNodeCellState<GridTextCell> {
|
||||||
late TextCellBloc _cellBloc;
|
late TextCellBloc _cellBloc;
|
||||||
late TextEditingController _controller;
|
late TextEditingController _controller;
|
||||||
late CellSingleFocusNode _focusNode;
|
|
||||||
Timer? _delayOperation;
|
Timer? _delayOperation;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -44,10 +43,6 @@ class _GridTextCellState extends State<GridTextCell> {
|
|||||||
_cellBloc = getIt<TextCellBloc>(param1: cellContext);
|
_cellBloc = getIt<TextCellBloc>(param1: cellContext);
|
||||||
_cellBloc.add(const TextCellEvent.initial());
|
_cellBloc.add(const TextCellEvent.initial());
|
||||||
_controller = TextEditingController(text: _cellBloc.state.content);
|
_controller = TextEditingController(text: _cellBloc.state.content);
|
||||||
_focusNode = CellSingleFocusNode();
|
|
||||||
|
|
||||||
_listenFocusNode();
|
|
||||||
_listenRequestFocus(context);
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,9 +58,9 @@ class _GridTextCellState extends State<GridTextCell> {
|
|||||||
},
|
},
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
focusNode: _focusNode,
|
focusNode: focusNode,
|
||||||
onChanged: (value) => focusChanged(),
|
onChanged: (value) => focusChanged(),
|
||||||
onEditingComplete: () => _focusNode.unfocus(),
|
onEditingComplete: () => focusNode.unfocus(),
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
@ -81,39 +76,12 @@ class _GridTextCellState extends State<GridTextCell> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
widget.requestFocus.removeAllListener();
|
|
||||||
_delayOperation?.cancel();
|
_delayOperation?.cancel();
|
||||||
_cellBloc.close();
|
_cellBloc.close();
|
||||||
_focusNode.removeSingleListener();
|
|
||||||
_focusNode.dispose();
|
|
||||||
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant GridTextCell oldWidget) {
|
|
||||||
if (oldWidget != widget) {
|
|
||||||
_listenFocusNode();
|
|
||||||
}
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _listenFocusNode() {
|
|
||||||
widget.onFocus.value = _focusNode.hasFocus;
|
|
||||||
_focusNode.setSingleListener(() {
|
|
||||||
widget.onFocus.value = _focusNode.hasFocus;
|
|
||||||
focusChanged();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _listenRequestFocus(BuildContext context) {
|
|
||||||
widget.requestFocus.addListener(() {
|
|
||||||
if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) {
|
|
||||||
FocusScope.of(context).requestFocus(_focusNode);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> focusChanged() async {
|
Future<void> focusChanged() async {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
_delayOperation?.cancel();
|
_delayOperation?.cancel();
|
||||||
@ -124,4 +92,12 @@ class _GridTextCellState extends State<GridTextCell> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? onCopy() => _cellBloc.state.content;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInsert(String value) {
|
||||||
|
_cellBloc.add(TextCellEvent.updateText(value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,113 @@
|
|||||||
|
import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
|
||||||
|
import 'package:app_flowy/workspace/application/grid/cell/url_cell_editor_bloc.dart';
|
||||||
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class URLCellEditor extends StatefulWidget with FlowyOverlayDelegate {
|
||||||
|
final GridURLCellContext cellContext;
|
||||||
|
final VoidCallback completed;
|
||||||
|
const URLCellEditor({required this.cellContext, required this.completed, Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<URLCellEditor> createState() => _URLCellEditorState();
|
||||||
|
|
||||||
|
static void show(
|
||||||
|
BuildContext context,
|
||||||
|
GridURLCellContext cellContext,
|
||||||
|
VoidCallback completed,
|
||||||
|
) {
|
||||||
|
FlowyOverlay.of(context).remove(identifier());
|
||||||
|
final editor = URLCellEditor(
|
||||||
|
cellContext: cellContext,
|
||||||
|
completed: completed,
|
||||||
|
);
|
||||||
|
|
||||||
|
//
|
||||||
|
FlowyOverlay.of(context).insertWithAnchor(
|
||||||
|
widget: OverlayContainer(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 200,
|
||||||
|
child: Padding(padding: const EdgeInsets.all(6), child: editor),
|
||||||
|
),
|
||||||
|
constraints: BoxConstraints.loose(const Size(300, 160)),
|
||||||
|
),
|
||||||
|
identifier: URLCellEditor.identifier(),
|
||||||
|
anchorContext: context,
|
||||||
|
anchorDirection: AnchorDirection.bottomWithCenterAligned,
|
||||||
|
delegate: editor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String identifier() {
|
||||||
|
return (URLCellEditor).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool asBarrier() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didRemove() {
|
||||||
|
completed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _URLCellEditorState extends State<URLCellEditor> {
|
||||||
|
late URLCellEditorBloc _cellBloc;
|
||||||
|
late TextEditingController _controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_cellBloc = URLCellEditorBloc(cellContext: widget.cellContext);
|
||||||
|
_cellBloc.add(const URLCellEditorEvent.initial());
|
||||||
|
_controller = TextEditingController(text: _cellBloc.state.content);
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider.value(
|
||||||
|
value: _cellBloc,
|
||||||
|
child: BlocListener<URLCellEditorBloc, URLCellEditorState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
if (_controller.text != state.content) {
|
||||||
|
_controller.text = state.content;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: TextField(
|
||||||
|
autofocus: true,
|
||||||
|
controller: _controller,
|
||||||
|
onChanged: (value) => focusChanged(),
|
||||||
|
maxLines: null,
|
||||||
|
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
border: InputBorder.none,
|
||||||
|
hintText: "",
|
||||||
|
isDense: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> dispose() async {
|
||||||
|
_cellBloc.close();
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> focusChanged() async {
|
||||||
|
if (mounted) {
|
||||||
|
if (_cellBloc.isClosed == false && _controller.text != _cellBloc.state.content) {
|
||||||
|
_cellBloc.add(URLCellEditorEvent.updateText(_controller.text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,194 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:app_flowy/workspace/application/grid/cell/url_cell_bloc.dart';
|
||||||
|
import 'package:app_flowy/workspace/presentation/home/toast.dart';
|
||||||
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_accessory.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flowy_infra/image.dart';
|
||||||
|
import 'package:flowy_infra/theme.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
import '../cell_builder.dart';
|
||||||
|
import 'cell_editor.dart';
|
||||||
|
|
||||||
|
class GridURLCellStyle extends GridCellStyle {
|
||||||
|
String? placeholder;
|
||||||
|
|
||||||
|
List<GridURLCellAccessoryType> accessoryTypes;
|
||||||
|
|
||||||
|
GridURLCellStyle({
|
||||||
|
this.placeholder,
|
||||||
|
this.accessoryTypes = const [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
enum GridURLCellAccessoryType {
|
||||||
|
edit,
|
||||||
|
copyURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridURLCell extends GridCellWidget {
|
||||||
|
final GridCellContextBuilder cellContextBuilder;
|
||||||
|
late final GridURLCellStyle? cellStyle;
|
||||||
|
GridURLCell({
|
||||||
|
required this.cellContextBuilder,
|
||||||
|
GridCellStyle? style,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key) {
|
||||||
|
if (style != null) {
|
||||||
|
cellStyle = (style as GridURLCellStyle);
|
||||||
|
} else {
|
||||||
|
cellStyle = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
GridCellState<GridURLCell> createState() => _GridURLCellState();
|
||||||
|
|
||||||
|
GridCellAccessory accessoryFromType(GridURLCellAccessoryType ty, GridCellAccessoryBuildContext buildContext) {
|
||||||
|
switch (ty) {
|
||||||
|
case GridURLCellAccessoryType.edit:
|
||||||
|
final cellContext = cellContextBuilder.build() as GridURLCellContext;
|
||||||
|
return _EditURLAccessory(cellContext: cellContext, anchorContext: buildContext.anchorContext);
|
||||||
|
|
||||||
|
case GridURLCellAccessoryType.copyURL:
|
||||||
|
final cellContext = cellContextBuilder.build() as GridURLCellContext;
|
||||||
|
return _CopyURLAccessory(cellContext: cellContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<GridCellAccessory> Function(GridCellAccessoryBuildContext buildContext) get accessoryBuilder => (buildContext) {
|
||||||
|
final List<GridCellAccessory> accessories = [];
|
||||||
|
if (cellStyle != null) {
|
||||||
|
accessories.addAll(cellStyle!.accessoryTypes.map((ty) {
|
||||||
|
return accessoryFromType(ty, buildContext);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the accessories is empty then the default accessory will be GridURLCellAccessoryType.edit
|
||||||
|
if (accessories.isEmpty) {
|
||||||
|
accessories.add(accessoryFromType(GridURLCellAccessoryType.edit, buildContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessories;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GridURLCellState extends GridCellState<GridURLCell> {
|
||||||
|
late URLCellBloc _cellBloc;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
final cellContext = widget.cellContextBuilder.build() as GridURLCellContext;
|
||||||
|
_cellBloc = URLCellBloc(cellContext: cellContext);
|
||||||
|
_cellBloc.add(const URLCellEvent.initial());
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = context.watch<AppTheme>();
|
||||||
|
return BlocProvider.value(
|
||||||
|
value: _cellBloc,
|
||||||
|
child: BlocBuilder<URLCellBloc, URLCellState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final richText = RichText(
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
text: TextSpan(
|
||||||
|
text: state.content,
|
||||||
|
style: TextStyle(
|
||||||
|
color: theme.main2,
|
||||||
|
fontSize: 14,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return SizedBox.expand(
|
||||||
|
child: GestureDetector(
|
||||||
|
child: Align(alignment: Alignment.centerLeft, child: richText),
|
||||||
|
onTap: () async {
|
||||||
|
final url = context.read<URLCellBloc>().state.url;
|
||||||
|
await _openUrlOrEdit(url);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> dispose() async {
|
||||||
|
_cellBloc.close();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _openUrlOrEdit(String url) async {
|
||||||
|
final uri = Uri.parse(url);
|
||||||
|
if (url.isNotEmpty && await canLaunchUrl(uri)) {
|
||||||
|
await launchUrl(uri);
|
||||||
|
} else {
|
||||||
|
final cellContext = widget.cellContextBuilder.build() as GridURLCellContext;
|
||||||
|
widget.onCellEditing.value = true;
|
||||||
|
URLCellEditor.show(context, cellContext, () {
|
||||||
|
widget.onCellEditing.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void requestBeginFocus() {
|
||||||
|
_openUrlOrEdit(_cellBloc.state.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? onCopy() => _cellBloc.state.content;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInsert(String value) {
|
||||||
|
_cellBloc.add(URLCellEvent.updateURL(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EditURLAccessory extends StatelessWidget with GridCellAccessory {
|
||||||
|
final GridURLCellContext cellContext;
|
||||||
|
final BuildContext anchorContext;
|
||||||
|
const _EditURLAccessory({
|
||||||
|
required this.cellContext,
|
||||||
|
required this.anchorContext,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = context.watch<AppTheme>();
|
||||||
|
return svgWidget("editor/edit", color: theme.iconColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTap() {
|
||||||
|
URLCellEditor.show(anchorContext, cellContext, () {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CopyURLAccessory extends StatelessWidget with GridCellAccessory {
|
||||||
|
final GridURLCellContext cellContext;
|
||||||
|
const _CopyURLAccessory({required this.cellContext, Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = context.watch<AppTheme>();
|
||||||
|
return svgWidget("editor/copy", color: theme.iconColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTap() {
|
||||||
|
final content = cellContext.getCellData(loadIfNoCache: false)?.content ?? "";
|
||||||
|
Clipboard.setData(ClipboardData(text: content));
|
||||||
|
showMessageToast(LocaleKeys.grid_row_copyProperty.tr());
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ class InputTextField extends StatefulWidget {
|
|||||||
final void Function(String)? onDone;
|
final void Function(String)? onDone;
|
||||||
final void Function(String)? onChanged;
|
final void Function(String)? onChanged;
|
||||||
final void Function() onCanceled;
|
final void Function() onCanceled;
|
||||||
|
final bool autoClearWhenDone;
|
||||||
final String text;
|
final String text;
|
||||||
|
|
||||||
const InputTextField({
|
const InputTextField({
|
||||||
@ -14,6 +15,7 @@ class InputTextField extends StatefulWidget {
|
|||||||
this.onDone,
|
this.onDone,
|
||||||
required this.onCanceled,
|
required this.onCanceled,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
|
this.autoClearWhenDone = false,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@ -57,6 +59,10 @@ class _InputTextFieldState extends State<InputTextField> {
|
|||||||
if (widget.onDone != null) {
|
if (widget.onDone != null) {
|
||||||
widget.onDone!(_controller.text);
|
widget.onDone!(_controller.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (widget.autoClearWhenDone) {
|
||||||
|
_controller.text = "";
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import 'package:flowy_infra/theme.dart';
|
|||||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
import 'package:flowy_sdk/log.dart';
|
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field;
|
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@ -24,6 +23,7 @@ class GridFieldCell extends StatelessWidget {
|
|||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => FieldCellBloc(cellContext: cellContext)..add(const FieldCellEvent.initial()),
|
create: (context) => FieldCellBloc(cellContext: cellContext)..add(const FieldCellEvent.initial()),
|
||||||
child: BlocBuilder<FieldCellBloc, FieldCellState>(
|
child: BlocBuilder<FieldCellBloc, FieldCellState>(
|
||||||
|
// buildWhen: (p, c) => p.field != c.field,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final button = FieldCellButton(
|
final button = FieldCellButton(
|
||||||
field: state.field,
|
field: state.field,
|
||||||
@ -37,8 +37,8 @@ class GridFieldCell extends StatelessWidget {
|
|||||||
child: _DragToExpandLine(),
|
child: _DragToExpandLine(),
|
||||||
);
|
);
|
||||||
|
|
||||||
return _CellContainer(
|
return _GridHeaderCellContainer(
|
||||||
width: state.field.width.toDouble(),
|
width: state.width,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
@ -60,21 +60,23 @@ class GridFieldCell extends StatelessWidget {
|
|||||||
|
|
||||||
void _showFieldEditor(BuildContext context) {
|
void _showFieldEditor(BuildContext context) {
|
||||||
final state = context.read<FieldCellBloc>().state;
|
final state = context.read<FieldCellBloc>().state;
|
||||||
|
final field = state.field;
|
||||||
|
|
||||||
FieldEditor(
|
FieldEditor(
|
||||||
gridId: state.gridId,
|
gridId: state.gridId,
|
||||||
fieldContextLoader: FieldContextLoaderAdaptor(
|
fieldName: field.name,
|
||||||
|
contextLoader: FieldContextLoader(
|
||||||
gridId: state.gridId,
|
gridId: state.gridId,
|
||||||
field: state.field,
|
field: field,
|
||||||
),
|
),
|
||||||
).show(context);
|
).show(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CellContainer extends StatelessWidget {
|
class _GridHeaderCellContainer extends StatelessWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final double width;
|
final double width;
|
||||||
const _CellContainer({
|
const _GridHeaderCellContainer({
|
||||||
required this.child,
|
required this.child,
|
||||||
required this.width,
|
required this.width,
|
||||||
Key? key,
|
Key? key,
|
||||||
@ -83,7 +85,7 @@ class _CellContainer extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
final borderSide = BorderSide(color: theme.shader4, width: 0.4);
|
final borderSide = BorderSide(color: theme.shader5, width: 1.0);
|
||||||
final decoration = BoxDecoration(
|
final decoration = BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
top: borderSide,
|
top: borderSide,
|
||||||
@ -112,21 +114,19 @@ class _DragToExpandLine extends StatelessWidget {
|
|||||||
onTap: () {},
|
onTap: () {},
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onHorizontalDragCancel: () {},
|
|
||||||
onHorizontalDragUpdate: (value) {
|
onHorizontalDragUpdate: (value) {
|
||||||
// context.read<FieldCellBloc>().add(FieldCellEvent.updateWidth(value.delta.dx));
|
context.read<FieldCellBloc>().add(FieldCellEvent.startUpdateWidth(value.delta.dx));
|
||||||
Log.info(value);
|
|
||||||
},
|
},
|
||||||
onHorizontalDragEnd: (end) {
|
onHorizontalDragEnd: (end) {
|
||||||
Log.info(end);
|
context.read<FieldCellBloc>().add(const FieldCellEvent.endUpdateWidth());
|
||||||
},
|
},
|
||||||
child: FlowyHover(
|
child: FlowyHover(
|
||||||
style: HoverStyle(
|
style: HoverStyle(
|
||||||
hoverColor: theme.main1,
|
hoverColor: theme.main1,
|
||||||
borderRadius: BorderRadius.zero,
|
borderRadius: BorderRadius.zero,
|
||||||
contentMargin: const EdgeInsets.only(left: 5),
|
contentMargin: const EdgeInsets.only(left: 6),
|
||||||
),
|
),
|
||||||
child: const SizedBox(width: 2),
|
child: const SizedBox(width: 4),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:app_flowy/startup/startup.dart';
|
|
||||||
import 'package:app_flowy/workspace/application/grid/field/field_editor_bloc.dart';
|
import 'package:app_flowy/workspace/application/grid/field/field_editor_bloc.dart';
|
||||||
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
|
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@ -11,16 +10,42 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
|
|||||||
import 'field_name_input.dart';
|
import 'field_name_input.dart';
|
||||||
import 'field_editor_pannel.dart';
|
import 'field_editor_pannel.dart';
|
||||||
|
|
||||||
class FieldEditor extends FlowyOverlayDelegate {
|
class FieldEditor extends StatelessWidget with FlowyOverlayDelegate {
|
||||||
final String gridId;
|
final String gridId;
|
||||||
final FieldEditorBloc _fieldEditorBloc;
|
final String fieldName;
|
||||||
final EditFieldContextLoader fieldContextLoader;
|
|
||||||
FieldEditor({
|
final IFieldContextLoader contextLoader;
|
||||||
|
const FieldEditor({
|
||||||
required this.gridId,
|
required this.gridId,
|
||||||
required this.fieldContextLoader,
|
required this.fieldName,
|
||||||
|
required this.contextLoader,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : _fieldEditorBloc = getIt<FieldEditorBloc>(param1: gridId, param2: fieldContextLoader) {
|
}) : super(key: key);
|
||||||
_fieldEditorBloc.add(const FieldEditorEvent.initial());
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (context) => FieldEditorBloc(
|
||||||
|
gridId: gridId,
|
||||||
|
fieldName: fieldName,
|
||||||
|
fieldContextLoader: contextLoader,
|
||||||
|
)..add(const FieldEditorEvent.initial()),
|
||||||
|
child: BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
||||||
|
buildWhen: (p, c) => false,
|
||||||
|
builder: (context, state) {
|
||||||
|
return ListView(
|
||||||
|
shrinkWrap: true,
|
||||||
|
children: [
|
||||||
|
FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(), fontSize: 12),
|
||||||
|
const VSpace(10),
|
||||||
|
const _FieldNameTextField(),
|
||||||
|
const VSpace(10),
|
||||||
|
const _FieldPannel(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void show(
|
void show(
|
||||||
@ -30,7 +55,7 @@ class FieldEditor extends FlowyOverlayDelegate {
|
|||||||
FlowyOverlay.of(context).remove(identifier());
|
FlowyOverlay.of(context).remove(identifier());
|
||||||
FlowyOverlay.of(context).insertWithAnchor(
|
FlowyOverlay.of(context).insertWithAnchor(
|
||||||
widget: OverlayContainer(
|
widget: OverlayContainer(
|
||||||
child: _FieldEditorWidget(_fieldEditorBloc, fieldContextLoader),
|
child: this,
|
||||||
constraints: BoxConstraints.loose(const Size(280, 400)),
|
constraints: BoxConstraints.loose(const Size(280, 400)),
|
||||||
),
|
),
|
||||||
identifier: identifier(),
|
identifier: identifier(),
|
||||||
@ -45,49 +70,23 @@ class FieldEditor extends FlowyOverlayDelegate {
|
|||||||
return (FieldEditor).toString();
|
return (FieldEditor).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void didRemove() {
|
|
||||||
_fieldEditorBloc.add(const FieldEditorEvent.done());
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool asBarrier() => true;
|
bool asBarrier() => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FieldEditorWidget extends StatelessWidget {
|
class _FieldPannel extends StatelessWidget {
|
||||||
final FieldEditorBloc editorBloc;
|
const _FieldPannel({Key? key}) : super(key: key);
|
||||||
final EditFieldContextLoader fieldContextLoader;
|
|
||||||
const _FieldEditorWidget(this.editorBloc, this.fieldContextLoader, {Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider.value(
|
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
||||||
value: editorBloc,
|
buildWhen: (p, c) => p.fieldContext != c.fieldContext,
|
||||||
child: BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return state.editFieldContext.fold(
|
return state.fieldContext.fold(
|
||||||
() => const SizedBox(),
|
() => const SizedBox(),
|
||||||
(editFieldContext) => ListView(
|
(fieldContext) => FieldEditorPannel(fieldContext: fieldContext),
|
||||||
shrinkWrap: true,
|
|
||||||
children: [
|
|
||||||
FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(), fontSize: 12),
|
|
||||||
const VSpace(10),
|
|
||||||
const _FieldNameTextField(),
|
|
||||||
const VSpace(10),
|
|
||||||
FieldEditorPannel(
|
|
||||||
editFieldContext: editFieldContext,
|
|
||||||
onSwitchToField: (fieldId, fieldType) {
|
|
||||||
return fieldContextLoader.switchToField(fieldId, fieldType);
|
|
||||||
},
|
|
||||||
onUpdated: (field, typeOptionData) {
|
|
||||||
context.read<FieldEditorBloc>().add(FieldEditorEvent.updateField(field, typeOptionData));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,16 +96,10 @@ class _FieldNameTextField extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocSelector<FieldEditorBloc, FieldEditorState, String>(
|
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
||||||
selector: (state) {
|
builder: (context, state) {
|
||||||
return state.editFieldContext.fold(
|
|
||||||
() => "",
|
|
||||||
(editFieldContext) => editFieldContext.gridField.name,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
builder: (context, name) {
|
|
||||||
return FieldNameTextField(
|
return FieldNameTextField(
|
||||||
name: name,
|
name: state.name,
|
||||||
errorText: context.read<FieldEditorBloc>().state.errorText,
|
errorText: context.read<FieldEditorBloc>().state.errorText,
|
||||||
onNameChanged: (newName) {
|
onNameChanged: (newName) {
|
||||||
context.read<FieldEditorBloc>().add(FieldEditorEvent.updateName(newName));
|
context.read<FieldEditorBloc>().add(FieldEditorEvent.updateName(newName));
|
||||||
|
@ -1,20 +1,18 @@
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:app_flowy/workspace/application/grid/field/type_option/multi_select_type_option.dart';
|
||||||
import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
|
import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
|
||||||
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/checkbox.dart';
|
||||||
import 'package:dartz/dartz.dart' show Either;
|
import 'package:dartz/dartz.dart' show Either;
|
||||||
import 'package:flowy_infra/image.dart';
|
import 'package:flowy_infra/image.dart';
|
||||||
import 'package:flowy_infra/theme.dart';
|
import 'package:flowy_infra/theme.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
import 'package:flowy_sdk/log.dart';
|
|
||||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pbserver.dart';
|
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/text_type_option.pb.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:app_flowy/startup/startup.dart';
|
|
||||||
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_type_list.dart';
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_type_list.dart';
|
||||||
@ -22,23 +20,21 @@ import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header
|
|||||||
import 'field_type_extension.dart';
|
import 'field_type_extension.dart';
|
||||||
import 'type_option/multi_select.dart';
|
import 'type_option/multi_select.dart';
|
||||||
import 'type_option/number.dart';
|
import 'type_option/number.dart';
|
||||||
|
import 'type_option/rich_text.dart';
|
||||||
import 'type_option/single_select.dart';
|
import 'type_option/single_select.dart';
|
||||||
|
import 'type_option/url.dart';
|
||||||
|
|
||||||
typedef UpdateFieldCallback = void Function(Field, Uint8List);
|
typedef UpdateFieldCallback = void Function(Field, Uint8List);
|
||||||
typedef SwitchToFieldCallback = Future<Either<EditFieldContext, FlowyError>> Function(
|
typedef SwitchToFieldCallback = Future<Either<FieldTypeOptionData, FlowyError>> Function(
|
||||||
String fieldId,
|
String fieldId,
|
||||||
FieldType fieldType,
|
FieldType fieldType,
|
||||||
);
|
);
|
||||||
|
|
||||||
class FieldEditorPannel extends StatefulWidget {
|
class FieldEditorPannel extends StatefulWidget {
|
||||||
final EditFieldContext editFieldContext;
|
final GridFieldContext fieldContext;
|
||||||
final UpdateFieldCallback onUpdated;
|
|
||||||
final SwitchToFieldCallback onSwitchToField;
|
|
||||||
|
|
||||||
const FieldEditorPannel({
|
const FieldEditorPannel({
|
||||||
required this.editFieldContext,
|
required this.fieldContext,
|
||||||
required this.onUpdated,
|
|
||||||
required this.onSwitchToField,
|
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@ -52,13 +48,10 @@ class _FieldEditorPannelState extends State<FieldEditorPannel> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => getIt<FieldEditorPannelBloc>(param1: widget.editFieldContext),
|
create: (context) => FieldEditorPannelBloc(widget.fieldContext)..add(const FieldEditorPannelEvent.initial()),
|
||||||
child: BlocConsumer<FieldEditorPannelBloc, FieldEditorPannelState>(
|
child: BlocBuilder<FieldEditorPannelBloc, FieldEditorPannelState>(
|
||||||
listener: (context, state) {
|
|
||||||
widget.onUpdated(state.field, state.typeOptionData);
|
|
||||||
},
|
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
List<Widget> children = [_switchFieldTypeButton(context, state.field)];
|
List<Widget> children = [_switchFieldTypeButton(context, widget.fieldContext.field)];
|
||||||
final typeOptionWidget = _typeOptionWidget(context: context, state: state);
|
final typeOptionWidget = _typeOptionWidget(context: context, state: state);
|
||||||
|
|
||||||
if (typeOptionWidget != null) {
|
if (typeOptionWidget != null) {
|
||||||
@ -84,19 +77,7 @@ class _FieldEditorPannelState extends State<FieldEditorPannel> {
|
|||||||
hoverColor: theme.hover,
|
hoverColor: theme.hover,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
final list = FieldTypeList(onSelectField: (newFieldType) {
|
final list = FieldTypeList(onSelectField: (newFieldType) {
|
||||||
widget.onSwitchToField(field.id, newFieldType).then((result) {
|
widget.fieldContext.switchToField(newFieldType);
|
||||||
result.fold(
|
|
||||||
(editFieldContext) {
|
|
||||||
context.read<FieldEditorPannelBloc>().add(
|
|
||||||
FieldEditorPannelEvent.toFieldType(
|
|
||||||
editFieldContext.gridField,
|
|
||||||
editFieldContext.typeOptionData,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
(err) => Log.error(err),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
_showOverlay(context, list);
|
_showOverlay(context, list);
|
||||||
},
|
},
|
||||||
@ -115,18 +96,9 @@ class _FieldEditorPannelState extends State<FieldEditorPannel> {
|
|||||||
hideOverlay: _hideOverlay,
|
hideOverlay: _hideOverlay,
|
||||||
);
|
);
|
||||||
|
|
||||||
final dataDelegate = TypeOptionDataDelegate(didUpdateTypeOptionData: (data) {
|
|
||||||
context.read<FieldEditorPannelBloc>().add(FieldEditorPannelEvent.didUpdateTypeOptionData(data));
|
|
||||||
});
|
|
||||||
|
|
||||||
final builder = _makeTypeOptionBuild(
|
final builder = _makeTypeOptionBuild(
|
||||||
typeOptionContext: TypeOptionContext(
|
typeOptionContext: _makeTypeOptionContext(widget.fieldContext),
|
||||||
gridId: state.gridId,
|
|
||||||
field: state.field,
|
|
||||||
data: state.typeOptionData,
|
|
||||||
),
|
|
||||||
overlayDelegate: overlayDelegate,
|
overlayDelegate: overlayDelegate,
|
||||||
dataDelegate: dataDelegate,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return builder.customWidget;
|
return builder.customWidget;
|
||||||
@ -166,25 +138,86 @@ abstract class TypeOptionBuilder {
|
|||||||
TypeOptionBuilder _makeTypeOptionBuild({
|
TypeOptionBuilder _makeTypeOptionBuild({
|
||||||
required TypeOptionContext typeOptionContext,
|
required TypeOptionContext typeOptionContext,
|
||||||
required TypeOptionOverlayDelegate overlayDelegate,
|
required TypeOptionOverlayDelegate overlayDelegate,
|
||||||
required TypeOptionDataDelegate dataDelegate,
|
|
||||||
}) {
|
}) {
|
||||||
switch (typeOptionContext.field.fieldType) {
|
switch (typeOptionContext.field.fieldType) {
|
||||||
case FieldType.Checkbox:
|
case FieldType.Checkbox:
|
||||||
return CheckboxTypeOptionBuilder(typeOptionContext.data);
|
return CheckboxTypeOptionBuilder(
|
||||||
|
typeOptionContext as CheckboxTypeOptionContext,
|
||||||
|
);
|
||||||
case FieldType.DateTime:
|
case FieldType.DateTime:
|
||||||
return DateTypeOptionBuilder(typeOptionContext.data, overlayDelegate, dataDelegate);
|
return DateTypeOptionBuilder(
|
||||||
|
typeOptionContext as DateTypeOptionContext,
|
||||||
|
overlayDelegate,
|
||||||
|
);
|
||||||
case FieldType.SingleSelect:
|
case FieldType.SingleSelect:
|
||||||
return SingleSelectTypeOptionBuilder(typeOptionContext, overlayDelegate, dataDelegate);
|
return SingleSelectTypeOptionBuilder(
|
||||||
|
typeOptionContext as SingleSelectTypeOptionContext,
|
||||||
|
overlayDelegate,
|
||||||
|
);
|
||||||
case FieldType.MultiSelect:
|
case FieldType.MultiSelect:
|
||||||
return MultiSelectTypeOptionBuilder(typeOptionContext, overlayDelegate, dataDelegate);
|
return MultiSelectTypeOptionBuilder(
|
||||||
|
typeOptionContext as MultiSelectTypeOptionContext,
|
||||||
|
overlayDelegate,
|
||||||
|
);
|
||||||
case FieldType.Number:
|
case FieldType.Number:
|
||||||
return NumberTypeOptionBuilder(typeOptionContext.data, overlayDelegate, dataDelegate);
|
return NumberTypeOptionBuilder(
|
||||||
|
typeOptionContext as NumberTypeOptionContext,
|
||||||
|
overlayDelegate,
|
||||||
|
);
|
||||||
case FieldType.RichText:
|
case FieldType.RichText:
|
||||||
return RichTextTypeOptionBuilder(typeOptionContext.data);
|
return RichTextTypeOptionBuilder(
|
||||||
|
typeOptionContext as RichTextTypeOptionContext,
|
||||||
|
);
|
||||||
|
|
||||||
default:
|
case FieldType.URL:
|
||||||
throw UnimplementedError;
|
return URLTypeOptionBuilder(
|
||||||
|
typeOptionContext as URLTypeOptionContext,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
throw UnimplementedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeOptionContext _makeTypeOptionContext(GridFieldContext fieldContext) {
|
||||||
|
switch (fieldContext.field.fieldType) {
|
||||||
|
case FieldType.Checkbox:
|
||||||
|
return CheckboxTypeOptionContext(
|
||||||
|
fieldContext: fieldContext,
|
||||||
|
dataBuilder: CheckboxTypeOptionDataBuilder(),
|
||||||
|
);
|
||||||
|
case FieldType.DateTime:
|
||||||
|
return DateTypeOptionContext(
|
||||||
|
fieldContext: fieldContext,
|
||||||
|
dataBuilder: DateTypeOptionDataBuilder(),
|
||||||
|
);
|
||||||
|
case FieldType.MultiSelect:
|
||||||
|
return MultiSelectTypeOptionContext(
|
||||||
|
fieldContext: fieldContext,
|
||||||
|
dataBuilder: MultiSelectTypeOptionDataBuilder(),
|
||||||
|
);
|
||||||
|
case FieldType.Number:
|
||||||
|
return NumberTypeOptionContext(
|
||||||
|
fieldContext: fieldContext,
|
||||||
|
dataBuilder: NumberTypeOptionDataBuilder(),
|
||||||
|
);
|
||||||
|
case FieldType.RichText:
|
||||||
|
return RichTextTypeOptionContext(
|
||||||
|
fieldContext: fieldContext,
|
||||||
|
dataBuilder: RichTextTypeOptionDataBuilder(),
|
||||||
|
);
|
||||||
|
case FieldType.SingleSelect:
|
||||||
|
return SingleSelectTypeOptionContext(
|
||||||
|
fieldContext: fieldContext,
|
||||||
|
dataBuilder: SingleSelectTypeOptionDataBuilder(),
|
||||||
|
);
|
||||||
|
|
||||||
|
case FieldType.URL:
|
||||||
|
return URLTypeOptionContext(
|
||||||
|
fieldContext: fieldContext,
|
||||||
|
dataBuilder: URLTypeOptionDataBuilder(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class TypeOptionWidget extends StatelessWidget {
|
abstract class TypeOptionWidget extends StatelessWidget {
|
||||||
@ -208,29 +241,3 @@ class TypeOptionOverlayDelegate {
|
|||||||
required this.hideOverlay,
|
required this.hideOverlay,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class TypeOptionDataDelegate {
|
|
||||||
TypeOptionDataCallback didUpdateTypeOptionData;
|
|
||||||
|
|
||||||
TypeOptionDataDelegate({
|
|
||||||
required this.didUpdateTypeOptionData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class RichTextTypeOptionBuilder extends TypeOptionBuilder {
|
|
||||||
RichTextTypeOption typeOption;
|
|
||||||
|
|
||||||
RichTextTypeOptionBuilder(TypeOptionData typeOptionData) : typeOption = RichTextTypeOption.fromBuffer(typeOptionData);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget? get customWidget => null;
|
|
||||||
}
|
|
||||||
|
|
||||||
class CheckboxTypeOptionBuilder extends TypeOptionBuilder {
|
|
||||||
CheckboxTypeOption typeOption;
|
|
||||||
|
|
||||||
CheckboxTypeOptionBuilder(TypeOptionData typeOptionData) : typeOption = CheckboxTypeOption.fromBuffer(typeOptionData);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget? get customWidget => null;
|
|
||||||
}
|
|
||||||
|
@ -3,7 +3,7 @@ import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class FieldNameTextField extends StatelessWidget {
|
class FieldNameTextField extends StatefulWidget {
|
||||||
final void Function(String) onNameChanged;
|
final void Function(String) onNameChanged;
|
||||||
final String name;
|
final String name;
|
||||||
final String errorText;
|
final String errorText;
|
||||||
@ -14,19 +14,41 @@ class FieldNameTextField extends StatelessWidget {
|
|||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<FieldNameTextField> createState() => _FieldNameTextFieldState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FieldNameTextFieldState extends State<FieldNameTextField> {
|
||||||
|
late String name;
|
||||||
|
TextEditingController controller = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
controller.text = widget.name;
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
return RoundedInputField(
|
return RoundedInputField(
|
||||||
height: 36,
|
height: 36,
|
||||||
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
|
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
|
||||||
initialValue: name,
|
controller: controller,
|
||||||
normalBorderColor: theme.shader4,
|
normalBorderColor: theme.shader4,
|
||||||
errorBorderColor: theme.red,
|
errorBorderColor: theme.red,
|
||||||
focusBorderColor: theme.main1,
|
focusBorderColor: theme.main1,
|
||||||
cursorColor: theme.main1,
|
cursorColor: theme.main1,
|
||||||
errorText: errorText,
|
errorText: widget.errorText,
|
||||||
onChanged: onNameChanged,
|
onChanged: widget.onNameChanged,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant FieldNameTextField oldWidget) {
|
||||||
|
controller.text = widget.name;
|
||||||
|
controller.selection = TextSelection.fromPosition(TextPosition(offset: controller.text.length));
|
||||||
|
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,10 @@ extension FieldTypeListExtension on FieldType {
|
|||||||
return "grid/field/text";
|
return "grid/field/text";
|
||||||
case FieldType.SingleSelect:
|
case FieldType.SingleSelect:
|
||||||
return "grid/field/single_select";
|
return "grid/field/single_select";
|
||||||
default:
|
case FieldType.URL:
|
||||||
throw UnimplementedError;
|
return "grid/field/url";
|
||||||
}
|
}
|
||||||
|
throw UnimplementedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
String title() {
|
String title() {
|
||||||
@ -36,8 +37,9 @@ extension FieldTypeListExtension on FieldType {
|
|||||||
return LocaleKeys.grid_field_textFieldName.tr();
|
return LocaleKeys.grid_field_textFieldName.tr();
|
||||||
case FieldType.SingleSelect:
|
case FieldType.SingleSelect:
|
||||||
return LocaleKeys.grid_field_singleSelectFieldName.tr();
|
return LocaleKeys.grid_field_singleSelectFieldName.tr();
|
||||||
default:
|
case FieldType.URL:
|
||||||
|
return LocaleKeys.grid_field_urlFieldName.tr();
|
||||||
|
}
|
||||||
throw UnimplementedError;
|
throw UnimplementedError;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,8 @@ class CreateFieldButton extends StatelessWidget {
|
|||||||
hoverColor: theme.hover,
|
hoverColor: theme.hover,
|
||||||
onTap: () => FieldEditor(
|
onTap: () => FieldEditor(
|
||||||
gridId: gridId,
|
gridId: gridId,
|
||||||
fieldContextLoader: NewFieldContextLoader(gridId: gridId),
|
fieldName: "",
|
||||||
|
contextLoader: NewFieldContextLoader(gridId: gridId),
|
||||||
).show(context),
|
).show(context),
|
||||||
leftIcon: svgWidget("home/add"),
|
leftIcon: svgWidget("home/add"),
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
|
||||||
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart';
|
||||||
|
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pb.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
typedef CheckboxTypeOptionContext = TypeOptionContext<CheckboxTypeOption>;
|
||||||
|
|
||||||
|
class CheckboxTypeOptionDataBuilder extends TypeOptionDataBuilder<CheckboxTypeOption> {
|
||||||
|
@override
|
||||||
|
CheckboxTypeOption fromBuffer(List<int> buffer) {
|
||||||
|
return CheckboxTypeOption.fromBuffer(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CheckboxTypeOptionBuilder extends TypeOptionBuilder {
|
||||||
|
CheckboxTypeOptionBuilder(CheckboxTypeOptionContext typeOptionContext);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget? get customWidget => null;
|
||||||
|
}
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:app_flowy/startup/startup.dart';
|
|
||||||
import 'package:app_flowy/workspace/application/grid/field/type_option/date_bloc.dart';
|
import 'package:app_flowy/workspace/application/grid/field/type_option/date_bloc.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart';
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart';
|
||||||
@ -18,12 +17,10 @@ class DateTypeOptionBuilder extends TypeOptionBuilder {
|
|||||||
final DateTypeOptionWidget _widget;
|
final DateTypeOptionWidget _widget;
|
||||||
|
|
||||||
DateTypeOptionBuilder(
|
DateTypeOptionBuilder(
|
||||||
TypeOptionData typeOptionData,
|
DateTypeOptionContext typeOptionContext,
|
||||||
TypeOptionOverlayDelegate overlayDelegate,
|
TypeOptionOverlayDelegate overlayDelegate,
|
||||||
TypeOptionDataDelegate dataDelegate,
|
|
||||||
) : _widget = DateTypeOptionWidget(
|
) : _widget = DateTypeOptionWidget(
|
||||||
typeOption: DateTypeOption.fromBuffer(typeOptionData),
|
typeOptionContext: typeOptionContext,
|
||||||
dataDelegate: dataDelegate,
|
|
||||||
overlayDelegate: overlayDelegate,
|
overlayDelegate: overlayDelegate,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -32,12 +29,11 @@ class DateTypeOptionBuilder extends TypeOptionBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class DateTypeOptionWidget extends TypeOptionWidget {
|
class DateTypeOptionWidget extends TypeOptionWidget {
|
||||||
final DateTypeOption typeOption;
|
final DateTypeOptionContext typeOptionContext;
|
||||||
final TypeOptionOverlayDelegate overlayDelegate;
|
final TypeOptionOverlayDelegate overlayDelegate;
|
||||||
final TypeOptionDataDelegate dataDelegate;
|
|
||||||
const DateTypeOptionWidget({
|
const DateTypeOptionWidget({
|
||||||
required this.typeOption,
|
required this.typeOptionContext,
|
||||||
required this.dataDelegate,
|
|
||||||
required this.overlayDelegate,
|
required this.overlayDelegate,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
@ -45,9 +41,9 @@ class DateTypeOptionWidget extends TypeOptionWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => getIt<DateTypeOptionBloc>(param1: typeOption),
|
create: (context) => DateTypeOptionBloc(typeOptionContext: typeOptionContext),
|
||||||
child: BlocConsumer<DateTypeOptionBloc, DateTypeOptionState>(
|
child: BlocConsumer<DateTypeOptionBloc, DateTypeOptionState>(
|
||||||
listener: (context, state) => dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()),
|
listener: (context, state) => typeOptionContext.typeOption = state.typeOption,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Column(children: [
|
return Column(children: [
|
||||||
_renderDateFormatButton(context, state.typeOption.dateFormat),
|
_renderDateFormatButton(context, state.typeOption.dateFormat),
|
||||||
|
@ -1,22 +1,18 @@
|
|||||||
import 'package:app_flowy/workspace/application/grid/field/type_option/multi_select_bloc.dart';
|
import 'package:app_flowy/workspace/application/grid/field/type_option/multi_select_type_option.dart';
|
||||||
import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
|
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart';
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
|
|
||||||
import 'field_option_pannel.dart';
|
import 'select_option.dart';
|
||||||
|
|
||||||
class MultiSelectTypeOptionBuilder extends TypeOptionBuilder {
|
class MultiSelectTypeOptionBuilder extends TypeOptionBuilder {
|
||||||
final MultiSelectTypeOptionWidget _widget;
|
final MultiSelectTypeOptionWidget _widget;
|
||||||
|
|
||||||
MultiSelectTypeOptionBuilder(
|
MultiSelectTypeOptionBuilder(
|
||||||
TypeOptionContext typeOptionContext,
|
MultiSelectTypeOptionContext typeOptionContext,
|
||||||
TypeOptionOverlayDelegate overlayDelegate,
|
TypeOptionOverlayDelegate overlayDelegate,
|
||||||
TypeOptionDataDelegate dataDelegate,
|
|
||||||
) : _widget = MultiSelectTypeOptionWidget(
|
) : _widget = MultiSelectTypeOptionWidget(
|
||||||
typeOptionContext: typeOptionContext,
|
typeOptionContext: typeOptionContext,
|
||||||
overlayDelegate: overlayDelegate,
|
overlayDelegate: overlayDelegate,
|
||||||
dataDelegate: dataDelegate,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -24,44 +20,23 @@ class MultiSelectTypeOptionBuilder extends TypeOptionBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class MultiSelectTypeOptionWidget extends TypeOptionWidget {
|
class MultiSelectTypeOptionWidget extends TypeOptionWidget {
|
||||||
final TypeOptionContext typeOptionContext;
|
final MultiSelectTypeOptionContext typeOptionContext;
|
||||||
final TypeOptionOverlayDelegate overlayDelegate;
|
final TypeOptionOverlayDelegate overlayDelegate;
|
||||||
final TypeOptionDataDelegate dataDelegate;
|
|
||||||
const MultiSelectTypeOptionWidget({
|
const MultiSelectTypeOptionWidget({
|
||||||
required this.typeOptionContext,
|
required this.typeOptionContext,
|
||||||
required this.overlayDelegate,
|
required this.overlayDelegate,
|
||||||
required this.dataDelegate,
|
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return SelectOptionTypeOptionWidget(
|
||||||
create: (context) => MultiSelectTypeOptionBloc(typeOptionContext),
|
options: typeOptionContext.typeOption.options,
|
||||||
child: BlocConsumer<MultiSelectTypeOptionBloc, MultiSelectTypeOptionState>(
|
beginEdit: () => overlayDelegate.hideOverlay(context),
|
||||||
listener: (context, state) {
|
|
||||||
dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer());
|
|
||||||
},
|
|
||||||
builder: (context, state) {
|
|
||||||
return FieldSelectOptionPannel(
|
|
||||||
options: state.typeOption.options,
|
|
||||||
beginEdit: () {
|
|
||||||
overlayDelegate.hideOverlay(context);
|
|
||||||
},
|
|
||||||
createOptionCallback: (name) {
|
|
||||||
context.read<MultiSelectTypeOptionBloc>().add(MultiSelectTypeOptionEvent.createOption(name));
|
|
||||||
},
|
|
||||||
updateOptionCallback: (updateOption) {
|
|
||||||
context.read<MultiSelectTypeOptionBloc>().add(MultiSelectTypeOptionEvent.updateOption(updateOption));
|
|
||||||
},
|
|
||||||
deleteOptionCallback: (deleteOption) {
|
|
||||||
context.read<MultiSelectTypeOptionBloc>().add(MultiSelectTypeOptionEvent.deleteOption(deleteOption));
|
|
||||||
},
|
|
||||||
overlayDelegate: overlayDelegate,
|
overlayDelegate: overlayDelegate,
|
||||||
key: ValueKey(state.typeOption.hashCode),
|
typeOptionAction: typeOptionContext,
|
||||||
);
|
// key: ValueKey(state.typeOption.hashCode),
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:app_flowy/startup/startup.dart';
|
|
||||||
import 'package:app_flowy/workspace/application/grid/field/type_option/number_bloc.dart';
|
import 'package:app_flowy/workspace/application/grid/field/type_option/number_bloc.dart';
|
||||||
import 'package:app_flowy/workspace/application/grid/field/type_option/number_format_bloc.dart';
|
import 'package:app_flowy/workspace/application/grid/field/type_option/number_format_bloc.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
|
||||||
@ -10,7 +9,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
|||||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/format.pbenum.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart' hide NumberFormat;
|
import 'package:easy_localization/easy_localization.dart' hide NumberFormat;
|
||||||
@ -20,12 +19,10 @@ class NumberTypeOptionBuilder extends TypeOptionBuilder {
|
|||||||
final NumberTypeOptionWidget _widget;
|
final NumberTypeOptionWidget _widget;
|
||||||
|
|
||||||
NumberTypeOptionBuilder(
|
NumberTypeOptionBuilder(
|
||||||
TypeOptionData typeOptionData,
|
NumberTypeOptionContext typeOptionContext,
|
||||||
TypeOptionOverlayDelegate overlayDelegate,
|
TypeOptionOverlayDelegate overlayDelegate,
|
||||||
TypeOptionDataDelegate dataDelegate,
|
|
||||||
) : _widget = NumberTypeOptionWidget(
|
) : _widget = NumberTypeOptionWidget(
|
||||||
typeOption: NumberTypeOption.fromBuffer(typeOptionData),
|
typeOptionContext: typeOptionContext,
|
||||||
dataDelegate: dataDelegate,
|
|
||||||
overlayDelegate: overlayDelegate,
|
overlayDelegate: overlayDelegate,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -34,22 +31,23 @@ class NumberTypeOptionBuilder extends TypeOptionBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class NumberTypeOptionWidget extends TypeOptionWidget {
|
class NumberTypeOptionWidget extends TypeOptionWidget {
|
||||||
final TypeOptionDataDelegate dataDelegate;
|
|
||||||
final TypeOptionOverlayDelegate overlayDelegate;
|
final TypeOptionOverlayDelegate overlayDelegate;
|
||||||
final NumberTypeOption typeOption;
|
final NumberTypeOptionContext typeOptionContext;
|
||||||
const NumberTypeOptionWidget(
|
const NumberTypeOptionWidget({
|
||||||
{required this.typeOption, required this.dataDelegate, required this.overlayDelegate, Key? key})
|
required this.typeOptionContext,
|
||||||
: super(key: key);
|
required this.overlayDelegate,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => getIt<NumberTypeOptionBloc>(param1: typeOption),
|
create: (context) => NumberTypeOptionBloc(typeOptionContext: typeOptionContext),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: GridSize.typeOptionItemHeight,
|
height: GridSize.typeOptionItemHeight,
|
||||||
child: BlocConsumer<NumberTypeOptionBloc, NumberTypeOptionState>(
|
child: BlocConsumer<NumberTypeOptionBloc, NumberTypeOptionState>(
|
||||||
listener: (context, state) => dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()),
|
listener: (context, state) => typeOptionContext.typeOption = state.typeOption,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return FlowyButton(
|
return FlowyButton(
|
||||||
text: Row(
|
text: Row(
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
|
||||||
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart';
|
||||||
|
import 'package:flowy_sdk/protobuf/flowy-grid/text_type_option.pb.dart';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
typedef RichTextTypeOptionContext = TypeOptionContext<RichTextTypeOption>;
|
||||||
|
|
||||||
|
class RichTextTypeOptionDataBuilder extends TypeOptionDataBuilder<RichTextTypeOption> {
|
||||||
|
@override
|
||||||
|
RichTextTypeOption fromBuffer(List<int> buffer) {
|
||||||
|
return RichTextTypeOption.fromBuffer(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RichTextTypeOptionBuilder extends TypeOptionBuilder {
|
||||||
|
RichTextTypeOptionBuilder(RichTextTypeOptionContext typeOptionContext);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget? get customWidget => null;
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user